2016-02-18 10:39:08 +01:00
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*
*/
2016-06-12 23:11:37 +02:00
/** library to drive a WS2812B LED chain (code)
2016-05-01 15:11:33 +02:00
* @ file led_ws2812b . c
2016-03-24 10:37:42 +01:00
* @ author King Kévin < kingkevin @ cuvoodoo . info >
* @ date 2016
2016-05-01 15:11:33 +02:00
* @ note peripherals used : SPI @ ref led_ws2812b_spi , timer @ ref led_ws2812b_timer , DMA @ ref led_ws2812b_dma
2016-03-24 10:37:42 +01: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 */
# 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/timer.h> // timer library
# include <libopencm3/stm32/dma.h> // DMA library
# include <libopencm3/cm3/nvic.h> // interrupt handler
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
2016-06-12 23:11:37 +02:00
# include "led_ws2812b.h" // LED WS2812B library API
2016-03-28 18:55:32 +02:00
# include "global.h" // common methods
2016-02-18 10:39:08 +01:00
2016-06-12 23:11:37 +02:00
/** bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs
* @ details For each WS2812B bit which needs to be transfered we require to transfer 3 SPI bits .
* 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 .
2016-03-28 18:55:32 +02:00
* The binary pattern is 0 b100100100100100100100100
2016-03-24 10:37:42 +01:00
*/
2016-05-01 15:03:41 +02:00
# define LED_WS2812B_SPI_TEMPLATE 0x924924
2016-03-24 10:37:42 +01:00
2016-06-12 23:11:37 +02:00
uint8_t led_ws2812b_data [ LED_WS2812B_LEDS * 3 * 3 + 40 * 3 / 8 + 1 ] = { 0 } ; /**< data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) */
2016-03-25 11:42:41 +01:00
static volatile bool transmit_flag = false ; /**< flag set in software when transmission started, clear by interrupt when transmission completed */
2016-02-18 10:39:08 +01:00
2016-05-01 15:03:41 +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
2016-05-01 15:03:41 +02:00
if ( led > = LED_WS2812B_LEDS ) {
2016-02-18 10:39:08 +01:00
return ;
}
// wait for transmission to complete before changing the color
while ( transmit_flag ) {
__WFI ( ) ;
}
2016-06-12 23:11:37 +02:00
const uint8_t colors [ ] = { green , red , blue } ; // color order for the WS2812B
2016-03-28 18:55:32 +02:00
const uint8_t pattern_bit [ ] = { 0x02 , 0x10 , 0x80 , 0x04 , 0x20 , 0x01 , 0x08 , 0x40 } ; // which bit to change in the pattern
const uint8_t pattern_byte [ ] = { 2 , 2 , 2 , 1 , 1 , 0 , 0 , 0 } ; // in which byte in the pattern to write the pattern bit
for ( uint8_t color = 0 ; color < LENGTH ( colors ) ; color + + ) { // colors are encoded similarly
// fill the middle bit (fixed is faster than calculating it)
for ( uint8_t bit = 0 ; bit < 8 ; bit + + ) { // bit from the color to set/clear
if ( colors [ color ] & ( 1 < < bit ) ) { // setting bit
2016-05-01 15:03:41 +02:00
led_ws2812b_data [ led * 3 * 3 + color * 3 + pattern_byte [ bit ] ] | = pattern_bit [ bit ] ; // setting bit is pattern
2016-03-28 18:55:32 +02:00
} else { // clear bit
2016-05-01 15:03:41 +02:00
led_ws2812b_data [ led * 3 * 3 + color * 3 + pattern_byte [ bit ] ] & = ~ pattern_bit [ bit ] ; // clearing bit is pattern
2016-03-28 18:55:32 +02:00
}
2016-02-18 10:39:08 +01:00
}
}
}
2016-05-01 15:03:41 +02:00
bool led_ws2812b_transmit ( void )
2016-02-18 10:39:08 +01:00
{
2016-04-16 12:12:47 +02:00
if ( transmit_flag ) { // a transmission is already ongoing
return false ;
2016-02-18 10:39:08 +01:00
}
transmit_flag = true ; // remember transmission started
2016-05-01 15:03:41 +02:00
dma_set_memory_address ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , ( uint32_t ) led_ws2812b_data ) ;
dma_set_number_of_data ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , LENGTH ( led_ws2812b_data ) ) ; // set the size of the data to transmit
dma_enable_transfer_complete_interrupt ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH ) ; // warm when transfer is complete to stop transmission
dma_enable_channel ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH ) ; // enable DMA channel
2016-02-18 10:39:08 +01:00
2016-05-01 15:03:41 +02:00
spi_enable_tx_dma ( LED_WS2812B_SPI ) ; // use DMA to provide data stream to be transfered
2016-02-18 10:39:08 +01:00
2016-05-01 15:03:41 +02:00
timer_set_counter ( LED_WS2812B_TIMER , 0 ) ; // reset timer counter fro clean clock
timer_enable_counter ( LED_WS2812B_TIMER ) ; // start timer to generate clock
2016-04-16 12:12:47 +02:00
return true ;
2016-02-18 10:39:08 +01:00
}
2016-05-01 15:03:41 +02:00
void led_ws2812b_setup ( void )
2016-02-18 10:39:08 +01:00
{
2016-05-01 20:56:40 +02:00
// setup timer to generate clock of (using PWM): 800kHz*3
2016-05-01 15:03:41 +02:00
rcc_periph_clock_enable ( LED_WS2812B_CLK_RCC ) ; // enable clock for GPIO peripheral
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
2016-02-18 10:39:08 +01:00
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable clock for alternate function (PWM)
2016-05-01 15:03:41 +02:00
rcc_periph_clock_enable ( LED_WS2812B_TIMER_RCC ) ; // enable clock for timer peripheral
timer_reset ( LED_WS2812B_TIMER ) ; // reset timer state
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
timer_set_prescaler ( LED_WS2812B_TIMER , 0 ) ; // no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
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
timer_set_oc_value ( LED_WS2812B_TIMER , LED_WS2812B_TIMER_OC , rcc_ahb_frequency / 800000 / 3 / 2 ) ; // duty cycle to 50%
timer_set_oc_mode ( LED_WS2812B_TIMER , LED_WS2812B_TIMER_OC , TIM_OCM_PWM1 ) ; // set timer to generate PWM (used as clock)
timer_enable_oc_output ( LED_WS2812B_TIMER , LED_WS2812B_TIMER_OC ) ; // enable output to generate the clock
2016-02-18 10:39:08 +01:00
2016-06-12 23:11:37 +02:00
// setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812B bit
2016-05-01 20:27:42 +02:00
rcc_periph_clock_enable ( LED_WS2812B_SPI_PORT_RCC ) ; // enable clock for SPI IO peripheral
2016-05-01 15:03:41 +02:00
gpio_set_mode ( LED_WS2812B_SPI_PORT , GPIO_MODE_INPUT , GPIO_CNF_INPUT_FLOAT , LED_WS2812B_SPI_CLK ) ; // set clock as input
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
2016-05-01 20:27:42 +02:00
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable clock for SPI alternate function
rcc_periph_clock_enable ( LED_WS2812B_SPI_RCC ) ; // enable clock for SPI peripheral
2016-05-01 15:03:41 +02:00
spi_reset ( LED_WS2812B_SPI ) ; // clear SPI values to default
spi_set_slave_mode ( LED_WS2812B_SPI ) ; // set SPI as slave (since we use the clock as input)
spi_set_bidirectional_transmit_only_mode ( LED_WS2812B_SPI ) ; // we won't receive data
spi_set_unidirectional_mode ( LED_WS2812B_SPI ) ; // we only need to transmit data
spi_set_dff_8bit ( LED_WS2812B_SPI ) ; // use 8 bits for simpler encoding (but there will be more interrupts)
spi_set_clock_polarity_1 ( LED_WS2812B_SPI ) ; // clock is high when idle
spi_set_clock_phase_1 ( LED_WS2812B_SPI ) ; // output data on second edge (rising)
spi_send_msb_first ( LED_WS2812B_SPI ) ; // send least significant bit first
spi_enable_software_slave_management ( LED_WS2812B_SPI ) ; // control the slave select in software (since there is no master)
spi_set_nss_low ( LED_WS2812B_SPI ) ; // set NSS low so we can output
spi_enable ( LED_WS2812B_SPI ) ; // enable SPI
2016-02-18 10:39:08 +01:00
// do not disable SPI or set NSS high since it will put MISO high, breaking the beginning of the next transmission
2016-06-12 23:11:37 +02:00
// configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
2016-05-01 15:03:41 +02:00
rcc_periph_clock_enable ( LED_WS2812B_DMA_RCC ) ; // enable clock for DMA peripheral
dma_channel_reset ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH ) ; // start with fresh channel configuration
dma_set_memory_address ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , ( uint32_t ) led_ws2812b_data ) ; // set bit pattern as source address
dma_set_peripheral_address ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , ( uint32_t ) & LED_WS2812B_SPI_DR ) ; // set SPI as peripheral destination address
dma_set_read_from_memory ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH ) ; // set direction from memory to peripheral
dma_enable_memory_increment_mode ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH ) ; // go through bit pattern
dma_set_memory_size ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , DMA_CCR_MSIZE_8BIT ) ; // read 8 bits from memory
dma_set_peripheral_size ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , DMA_CCR_PSIZE_8BIT ) ; // write 8 bits to peripheral
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
nvic_enable_irq ( LED_WS2812B_DMA_IRQ ) ; // enable interrupts for this DMA channel
2016-02-18 10:39:08 +01:00
2016-03-28 18:55:32 +02:00
// fill buffer with bit pattern
2016-05-01 15:03:41 +02:00
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 ) ;
2016-03-28 18:55:32 +02:00
}
// fill remaining with with 0 to encode the reset code
2016-05-01 15:03:41 +02:00
for ( uint16_t i = LED_WS2812B_LEDS * 3 * 3 ; i < LENGTH ( led_ws2812b_data ) ; i + + ) {
led_ws2812b_data [ i ] = 0 ;
2016-02-18 10:39:08 +01:00
}
2016-05-01 15:03:41 +02:00
led_ws2812b_transmit ( ) ; // set LEDs
2016-02-18 10:39:08 +01:00
}
2016-05-05 23:47:50 +02:00
/** DMA interrupt service routine to stop transmission after it finished */
2016-05-01 15:03:41 +02:00
void LED_WS2812B_DMA_ISR ( void )
2016-02-18 10:39:08 +01:00
{
2016-05-01 15:03:41 +02:00
if ( dma_get_interrupt_flag ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , DMA_TCIF ) ) { // transfer completed
dma_clear_interrupt_flags ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH , DMA_TCIF ) ; // clear flag
dma_disable_transfer_complete_interrupt ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH ) ; // stop warning transfer completed
spi_disable_tx_dma ( LED_WS2812B_SPI ) ; // stop SPI asking for more data
while ( SPI_SR ( LED_WS2812B_SPI ) & SPI_SR_BSY ) ; // wait for data to be shifted out
timer_disable_counter ( LED_WS2812B_TIMER ) ; // stop clock
dma_disable_channel ( LED_WS2812B_DMA , LED_WS2812B_DMA_CH ) ; // stop using DMA
2016-02-18 10:39:08 +01:00
transmit_flag = false ; // transmission completed
}
}