LED clock
 All Files Functions Variables Macros Groups Pages
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 
42 #define WS2812B_SPI SPI1
43 #define WS2812B_SPI_DR SPI1_DR
44 #define WS2812B_SPI_RCC RCC_SPI1
45 #define WS2812B_SPI_PORT GPIOA
46 #define WS2812B_SPI_CLK GPIO_SPI1_SCK
47 #define WS2812B_SPI_DOUT GPIO_SPI1_MISO
52 #define WS2812B_TIMER TIM3
53 #define WS2812B_TIMER_RCC RCC_TIM3
54 #define WS2812B_TIMER_OC TIM_OC3
55 #define WS2812B_CLK_RCC RCC_GPIOB
56 #define WS2812B_CLK_PORT GPIOB
57 #define WS2812B_CLK_PIN GPIO_TIM3_CH3
62 #define WS2812B_DMA DMA1
63 #define WS2812B_DMA_RCC RCC_DMA1
64 #define WS2812B_DMA_CH DMA_CHANNEL3
65 #define WS2812B_DMA_IRQ NVIC_DMA1_CHANNEL3_IRQ
66 #define WS2812B_DMA_ISR dma1_channel3_isr
76 #define WS2812B_SPI_TEMPLATE 0x924924
77 
78 uint8_t ws2812b_data[WS2812B_LEDS*3*3+40*3/8+1] = {0};
79 static volatile bool transmit_flag = false;
81 void ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
82 {
83  // verify the led exists
84  if (led>=WS2812B_LEDS) {
85  return;
86  }
87  // wait for transmission to complete before changing the color
88  while (transmit_flag) {
89  __WFI();
90  }
91 
92  const uint8_t colors[] = {green, red, blue}; // color order for the WS2812b
93  const uint8_t pattern_bit[] = {0x02, 0x10, 0x80, 0x04, 0x20, 0x01, 0x08, 0x40}; // which bit to change in the pattern
94  const uint8_t pattern_byte[] = {2,2,2,1,1,0,0,0}; // in which byte in the pattern to write the pattern bit
95  for (uint8_t color=0; color<LENGTH(colors); color++) { // colors are encoded similarly
96  // fill the middle bit (fixed is faster than calculating it)
97  for (uint8_t bit=0; bit<8; bit++) { // bit from the color to set/clear
98  if (colors[color]&(1<<bit)) { // setting bit
99  ws2812b_data[led*3*3+color*3+pattern_byte[bit]] |= pattern_bit[bit]; // setting bit is pattern
100  } else { // clear bit
101  ws2812b_data[led*3*3+color*3+pattern_byte[bit]] &= ~pattern_bit[bit]; // clearing bit is pattern
102  }
103  }
104  }
105 }
106 
108 {
109  while (transmit_flag) { // wait for previous transmission to complete
110  __WFI();
111  }
112  transmit_flag = true; // remember transmission started
113  dma_set_memory_address(WS2812B_DMA, WS2812B_DMA_CH, (uint32_t)ws2812b_data);
114  dma_set_number_of_data(WS2812B_DMA, WS2812B_DMA_CH, LENGTH(ws2812b_data)); // set the size of the data to transmit
115  dma_enable_transfer_complete_interrupt(WS2812B_DMA, WS2812B_DMA_CH); // warm when transfer is complete to stop transmission
116  dma_enable_channel(WS2812B_DMA, WS2812B_DMA_CH); // enable DMA channel
117 
118  spi_enable_tx_dma(WS2812B_SPI); // use DMA to provide data stream to be transfered
119 
120  timer_set_counter(WS2812B_TIMER, 0); // reset timer counter fro clean clock
121  timer_enable_counter(WS2812B_TIMER); // start timer to generate clock
122 }
123 
124 void ws2812b_setup(void)
125 {
126  /* setup timer to generate clock of (using PWM): 800kHz*3 */
127  rcc_periph_clock_enable(WS2812B_CLK_RCC); // enable clock for GPIO peripheral
128  gpio_set_mode(WS2812B_CLK_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, WS2812B_CLK_PIN); // set pin as output
129  rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function (PWM)
130  rcc_periph_clock_enable(WS2812B_TIMER_RCC); // enable clock for timer peripheral
131  timer_reset(WS2812B_TIMER); // reset timer state
132  timer_set_mode(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
133  timer_set_prescaler(WS2812B_TIMER, 0); // no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
134  timer_set_period(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
135  timer_set_oc_value(WS2812B_TIMER, WS2812B_TIMER_OC, rcc_ahb_frequency/800000/3/2); // duty cycle to 50%
136  timer_set_oc_mode(WS2812B_TIMER, WS2812B_TIMER_OC, TIM_OCM_PWM1); // set timer to generate PWM (used as clock)
137  timer_enable_oc_output(WS2812B_TIMER, WS2812B_TIMER_OC); // enable output to generate the clock
138 
139  /* setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812b bit */
140  rcc_periph_clock_enable(WS2812B_SPI_RCC); // enable clock for SPI peripheral
141  gpio_set_mode(WS2812B_SPI_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, WS2812B_SPI_CLK); // set clock as input
142  gpio_set_mode(WS2812B_SPI_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, WS2812B_SPI_DOUT); // set MISO as output
143  spi_reset(WS2812B_SPI); // clear SPI values to default
144  spi_set_slave_mode(WS2812B_SPI); // set SPI as slave (since we use the clock as input)
145  spi_set_bidirectional_transmit_only_mode(WS2812B_SPI); // we won't receive data
146  spi_set_unidirectional_mode(WS2812B_SPI); // we only need to transmit data
147  spi_set_dff_8bit(WS2812B_SPI); // use 8 bits for simpler encoding (but there will be more interrupts)
148  spi_set_clock_polarity_1(WS2812B_SPI); // clock is high when idle
149  spi_set_clock_phase_1(WS2812B_SPI); // output data on second edge (rising)
150  spi_send_msb_first(WS2812B_SPI); // send least significant bit first
151  spi_enable_software_slave_management(WS2812B_SPI); // control the slave select in software (since there is no master)
152  spi_set_nss_low(WS2812B_SPI); // set NSS low so we can output
153  spi_enable(WS2812B_SPI); // enable SPI
154  // do not disable SPI or set NSS high since it will put MISO high, breaking the beginning of the next transmission
155 
156  /* configure DMA to provide the pattern to be shifted out from SPI to the WS2812b LEDs */
157  rcc_periph_clock_enable(WS2812B_DMA_RCC); // enable clock for DMA peripheral
158  dma_channel_reset(WS2812B_DMA, WS2812B_DMA_CH); // start with fresh channel configuration
159  dma_set_memory_address(WS2812B_DMA, WS2812B_DMA_CH, (uint32_t)ws2812b_data); // set bit pattern as source address
160  dma_set_peripheral_address(WS2812B_DMA, WS2812B_DMA_CH, (uint32_t)&WS2812B_SPI_DR); // set SPI as peripheral destination address
161  dma_set_read_from_memory(WS2812B_DMA, WS2812B_DMA_CH); // set direction from memory to peripheral
162  dma_enable_memory_increment_mode(WS2812B_DMA, WS2812B_DMA_CH); // go through bit pattern
163  dma_set_memory_size(WS2812B_DMA, WS2812B_DMA_CH, DMA_CCR_MSIZE_8BIT); // read 8 bits from memory
164  dma_set_peripheral_size(WS2812B_DMA, WS2812B_DMA_CH, DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral
165  dma_set_priority(WS2812B_DMA, WS2812B_DMA_CH, DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
166  nvic_enable_irq(WS2812B_DMA_IRQ); // enable interrupts for this DMA channel
167 
168  // fill buffer with bit pattern
169  for (uint16_t i=0; i<WS2812B_LEDS*3; i++) {
170  ws2812b_data[i*3+0] = (uint8_t)(WS2812B_SPI_TEMPLATE>>16);
171  ws2812b_data[i*3+1] = (uint8_t)(WS2812B_SPI_TEMPLATE>>8);
172  ws2812b_data[i*3+2] = (uint8_t)(WS2812B_SPI_TEMPLATE>>0);
173  }
174  // fill remaining with with 0 to encode the reset code
175  for (uint16_t i=WS2812B_LEDS*3*3; i<LENGTH(ws2812b_data); i++) {
176  ws2812b_data[i] = 0;
177  }
178  ws2812b_transmit(); // set LEDs
179 }
180 
182 void WS2812B_DMA_ISR(void)
183 {
184  if (dma_get_interrupt_flag(WS2812B_DMA, WS2812B_DMA_CH, DMA_TCIF)) { // transfer completed
185  dma_clear_interrupt_flags(WS2812B_DMA, WS2812B_DMA_CH, DMA_TCIF); // clear flag
186  dma_disable_transfer_complete_interrupt(WS2812B_DMA, WS2812B_DMA_CH); // stop warning transfer completed
187  spi_disable_tx_dma(WS2812B_SPI); // stop SPI asking for more data
188  while (SPI_SR(WS2812B_SPI) & SPI_SR_BSY); // wait for data to be shifted out
189  timer_disable_counter(WS2812B_TIMER); // stop clock
190  dma_disable_channel(WS2812B_DMA, WS2812B_DMA_CH); // stop using DMA
191  transmit_flag = false; // transmission completed
192  }
193 }
#define WS2812B_SPI
Definition: led_ws2812b.c:42
void ws2812b_setup(void)
setup WS2812b LED driver
Definition: led_ws2812b.c:124
#define WS2812B_SPI_PORT
Definition: led_ws2812b.c:45
#define WS2812B_SPI_DOUT
Definition: led_ws2812b.c:47
global definitions and methods
static volatile bool transmit_flag
Definition: led_ws2812b.c:79
#define WS2812B_SPI_TEMPLATE
bit template to encode one byte to be shifted out by SPI to the WS2812b LEDs
Definition: led_ws2812b.c:76
#define WS2812B_TIMER
Definition: led_ws2812b.c:52
#define WS2812B_CLK_PIN
Definition: led_ws2812b.c:57
#define WS2812B_TIMER_OC
Definition: led_ws2812b.c:54
#define WS2812B_SPI_RCC
Definition: led_ws2812b.c:44
#define WS2812B_DMA_IRQ
Definition: led_ws2812b.c:65
#define WS2812B_DMA
Definition: led_ws2812b.c:62
void ws2812b_transmit(void)
transmit color values to WS2812b LEDs
Definition: led_ws2812b.c:107
#define WS2812B_SPI_CLK
Definition: led_ws2812b.c:46
#define WS2812B_DMA_RCC
Definition: led_ws2812b.c:63
#define WS2812B_DMA_CH
Definition: led_ws2812b.c:64
#define WS2812B_LEDS
Definition: led_ws2812b.h:24
#define WS2812B_CLK_PORT
Definition: led_ws2812b.c:56
#define WS2812B_DMA_ISR
Definition: led_ws2812b.c:66
#define WS2812B_SPI_DR
Definition: led_ws2812b.c:43
#define LENGTH(x)
Definition: global.h:26
#define WS2812B_TIMER_RCC
Definition: led_ws2812b.c:53
#define WS2812B_CLK_RCC
Definition: led_ws2812b.c:55
uint8_t ws2812b_data[WS2812B_LEDS *3 *3+40 *3/8+1]
Definition: led_ws2812b.c:78
library to drive a WS2812b LED chain (API)
void 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:81