diff options
| author | Joel Challis <git@zvecr.com> | 2019-12-31 17:26:40 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-12-31 17:26:40 +0000 |
| commit | 680ebef0869b7860017a80bd601faac61227cd28 (patch) | |
| tree | 4915395331bac5952a17d9e8a2912433f4c5b916 | |
| parent | 7ba6456c0b2e041bb9f97dbed265c5b8b4b12192 (diff) | |
| download | qmk_firmware-680ebef0869b7860017a80bd601faac61227cd28.tar.gz qmk_firmware-680ebef0869b7860017a80bd601faac61227cd28.zip | |
SPI DMA based RGB Underglow for STM32 (#7674)
* Initial stash of ws2812 spi driver
* Update comment, add sync backup plan
* Add testing notes to spi ws2812 driver
* Align RGBW error messages
| -rw-r--r-- | docs/ws2812_driver.md | 29 | ||||
| -rw-r--r-- | drivers/arm/ws2812_spi.c | 91 |
2 files changed, 118 insertions, 2 deletions
diff --git a/docs/ws2812_driver.md b/docs/ws2812_driver.md index 67481c458..80b694858 100644 --- a/docs/ws2812_driver.md +++ b/docs/ws2812_driver.md | |||
| @@ -14,7 +14,7 @@ These LEDs are called "addressable" because instead of using a wire per color, e | |||
| 14 | |----------|--------------------|--------------------| | 14 | |----------|--------------------|--------------------| |
| 15 | | bit bang | :heavy_check_mark: | :heavy_check_mark: | | 15 | | bit bang | :heavy_check_mark: | :heavy_check_mark: | |
| 16 | | I2C | :heavy_check_mark: | | | 16 | | I2C | :heavy_check_mark: | | |
| 17 | | SPI | | Soon™ | | 17 | | SPI | | :heavy_check_mark: | |
| 18 | | PWM | | Soon™ | | 18 | | PWM | | Soon™ | |
| 19 | 19 | ||
| 20 | ## Driver configuration | 20 | ## Driver configuration |
| @@ -40,3 +40,30 @@ Configure the hardware via your config.h: | |||
| 40 | #define WS2812_ADDRESS 0xb0 // default: 0xb0 | 40 | #define WS2812_ADDRESS 0xb0 // default: 0xb0 |
| 41 | #define WS2812_TIMEOUT 100 // default: 100 | 41 | #define WS2812_TIMEOUT 100 // default: 100 |
| 42 | ``` | 42 | ``` |
| 43 | |||
| 44 | ### SPI | ||
| 45 | 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: | ||
| 46 | |||
| 47 | ```make | ||
| 48 | WS2812_DRIVER = spi | ||
| 49 | ``` | ||
| 50 | |||
| 51 | Configure the hardware via your config.h: | ||
| 52 | ```c | ||
| 53 | #define WS2812_SPI SPID1 // default: SPID1 | ||
| 54 | #define WS2812_SPI_MOSI_PAL_MODE 5 // Pin "alternate function", see the respective datasheet for the appropriate values for your MCU. default: 5 | ||
| 55 | ``` | ||
| 56 | |||
| 57 | You must also turn on the SPI feature in your halconf.h and mcuconf.h | ||
| 58 | |||
| 59 | #### Testing Notes | ||
| 60 | |||
| 61 | While not an exhaustive list, the following table provides the scenarios that have been partially validated: | ||
| 62 | |||
| 63 | | | SPI1 | SPI2 | SPI3 | | ||
| 64 | |-|-|-|-| | ||
| 65 | | f072 | ? | B15 :heavy_check_mark: | N/A | | ||
| 66 | | f103 | A7 :heavy_check_mark: | B15 :heavy_check_mark: | N/A | | ||
| 67 | | f303 | A7 :heavy_check_mark: B5 :heavy_check_mark: | B15 :heavy_check_mark: | B5 :heavy_check_mark: | | ||
| 68 | |||
| 69 | *Other supported ChibiOS boards and/or pins may function, it will be highly chip and configuration dependent.* \ No newline at end of file | ||
diff --git a/drivers/arm/ws2812_spi.c b/drivers/arm/ws2812_spi.c index 2094e5009..0e954ec50 100644 --- a/drivers/arm/ws2812_spi.c +++ b/drivers/arm/ws2812_spi.c | |||
| @@ -1 +1,90 @@ | |||
| 1 | #error("NOT SUPPORTED") \ No newline at end of file | 1 | #include "quantum.h" |
| 2 | #include "ws2812.h" | ||
| 3 | |||
| 4 | /* Adapted from https://github.com/gamazeps/ws2812b-chibios-SPIDMA/ */ | ||
| 5 | |||
| 6 | #ifdef RGBW | ||
| 7 | # error "RGBW not supported" | ||
| 8 | #endif | ||
| 9 | |||
| 10 | // Define the spi your LEDs are plugged to here | ||
| 11 | #ifndef WS2812_SPI | ||
| 12 | # define WS2812_SPI SPID1 | ||
| 13 | #endif | ||
| 14 | |||
| 15 | #ifndef WS2812_SPI_MOSI_PAL_MODE | ||
| 16 | # define WS2812_SPI_MOSI_PAL_MODE 5 | ||
| 17 | #endif | ||
| 18 | |||
| 19 | #define BYTES_FOR_LED_BYTE 4 | ||
| 20 | #define NB_COLORS 3 | ||
| 21 | #define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * NB_COLORS) | ||
| 22 | #define DATA_SIZE (BYTES_FOR_LED * RGBLED_NUM) | ||
| 23 | #define RESET_SIZE 200 | ||
| 24 | #define PREAMBLE_SIZE 4 | ||
| 25 | |||
| 26 | static uint8_t txbuf[PREAMBLE_SIZE + DATA_SIZE + RESET_SIZE] = {0}; | ||
| 27 | |||
| 28 | /* | ||
| 29 | * As the trick here is to use the SPI to send a huge pattern of 0 and 1 to | ||
| 30 | * the ws2812b protocol, we use this helper function to translate bytes into | ||
| 31 | * 0s and 1s for the LED (with the appropriate timing). | ||
| 32 | */ | ||
| 33 | static uint8_t get_protocol_eq(uint8_t data, int pos) { | ||
| 34 | uint8_t eq = 0; | ||
| 35 | if (data & (1 << (2 * (3 - pos)))) | ||
| 36 | eq = 0b1110; | ||
| 37 | else | ||
| 38 | eq = 0b1000; | ||
| 39 | if (data & (2 << (2 * (3 - pos)))) | ||
| 40 | eq += 0b11100000; | ||
| 41 | else | ||
| 42 | eq += 0b10000000; | ||
| 43 | return eq; | ||
| 44 | } | ||
| 45 | |||
| 46 | static void set_led_color_rgb(LED_TYPE color, int pos) { | ||
| 47 | uint8_t* tx_start = &txbuf[PREAMBLE_SIZE]; | ||
| 48 | |||
| 49 | for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.g, j); | ||
| 50 | for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.r, j); | ||
| 51 | 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); | ||
| 52 | } | ||
| 53 | |||
| 54 | void ws2812_init(void) { | ||
| 55 | #if defined(USE_GPIOV1) | ||
| 56 | palSetLineMode(RGB_DI_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL); | ||
| 57 | #else | ||
| 58 | palSetLineMode(RGB_DI_PIN, PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL); | ||
| 59 | #endif | ||
| 60 | |||
| 61 | // TODO: more dynamic baudrate | ||
| 62 | static const SPIConfig spicfg = { | ||
| 63 | NULL, PAL_PORT(RGB_DI_PIN), PAL_PAD(RGB_DI_PIN), | ||
| 64 | SPI_CR1_BR_1 | SPI_CR1_BR_0 // baudrate : fpclk / 8 => 1tick is 0.32us (2.25 MHz) | ||
| 65 | }; | ||
| 66 | |||
| 67 | spiAcquireBus(&WS2812_SPI); /* Acquire ownership of the bus. */ | ||
| 68 | spiStart(&WS2812_SPI, &spicfg); /* Setup transfer parameters. */ | ||
| 69 | spiSelect(&WS2812_SPI); /* Slave Select assertion. */ | ||
| 70 | } | ||
| 71 | |||
| 72 | void ws2812_setleds(LED_TYPE* ledarray, uint16_t leds) { | ||
| 73 | static bool s_init = false; | ||
| 74 | if (!s_init) { | ||
| 75 | ws2812_init(); | ||
| 76 | s_init = true; | ||
| 77 | } | ||
| 78 | |||
| 79 | for (uint8_t i = 0; i < leds; i++) { | ||
| 80 | set_led_color_rgb(ledarray[i], i); | ||
| 81 | } | ||
| 82 | |||
| 83 | // Send async - each led takes ~0.03ms, 50 leds ~1.5ms, animations flushing faster than send will cause issues. | ||
| 84 | // Instead spiSend can be used to send synchronously (or the thread logic can be added back). | ||
| 85 | #ifdef WS2812_SPI_SYNC | ||
| 86 | spiSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf); | ||
| 87 | #else | ||
| 88 | spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf); | ||
| 89 | #endif | ||
| 90 | } | ||
