forked from mirrors/qmk_userspace
		
	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
This commit is contained in:
		
					parent
					
						
							
								7ba6456c0b
							
						
					
				
			
			
				commit
				
					
						680ebef086
					
				
			
		
					 2 changed files with 118 additions and 2 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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.*
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue