led_sk6812rgbw: extended version of WS2812B library, supporting 4th color, using bit banding, removing need of timer

This commit is contained in:
King Kévin 2020-09-27 11:57:46 +02:00
parent 620e46938e
commit 6574a65ea6
2 changed files with 139 additions and 0 deletions

115
lib/led_sk6812rgbw.c Normal file
View File

@ -0,0 +1,115 @@
/** library to drive a SK6812RGBW 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_sk6812rgbw_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_sk6812rgbw.h" // LED SK6812RGBW library API
#include "global.h" // common methods
/** peripheral configuration */
/** @defgroup led_sk6812rgbw_spi SPI peripheral used to control the SK6812RGBW LEDs
* @{
*/
/** SPI peripheral
* @note SPI2 is 5V tolerant and can be operated in open-drain mode with 1-10 kO pull-up resistor to 5V to get 5V signal level
*/
#define LED_SK6812RGBW_SPI 2 /**< SPI peripheral */
/** @} */
/** bit template to encode one byte to be shifted out by SPI to the SK6812RGBW LEDs
* @details For each SK6812RGBW bit which needs to be transferred we require to transfer 3 SPI bits.
* The first SPI bit is the high start of the SK6812RGBW bit frame.
* The second SPI bit determines if the SK6812RGBW bit is a 0 or 1.
* The third SPI bit is the last part of the SK6812RGBW bit frame, which is always low.
* The binary pattern is 0b100100100100100100100100
* Since we encode it LSb first (for easy bit banding across bytes): 01001001 10010010 00100100 = 0x499224
*/
#define LED_SK6812RGBW_SPI_TEMPLATE 0x499224
/** data encoded to be shifted out by SPI for the SK6812RGBW, plus the 50us reset (~40 data bits) */
static uint8_t led_sk6812rgbw_data[LED_SK6812RGBW_LEDS * 3 * 4 + 40 * 3 / 8 + 1] = {0};
void led_sk6812rgbw_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white)
{
// verify the led exists
if (led >= LED_SK6812RGBW_LEDS) {
return;
}
// we use bit-banding to set the bit. I did not test if it is more efficient than setting the bit directly, but I wanted to use this cool feature
uint32_t data_bitband = 0x22000000 + ((uint32_t)&led_sk6812rgbw_data[0] - 0x20000000) * 32; // get base address of data to bit band
data_bitband += led * 4 * 24 * 4; // get address for current LED (there are 4 colors per LED, each color uses 24 bits, each bit is encoded in one 32-bit word)
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
const uint8_t data_bit = (7 - bit) * 3 + 1; // the bit in the data stream
// set the bitband bit to the corresponding color bit
*(uint32_t*)(data_bitband + (0 * 24 + data_bit) * 4) = ((green & byte_bit) ? 1 : 0);
*(uint32_t*)(data_bitband + (1 * 24 + data_bit) * 4) = ((red & byte_bit) ? 1 : 0);
*(uint32_t*)(data_bitband + (2 * 24 + data_bit) * 4) = ((blue & byte_bit) ? 1 : 0);
*(uint32_t*)(data_bitband + (3 * 24 + data_bit) * 4) = ((white & byte_bit) ? 1 : 0);
}
}
void led_sk6812rgbw_setup(void)
{
// fill buffer with bit pattern
for (uint16_t led = 0; led < LED_SK6812RGBW_LEDS; led++) {
for (uint8_t color = 0; color < 4; color++) {
led_sk6812rgbw_data[led * 3 * 4 + color * 3 + 0] = (uint8_t)(LED_SK6812RGBW_SPI_TEMPLATE >> 16);
led_sk6812rgbw_data[led * 3 * 4 + color * 3 + 1] = (uint8_t)(LED_SK6812RGBW_SPI_TEMPLATE >> 8);
led_sk6812rgbw_data[led * 3 * 4 + color * 3 + 2] = (uint8_t)(LED_SK6812RGBW_SPI_TEMPLATE >> 0);
}
}
// fill remaining with with 0 to encode the reset code
for (uint16_t i = LED_SK6812RGBW_LEDS * 3 * 4; i < LENGTH(led_sk6812rgbw_data); i++) {
led_sk6812rgbw_data[i] = 0;
}
// setup SPI to transmit data
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(LED_SK6812RGBW_SPI)); // enable clock for SPI output peripheral
gpio_set_mode(SPI_MOSI_PORT(LED_SK6812RGBW_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(LED_SK6812RGBW_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_SK6812RGBW_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(LED_SK6812RGBW_SPI)); // clear SPI values to default
spi_set_master_mode(SPI(LED_SK6812RGBW_SPI)); // set SPI as master since we generate the clock
spi_set_baudrate_prescaler(SPI(LED_SK6812RGBW_SPI), SPI_CR1_BR_FPCLK_DIV_16); // set clock to 0.44 us per bit
spi_set_clock_polarity_1(SPI(LED_SK6812RGBW_SPI)); // clock is high when idle
spi_set_clock_phase_1(SPI(LED_SK6812RGBW_SPI)); // output data on second edge (rising)
spi_set_dff_8bit(SPI(LED_SK6812RGBW_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts)
spi_send_lsb_first(SPI(LED_SK6812RGBW_SPI)); // send least significant bit first (helps during bit banding)
spi_set_bidirectional_transmit_only_mode(SPI(LED_SK6812RGBW_SPI)); // we won't receive data
spi_set_unidirectional_mode(SPI(LED_SK6812RGBW_SPI)); // we only need to transmit data
spi_enable_software_slave_management(SPI(LED_SK6812RGBW_SPI)); // control the slave select in software (so we can start transmission)
spi_set_nss_high(SPI(LED_SK6812RGBW_SPI)); // set NSS high so we can output
spi_enable(SPI(LED_SK6812RGBW_SPI)); // enable SPI
// configure DMA to provide the pattern to be shifted out from SPI to the SK6812RGBW LEDs
rcc_periph_clock_enable(RCC_DMA_SPI(LED_SK6812RGBW_SPI)); // enable clock for DMA peripheral
dma_channel_reset(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI)); // start with fresh channel configuration
dma_set_read_from_memory(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI)); // set direction from memory to peripheral
dma_set_memory_size(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI), DMA_CCR_MSIZE_8BIT); // read 8 bits from memory
dma_set_memory_address(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI), (uint32_t)led_sk6812rgbw_data); // set bit pattern as source address
dma_enable_memory_increment_mode(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI)); // go through bit pattern
dma_set_number_of_data(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI), LENGTH(led_sk6812rgbw_data)); // set the size of the data to transmit
dma_set_peripheral_address(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI), (uint32_t)&SPI_DR(SPI(LED_SK6812RGBW_SPI))); // set SPI as peripheral destination address
dma_set_peripheral_size(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI), DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral
dma_set_priority(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI), DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
dma_enable_circular_mode(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI)); // always send the data
dma_enable_channel(DMA_SPI(LED_SK6812RGBW_SPI), DMA_CHANNEL_SPI_TX(LED_SK6812RGBW_SPI)); // enable DMA channel
spi_enable_tx_dma(SPI(LED_SK6812RGBW_SPI)); // use DMA to provide data stream to be transferred
}

24
lib/led_sk6812rgbw.h Normal file
View File

@ -0,0 +1,24 @@
/** library to drive a SK6812RGBW 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_sk6812rgbw_spi, DMA
*/
#pragma once
/** number of LEDs on the SK6812RGBW strip */
#define LED_SK6812RGBW_LEDS (16 * 2)
/** setup SK6812RGBW LED driver
* @note this starts the continuous transmission
*/
void led_sk6812rgbw_setup(void);
/** set color of a single LED
* @param[in] led the LED number to set the color
* @param[in] red the red color value to set on the LED
* @param[in] green the green color value to set on the LED
* @param[in] blue the blue color value to set on the LED
* @param[in] white the white color value to set on the LED
*/
void led_sk6812rgbw_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white);