/** library to drive a WS2812B LED chain * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2016-2022 * @note peripherals used: SPI @ref led_ws2812b_spi, DMA */ /* standard libraries */ #include // standard integer types #include // general utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // real-time control clock library #include // general purpose input output library #include // SPI library #include // DMA library #include // interrupt handler #include "led_ws2812b.h" // LED WS2812B library API #include "global.h" // common methods /** peripheral configuration */ /** @defgroup led_ws2812b_spi SPI peripheral used to control the WS2812B LEDs * @{ */ /** SPI peripheral * @note if pin is 5V tolerant, it can be operated in open-drain mode will 1 kOhm pull-up resistor to 5V to get 5V signal level */ #define LED_WS2812B_SPI 1 /**< SPI peripheral */ #define LED_WS2812B_DR &SPI1_DR /**< SPI Data Register to transmit data */ #define LED_WS2812B_DOUT PB5 /**< SPI MOSI pin used for data output */ #define LED_WS2812B_AF GPIO_AF5 /**< alternate function for SPI pin */ #define LED_WS2812B_RCC_DMA RCC_DMA2 /**< RCC for DMA for SPI peripheral */ #define LED_WS2812B_DMA DMA2 /**< DMA for SPI peripheral */ #define LED_WS2812B_STREAM DMA_STREAM3 /**< DMA stream for SPI TX */ #define LED_WS2812B_CHANNEL DMA_SxCR_CHSEL_3 /**< DMA channel for SPI TX */ /** @} */ /** bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs * @details For each WS2812B bit which needs to be transferred we require to transfer 3 SPI bits. * The first SPI bit is the high start of the WS2812B bit frame. * The second SPI bit determines if the WS2812B bit is a 0 or 1. * The third SPI bit is the last part of the WS2812B bit frame, which is always low. * The binary pattern is 0b100100100100100100100100 */ #define LED_WS2812B_SPI_TEMPLATE 0x924924 /** data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset */ uint8_t led_ws2812b_data[LED_WS2812B_LEDS * 3 * 3 + 25] = {0}; void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue) { // verify the led exists if (led >= LED_WS2812B_LEDS) { return; } // convert RGB bit to SPI bit (each bit is encoded in 3 bits) uint32_t red_spi = LED_WS2812B_SPI_TEMPLATE, green_spi = LED_WS2812B_SPI_TEMPLATE, blue_spi = LED_WS2812B_SPI_TEMPLATE; uint32_t bit_spi = 0x2; // the bit to set in the SPI stream for (uint8_t bit = 0; bit < 8; bit++) { // bit from the color to set/clear const uint8_t byte_bit = (1 << bit); // which bit to check if (red & byte_bit) { red_spi |= bit_spi; } if (green & byte_bit) { green_spi |= bit_spi; } if (blue & byte_bit) { blue_spi |= bit_spi; } bit_spi <<= 3; // go to next SPI bit } // set calculated pattern in stream (GRB) // we don't stop the DMA, instead we set LSB to MSB for gradient transition led_ws2812b_data[led * 3 * 3 + 0 + 2] = (green_spi >> 0); led_ws2812b_data[led * 3 * 3 + 3 + 2] = (red_spi >> 0); led_ws2812b_data[led * 3 * 3 + 6 + 2] = (blue_spi >> 0); led_ws2812b_data[led * 3 * 3 + 0 + 1] = (green_spi >> 8); led_ws2812b_data[led * 3 * 3 + 3 + 1] = (red_spi >> 8); led_ws2812b_data[led * 3 * 3 + 6 + 1] = (blue_spi >> 8); led_ws2812b_data[led * 3 * 3 + 0 + 0] = (green_spi >> 16); led_ws2812b_data[led * 3 * 3 + 3 + 0] = (red_spi >> 16); led_ws2812b_data[led * 3 * 3 + 6 + 0] = (blue_spi >> 16); } void led_ws2812b_setup(void) { // fill buffer with bit pattern for (uint16_t i = 0; i < LED_WS2812B_LEDS * 3; i++) { led_ws2812b_data[i * 3 + 0] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE >> 16); led_ws2812b_data[i * 3 + 1] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE >> 8); led_ws2812b_data[i * 3 + 2] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE >> 0); } // fill remaining with with 0 to encode the reset code for (uint16_t i = LED_WS2812B_LEDS * 3 * 3; i < LENGTH(led_ws2812b_data); i++) { led_ws2812b_data[i] = 0; } // setup SPI to transmit data rcc_periph_clock_enable(GPIO_RCC(LED_WS2812B_DOUT)); // enable clock for SPI output peripheral gpio_mode_setup(GPIO_PORT(LED_WS2812B_DOUT), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(LED_WS2812B_DOUT)); // set SPI MOSI pin to alternate function gpio_set_output_options(GPIO_PORT(LED_WS2812B_DOUT), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(LED_WS2812B_DOUT)); // set SPI MOSI pin output as open-drain gpio_set_af(GPIO_PORT(LED_WS2812B_DOUT), LED_WS2812B_AF, GPIO_PIN(LED_WS2812B_DOUT)); // set alternate function to SPI rcc_periph_clock_enable(RCC_SPI(LED_WS2812B_SPI)); // enable clock for SPI peripheral spi_reset(SPI(LED_WS2812B_SPI)); // clear SPI values to default spi_set_master_mode(SPI(LED_WS2812B_SPI)); // set SPI as master since we generate the clock spi_set_baudrate_prescaler(SPI(LED_WS2812B_SPI), SPI_CR1_BR_FPCLK_DIV_32); // set clock to 0.30 us per bit spi_set_clock_polarity_1(SPI(LED_WS2812B_SPI)); // clock is high when idle spi_set_clock_phase_1(SPI(LED_WS2812B_SPI)); // output data on second edge (rising) spi_set_dff_8bit(SPI(LED_WS2812B_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts) spi_send_msb_first(SPI(LED_WS2812B_SPI)); // send most significant bit first spi_set_bidirectional_transmit_only_mode(SPI(LED_WS2812B_SPI)); // we won't receive data spi_set_unidirectional_mode(SPI(LED_WS2812B_SPI)); // we only need to transmit data spi_enable_software_slave_management(SPI(LED_WS2812B_SPI)); // control the slave select in software (so we can start transmission) spi_set_nss_high(SPI(LED_WS2812B_SPI)); // set NSS high so we can output spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transferred spi_enable(SPI(LED_WS2812B_SPI)); // enable SPI // configure DMA rcc_periph_clock_enable(LED_WS2812B_RCC_DMA); // enable clock for DMA peripheral dma_disable_stream(LED_WS2812B_DMA, LED_WS2812B_STREAM); // disable stream before re-configuring while (DMA_SCR(LED_WS2812B_DMA, LED_WS2812B_STREAM) & DMA_SxCR_EN); // wait until transfer is finished before we can reconfigure dma_stream_reset(LED_WS2812B_DMA, LED_WS2812B_STREAM); // use default values dma_channel_select(LED_WS2812B_DMA, LED_WS2812B_STREAM, LED_WS2812B_CHANNEL); // set the channel for this stream dma_set_transfer_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_DIR_MEM_TO_PERIPHERAL); // set transfer from memory to memory dma_set_memory_size(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_MSIZE_8BIT); // read 8 bits for transfer dma_set_memory_address(LED_WS2812B_DMA, LED_WS2812B_STREAM, (uint32_t)led_ws2812b_data); // set memory address to read from dma_enable_memory_increment_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // increment address of memory to read dma_set_peripheral_address(LED_WS2812B_DMA, LED_WS2812B_STREAM, (uint32_t)LED_WS2812B_DR); // set peripheral address to write data to dma_set_peripheral_size(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_PSIZE_8BIT); // we only write the 8 first bit dma_disable_peripheral_increment_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // always write to the SPI data register dma_set_number_of_data(LED_WS2812B_DMA, LED_WS2812B_STREAM, LENGTH(led_ws2812b_data)); // set transfer size dma_set_priority(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_PL_HIGH); // set priority to high since time is crucial for the peripheral dma_enable_circular_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // always send the data dma_enable_stream(LED_WS2812B_DMA, LED_WS2812B_STREAM); // enable DMA channel }