/* 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 . * */ /* Copyright (c) 2016 King Kévin */ /* this library is used to drive a WS2812b LED chain */ /* peripherals used: SPI , timer, DMA (check source for details) */ /* standard libraries */ #include // standard integer types #include // general utilities /* STM32 (including CM3) libraries */ #include // real-time control clock library #include // general purpose input output library #include // SPI library #include // timer library #include // DMA library #include // interrupt handler #include // Cortex M3 utilities #include "led_ws2812b.h" // LED WS2812b library API #include "global.h" // global definitions /* WS2812b peripheral configuration */ #define WS2812B_SPI SPI1 #define WS2812B_SPI_DR SPI1_DR #define WS2812B_SPI_RCC RCC_SPI1 #define WS2812B_SPI_PORT GPIOA #define WS2812B_SPI_CLK GPIO_SPI1_SCK #define WS2812B_SPI_DOUT GPIO_SPI1_MISO #define WS2812B_TIMER TIM3 #define WS2812B_TIMER_RCC RCC_TIM3 #define WS2812B_TIMER_OC TIM_OC3 #define WS2812B_CLK_RCC RCC_GPIOB #define WS2812B_CLK_PORT GPIOB #define WS2812B_CLK_PIN GPIO_TIM3_CH3 #define WS2812B_DMA DMA1 // DMA1 supports SPI1_TX interrupt #define WS2812B_DMA_RCC RCC_DMA1 // follows previous definition #define WS2812B_DMA_CH DMA_CHANNEL3 // only DMA1 channel 3 supports SPI1_TX interrupt #define WS2812B_DMA_IRQ NVIC_DMA1_CHANNEL3_IRQ // follows previous definition #define WS2812B_DMA_ISR dma1_channel3_isr // follows previous definition /* template to encode one byte * 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 * only the first 24 bits (3*8) are used */ #define WS2812B_SPI_TEMPLATE 0b10010010010010010010010000000000 uint8_t ws2812b_data[WS2812B_LEDS*3*3+40*3/8] = {0}; // SPI encode data to be shifted out for WS2812b + the 50us reset static volatile bool transmit_flag = false; // is transmission ongoing /* set color of a single LED * transmission needs to be done separately */ void ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue) { // verify the led exists if (led>=WS2812B_LEDS) { return; } // wait for transmission to complete before changing the color while (transmit_flag) { __WFI(); } uint8_t colors[] = {green, red, blue}; // generate the pattern for (uint8_t color=0; color>bit)&0b1)<<(bit*3+9); // encode the data bits in the pattern } // store pattern ws2812b_data[led*3*3+color*3+0] = (bits_color>>24); ws2812b_data[led*3*3+color*3+1] = (bits_color>>16); ws2812b_data[led*3*3+color*3+2] = (bits_color>>8); } } /* transmit colors to LEDs */ void ws2812b_transmit(void) { while (transmit_flag) { // wait for previous transmission to complete __WFI(); } transmit_flag = true; // remember transmission started dma_set_memory_address(WS2812B_DMA, WS2812B_DMA_CH, (uint32_t)ws2812b_data); dma_set_number_of_data(WS2812B_DMA, WS2812B_DMA_CH, LENGTH(ws2812b_data)); // set the size of the data to transmit dma_enable_transfer_complete_interrupt(WS2812B_DMA, WS2812B_DMA_CH); // warm when transfer is complete to stop transmission dma_enable_channel(WS2812B_DMA, WS2812B_DMA_CH); // enable DMA channel spi_enable_tx_dma(WS2812B_SPI); // use DMA to provide data stream to be transfered timer_set_counter(WS2812B_TIMER, 0); // reset timer counter fro clean clock timer_enable_counter(WS2812B_TIMER); // start timer to generate clock } /* setup WS2812b LED controller */ void ws2812b_setup(void) { /* setup timer to generate clock of (using PWM): 800kHz*3 */ rcc_periph_clock_enable(WS2812B_CLK_RCC); // enable clock for GPIO peripheral gpio_set_mode(WS2812B_CLK_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, WS2812B_CLK_PIN); // set pin a output rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function (PWM) rcc_periph_clock_enable(WS2812B_TIMER_RCC); // enable clock for timer peripheral timer_reset(WS2812B_TIMER); // reset timer state 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 timer_set_prescaler(WS2812B_TIMER, 0); // no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz) 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 timer_set_oc_value(WS2812B_TIMER, WS2812B_TIMER_OC, rcc_ahb_frequency/800000/3/2); // duty cycle to 50% timer_set_oc_mode(WS2812B_TIMER, WS2812B_TIMER_OC, TIM_OCM_PWM1); // set timer to generate PWM (used as clock) timer_enable_oc_output(WS2812B_TIMER, WS2812B_TIMER_OC); // enable output to generate the clock /* setup SPI to transmit data (we are slave and the clock comes from the above PWM): 3 SPI bits for 1 WS2812b bit */ rcc_periph_clock_enable(WS2812B_SPI_RCC); // enable clock for SPI peripheral gpio_set_mode(WS2812B_SPI_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, WS2812B_SPI_CLK); // set clock as input gpio_set_mode(WS2812B_SPI_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, WS2812B_SPI_DOUT); // set MISO as output spi_reset(WS2812B_SPI); // clear SPI values to default spi_set_slave_mode(WS2812B_SPI); // set SPI as slave (since we use the clock as input) spi_set_bidirectional_transmit_only_mode(WS2812B_SPI); // we won't receive data spi_set_unidirectional_mode(WS2812B_SPI); // we only need to transmit data spi_set_dff_8bit(WS2812B_SPI); // use 8 bits for simpler encoding (but there will be more interrupts) spi_set_clock_polarity_1(WS2812B_SPI); // clock is high when idle spi_set_clock_phase_1(WS2812B_SPI); // output data on second edge (rising) spi_send_msb_first(WS2812B_SPI); // send least significant bit first spi_enable_software_slave_management(WS2812B_SPI); // control the slave select in software (since there is no master) spi_set_nss_low(WS2812B_SPI); // set NSS low so we can output spi_enable(WS2812B_SPI); // enable SPI // do not disable SPI or set NSS high since it will put MISO high, breaking the beginning of the next transmission /* configure DMA to provide the pattern to be shifted out from SPI to the WS2812b LEDs */ rcc_periph_clock_enable(WS2812B_DMA_RCC); // enable clock for DMA peripheral dma_channel_reset(WS2812B_DMA, WS2812B_DMA_CH); // start with fresh channel configuration dma_set_memory_address(WS2812B_DMA, WS2812B_DMA_CH, (uint32_t)ws2812b_data); // set bit pattern as source address dma_set_peripheral_address(WS2812B_DMA, WS2812B_DMA_CH, (uint32_t)&WS2812B_SPI_DR); // set SPI as peripheral destination address dma_set_read_from_memory(WS2812B_DMA, WS2812B_DMA_CH); // set direction from memory to peripheral dma_enable_memory_increment_mode(WS2812B_DMA, WS2812B_DMA_CH); // go through bit pattern dma_set_memory_size(WS2812B_DMA, WS2812B_DMA_CH, DMA_CCR_MSIZE_8BIT); // read 8 bits from memory dma_set_peripheral_size(WS2812B_DMA, WS2812B_DMA_CH, DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral dma_set_priority(WS2812B_DMA, WS2812B_DMA_CH, DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral nvic_enable_irq(WS2812B_DMA_IRQ); // enable interrupts for this DMA channel // reset color for (uint16_t led=0; led