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
2020-02-17 14:01:54 +01:00
* @ date 2016 - 2020
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
* @ note SPI2 is 5 V tolerant and can be operated in open - drain mode will 1 kO pull - up resistor to 5 V to get 5 V signal level
*/
2020-03-08 22:31:23 +01:00
# define LED_WS2812B_SPI 2 /**< SPI peripheral */
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
2020-03-09 09:30:32 +01:00
/** data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) */
uint8_t led_ws2812b_data [ LED_WS2812B_LEDS * 3 * 3 + 40 * 3 / 8 + 1 ] = { 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
2020-03-09 09:30:32 +01:00
rcc_periph_clock_enable ( RCC_SPI_MOSI_PORT ( LED_WS2812B_SPI ) ) ; // enable clock for SPI output peripheral
2020-03-09 09:44:29 +01:00
gpio_set_mode ( SPI_MOSI_PORT ( LED_WS2812B_SPI ) , GPIO_MODE_OUTPUT_50_MHZ , GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN , SPI_MOSI_PIN ( LED_WS2812B_SPI ) ) ; // set MOSI as output (push-pull needs to be shifted to 5V, open-drain consumes up to 5 mA with pull-up resistor of 1 kO)
2016-08-14 18:37:30 +02:00
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable clock for SPI alternate function
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
spi_set_baudrate_prescaler ( SPI ( LED_WS2812B_SPI ) , SPI_CR1_BR_FPCLK_DIV_16 ) ; // set clock to 0.44 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
2019-03-26 19:27:40 +01:00
spi_enable ( SPI ( LED_WS2812B_SPI ) ) ; // enable SPI
2016-08-14 18:37:30 +02:00
// configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
2017-10-09 09:36:53 +02:00
rcc_periph_clock_enable ( RCC_DMA_SPI ( LED_WS2812B_SPI ) ) ; // enable clock for DMA peripheral
dma_channel_reset ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) ) ; // start with fresh channel configuration
dma_set_read_from_memory ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) ) ; // set direction from memory to peripheral
dma_set_memory_size ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) , DMA_CCR_MSIZE_8BIT ) ; // read 8 bits from memory
2020-03-09 09:30:32 +01:00
dma_set_memory_address ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) , ( uint32_t ) led_ws2812b_data ) ; // set bit pattern as source address
dma_enable_memory_increment_mode ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) ) ; // go through bit pattern
dma_set_number_of_data ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) , LENGTH ( led_ws2812b_data ) ) ; // set the size of the data to transmit
dma_set_peripheral_address ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) , ( uint32_t ) & SPI_DR ( SPI ( LED_WS2812B_SPI ) ) ) ; // set SPI as peripheral destination address
2017-10-09 09:36:53 +02:00
dma_set_peripheral_size ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) , DMA_CCR_PSIZE_8BIT ) ; // write 8 bits to peripheral
dma_set_priority ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) , DMA_CCR_PL_HIGH ) ; // set priority to high since time is crucial for the peripheral
2020-03-09 09:30:32 +01:00
dma_enable_circular_mode ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) ) ; // always send the data
dma_enable_channel ( DMA_SPI ( LED_WS2812B_SPI ) , DMA_CHANNEL_SPI_TX ( LED_WS2812B_SPI ) ) ; // enable DMA channel
spi_enable_tx_dma ( SPI ( LED_WS2812B_SPI ) ) ; // use DMA to provide data stream to be transferred
2016-02-18 10:39:08 +01:00
}