Skip to content

Commit

Permalink
SPI DMA based RGB Underglow for STM32 (qmk#7674)
Browse files Browse the repository at this point in the history
* Initial stash of ws2812 spi driver

* Update comment, add sync backup plan

* Add testing notes to spi ws2812 driver

* Align RGBW error messages
  • Loading branch information
zvecr authored Dec 31, 2019
1 parent 7ba6456 commit 680ebef
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 2 deletions.
29 changes: 28 additions & 1 deletion docs/ws2812_driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ These LEDs are called "addressable" because instead of using a wire per color, e
|----------|--------------------|--------------------|
| bit bang | :heavy_check_mark: | :heavy_check_mark: |
| I2C | :heavy_check_mark: | |
| SPI | | Soon™ |
| SPI | | :heavy_check_mark: |
| PWM | | Soon™ |

## Driver configuration
Expand All @@ -40,3 +40,30 @@ Configure the hardware via your config.h:
#define WS2812_ADDRESS 0xb0 // default: 0xb0
#define WS2812_TIMEOUT 100 // default: 100
```
### SPI
Targeting STM32 boards where WS2812 support is offloaded to an SPI hardware device. The advantage is that the use of DMA offloads processing of the WS2812 protocol from the MCU. `RGB_DI_PIN` for this driver is the configured SPI MOSI pin. Due to the nature of repurposing SPI to drive the LEDs, the other SPI pins, MISO and SCK, **must** remain unused. To configure it, add this to your rules.mk:
```make
WS2812_DRIVER = spi
```

Configure the hardware via your config.h:
```c
#define WS2812_SPI SPID1 // default: SPID1
#define WS2812_SPI_MOSI_PAL_MODE 5 // Pin "alternate function", see the respective datasheet for the appropriate values for your MCU. default: 5
```
You must also turn on the SPI feature in your halconf.h and mcuconf.h
#### Testing Notes
While not an exhaustive list, the following table provides the scenarios that have been partially validated:
| | SPI1 | SPI2 | SPI3 |
|-|-|-|-|
| f072 | ? | B15 :heavy_check_mark: | N/A |
| f103 | A7 :heavy_check_mark: | B15 :heavy_check_mark: | N/A |
| f303 | A7 :heavy_check_mark: B5 :heavy_check_mark: | B15 :heavy_check_mark: | B5 :heavy_check_mark: |
*Other supported ChibiOS boards and/or pins may function, it will be highly chip and configuration dependent.*
91 changes: 90 additions & 1 deletion drivers/arm/ws2812_spi.c
Original file line number Diff line number Diff line change
@@ -1 +1,90 @@
#error("NOT SUPPORTED")
#include "quantum.h"
#include "ws2812.h"

/* Adapted from https://github.com/gamazeps/ws2812b-chibios-SPIDMA/ */

#ifdef RGBW
# error "RGBW not supported"
#endif

// Define the spi your LEDs are plugged to here
#ifndef WS2812_SPI
# define WS2812_SPI SPID1
#endif

#ifndef WS2812_SPI_MOSI_PAL_MODE
# define WS2812_SPI_MOSI_PAL_MODE 5
#endif

#define BYTES_FOR_LED_BYTE 4
#define NB_COLORS 3
#define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * NB_COLORS)
#define DATA_SIZE (BYTES_FOR_LED * RGBLED_NUM)
#define RESET_SIZE 200
#define PREAMBLE_SIZE 4

static uint8_t txbuf[PREAMBLE_SIZE + DATA_SIZE + RESET_SIZE] = {0};

/*
* As the trick here is to use the SPI to send a huge pattern of 0 and 1 to
* the ws2812b protocol, we use this helper function to translate bytes into
* 0s and 1s for the LED (with the appropriate timing).
*/
static uint8_t get_protocol_eq(uint8_t data, int pos) {
uint8_t eq = 0;
if (data & (1 << (2 * (3 - pos))))
eq = 0b1110;
else
eq = 0b1000;
if (data & (2 << (2 * (3 - pos))))
eq += 0b11100000;
else
eq += 0b10000000;
return eq;
}

static void set_led_color_rgb(LED_TYPE color, int pos) {
uint8_t* tx_start = &txbuf[PREAMBLE_SIZE];

for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.g, j);
for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.r, j);
for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.b, j);
}

void ws2812_init(void) {
#if defined(USE_GPIOV1)
palSetLineMode(RGB_DI_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
#else
palSetLineMode(RGB_DI_PIN, PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL);
#endif

// TODO: more dynamic baudrate
static const SPIConfig spicfg = {
NULL, PAL_PORT(RGB_DI_PIN), PAL_PAD(RGB_DI_PIN),
SPI_CR1_BR_1 | SPI_CR1_BR_0 // baudrate : fpclk / 8 => 1tick is 0.32us (2.25 MHz)
};

spiAcquireBus(&WS2812_SPI); /* Acquire ownership of the bus. */
spiStart(&WS2812_SPI, &spicfg); /* Setup transfer parameters. */
spiSelect(&WS2812_SPI); /* Slave Select assertion. */
}

void ws2812_setleds(LED_TYPE* ledarray, uint16_t leds) {
static bool s_init = false;
if (!s_init) {
ws2812_init();
s_init = true;
}

for (uint8_t i = 0; i < leds; i++) {
set_led_color_rgb(ledarray[i], i);
}

// Send async - each led takes ~0.03ms, 50 leds ~1.5ms, animations flushing faster than send will cause issues.
// Instead spiSend can be used to send synchronously (or the thread logic can be added back).
#ifdef WS2812_SPI_SYNC
spiSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
#else
spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
#endif
}

0 comments on commit 680ebef

Please sign in to comment.