From a01770282d7f64edc642caf1986d70d119f6b314 Mon Sep 17 00:00:00 2001 From: Tropicananass Date: Sun, 25 Apr 2021 00:28:26 +0200 Subject: [PATCH] starting LedBar for rasperry pi via smi (C) --- .gitignore | 52 +++++ .vscode/tasks.json | 40 ++++ RpiLedBars/Makefile | 23 ++ RpiLedBars/src/rpi_dma_utils.c | 346 +++++++++++++++++++++++++++++ RpiLedBars/src/rpi_dma_utils.h | 202 +++++++++++++++++ RpiLedBars/src/rpi_pixleds.c | 389 +++++++++++++++++++++++++++++++++ RpiLedBars/src/rpi_smi_defs.h | 100 +++++++++ src/main.cpp | 176 --------------- 8 files changed, 1152 insertions(+), 176 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 RpiLedBars/Makefile create mode 100644 RpiLedBars/src/rpi_dma_utils.c create mode 100644 RpiLedBars/src/rpi_dma_utils.h create mode 100644 RpiLedBars/src/rpi_pixleds.c create mode 100644 RpiLedBars/src/rpi_smi_defs.h delete mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore index 13d1490..7323b4f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,58 @@ __pycache__/ # C extensions *.so +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf # Distribution / packaging .Python diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..8a8fe47 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,40 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cppbuild", + "label": "Build project", + "command": "make", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "compiler: /usr/bin/gcc" + }, + { + "type": "cppbuild", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "compiler: /usr/bin/gcc" + } + ] +} \ No newline at end of file diff --git a/RpiLedBars/Makefile b/RpiLedBars/Makefile new file mode 100644 index 0000000..0536f1e --- /dev/null +++ b/RpiLedBars/Makefile @@ -0,0 +1,23 @@ +CC=gcc +CFLAGS=-Wall -g -DDEBUG +LDFLAGS=#-lpthread +SRCDIR=src +OBJDIR=obj +BINDIR=bin +SRC=$(notdir $(wildcard $(SRCDIR)/*.c)) +OBJ=$(SRC:.c=.o) +BIN=pixled + +all: $(addprefix $(BINDIR)/, $(BIN)) + +$(OBJDIR)/%.o: $(SRCDIR)/%.c + if [ ! -d $(OBJDIR) ]; then mkdir "$(OBJDIR)"; fi + $(CC) -c -o $@ $< $(CFLAGS) + +$(BINDIR)/$(BIN) : $(addprefix $(OBJDIR)/, $(OBJ)) + if [ ! -d "$(BINDIR)" ]; then mkdir "$(BINDIR)"; fi + $(CC) -o $@ $^ $(LDFLAGS) + +clean: + rm -rf $(BINDIR)/* $(OBJDIR)/* + rmdir $(BINDIR) $(OBJDIR) \ No newline at end of file diff --git a/RpiLedBars/src/rpi_dma_utils.c b/RpiLedBars/src/rpi_dma_utils.c new file mode 100644 index 0000000..d32dfe9 --- /dev/null +++ b/RpiLedBars/src/rpi_dma_utils.c @@ -0,0 +1,346 @@ +// Raspberry Pi DMA utilities; see https://iosoft.blog for details +// +// Copyright (c) 2020 Jeremy P Bentham +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpi_dma_utils.h" + +// If non-zero, print debug information +#define DEBUG 0 +// If non-zero, enable PWM hardware output +#define PWM_OUT 0 + +char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD", + "TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""}; +char *gpio_mode_strs[] = {GPIO_MODE_STRS}; + +// Virtual memory pointers to acceess GPIO, DMA and PWM from user space +MEM_MAP pwm_regs, gpio_regs, dma_regs, clk_regs; + +// Use mmap to obtain virtual address, given physical +void *map_periph(MEM_MAP *mp, void *phys, int size) +{ + mp->phys = phys; + mp->size = PAGE_ROUNDUP(size); + mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE); + mp->virt = map_segment(phys, mp->size); + return(mp->virt); +} + +// Allocate uncached memory, get bus & phys addresses +void *map_uncached_mem(MEM_MAP *mp, int size) +{ + void *ret; + mp->size = PAGE_ROUNDUP(size); + mp->fd = open_mbox(); + ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 && + (mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 && + (mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0 + ? mp->virt : 0; + printf("VC mem handle %u, phys %p, virt %p\n", mp->h, mp->bus, mp->virt); + return(ret); +} + +// Free mapped peripheral or memory +void unmap_periph_mem(MEM_MAP *mp) +{ + if (mp) + { + if (mp->fd) + { + unmap_segment(mp->virt, mp->size); + unlock_vc_mem(mp->fd, mp->h); + free_vc_mem(mp->fd, mp->h); + close_mbox(mp->fd); + } + else + unmap_segment(mp->virt, mp->size); + } +} + +// ----- GPIO ----- + +// Set input or output with pullups +void gpio_set(int pin, int mode, int pull) +{ + gpio_mode(pin, mode); + gpio_pull(pin, pull); +} +// Set I/P pullup or pulldown +void gpio_pull(int pin, int pull) +{ + volatile uint32_t *reg = REG32(gpio_regs, GPIO_GPPUDCLK0) + pin / 32; + + *REG32(gpio_regs, GPIO_GPPUD) = pull; + usleep(2); + *reg = pin << (pin % 32); + usleep(2); + *REG32(gpio_regs, GPIO_GPPUD) = 0; + *reg = 0; +} + +// Set input or output +void gpio_mode(int pin, int mode) +{ + volatile uint32_t *reg = REG32(gpio_regs, GPIO_MODE0) + pin / 10, shift = (pin % 10) * 3; + *reg = (*reg & ~(7 << shift)) | (mode << shift); +} + +// Set an O/P pin +void gpio_out(int pin, int val) +{ + volatile uint32_t *reg = REG32(gpio_regs, val ? GPIO_SET0 : GPIO_CLR0) + pin/32; + *reg = 1 << (pin % 32); +} + +// Get an I/P pin value +uint8_t gpio_in(int pin) +{ + volatile uint32_t *reg = REG32(gpio_regs, GPIO_LEV0) + pin/32; + return (((*reg) >> (pin % 32)) & 1); +} + +// Display the values in a GPIO mode register +void disp_mode_vals(uint32_t mode) +{ + int i; + + for (i=0; i<10; i++) + printf("%u:%-4s ", i, gpio_mode_strs[(mode>>(i*3)) & 7]); + printf("\n"); +} + +// ----- VIDEOCORE MAILBOX ----- + +// Open mailbox interface, return file descriptor +int open_mbox(void) +{ + int fd; + + if ((fd = open("/dev/vcio", 0)) < 0) + fail("Error: can't open VC mailbox\n"); + return(fd); +} +// Close mailbox interface +void close_mbox(int fd) +{ + if (fd >= 0) + close(fd); +} + +// Send message to mailbox, return first response int, 0 if error +uint32_t msg_mbox(int fd, VC_MSG *msgp) +{ + uint32_t ret=0, i; + + for (i=msgp->dlen/4; i<=msgp->blen/4; i+=4) + msgp->uints[i++] = 0; + msgp->len = (msgp->blen + 6) * 4; + msgp->req = 0; + if (ioctl(fd, _IOWR(100, 0, void *), msgp) < 0) + printf("VC IOCTL failed\n"); + else if ((msgp->req&0x80000000) == 0) + printf("VC IOCTL error\n"); + else if (msgp->req == 0x80000001) + printf("VC IOCTL partial error\n"); + else + ret = msgp->uints[0]; +#if DEBUG + disp_vc_msg(msgp); +#endif + return(ret); +} + +// Allocate memory on PAGE_SIZE boundary, return handle +uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags) +{ + VC_MSG msg={.tag=0x3000c, .blen=12, .dlen=12, + .uints={PAGE_ROUNDUP(size), PAGE_SIZE, flags}}; + return(msg_mbox(fd, &msg)); +} +// Lock allocated memory, return bus address +void *lock_vc_mem(int fd, int h) +{ + VC_MSG msg={.tag=0x3000d, .blen=4, .dlen=4, .uints={h}}; + return(h ? (void *)msg_mbox(fd, &msg) : 0); +} +// Unlock allocated memory +uint32_t unlock_vc_mem(int fd, int h) +{ + VC_MSG msg={.tag=0x3000e, .blen=4, .dlen=4, .uints={h}}; + return(h ? msg_mbox(fd, &msg) : 0); +} +// Free memory +uint32_t free_vc_mem(int fd, int h) +{ + VC_MSG msg={.tag=0x3000f, .blen=4, .dlen=4, .uints={h}}; + return(h ? msg_mbox(fd, &msg) : 0); +} +uint32_t set_vc_clock(int fd, int id, uint32_t freq) +{ + VC_MSG msg1={.tag=0x38001, .blen=8, .dlen=8, .uints={id, 1}}; + VC_MSG msg2={.tag=0x38002, .blen=12, .dlen=12, .uints={id, freq, 0}}; + msg_mbox(fd, &msg1); + disp_vc_msg(&msg1); + msg_mbox(fd, &msg2); + disp_vc_msg(&msg2); + return(0); +} + +// Display mailbox message +void disp_vc_msg(VC_MSG *msgp) +{ + int i; + + printf("VC msg len=%X, req=%X, tag=%X, blen=%x, dlen=%x, data ", + msgp->len, msgp->req, msgp->tag, msgp->blen, msgp->dlen); + for (i=0; iblen/4; i++) + printf("%08X ", msgp->uints[i]); + printf("\n"); +} + +// ----- VIRTUAL MEMORY ----- + +// Get virtual memory segment for peripheral regs or physical mem +void *map_segment(void *addr, int size) +{ + int fd; + void *mem; + + size = PAGE_ROUNDUP(size); + if ((fd = open ("/dev/mem", O_RDWR|O_SYNC|O_CLOEXEC)) < 0) + fail("Error: can't open /dev/mem, run using sudo\n"); + mem = mmap(0, size, PROT_WRITE|PROT_READ, MAP_SHARED, fd, (uint32_t)addr); + close(fd); +#if DEBUG + printf("Map %p -> %p\n", (void *)addr, mem); +#endif + if (mem == MAP_FAILED) + fail("Error: can't map memory\n"); + return(mem); +} +// Free mapped memory +void unmap_segment(void *mem, int size) +{ + if (mem) + munmap(mem, PAGE_ROUNDUP(size)); +} + +// ----- DMA ----- + +// Enable and reset DMA +void enable_dma(int chan) +{ + *REG32(dma_regs, DMA_ENABLE) |= (1 << chan); + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31; +} + +// Start DMA, given first control block +void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) +{ + *REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp); + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag + *REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1|csval; // Start DMA +} + +// Return remaining transfer length +uint32_t dma_transfer_len(int chan) +{ + return(*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); +} + +// Check if DMA is active +uint32_t dma_active(int chan) +{ + return((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); +} + +// Halt current DMA operation by resetting controller +void stop_dma(int chan) +{ + if (dma_regs.virt) + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31; +} + +// Display DMA registers +void disp_dma(int chan) +{ + volatile uint32_t *p=REG32(dma_regs, DMA_REG(chan, DMA_CS)); + int i=0; + + while (dma_regstrs[i][0]) + { + printf("%-7s %08X ", dma_regstrs[i++], *p++); + if (i%5==0 || dma_regstrs[i][0]==0) + printf("\n"); + } +} + +// ----- PWM ----- + +// Initialise PWM +void init_pwm(int freq, int range, int val) +{ + stop_pwm(); + if (*REG32(pwm_regs, PWM_STA) & 0x100) + { + printf("PWM bus error\n"); + *REG32(pwm_regs, PWM_STA) = 0x100; + } +#if USE_VC_CLOCK_SET + set_vc_clock(mbox_fd, PWM_CLOCK_ID, freq); +#else + int divi=CLOCK_HZ / freq; + *REG32(clk_regs, CLK_PWM_CTL) = CLK_PASSWD | (1 << 5); + while (*REG32(clk_regs, CLK_PWM_CTL) & (1 << 7)) ; + *REG32(clk_regs, CLK_PWM_DIV) = CLK_PASSWD | (divi << 12); + *REG32(clk_regs, CLK_PWM_CTL) = CLK_PASSWD | 6 | (1 << 4); + while ((*REG32(clk_regs, CLK_PWM_CTL) & (1 << 7)) == 0) ; +#endif + usleep(100); + *REG32(pwm_regs, PWM_RNG1) = range; + *REG32(pwm_regs, PWM_FIF1) = val; +#if PWM_OUT + gpio_mode(PWM_PIN, PWM_PIN==12 ? GPIO_ALT0 : GPIO_ALT5); +#endif +} + +// Start PWM operation +void start_pwm(void) +{ + *REG32(pwm_regs, PWM_CTL) = PWM_CTL_USEF1 | PWM_ENAB; +} + +// Stop PWM operation +void stop_pwm(void) +{ + if (pwm_regs.virt) + { + *REG32(pwm_regs, PWM_CTL) = 0; + usleep(100); + } +} + +// EOF diff --git a/RpiLedBars/src/rpi_dma_utils.h b/RpiLedBars/src/rpi_dma_utils.h new file mode 100644 index 0000000..2fff08b --- /dev/null +++ b/RpiLedBars/src/rpi_dma_utils.h @@ -0,0 +1,202 @@ +// Raspberry Pi DMA utility definitions; see https://iosoft.blog for details +// +// Copyright (c) 2020 Jeremy P Bentham +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Location of peripheral registers in physical memory +#define PHYS_REG_BASE PI_01_REG_BASE +#define PI_01_REG_BASE 0x20000000 // Pi Zero or 1 +#define PI_23_REG_BASE 0x3F000000 // Pi 2 or 3 +#define PI_4_REG_BASE 0xFE000000 // Pi 4 + +#define CLOCK_HZ 250000000 // Pi 2 - 4 +//#define CLOCK_HZ 400000000 // Pi Zero + +// Location of peripheral registers in bus memory +#define BUS_REG_BASE 0x7E000000 + +// If non-zero, print debug information +#define DEBUG 0 + +// If non-zero, set PWM clock using VideoCore mailbox +#define USE_VC_CLOCK_SET 0 + +// Size of memory page +#define PAGE_SIZE 0x1000 +// Round up to nearest page +#define PAGE_ROUNDUP(n) ((n)%PAGE_SIZE==0 ? (n) : ((n)+PAGE_SIZE)&~(PAGE_SIZE-1)) + +// Structure for mapped peripheral or memory +typedef struct { + int fd, // File descriptor + h, // Memory handle + size; // Memory size + void *bus, // Bus address + *virt, // Virtual address + *phys; // Physical address +} MEM_MAP; + +// Get virtual 8 and 32-bit pointers to register +#define REG8(m, x) ((volatile uint8_t *) ((uint32_t)(m.virt)+(uint32_t)(x))) +#define REG32(m, x) ((volatile uint32_t *)((uint32_t)(m.virt)+(uint32_t)(x))) +// Get bus address of register +#define REG_BUS_ADDR(m, x) ((uint32_t)(m.bus) + (uint32_t)(x)) +// Convert uncached memory virtual address to bus address +#define MEM_BUS_ADDR(mp, a) ((uint32_t)a-(uint32_t)mp->virt+(uint32_t)mp->bus) +// Convert bus address to physical address (for mmap) +#define BUS_PHYS_ADDR(a) ((void *)((uint32_t)(a)&~0xC0000000)) + +// GPIO register definitions +#define GPIO_BASE (PHYS_REG_BASE + 0x200000) +#define GPIO_MODE0 0x00 +#define GPIO_SET0 0x1c +#define GPIO_CLR0 0x28 +#define GPIO_LEV0 0x34 +#define GPIO_GPPUD 0x94 +#define GPIO_GPPUDCLK0 0x98 +// GPIO I/O definitions +#define GPIO_IN 0 +#define GPIO_OUT 1 +#define GPIO_ALT0 4 +#define GPIO_ALT1 5 +#define GPIO_ALT2 6 +#define GPIO_ALT3 7 +#define GPIO_ALT4 3 +#define GPIO_ALT5 2 +#define GPIO_MODE_STRS "IN","OUT","ALT5","ALT4","ALT0","ALT1","ALT2","ALT3" +#define GPIO_NOPULL 0 +#define GPIO_PULLDN 1 +#define GPIO_PULLUP 2 + +// Videocore mailbox memory allocation flags, see: +// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface +typedef enum { + MEM_FLAG_DISCARDABLE = 1<<0, // can be resized to 0 at any time. Use for cached data + MEM_FLAG_NORMAL = 0<<2, // normal allocating alias. Don't use from ARM + MEM_FLAG_DIRECT = 1<<2, // 0xC alias uncached + MEM_FLAG_COHERENT = 2<<2, // 0x8 alias. Non-allocating in L2 but coherent + MEM_FLAG_ZERO = 1<<4, // initialise buffer to all zeros + MEM_FLAG_NO_INIT = 1<<5, // don't initialise (default is initialise to all ones) + MEM_FLAG_HINT_PERMALOCK = 1<<6, // Likely to be locked for long periods of time + MEM_FLAG_L1_NONALLOCATING=(MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) // Allocating in L2 +} VC_ALLOC_FLAGS; +// VC flags for unchached DMA memory +#define DMA_MEM_FLAGS (MEM_FLAG_DIRECT|MEM_FLAG_ZERO) + +// Mailbox command/response structure +typedef struct { + uint32_t len, // Overall length (bytes) + req, // Zero for request, 1<<31 for response + tag, // Command number + blen, // Buffer length (bytes) + dlen; // Data length (bytes) + uint32_t uints[32-5]; // Data (108 bytes maximum) +} VC_MSG __attribute__ ((aligned (16))); + +// DMA channels and data requests +#define DMA_CHAN_A 10 +#define DMA_CHAN_B 11 +#define DMA_PWM_DREQ 5 +#define DMA_SPI_TX_DREQ 6 +#define DMA_SPI_RX_DREQ 7 +#define DMA_BASE (PHYS_REG_BASE + 0x007000) +// DMA register addresses offset by 0x100 * chan_num +#define DMA_CS 0x00 +#define DMA_CONBLK_AD 0x04 +#define DMA_TI 0x08 +#define DMA_SRCE_AD 0x0c +#define DMA_DEST_AD 0x10 +#define DMA_TXFR_LEN 0x14 +#define DMA_STRIDE 0x18 +#define DMA_NEXTCONBK 0x1c +#define DMA_DEBUG 0x20 +#define DMA_REG(ch, r) ((r)==DMA_ENABLE ? DMA_ENABLE : (ch)*0x100+(r)) +#define DMA_ENABLE 0xff0 +// DMA register values +#define DMA_WAIT_RESP (1 << 3) +#define DMA_CB_DEST_INC (1 << 4) +#define DMA_DEST_DREQ (1 << 6) +#define DMA_CB_SRCE_INC (1 << 8) +#define DMA_SRCE_DREQ (1 << 10) +#define DMA_PRIORITY(n) ((n) << 16) + +// DMA control block (must be 32-byte aligned) +typedef struct { + uint32_t ti, // Transfer info + srce_ad, // Source address + dest_ad, // Destination address + tfr_len, // Transfer length + stride, // Transfer stride + next_cb, // Next control block + debug, // Debug register, zero in control block + unused; +} DMA_CB __attribute__ ((aligned(32))); + +// PWM controller registers +#define PWM_BASE (PHYS_REG_BASE + 0x20C000) +#define PWM_CTL 0x00 // Control +#define PWM_STA 0x04 // Status +#define PWM_DMAC 0x08 // DMA control +#define PWM_RNG1 0x10 // Channel 1 range +#define PWM_DAT1 0x14 // Channel 1 data +#define PWM_FIF1 0x18 // Channel 1 fifo +#define PWM_RNG2 0x20 // Channel 2 range +#define PWM_DAT2 0x24 // Channel 2 data +// PWM register values +#define PWM_CTL_RPTL1 (1<<2) // Chan 1: repeat last data when FIFO empty +#define PWM_CTL_USEF1 (1<<5) // Chan 1: use FIFO +#define PWM_DMAC_ENAB (1<<31) // Start PWM DMA +#define PWM_ENAB 1 // Enable PWM +#define PWM_PIN 12 // GPIO pin for PWM output, 12 or 18 + +// Clock registers and values +#define CLK_BASE (PHYS_REG_BASE + 0x101000) +#define CLK_PWM_CTL 0xa0 +#define CLK_PWM_DIV 0xa4 +#define CLK_PASSWD 0x5a000000 +#define PWM_CLOCK_ID 0xa + +void fail(char *s); +void *map_periph(MEM_MAP *mp, void *phys, int size); +void *map_uncached_mem(MEM_MAP *mp, int size); +void unmap_periph_mem(MEM_MAP *mp); +void gpio_set(int pin, int mode, int pull); +void gpio_pull(int pin, int pull); +void gpio_mode(int pin, int mode); +void gpio_out(int pin, int val); +uint8_t gpio_in(int pin); +void disp_mode_vals(uint32_t mode); +int open_mbox(void); +void close_mbox(int fd); +uint32_t msg_mbox(int fd, VC_MSG *msgp); +void *map_segment(void *addr, int size); +void unmap_segment(void *addr, int size); +uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags); +void *lock_vc_mem(int fd, int h); +uint32_t unlock_vc_mem(int fd, int h); +uint32_t free_vc_mem(int fd, int h); +uint32_t set_vc_clock(int fd, int id, uint32_t freq); +void disp_vc_msg(VC_MSG *msgp); +void enable_dma(int chan); +void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval); +uint32_t dma_transfer_len(int chan); +uint32_t dma_active(int chan); +void stop_dma(int chan); +void disp_dma(int chan); +void init_pwm(int freq, int range, int val); +void start_pwm(void); +void stop_pwm(void); + +// EOF diff --git a/RpiLedBars/src/rpi_pixleds.c b/RpiLedBars/src/rpi_pixleds.c new file mode 100644 index 0000000..e545d26 --- /dev/null +++ b/RpiLedBars/src/rpi_pixleds.c @@ -0,0 +1,389 @@ +// Raspberry Pi WS2812 LED driver using SMI +// For detailed description, see https://iosoft.blog +// +// Copyright (c) 2020 Jeremy P Bentham +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// v0.01 JPB 16/7/20 Adapted from rpi_smi_adc_test v0.06 +// v0.02 JPB 15/9/20 Addded RGB to GRB conversion +// v0.03 JPB 15/9/20 Added red-green flashing +// v0.04 JPB 16/9/20 Added test mode +// v0.05 JPB 19/9/20 Changed test mode colours +// v0.06 JPB 20/9/20 Outlined command-line data input +// v0.07 JPB 25/9/20 Command-line data input if not in test mode +// v0.08 JPB 26/9/20 Changed from 4 to 3 pulses per LED bit +// Added 4-bit zero preamble +// Added raw Tx data test +// v0.09 JPB 27/9/20 Added 16-channel option +// v0.10 JPB 28/9/20 Corrected Pi Zero caching problem +// v0.11 JPB 29/9/20 Added enable_dma before transfer (in case still active) +// Corrected DMA nsamp value (was byte count) + +#include +#include +#include +#include +#include +#include +#include +#include "rpi_dma_utils.h" +#include "rpi_smi_defs.h" + +#if PHYS_REG_BASE==PI_4_REG_BASE // Timings for RPi v4 (1.5 GHz) +#define SMI_TIMING 10, 15, 30, 15 // 400 ns cycle time +#else // Timings for RPi v0-3 (1 GHz) +#define SMI_TIMING 10, 10, 20, 10 // 400 ns cycle time +#endif + +#define TX_TEST 0 // If non-zero, use dummy Tx data +#define LED_D0_PIN 8 // GPIO pin for D0 output +#define LED_NCHANS 8 // Number of LED channels (8 or 16) +#define LED_NBITS 24 // Number of data bits per LED +#define LED_PREBITS 4 // Number of zero bits before LED data +#define LED_POSTBITS 4 // Number of zero bits after LED data +#define BIT_NPULSES 3 // Number of O/P pulses per LED bit +#define CHAN_MAXLEDS 50 // Maximum number of LEDs per channel +#define CHASE_MSEC 100 // Delay time for chaser light test +#define REQUEST_THRESH 2 // DMA request threshold +#define DMA_CHAN 10 // DMA channel to use + +// Length of data for 1 row (1 LED on each channel) +#define LED_DLEN (LED_NBITS * BIT_NPULSES) + +// Transmit data type, 8 or 16 bits +#if LED_NCHANS > 8 +#define TXDATA_T uint16_t +#else +#define TXDATA_T uint8_t +#endif + +// Structures for mapped I/O devices, and non-volatile memory +extern MEM_MAP gpio_regs, dma_regs; +MEM_MAP vc_mem, clk_regs, smi_regs; + +// Pointers to SMI registers +volatile SMI_CS_REG *smi_cs; +volatile SMI_L_REG *smi_l; +volatile SMI_A_REG *smi_a; +volatile SMI_D_REG *smi_d; +volatile SMI_DMC_REG *smi_dmc; +volatile SMI_DSR_REG *smi_dsr; +volatile SMI_DSW_REG *smi_dsw; +volatile SMI_DCS_REG *smi_dcs; +volatile SMI_DCA_REG *smi_dca; +volatile SMI_DCD_REG *smi_dcd; + +// Ofset into Tx data buffer, given LED number in chan +#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n))) + +// Size of data buffers & NV memory, given number of LEDs per chan +#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS) +#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T)) +#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS)) + +// RGB values for test mode (1 value for each of 16 channels) +int on_rgbs[16] = {0xff0000, 0x00ff00, 0x0000ff, 0xffffff, + 0xff4040, 0x40ff40, 0x4040ff, 0x404040, + 0xff0000, 0x00ff00, 0x0000ff, 0xffffff, + 0xff4040, 0x40ff40, 0x4040ff, 0x404040}; +int off_rgbs[16]; + +#if TX_TEST +// Data for simple transmission test +TXDATA_T tx_test_data[] = {1, 2, 3, 4, 5, 6, 7, 0}; +#endif + +TXDATA_T *txdata; // Pointer to uncached Tx data buffer +TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; // Tx buffer for assembling data +int testmode, chan_ledcount=1; // Command-line parameters +int rgb_data[CHAN_MAXLEDS][LED_NCHANS]; // RGB data +int chan_num; // Current channel for data I/P + +void rgb_txdata(int *rgbs, TXDATA_T *txd); +int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan); +void swap_bytes(void *data, int len); +int hexdig(char c); +void map_devices(void); +void fail(char *s); +void terminate(int sig); +void init_smi(int width, int ns, int setup, int hold, int strobe); +void setup_smi_dma(MEM_MAP *mp, int nsamp); +void start_smi(MEM_MAP *mp); + +int main(int argc, char *argv[]) +{ + int args=0, n, oset=0; + + while (argc > ++args) // Process command-line args + { + if (argv[args][0] == '-') + { + switch (toupper(argv[args][1])) + { + case 'N': // -N: number of LEDs per channel + if (args >= argc-1) + fprintf(stderr, "Error: no numeric value\n"); + else + chan_ledcount = atoi(argv[++args]); + break; + case 'T': // -T: test mode + testmode = 1; + break; + default: // Otherwise error + printf("Unrecognised option '%c'\n", argv[args][1]); + printf("Options:\n" + " -n num number of LEDs per channel\n"\ + " -t Test mode (flash LEDs)\n"\ + ); + return(1); + } + } + else if (chan_num=0 && + (n=str_rgb(argv[args], rgb_data, chan_num))>0) + { + chan_ledcount = n > chan_ledcount ? n : chan_ledcount; + chan_num++; + } + } + signal(SIGINT, terminate); + map_devices(); + init_smi(LED_NCHANS>8 ? SMI_16_BITS : SMI_8_BITS, SMI_TIMING); + map_uncached_mem(&vc_mem, VC_MEM_SIZE); +#if TX_TEST + oset = oset; + setup_smi_dma(&vc_mem, sizeof(tx_test_data)/sizeof(TXDATA_T)); +#if LED_NCHANS <= 8 + swap_bytes(tx_test_data, sizeof(tx_test_data)); +#endif + memcpy(txdata, tx_test_data, sizeof(tx_test_data)); + start_smi(&vc_mem); + usleep(10); + while (dma_active(DMA_CHAN)) + usleep(10); +#else + setup_smi_dma(&vc_mem, TX_BUFF_LEN(chan_ledcount)); + printf("%s %u LED%s per channel, %u channels\n", testmode ? "Testing" : "Setting", + chan_ledcount, chan_ledcount==1 ? "" : "s", LED_NCHANS); + + if (testmode) + { + while (1) + { + if (chan_ledcount < 2) + rgb_txdata(oset&1 ? off_rgbs : on_rgbs, tx_buffer); + else + { + for (n=0; n=0) + { + rgbs[i++][chan] = strtoul(s, &p, 16); + s = *p ? p+1 : p; + } + return(i); +} +// Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan +// Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low +void rgb_txdata(int *rgbs, TXDATA_T *txd) +{ + int i, n, msk; + + // For each bit of the 24-bit RGB values.. + for (n=0; n>1; + // 1st byte or word is a high pulse on all lines + txd[0] = (TXDATA_T)0xffff; + // 2nd has high or low bits from data + // 3rd is a low pulse + txd[1] = txd[2] = 0; + for (i=0; i 0) + { + *wp = __builtin_bswap16(*wp); + wp++; + } +} + +// Return hex digit value, -ve if not hex +int hexdig(char c) +{ + c = toupper(c); + return((c>='0' && c<='9') ? c-'0' : (c>='A' && c<='F') ? c-'A'+10 : -1); +} + +// Map GPIO, DMA and SMI registers into virtual mem (user space) +// If any of these fail, program will be terminated +void map_devices(void) +{ + map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE); + map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE); + map_periph(&clk_regs, (void *)CLK_BASE, PAGE_SIZE); + map_periph(&smi_regs, (void *)SMI_BASE, PAGE_SIZE); +} + +// Catastrophic failure in initial setup +void fail(char *s) +{ + printf(s); + terminate(0); +} + +// Free memory segments and exit +void terminate(int sig) +{ + int i; + + printf("Closing\n"); + if (gpio_regs.virt) + { + for (i=0; ivalue = smi_l->value = smi_a->value = 0; + smi_dsr->value = smi_dsw->value = smi_dcs->value = smi_dca->value = 0; + if (*REG32(clk_regs, CLK_SMI_DIV) != divi << 12) + { + *REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | (1 << 5); + usleep(10); + while (*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) ; + usleep(10); + *REG32(clk_regs, CLK_SMI_DIV) = CLK_PASSWD | (divi << 12); + usleep(10); + *REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | 6 | (1 << 4); + usleep(10); + while ((*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) == 0) ; + usleep(100); + } + if (smi_cs->seterr) + smi_cs->seterr = 1; + smi_dsr->rsetup = smi_dsw->wsetup = setup; + smi_dsr->rstrobe = smi_dsw->wstrobe = strobe; + smi_dsr->rhold = smi_dsw->whold = hold; + smi_dmc->panicr = smi_dmc->panicw = 8; + smi_dmc->reqr = smi_dmc->reqw = REQUEST_THRESH; + smi_dsr->rwidth = smi_dsw->wwidth = width; + for (i=0; ivirt; + + txdata = (TXDATA_T *)(cbs+1); + smi_dmc->dmaen = 1; + smi_cs->enable = 1; + smi_cs->clear = 1; + smi_cs->pxldat = 1; + smi_l->len = nsamp * sizeof(TXDATA_T); + smi_cs->write = 1; + enable_dma(DMA_CHAN); + cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP; + cbs[0].tfr_len = nsamp; + cbs[0].srce_ad = MEM_BUS_ADDR(mp, txdata); + cbs[0].dest_ad = REG_BUS_ADDR(smi_regs, SMI_D); +} + +// Start SMI DMA transfers +void start_smi(MEM_MAP *mp) +{ + DMA_CB *cbs=mp->virt; + + start_dma(mp, DMA_CHAN, &cbs[0], 0); + smi_cs->start = 1; +} + +// EOF diff --git a/RpiLedBars/src/rpi_smi_defs.h b/RpiLedBars/src/rpi_smi_defs.h new file mode 100644 index 0000000..49f734f --- /dev/null +++ b/RpiLedBars/src/rpi_smi_defs.h @@ -0,0 +1,100 @@ +// Secondary Memory Interface definitions for Raspberry Pi +// +// v0.01 JPB 12/7/20 Adapted from rpi_smi_test v0.19 + +// Register definitions +#define SMI_BASE (PHYS_REG_BASE + 0x600000) +#define SMI_CS 0x00 // Control & status +#define SMI_L 0x04 // Transfer length +#define SMI_A 0x08 // Address +#define SMI_D 0x0c // Data +#define SMI_DSR0 0x10 // Read settings device 0 +#define SMI_DSW0 0x14 // Write settings device 0 +#define SMI_DSR1 0x18 // Read settings device 1 +#define SMI_DSW1 0x1c // Write settings device 1 +#define SMI_DSR2 0x20 // Read settings device 2 +#define SMI_DSW2 0x24 // Write settings device 2 +#define SMI_DSR3 0x28 // Read settings device 3 +#define SMI_DSW3 0x2c // Write settings device 3 +#define SMI_DMC 0x30 // DMA control +#define SMI_DCS 0x34 // Direct control/status +#define SMI_DCA 0x38 // Direct address +#define SMI_DCD 0x3c // Direct data +#define SMI_FD 0x40 // FIFO debug +#define SMI_REGLEN (SMI_FD * 4) + +// Data widths +#define SMI_8_BITS 0 +#define SMI_16_BITS 1 +#define SMI_18_BITS 2 +#define SMI_9_BITS 3 + +// DMA request +#define DMA_SMI_DREQ 4 + +// Union of 32-bit value with register bitfields +#define REG_DEF(name, fields) typedef union {struct {volatile uint32_t fields;}; volatile uint32_t value;} name + +// Control and status register +#define SMI_CS_FIELDS \ + enable:1, done:1, active:1, start:1, clear:1, write:1, _x1:2,\ + teen:1, intd:1, intt:1, intr:1, pvmode:1, seterr:1, pxldat:1, edreq:1,\ + _x2:8, _x3:1, aferr:1, txw:1, rxr:1, txd:1, rxd:1, txe:1, rxf:1 +REG_DEF(SMI_CS_REG, SMI_CS_FIELDS); + +// Data length register +#define SMI_L_FIELDS \ + len:32 +REG_DEF(SMI_L_REG, SMI_L_FIELDS); + +// Address & device number +#define SMI_A_FIELDS \ + addr:6, _x1:2, dev:2 +REG_DEF(SMI_A_REG, SMI_A_FIELDS); + +// Data FIFO +#define SMI_D_FIELDS \ + data:32 +REG_DEF(SMI_D_REG, SMI_D_FIELDS); + +// DMA control register +#define SMI_DMC_FIELDS \ + reqw:6, reqr:6, panicw:6, panicr:6, dmap:1, _x1:3, dmaen:1 +REG_DEF(SMI_DMC_REG, SMI_DMC_FIELDS); + +// Device settings: read (1 of 4) +#define SMI_DSR_FIELDS \ + rstrobe:7, rdreq:1, rpace:7, rpaceall:1, \ + rhold:6, fsetup:1, mode68:1, rsetup:6, rwidth:2 +REG_DEF(SMI_DSR_REG, SMI_DSR_FIELDS); + +// Device settings: write (1 of 4) +#define SMI_DSW_FIELDS \ + wstrobe:7, wdreq:1, wpace:7, wpaceall:1, \ + whold:6, wswap:1, wformat:1, wsetup:6, wwidth:2 +REG_DEF(SMI_DSW_REG, SMI_DSW_FIELDS); + +// Direct control register +#define SMI_DCS_FIELDS \ + enable:1, start:1, done:1, write:1 +REG_DEF(SMI_DCS_REG, SMI_DCS_FIELDS); + +// Direct control address & device number +#define SMI_DCA_FIELDS \ + addr:6, _x1:2, dev:2 +REG_DEF(SMI_DCA_REG, SMI_DCA_FIELDS); + +// Direct control data +#define SMI_DCD_FIELDS \ + data:32 +REG_DEF(SMI_DCD_REG, SMI_DCD_FIELDS); + +// Debug register +#define SMI_FLVL_FIELDS \ + fcnt:6, _x1:2, flvl:6 +REG_DEF(SMI_FLVL_REG, SMI_FLVL_FIELDS); + +#define CLK_SMI_CTL 0xb0 +#define CLK_SMI_DIV 0xb4 + +// EOF diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 48a7319..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include - -// TODO : use core built-in lib tinyNeoPixel -#include -#include - -#include "pin_map.hpp" - -/* Types and Constants */ -/*------------------------------------------------------------------------------------------------*/ - -/* State machine */ -#define FOREACH_MODE(MODE) \ - MODE(Idle) \ - MODE(Chaser) \ - MODE(Rainbow) - -#define GENERATE_MODE_ENUM(ENUM) ENUM, - -enum class Mode_e { FOREACH_MODE(GENERATE_MODE_ENUM) }; - -Mode_e &operator++(Mode_e &mode) { - if (mode == Mode_e::Rainbow) { - return mode = Mode_e::Idle; - } - - return mode = static_cast(static_cast(mode) + 1); -} - -#ifdef DEBUG -#define GENERATE_MODE_STRING(STRING) #STRING, -char static const *mode_str[] = {FOREACH_MODE(GENERATE_MODE_STRING)}; -#endif /*DEBUG*/ - -/* Leds */ -uint16_t static const LedCount{300}; - -/* Colors */ -uint32_t const Red{0x00FF0000}; -uint32_t const Green{0x0000FF00}; -uint32_t const Blue{0x000000FF}; - -/* Global variables */ -/*------------------------------------------------------------------------------------------------*/ - -/* State machine */ -Mode_e currentMode{Mode_e::Idle}; - -/* Feedback led state */ -bool feedbackLedState{HIGH}; - -/* IO Objects */ -Adafruit_NeoPixel ledBar{LedCount, LedPin, NEO_GRB + NEO_KHZ800}; - -/* debug Serial definition */ -#if defined(__PLATFORMIO_BUILD_DEBUG__) -#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_PROMICRO16) -Serial_ &debugSerial = Serial; -#else -#warning "No serial defined for debug" -#endif -#endif - -/* Private function declarations */ -/*------------------------------------------------------------------------------------------------*/ - -bool has_mode_changed(char c); - -/* State machine */ -#define GENERATE_MODE_EXEC_DECLARTION(MODE) void MODE##_execute(char c); -FOREACH_MODE(GENERATE_MODE_EXEC_DECLARTION) - -/* Function definitions */ -/*------------------------------------------------------------------------------------------------*/ -void setup() { - pinMode(FeedbackLedPin, OUTPUT); - digitalWrite(FeedbackLedPin, feedbackLedState); -#if defined(__PLATFORMIO_BUILD_DEBUG__) - debugSerial.begin(9600); - while (!debugSerial.available()) - ; - debugSerial.println("Init ... "); -#endif /*DEBUG*/ - - ledBar.begin(); - ledBar.setBrightness(50); - ledBar.show(); // Initialize all pixels to 'off' -} - -unsigned i = 0; - -void loop() { - char c = debugSerial.read(); - - // if (c == '\n') { - // if (i >= LedCount) { - // i = 0; - // } - // debugSerial.print("Lighting "); - // debugSerial.println(i, DEC); - // ledBar.setPixelColor(i++, ledBar.Color(100, 0, 0)); - // } - // } - if (has_mode_changed(c)) { - ledBar.clear(); - } - - switch (currentMode) { -#define GENERATE_MODE_EXEC_CALL(MODE) \ - case Mode_e::MODE: \ - MODE##_execute(c); \ - break; - FOREACH_MODE(GENERATE_MODE_EXEC_CALL) - - default: - break; - } - - ledBar.show(); -} - -bool has_mode_changed(char c) { - Mode_e previousMode = currentMode; - switch (c) { - case 'y': - currentMode = Mode_e::Idle; - break; - case 'u': - currentMode = Mode_e::Chaser; - break; - case 'i': - currentMode = Mode_e::Rainbow; - break; - case 'o': - break; - case 'h': - break; - case 'j': - break; - case 'k': - break; - case 'l': - break; - default: - return false; - } - return currentMode != previousMode; -} - -void Idle_execute(char c) { - switch (c) { - case 'r': - debugSerial.println("\nLighting all in red"); - ledBar.fill(Red); - break; - case 'g': - debugSerial.println("\nLighting all in green"); - ledBar.fill(Green); - break; - case 'b': - debugSerial.println("\nLighting all in blue"); - ledBar.fill(Blue); - break; - case 'n': - debugSerial.println("\nReset"); - ledBar.clear(); - break; - } -} - -typedef struct { - -} chaser_state_t; - -void Chaser_execute(char c) { (void)c; } -void Rainbow_execute(char c) { (void)c; }