2020-02-17 14:01:54 +01:00
/** library to drive a WS2812B LED chain
* @ file
2016-08-14 18:37:30 +02:00
* @ author King Kévin < kingkevin @ cuvoodoo . info >
2020-06-06 14:35:55 +02:00
* @ copyright SPDX - License - Identifier : GPL - 3.0 - or - later
2022-07-14 14:59:24 +02:00
* @ date 2016 - 2022
2020-03-08 22:31:23 +01:00
* @ note peripherals used : SPI @ ref led_ws2812b_spi , DMA
2016-08-14 18:37:30 +02:00
*/
2016-02-18 10:39:08 +01:00
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
2017-10-09 09:36:53 +02:00
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
2016-02-18 10:39:08 +01:00
# include <libopencm3/stm32/rcc.h> // real-time control clock library
# include <libopencm3/stm32/gpio.h> // general purpose input output library
# include <libopencm3/stm32/spi.h> // SPI library
# include <libopencm3/stm32/dma.h> // DMA library
# include <libopencm3/cm3/nvic.h> // interrupt handler
2016-08-14 18:37:30 +02:00
# include "led_ws2812b.h" // LED WS2812B library API
# include "global.h" // common methods
2016-02-18 10:39:08 +01:00
2017-10-09 09:36:53 +02:00
/** peripheral configuration */
/** @defgroup led_ws2812b_spi SPI peripheral used to control the WS2812B LEDs
* @ {
*/
2020-03-09 09:44:29 +01:00
/** SPI peripheral
2022-07-14 14:59:24 +02:00
* @ note if pin is 5 V tolerant , it can be operated in open - drain mode will 1 kOhm pull - up resistor to 5 V to get 5 V signal level
2020-03-09 09:44:29 +01:00
*/
2022-07-14 14:59:24 +02:00
# 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 */
2017-10-09 09:36:53 +02:00
/** @} */
2016-08-14 18:37:30 +02:00
/** bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs
2020-03-08 22:31:23 +01:00
* @ details For each WS2812B bit which needs to be transferred we require to transfer 3 SPI bits .
2016-08-14 18:37:30 +02:00
* 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 0 b100100100100100100100100
*/
# define LED_WS2812B_SPI_TEMPLATE 0x924924
2016-02-18 10:39:08 +01:00
2022-07-14 14:59:24 +02:00
/** 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 } ;
2016-02-18 10:39:08 +01:00
2016-08-14 18:37:30 +02:00
void led_ws2812b_set_rgb ( uint16_t led , uint8_t red , uint8_t green , uint8_t blue )
2016-02-18 10:39:08 +01:00
{
// verify the led exists
2020-02-17 14:01:54 +01:00
if ( led > = LED_WS2812B_LEDS ) {
2016-02-18 10:39:08 +01:00
return ;
}
2020-03-09 09:43:39 +01:00
// 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 ;
2016-02-18 10:39:08 +01:00
}
2020-03-09 09:43:39 +01:00
if ( green & byte_bit ) {
green_spi | = bit_spi ;
}
if ( blue & byte_bit ) {
blue_spi | = bit_spi ;
}
bit_spi < < = 3 ; // go to next SPI bit
2016-02-18 10:39:08 +01:00
}
2020-03-09 09:43:39 +01:00
// 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 ) ;
2016-02-18 10:39:08 +01:00
}
2016-08-14 18:37:30 +02:00
void led_ws2812b_setup ( void )
2016-02-18 10:39:08 +01:00
{
2020-03-09 09:30:32 +01:00
// 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 ;
}
2020-03-08 22:31:23 +01:00
// setup SPI to transmit data
2022-07-14 14:59:24 +02:00
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
2017-10-09 09:36:53 +02:00
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
2020-03-08 22:31:23 +01:00
spi_set_master_mode ( SPI ( LED_WS2812B_SPI ) ) ; // set SPI as master since we generate the clock
2022-07-14 14:59:24 +02:00
spi_set_baudrate_prescaler ( SPI ( LED_WS2812B_SPI ) , SPI_CR1_BR_FPCLK_DIV_32 ) ; // set clock to 0.30 us per bit
2017-10-09 09:36:53 +02:00
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)
2020-03-08 22:31:23 +01:00
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
2022-07-14 14:59:24 +02:00
spi_enable_tx_dma ( SPI ( LED_WS2812B_SPI ) ) ; // use DMA to provide data stream to be transferred
2019-03-26 19:27:40 +01:00
spi_enable ( SPI ( LED_WS2812B_SPI ) ) ; // enable SPI
2022-07-14 14:59:24 +02:00
// 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
2016-02-18 10:39:08 +01:00
}