mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-25 11:39:58 +02:00 
			
		
		
		
	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
					
				| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Joel Challis
						Joel Challis