129 lines
7.0 KiB
C
129 lines
7.0 KiB
C
/** library to drive a WS2812B LED chain
|
|
* @file
|
|
* @author King Kévin <kingkevin@cuvoodoo.info>
|
|
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
|
* @date 2016-2020
|
|
* @note peripherals used: SPI @ref led_ws2812b_spi, DMA
|
|
*/
|
|
|
|
/* standard libraries */
|
|
#include <stdint.h> // standard integer types
|
|
#include <stdlib.h> // general utilities
|
|
|
|
/* STM32 (including CM3) libraries */
|
|
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
|
#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/dma.h> // DMA library
|
|
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
|
|
|
#include "led_ws2812b.h" // LED WS2812B library API
|
|
#include "global.h" // common methods
|
|
|
|
/** peripheral configuration */
|
|
/** @defgroup led_ws2812b_spi SPI peripheral used to control the WS2812B LEDs
|
|
* @{
|
|
*/
|
|
/** SPI peripheral
|
|
* @note SPI2 is 5V tolerant and can be operated in open-drain mode will 1 kO pull-up resistor to 5V to get 5V signal level
|
|
*/
|
|
#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 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.
|
|
* The binary pattern is 0b100100100100100100100100
|
|
*/
|
|
#define LED_WS2812B_SPI_TEMPLATE 0x924924
|
|
|
|
/** data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) */
|
|
uint8_t led_ws2812b_data[LED_WS2812B_LEDS * 3 * 3 + 40 * 3 / 8 + 1] = {0};
|
|
|
|
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
|
|
{
|
|
// verify the led exists
|
|
if (led >= LED_WS2812B_LEDS) {
|
|
return;
|
|
}
|
|
|
|
// convert RGB bit to SPI bit (each bit is encoded in 3 bits)
|
|
uint32_t red_spi = LED_WS2812B_SPI_TEMPLATE, green_spi = LED_WS2812B_SPI_TEMPLATE, blue_spi = LED_WS2812B_SPI_TEMPLATE;
|
|
uint32_t bit_spi = 0x2; // the bit to set in the SPI stream
|
|
for (uint8_t bit = 0; bit < 8; bit++) { // bit from the color to set/clear
|
|
const uint8_t byte_bit = (1 << bit); // which bit to check
|
|
if (red & byte_bit) {
|
|
red_spi |= bit_spi;
|
|
}
|
|
if (green & byte_bit) {
|
|
green_spi |= bit_spi;
|
|
}
|
|
if (blue & byte_bit) {
|
|
blue_spi |= bit_spi;
|
|
}
|
|
bit_spi <<= 3; // go to next SPI bit
|
|
}
|
|
|
|
// set calculated pattern in stream (GRB)
|
|
// we don't stop the DMA, instead we set LSB to MSB for gradient transition
|
|
led_ws2812b_data[led * 3 * 3 + 0 + 2] = (green_spi >> 0);
|
|
led_ws2812b_data[led * 3 * 3 + 3 + 2] = (red_spi >> 0);
|
|
led_ws2812b_data[led * 3 * 3 + 6 + 2] = (blue_spi >> 0);
|
|
led_ws2812b_data[led * 3 * 3 + 0 + 1] = (green_spi >> 8);
|
|
led_ws2812b_data[led * 3 * 3 + 3 + 1] = (red_spi >> 8);
|
|
led_ws2812b_data[led * 3 * 3 + 6 + 1] = (blue_spi >> 8);
|
|
led_ws2812b_data[led * 3 * 3 + 0 + 0] = (green_spi >> 16);
|
|
led_ws2812b_data[led * 3 * 3 + 3 + 0] = (red_spi >> 16);
|
|
led_ws2812b_data[led * 3 * 3 + 6 + 0] = (blue_spi >> 16);
|
|
}
|
|
|
|
void led_ws2812b_setup(void)
|
|
{
|
|
// fill buffer with bit pattern
|
|
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);
|
|
}
|
|
// fill remaining with with 0 to encode the reset code
|
|
for (uint16_t i = LED_WS2812B_LEDS * 3 * 3; i < LENGTH(led_ws2812b_data); i++) {
|
|
led_ws2812b_data[i] = 0;
|
|
}
|
|
|
|
// setup SPI to transmit data
|
|
rcc_periph_clock_enable(RCC_SPI_MOSI_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_OPENDRAIN, SPI_MOSI_PIN(LED_WS2812B_SPI)); // set MOSI as output (push-pull needs to be shifted to 5V, open-drain consumes up to 5 mA with pull-up resistor of 1 kO)
|
|
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_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_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
|
|
|
|
// 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
|
|
dma_channel_reset(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // start with fresh channel configuration
|
|
dma_set_read_from_memory(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // set direction from memory to peripheral
|
|
dma_set_memory_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_MSIZE_8BIT); // read 8 bits from memory
|
|
dma_set_memory_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)led_ws2812b_data); // set bit pattern as source address
|
|
dma_enable_memory_increment_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // go through bit pattern
|
|
dma_set_number_of_data(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), LENGTH(led_ws2812b_data)); // set the size of the data to transmit
|
|
dma_set_peripheral_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)&SPI_DR(SPI(LED_WS2812B_SPI))); // set SPI as peripheral destination address
|
|
dma_set_peripheral_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral
|
|
dma_set_priority(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
|
|
dma_enable_circular_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // always send the data
|
|
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 transferred
|
|
}
|