led_ws2812b: replace timer with SPI being master

This commit is contained in:
King Kévin 2020-03-08 22:31:23 +01:00
parent 7030480482
commit 607ba7e9b7
2 changed files with 16 additions and 43 deletions

View File

@ -16,7 +16,7 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
* @note peripherals used: SPI @ref led_ws2812b_spi, timer @ref led_ws2812b_timer, DMA (for SPI MISO)
* @note peripherals used: SPI @ref led_ws2812b_spi, DMA
*/
/* standard libraries */
@ -28,7 +28,6 @@
#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
@ -39,19 +38,11 @@
/** @defgroup led_ws2812b_spi SPI peripheral used to control the WS2812B LEDs
* @{
*/
#define LED_WS2812B_SPI 1 /**< SPI peripheral */
/** @} */
/** @defgroup led_ws2812b_timer timer peripheral used to generate SPI clock
* @{
*/
#define LED_WS2812B_TIMER 3 /**< timer peripheral */
#define LED_WS2812B_CLK_CH 3 /**< timer channel to output PWM (PB0), connect to SPI clock input */
#define LED_WS2812B_TIMER_OC TIM_OC3 /**< timer output compare used to set PWM frequency */
#define LED_WS2812B_SPI 2 /**< SPI peripheral */
/** @} */
/** 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.
* @details For each WS2812B bit which needs to be transferred 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.
@ -99,47 +90,30 @@ bool led_ws2812b_transmit(void)
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
dma_enable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // enable DMA channel
spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transfered
spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transferred
timer_set_counter(TIM(LED_WS2812B_TIMER), 0); // reset timer counter fro clean clock
timer_enable_counter(TIM(LED_WS2812B_TIMER)); // start timer to generate clock
return true;
}
void led_ws2812b_setup(void)
{
// setup timer to generate clock of (using PWM): 800kHz*3
rcc_periph_clock_enable(RCC_TIM_CH(LED_WS2812B_TIMER, LED_WS2812B_CLK_CH)); // enable clock for GPIO peripheral
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
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function (PWM)
rcc_periph_clock_enable(RCC_TIM(LED_WS2812B_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(LED_WS2812B_TIMER)); // reset timer state
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
timer_set_prescaler(TIM(LED_WS2812B_TIMER), 0); // no prescaler to keep most precise timer (72MHz/2^16=1099<800kHz)
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
timer_set_oc_value(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC, rcc_ahb_frequency/800000/3/2); // duty cycle to 50%
timer_set_oc_mode(TIM(LED_WS2812B_TIMER), LED_WS2812B_TIMER_OC, TIM_OCM_PWM1); // set timer to generate PWM (used as clock)
timer_enable_oc_output(TIM(LED_WS2812B_TIMER), LED_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(RCC_SPI_SCK_PORT(LED_WS2812B_SPI)); // enable clock for SPI IO peripheral
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
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(LED_WS2812B_SPI)); // enable clock for SPI IO peripheral
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
// setup SPI to transmit data
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(LED_WS2812B_SPI)); // enable clock for SPI output peripheral
gpio_set_mode(SPI_MOSI_PORT(LED_WS2812B_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(LED_WS2812B_SPI)); // set MISO as output (needs to be shifted to 5V)
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(RCC_SPI(LED_WS2812B_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(LED_WS2812B_SPI)); // clear SPI values to default
spi_set_slave_mode(SPI(LED_WS2812B_SPI)); // set SPI as slave (since we use the clock as input)
spi_set_bidirectional_transmit_only_mode(SPI(LED_WS2812B_SPI)); // we won't receive data
spi_set_unidirectional_mode(SPI(LED_WS2812B_SPI)); // we only need to transmit data
spi_set_dff_8bit(SPI(LED_WS2812B_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts)
spi_set_master_mode(SPI(LED_WS2812B_SPI)); // set SPI as master since we generate the clock
spi_set_baudrate_prescaler(SPI(LED_WS2812B_SPI), SPI_CR1_BR_FPCLK_DIV_16); // set clock to 0.44 us per bit
spi_set_clock_polarity_1(SPI(LED_WS2812B_SPI)); // clock is high when idle
spi_set_clock_phase_1(SPI(LED_WS2812B_SPI)); // output data on second edge (rising)
spi_send_msb_first(SPI(LED_WS2812B_SPI)); // send least significant bit first
spi_enable_software_slave_management(SPI(LED_WS2812B_SPI)); // control the slave select in software (since there is no master)
spi_set_nss_low(SPI(LED_WS2812B_SPI)); // set NSS low so we can output
spi_set_dff_8bit(SPI(LED_WS2812B_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts)
spi_send_msb_first(SPI(LED_WS2812B_SPI)); // send most significant bit first
spi_set_bidirectional_transmit_only_mode(SPI(LED_WS2812B_SPI)); // we won't receive data
spi_set_unidirectional_mode(SPI(LED_WS2812B_SPI)); // we only need to transmit data
spi_enable_software_slave_management(SPI(LED_WS2812B_SPI)); // control the slave select in software (so we can start transmission)
spi_set_nss_high(SPI(LED_WS2812B_SPI)); // set NSS high so we can output
spi_enable(SPI(LED_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(RCC_DMA_SPI(LED_WS2812B_SPI)); // enable clock for DMA peripheral
@ -174,7 +148,6 @@ void DMA_ISR_SPI_TX(LED_WS2812B_SPI)(void)
dma_disable_transfer_complete_interrupt(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // stop warning transfer completed
spi_disable_tx_dma(SPI(LED_WS2812B_SPI)); // stop SPI asking for more data
while (SPI_SR(SPI(LED_WS2812B_SPI)) & SPI_SR_BSY); // wait for data to be shifted out
timer_disable_counter(TIM(LED_WS2812B_TIMER)); // stop clock
dma_disable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // stop using DMA
transmit_flag = false; // transmission completed
}

View File

@ -16,7 +16,7 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
* @note peripherals used: SPI @ref led_ws2812b_spi, timer @ref led_ws2812b_timer, DMA (for SPI MISO)
* @note peripherals used: SPI @ref led_ws2812b_spi, DMA
*/
#pragma once