LED clock
 All Files Functions Variables Macros Groups
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 <libopencm3/stm32/rcc.h> // real-time control clock library
28 #include <libopencm3/stm32/gpio.h> // general purpose input output library
29 #include <libopencm3/stm32/spi.h> // SPI library
30 #include <libopencm3/stm32/timer.h> // timer library
31 #include <libopencm3/stm32/dma.h> // DMA library
32 #include <libopencm3/cm3/nvic.h> // interrupt handler
33 #include <libopencmsis/core_cm3.h> // Cortex M3 utilities
34 
35 #include "led_ws2812b.h" // LED WS2812B library API
36 #include "global.h" // common methods
37 
45 #define LED_WS2812B_SPI_TEMPLATE 0x924924
46 
47 uint8_t led_ws2812b_data[LED_WS2812B_LEDS*3*3+40*3/8+1] = {0};
48 static volatile bool transmit_flag = false;
50 void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
51 {
52  // verify the led exists
53  if (led>=LED_WS2812B_LEDS) {
54  return;
55  }
56  // wait for transmission to complete before changing the color
57  while (transmit_flag) {
58  __WFI();
59  }
60 
61  const uint8_t colors[] = {green, red, blue}; // color order for the WS2812B
62  const uint8_t pattern_bit[] = {0x02, 0x10, 0x80, 0x04, 0x20, 0x01, 0x08, 0x40}; // which bit to change in the pattern
63  const uint8_t pattern_byte[] = {2,2,2,1,1,0,0,0}; // in which byte in the pattern to write the pattern bit
64  for (uint8_t color=0; color<LENGTH(colors); color++) { // colors are encoded similarly
65  // fill the middle bit (fixed is faster than calculating it)
66  for (uint8_t bit=0; bit<8; bit++) { // bit from the color to set/clear
67  if (colors[color]&(1<<bit)) { // setting bit
68  led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] |= pattern_bit[bit]; // setting bit is pattern
69  } else { // clear bit
70  led_ws2812b_data[led*3*3+color*3+pattern_byte[bit]] &= ~pattern_bit[bit]; // clearing bit is pattern
71  }
72  }
73  }
74 }
75 
77 {
78  if (transmit_flag) { // a transmission is already ongoing
79  return false;
80  }
81  transmit_flag = true; // remember transmission started
82  dma_set_memory_address(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, (uint32_t)led_ws2812b_data);
83  dma_set_number_of_data(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, LENGTH(led_ws2812b_data)); // set the size of the data to transmit
84  dma_enable_transfer_complete_interrupt(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // warm when transfer is complete to stop transmission
85  dma_enable_channel(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // enable DMA channel
86 
87  spi_enable_tx_dma(LED_WS2812B_SPI); // use DMA to provide data stream to be transfered
88 
89  timer_set_counter(LED_WS2812B_TIMER, 0); // reset timer counter fro clean clock
90  timer_enable_counter(LED_WS2812B_TIMER); // start timer to generate clock
91  return true;
92 }
93 
95 {
96  // setup timer to generate clock of (using PWM): 800kHz*3
97  rcc_periph_clock_enable(LED_WS2812B_CLK_RCC); // enable clock for GPIO peripheral
98  gpio_set_mode(LED_WS2812B_CLK_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, LED_WS2812B_CLK_PIN); // set pin as output
99  rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function (PWM)
100  rcc_periph_clock_enable(LED_WS2812B_TIMER_RCC); // enable clock for timer peripheral
101  timer_reset(LED_WS2812B_TIMER); // reset timer state
102  timer_set_mode(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
103  timer_set_prescaler(LED_WS2812B_TIMER, 0); // no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
104  timer_set_period(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
105  timer_set_oc_value(LED_WS2812B_TIMER, LED_WS2812B_TIMER_OC, rcc_ahb_frequency/800000/3/2); // duty cycle to 50%
106  timer_set_oc_mode(LED_WS2812B_TIMER, LED_WS2812B_TIMER_OC, TIM_OCM_PWM1); // set timer to generate PWM (used as clock)
107  timer_enable_oc_output(LED_WS2812B_TIMER, LED_WS2812B_TIMER_OC); // enable output to generate the clock
108 
109  // setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812B bit
110  rcc_periph_clock_enable(LED_WS2812B_SPI_PORT_RCC); // enable clock for SPI IO peripheral
111  gpio_set_mode(LED_WS2812B_SPI_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, LED_WS2812B_SPI_CLK); // set clock as input
112  gpio_set_mode(LED_WS2812B_SPI_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, LED_WS2812B_SPI_DOUT); // set MISO as output
113  rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
114  rcc_periph_clock_enable(LED_WS2812B_SPI_RCC); // enable clock for SPI peripheral
115  spi_reset(LED_WS2812B_SPI); // clear SPI values to default
116  spi_set_slave_mode(LED_WS2812B_SPI); // set SPI as slave (since we use the clock as input)
117  spi_set_bidirectional_transmit_only_mode(LED_WS2812B_SPI); // we won't receive data
118  spi_set_unidirectional_mode(LED_WS2812B_SPI); // we only need to transmit data
119  spi_set_dff_8bit(LED_WS2812B_SPI); // use 8 bits for simpler encoding (but there will be more interrupts)
120  spi_set_clock_polarity_1(LED_WS2812B_SPI); // clock is high when idle
121  spi_set_clock_phase_1(LED_WS2812B_SPI); // output data on second edge (rising)
122  spi_send_msb_first(LED_WS2812B_SPI); // send least significant bit first
123  spi_enable_software_slave_management(LED_WS2812B_SPI); // control the slave select in software (since there is no master)
124  spi_set_nss_low(LED_WS2812B_SPI); // set NSS low so we can output
125  spi_enable(LED_WS2812B_SPI); // enable SPI
126  // do not disable SPI or set NSS high since it will put MISO high, breaking the beginning of the next transmission
127 
128  // configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
129  rcc_periph_clock_enable(LED_WS2812B_DMA_RCC); // enable clock for DMA peripheral
130  dma_channel_reset(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // start with fresh channel configuration
131  dma_set_memory_address(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, (uint32_t)led_ws2812b_data); // set bit pattern as source address
132  dma_set_peripheral_address(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, (uint32_t)&LED_WS2812B_SPI_DR); // set SPI as peripheral destination address
133  dma_set_read_from_memory(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // set direction from memory to peripheral
134  dma_enable_memory_increment_mode(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // go through bit pattern
135  dma_set_memory_size(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, DMA_CCR_MSIZE_8BIT); // read 8 bits from memory
136  dma_set_peripheral_size(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral
137  dma_set_priority(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
138  nvic_enable_irq(LED_WS2812B_DMA_IRQ); // enable interrupts for this DMA channel
139 
140  // fill buffer with bit pattern
141  for (uint16_t i=0; i<LED_WS2812B_LEDS*3; i++) {
142  led_ws2812b_data[i*3+0] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>16);
143  led_ws2812b_data[i*3+1] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>8);
144  led_ws2812b_data[i*3+2] = (uint8_t)(LED_WS2812B_SPI_TEMPLATE>>0);
145  }
146  // fill remaining with with 0 to encode the reset code
147  for (uint16_t i=LED_WS2812B_LEDS*3*3; i<LENGTH(led_ws2812b_data); i++) {
148  led_ws2812b_data[i] = 0;
149  }
150  led_ws2812b_transmit(); // set LEDs
151 }
152 
155 {
156  if (dma_get_interrupt_flag(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, DMA_TCIF)) { // transfer completed
157  dma_clear_interrupt_flags(LED_WS2812B_DMA, LED_WS2812B_DMA_CH, DMA_TCIF); // clear flag
158  dma_disable_transfer_complete_interrupt(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // stop warning transfer completed
159  spi_disable_tx_dma(LED_WS2812B_SPI); // stop SPI asking for more data
160  while (SPI_SR(LED_WS2812B_SPI) & SPI_SR_BSY); // wait for data to be shifted out
161  timer_disable_counter(LED_WS2812B_TIMER); // stop clock
162  dma_disable_channel(LED_WS2812B_DMA, LED_WS2812B_DMA_CH); // stop using DMA
163  transmit_flag = false; // transmission completed
164  }
165 }
void led_ws2812b_setup(void)
setup WS2812B LED driver
Definition: led_ws2812b.c:94
#define LED_WS2812B_SPI
SPI peripheral.
Definition: led_ws2812b.h:30
#define LED_WS2812B_DMA_CH
DMA channel (only DMA1 channel 3 supports SPI1_TX interrupt)
Definition: led_ws2812b.h:53
#define LED_WS2812B_DMA_RCC
DMA peripheral clock.
Definition: led_ws2812b.h:52
#define LED_WS2812B_SPI_PORT
SPI port.
Definition: led_ws2812b.h:34
bool led_ws2812b_transmit(void)
transmit color values to WS2812B LEDs
Definition: led_ws2812b.c:76
#define LED_WS2812B_TIMER_OC
timer output compare used to set PWM frequency
Definition: led_ws2812b.h:43
#define LED_WS2812B_TIMER_RCC
timer peripheral clock
Definition: led_ws2812b.h:42
#define LED_WS2812B_SPI_CLK
SPI clock pin (PA5), connect to PWM output.
Definition: led_ws2812b.h:35
#define LED_WS2812B_SPI_RCC
SPI peripheral clock.
Definition: led_ws2812b.h:32
global definitions and methods
static volatile bool transmit_flag
flag set in software when transmission started, clear by interrupt when transmission completed ...
Definition: led_ws2812b.c:48
#define LED_WS2812B_CLK_RCC
timer port peripheral clock
Definition: led_ws2812b.h:44
#define LED_WS2812B_SPI_PORT_RCC
SPI I/O peripheral clock.
Definition: led_ws2812b.h:33
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:47
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:50
#define LED_WS2812B_CLK_PORT
timer port
Definition: led_ws2812b.h:45
#define LED_WS2812B_SPI_DOUT
SPI data pin (PA6), connect to WS2812B DIN.
Definition: led_ws2812b.h:36
#define LED_WS2812B_CLK_PIN
timer pin to output PWM (PB0), connect to SPI clock input
Definition: led_ws2812b.h:46
#define LED_WS2812B_LEDS
number of LEDs on the WS2812B strip
Definition: led_ws2812b.h:24
#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:45
#define LED_WS2812B_TIMER
timer peripheral
Definition: led_ws2812b.h:41
#define LED_WS2812B_SPI_DR
SPI data register for the DMA.
Definition: led_ws2812b.h:31
#define LED_WS2812B_DMA
DMA peripheral to put data for WS2812B LED in SPI queue (only DMA1 supports SPI1_TX interrupt) ...
Definition: led_ws2812b.h:51
void LED_WS2812B_DMA_ISR(void)
DMA interrupt service routine to stop transmission after it finished.
Definition: led_ws2812b.c:154
#define LED_WS2812B_DMA_IRQ
DMA channel interrupt signal.
Definition: led_ws2812b.h:54
library to drive a WS2812B LED chain (API)