CuVoodoo STM32F1 firmware template
led_ws2812b.c
Go to the documentation of this file.
1 /* This program is free software: you can redistribute it and/or modify
2  * it under the terms of the GNU General Public License as published by
3  * the Free Software Foundation, either version 3 of the License, or
4  * (at your option) any later version.
5  *
6  * This program is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9  * GNU General Public License for more details.
10  *
11  * You should have received a copy of the GNU General Public License
12  * along with this program. If not, see <http://www.gnu.org/licenses/>.
13  *
14  */
22 /* standard libraries */
23 #include <stdint.h> // standard integer types
24 #include <stdlib.h> // general utilities
25 
26 /* STM32 (including CM3) libraries */
27 #include <libopencmsis/core_cm3.h> // Cortex M3 utilities
28 #include <libopencm3/stm32/rcc.h> // real-time control clock library
29 #include <libopencm3/stm32/gpio.h> // general purpose input output library
30 #include <libopencm3/stm32/spi.h> // SPI library
31 #include <libopencm3/stm32/timer.h> // timer library
32 #include <libopencm3/stm32/dma.h> // DMA library
33 #include <libopencm3/cm3/nvic.h> // interrupt handler
34 
35 #include "led_ws2812b.h" // LED WS2812B library API
36 #include "global.h" // common methods
37 
42 #define LED_WS2812B_SPI 1
48 #define LED_WS2812B_TIMER 3
49 #define LED_WS2812B_CLK_CH 3
50 #define LED_WS2812B_TIMER_OC TIM_OC3
60 #define LED_WS2812B_SPI_TEMPLATE 0x924924
61 
62 uint8_t led_ws2812b_data[LED_WS2812B_LEDS*3*3+40*3/8+1] = {0};
63 static volatile bool transmit_flag = false;
65 void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
66 {
67  // verify the led exists
68  if (led>=LED_WS2812B_LEDS) {
69  return;
70  }
71  // wait for transmission to complete before changing the color
72  while (transmit_flag) {
73  __WFI();
74  }
75 
76  const uint8_t colors[] = {green, red, blue}; // color order for the WS2812B
77  const uint8_t pattern_bit[] = {0x02, 0x10, 0x80, 0x04, 0x20, 0x01, 0x08, 0x40}; // which bit to change in the pattern
78  const uint8_t pattern_byte[] = {2,2,2,1,1,0,0,0}; // in which byte in the pattern to write the pattern bit
79  for (uint8_t color=0; color<LENGTH(colors); color++) { // colors are encoded similarly
80  // fill the middle bit (fixed is faster than calculating it)
81  for (uint8_t bit=0; bit<8; bit++) { // bit from the color to set/clear
82  if (colors[color]&(1<<bit)) { // setting bit
83  led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] |= pattern_bit[bit]; // setting bit is pattern
84  } else { // clear bit
85  led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] &= ~pattern_bit[bit]; // clearing bit is pattern
86  }
87  }
88  }
89 }
90 
92 {
93  if (transmit_flag) { // a transmission is already ongoing
94  return false;
95  }
96  transmit_flag = true; // remember transmission started
97  dma_set_memory_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)led_ws2812b_data);
98  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
99  dma_enable_transfer_complete_interrupt(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // warm when transfer is complete to stop transmission
100  dma_enable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // enable DMA channel
101 
102  spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transfered
103 
104  timer_set_counter(TIM(LED_WS2812B_TIMER), 0); // reset timer counter fro clean clock
105  timer_enable_counter(TIM(LED_WS2812B_TIMER)); // start timer to generate clock
106  return true;
107 }
108 
110 {
111  // setup timer to generate clock of (using PWM): 800kHz*3
112  rcc_periph_clock_enable(RCC_TIM_CH(LED_WS2812B_TIMER, LED_WS2812B_CLK_CH)); // enable clock for GPIO peripheral
113  gpio_set_mode(TIM_CH_PORT(LED_WS2812B_TIMER, LED_WS2812B_CLK_CH), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, TIM_CH_PIN(LED_WS2812B_TIMER, LED_WS2812B_CLK_CH)); // set pin as output
114  rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function (PWM)
115  rcc_periph_clock_enable(RCC_TIM(LED_WS2812B_TIMER)); // enable clock for timer peripheral
116  timer_reset(TIM(LED_WS2812B_TIMER)); // reset timer state
117  timer_set_mode(TIM(LED_WS2812B_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
118  timer_set_prescaler(TIM(LED_WS2812B_TIMER), 0); // no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
119  timer_set_period(TIM(LED_WS2812B_TIMER), rcc_ahb_frequency/800000/3-1); // set the clock frequency to 800kHz*3bit since we need to send 3 bits to output a 800kbps stream
120  timer_set_oc_value(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC, rcc_ahb_frequency/800000/3/2); // duty cycle to 50%
121  timer_set_oc_mode(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC, TIM_OCM_PWM1); // set timer to generate PWM (used as clock)
122  timer_enable_oc_output(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC); // enable output to generate the clock
123 
124  // setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812B bit
125  rcc_periph_clock_enable(RCC_SPI_SCK_PORT(LED_WS2812B_SPI)); // enable clock for SPI IO peripheral
126  gpio_set_mode(SPI_SCK_PORT(LED_WS2812B_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_SCK_PIN(LED_WS2812B_SPI)); // set clock as input
127  rcc_periph_clock_enable(RCC_SPI_MISO_PORT(LED_WS2812B_SPI)); // enable clock for SPI IO peripheral
128  gpio_set_mode(SPI_MISO_PORT(LED_WS2812B_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MISO_PIN(LED_WS2812B_SPI)); // set MISO as output
129  rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
130  rcc_periph_clock_enable(RCC_SPI(LED_WS2812B_SPI)); // enable clock for SPI peripheral
131  spi_reset(SPI(LED_WS2812B_SPI)); // clear SPI values to default
132  spi_set_slave_mode(SPI(LED_WS2812B_SPI)); // set SPI as slave (since we use the clock as input)
133  spi_set_bidirectional_transmit_only_mode(SPI(LED_WS2812B_SPI)); // we won't receive data
134  spi_set_unidirectional_mode(SPI(LED_WS2812B_SPI)); // we only need to transmit data
135  spi_set_dff_8bit(SPI(LED_WS2812B_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts)
136  spi_set_clock_polarity_1(SPI(LED_WS2812B_SPI)); // clock is high when idle
137  spi_set_clock_phase_1(SPI(LED_WS2812B_SPI)); // output data on second edge (rising)
138  spi_send_msb_first(SPI(LED_WS2812B_SPI)); // send least significant bit first
139  spi_enable_software_slave_management(SPI(LED_WS2812B_SPI)); // control the slave select in software (since there is no master)
140  spi_set_nss_low(SPI(LED_WS2812B_SPI)); // set NSS low so we can output
141  spi_enable(SPI(LED_WS2812B_SPI)); // enable SPI
142  // do not disable SPI or set NSS high since it will put MISO high, breaking the beginning of the next transmission
143 
144  // configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
145  rcc_periph_clock_enable(RCC_DMA_SPI(LED_WS2812B_SPI)); // enable clock for DMA peripheral
146  dma_channel_reset(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // start with fresh channel configuration
147  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
148  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
149  dma_set_read_from_memory(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // set direction from memory to peripheral
150  dma_enable_memory_increment_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // go through bit pattern
151  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
152  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
153  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
154  nvic_enable_irq(DMA_IRQ_SPI_TX(LED_WS2812B_SPI)); // enable interrupts for this DMA channel
155 
156  // fill buffer with bit pattern
157  for (uint16_t i=0; i<LED_WS2812B_LEDS*3; i++) {
158  led_ws2812b_data[i*3+0] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>16);
159  led_ws2812b_data[i*3+1] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>8);
160  led_ws2812b_data[i*3+2] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>0);
161  }
162  // fill remaining with with 0 to encode the reset code
163  for (uint16_t i=LED_WS2812B_LEDS*3*3; i<LENGTH(led_ws2812b_data); i++) {
164  led_ws2812b_data[i] = 0;
165  }
166  led_ws2812b_transmit(); // set LEDs
167 }
168 
171 {
172  if (dma_get_interrupt_flag(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_TCIF)) { // transfer completed
173  dma_clear_interrupt_flags(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_TCIF); // clear flag
174  dma_disable_transfer_complete_interrupt(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // stop warning transfer completed
175  spi_disable_tx_dma(SPI(LED_WS2812B_SPI)); // stop SPI asking for more data
176  while (SPI_SR(SPI(LED_WS2812B_SPI)) & SPI_SR_BSY); // wait for data to be shifted out
177  timer_disable_counter(TIM(LED_WS2812B_TIMER)); // stop clock
178  dma_disable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // stop using DMA
179  transmit_flag = false; // transmission completed
180  }
181 }
void led_ws2812b_setup(void)
setup WS2812B LED driver
Definition: led_ws2812b.c:109
#define LED_WS2812B_SPI
SPI peripheral.
Definition: led_ws2812b.c:42
#define RCC_TIM_CH(x, y)
get RCC for port based on TIMx_CHy identifier
Definition: global.h:119
#define DMA_IRQ_SPI_TX(x)
get DMA NVIC IRQ for SPI TX based on SPI identifier
Definition: global.h:337
#define DMA_CHANNEL_SPI_TX(x)
get DMA channel for SPI TX based on SPI identifier
Definition: global.h:327
bool led_ws2812b_transmit(void)
transmit color values to WS2812B LEDs
Definition: led_ws2812b.c:91
#define LED_WS2812B_TIMER_OC
timer output compare used to set PWM frequency
Definition: led_ws2812b.c:50
#define LED_WS2812B_CLK_CH
timer channel to output PWM (PB0), connect to SPI clock input
Definition: led_ws2812b.c:49
global definitions and methods (API)
static volatile bool transmit_flag
flag set in software when transmission started, clear by interrupt when transmission completed ...
Definition: led_ws2812b.c:63
#define RCC_SPI_MISO_PORT(x)
get RCC for GPIO port for SPI MISO signals
Definition: global.h:285
#define RCC_TIM(x)
get RCC for timer based on TIM identifier
Definition: global.h:109
#define RCC_SPI(x)
get RCC for SPI based on SPI identifier
Definition: global.h:273
#define TIM_CH_PIN(x, y)
get pin based on TIMx_CHy identifier
Definition: global.h:117
#define RCC_DMA_SPI(x)
get RCC for DMA based on SPI identifier
Definition: global.h:322
#define DMA_ISR_SPI_TX(x)
get DMA ISR for SPI TX based on SPI identifier
Definition: global.h:347
#define SPI_SCK_PORT(x)
get SPI port for SCK signal based on SPI identifier
Definition: global.h:297
uint8_t led_ws2812b_data[LED_WS2812B_LEDS *3 *3+40 *3/8+1]
data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) ...
Definition: led_ws2812b.c:62
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
set color of a single LED
Definition: led_ws2812b.c:65
#define TIM_CH_PORT(x, y)
get port based on TIMx_CHy identifier
Definition: global.h:115
#define SPI_MISO_PORT(x)
get SPI port for MISO signal based on SPI identifier
Definition: global.h:299
#define SPI(x)
get SPI based on SPI identifier
Definition: global.h:271
#define LED_WS2812B_LEDS
number of LEDs on the WS2812B strip
Definition: led_ws2812b.h:24
#define RCC_SPI_SCK_PORT(x)
get RCC for GPIO port for SPI SCK signals
Definition: global.h:280
#define LENGTH(x)
get the length of an array
Definition: global.h:26
#define LED_WS2812B_SPI_TEMPLATE
bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs
Definition: led_ws2812b.c:60
#define LED_WS2812B_TIMER
timer peripheral
Definition: led_ws2812b.c:48
#define TIM(x)
get TIM based on TIM identifier
Definition: global.h:107
#define SPI_MISO_PIN(x)
get SPI pin for MISO signal based on SPI identifier
Definition: global.h:307
#define DMA_SPI(x)
get DMA based on SPI identifier
Definition: global.h:317
#define SPI_SCK_PIN(x)
get SPI pin for SCK signal based on SPI identifier
Definition: global.h:305
library to drive a WS2812B LED chain (API)