// 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 "rpi_pixleds.h" #include #include #include #include #include #include #include "rpi_smi_defs.h" // 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; // RGB values for test mode (1 value for each of 16 channels) // uint32_t on_rgbs[] = {0xef0000, 0x00ef00, 0x0000ef, 0xefef00, 0xef00ef, 0x00efef, 0xefefef}; // uint32_t off_rgbs = 0x000000; // 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 // int main(int argc, char *argv[]) { // // setup // int args = 0, n; // 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 < LED_NCHANS && hexdig(argv[args][0]) >= 0 && // (n = str_rgb(argv[args], rgb_data, chan_num)) > 0) { // chan_ledcount = n > chan_ledcount ? n : chan_ledcount; // chan_num++; // } // } // signal(SIGINT, terminate_smi); // map_devices(); // init_smi(LED_NCHANS > 8 ? SMI_16_BITS : SMI_8_BITS, SMI_TIMING); // map_uncached_mem(&vc_mem, VC_MEM_SIZE); // 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); // for (size_t colorIndex = 0; colorIndex < 3; ++colorIndex) { // for (size_t i = 0; i < chan_ledcount; ++i) { // set_color(on_rgbs[colorIndex], &tx_buffer[LED_TX_OSET(i)]); // } // #if LED_NCHANS <= 8 // swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount)); // #endif // memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); // start_smi(&vc_mem); // usleep(CHASE_MSEC * 1000); // sleep(1); // } // artnet_init(); // // loops // if (testmode) { // while (1) { // test_leds(); // sleep(3); // } // } else { // while (1) { // artDmx_t *artDmx; // if (artnet_read(&artDmx) == OpDmx) { // uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo; // unsigned int ledCountInFrame = dmxLength / 3; // uint16_t maxBound = ledCountInFrame < chan_ledcount ? ledCountInFrame : // chan_ledcount; unsigned int universe = artDmx->subUni & (LED_NCHANS - 1); for (size_t // i = 0; i < maxBound; ++i) { // uint8_t *rgb = artDmx->data + (i * 3); // rgb_data[i][universe] = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; // rgb_txdata(rgb_data[i], &tx_buffer[LED_TX_OSET(i)]); // } // #if LED_NCHANS <= 8 // swap_bytes(tx_buffer, TX_BUFF_SIZE(chan_ledcount)); // #endif // memcpy(txdata, tx_buffer, TX_BUFF_SIZE(chan_ledcount)); // // enable_dma(DMA_CHAN); // start_smi(&vc_mem); // // usleep(10); // // while (dma_active(DMA_CHAN)) // // usleep(10); // } // } // } // terminate_smi(0); // } // Convert RGB text string into integer data, for given channel // Return number of data points for this channel int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan) { int i = 0; char *p; while (chan < LED_NCHANS && i < CHAN_MAXLEDS && hexdig(*s) >= 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 set_color(uint32_t rgb, TXDATA_T *txd) { int msk; // For each bit of the 24-bit RGB values.. for (size_t n = 0; n < LED_NBITS; n++) { // Mask to convert RGB to GRB, M.S bit first msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 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; if (rgb & msk) { txd[1] = (TXDATA_T)0xffff; } txd += BIT_NPULSES; } } // 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 < LED_NBITS; n++) { // Mask to convert RGB to GRB, M.S bit first msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 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 < LED_NCHANS; i++) { if (rgbs[i] & msk) txd[1] |= (1 << i); } txd += BIT_NPULSES; } } // Swap adjacent bytes in transmit data void swap_bytes(void *data, int len) { uint16_t *wp = (uint16_t *)data; len = (len + 1) / 2; while (len-- > 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 terminate_smid 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_smi(0); } // Free memory segments and exit void terminate_smi(int sig) { int i; printf("Closing\n"); if (gpio_regs.virt) { for (i = 0; i < LED_NCHANS; i++) gpio_mode(LED_D0_PIN + i, GPIO_IN); } if (smi_regs.virt) *REG32(smi_regs, SMI_CS) = 0; stop_dma(DMA_CHAN); unmap_periph_mem(&vc_mem); unmap_periph_mem(&smi_regs); unmap_periph_mem(&dma_regs); unmap_periph_mem(&gpio_regs); exit(0); } // Initialise SMI, given data width, time step, and setup/hold/strobe counts // Step value is in nanoseconds: even numbers, 2 to 30 void init_smi(int ledChan, int ns, int setup, int strobe, int hold) { int width = ledChan > 8 ? SMI_16_BITS : SMI_8_BITS; int i, divi = ns / 2; smi_cs = (SMI_CS_REG *)REG32(smi_regs, SMI_CS); smi_l = (SMI_L_REG *)REG32(smi_regs, SMI_L); smi_a = (SMI_A_REG *)REG32(smi_regs, SMI_A); smi_d = (SMI_D_REG *)REG32(smi_regs, SMI_D); smi_dmc = (SMI_DMC_REG *)REG32(smi_regs, SMI_DMC); smi_dsr = (SMI_DSR_REG *)REG32(smi_regs, SMI_DSR0); smi_dsw = (SMI_DSW_REG *)REG32(smi_regs, SMI_DSW0); smi_dcs = (SMI_DCS_REG *)REG32(smi_regs, SMI_DCS); smi_dca = (SMI_DCA_REG *)REG32(smi_regs, SMI_DCA); smi_dcd = (SMI_DCD_REG *)REG32(smi_regs, SMI_DCD); smi_cs->value = 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; i < LED_NCHANS; i++) gpio_mode(LED_D0_PIN + i, GPIO_ALT1); } // Set up SMI transfers using DMA void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T **txdata) { DMA_CB *cbs = mp->virt; *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