Browse Source

Update WS2812 docs and add APA102 docs (#22106)

Co-authored-by: Nick Brassel <nick@tzarc.org>
pull/22123/head
Ryan 7 months ago
committed by GitHub
parent
commit
a931e6d7bd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 210 additions and 120 deletions
  1. +1
    -0
      docs/_summary.md
  2. +49
    -0
      docs/apa102_driver.md
  3. +1
    -1
      docs/reference_info_json.md
  4. +159
    -119
      docs/ws2812_driver.md

+ 1
- 0
docs/_summary.md View File

@ -148,6 +148,7 @@
* [Compatible Microcontrollers](compatible_microcontrollers.md)
* [Drivers](hardware_drivers.md)
* [ADC Driver](adc_driver.md)
* [APA102 Driver](apa102_driver.md)
* [Audio Driver](audio_driver.md)
* [I2C Driver](i2c_driver.md)
* [SPI Driver](spi_driver.md)


+ 49
- 0
docs/apa102_driver.md View File

@ -0,0 +1,49 @@
# APA102 Driver :id=apa102-driver
This driver provides support for APA102 addressable RGB LEDs. They are similar to the [WS2812](ws2812_driver.md) LEDs, but have increased data and refresh rates.
## Usage :id=usage
In most cases, the APA102 driver code is automatically included if you are using either the [RGBLight](feature_rgblight.md) or [RGB Matrix](feature_rgb_matrix.md) feature with the `apa102` driver set, and you would use those APIs instead.
However, if you need to use the driver standalone, add the following to your `rules.mk`:
```make
APA102_DRIVER_REQUIRED = yes
```
You can then call the APA102 API by including `apa102.h` in your code.
## Basic Configuration :id=basic-configuration
Add the following to your `config.h`:
|Define |Default |Description |
|---------------------------|-------------|------------------------------------------------------------------|
|`APA102_DI_PIN` |*Not defined*|The GPIO pin connected to the DI pin of the first LED in the chain|
|`APA102_CI_PIN` |*Not defined*|The GPIO pin connected to the CI pin of the first LED in the chain|
|`APA102_DEFAULT_BRIGHTNESS`|`31` |The default global brightness level of the LEDs, from 0 to 31 |
## API :id=api
### `void apa102_setleds(rgb_led_t *start_led, uint16_t num_leds)`
Send RGB data to the APA102 LED chain.
#### Arguments :id=api-apa102-setleds-arguments
- `rgb_led_t *start_led`
A pointer to the LED array.
- `uint16_t num_leds`
The length of the LED array.
---
### `void apa102_set_brightness(uint8_t brightness)`
Set the global brightness.
#### Arguments :id=api-apa102-set-brightness-arguments
- `uint8_t brightness`
The brightness level to set, from 0 to 31.

+ 1
- 1
docs/reference_info_json.md View File

@ -88,7 +88,7 @@ You can create `info.json` files at every level under `qmk_firmware/keyboards/<k
## APA102 :id=apa102
Configures the APA102 driver.
Configures the [APA102](apa102_driver.md) driver.
* `apa102`
* `clock_pin` (Required)


+ 159
- 119
docs/ws2812_driver.md View File

@ -1,189 +1,229 @@
# WS2812 Driver
This driver powers the [RGB Lighting](feature_rgblight.md) and [RGB Matrix](feature_rgb_matrix.md) features.
# WS2812 Driver :id=ws2812-driver
Currently QMK supports the following addressable LEDs (however, the white LED in RGBW variants is not supported):
This driver provides support for WorldSemi addressable RGB(W) LEDs, and compatible equivalents:
WS2811, WS2812, WS2812B, WS2812C, etc.
SK6812, SK6812MINI, SK6805
* WS2811, WS2812, WS2812B, WS2812C, etc.
* SK6812, SK6812MINI, SK6805
These LEDs are called "addressable" because instead of using a wire per color, each LED contains a small microchip that understands a special protocol sent over a single wire. The chip passes on the remaining data to the next LED, allowing them to be chained together. In this way, you can easily control the color of the individual LEDs.
These LEDs are often called "addressable" because instead of using a wire per color (and per LED), each LED contains a small microchip that understands a special protocol sent over a single wire.
The LEDs can be chained together, and the remaining data is passed on to the next. In this way, you can easily control the color of many LEDs using a single GPIO.
## Supported Driver Types
## Usage :id=usage
| | AVR | ARM |
| -------- | ------------------ | ------------------ |
| bit bang | :heavy_check_mark: | :heavy_check_mark: |
| I2C | :heavy_check_mark: | |
| SPI | | :heavy_check_mark: |
| PWM | | :heavy_check_mark: |
| PIO | | :heavy_check_mark: |
In most cases, the WS2812 driver code is automatically included if you are using either the [RGBLight](feature_rgblight.md) or [RGB Matrix](feature_rgb_matrix.md) feature with the `ws2812` driver set, and you would use those APIs instead.
## Driver configuration
However, if you need to use the driver standalone, add the following to your `rules.mk`:
### All drivers
```make
WS2812_DRIVER_REQUIRED = yes
```
You can then call the WS2812 API by including `ws2812.h` in your code.
## Basic Configuration :id=basic-configuration
Add the following to your `config.h`:
|Define |Default |Description |
|-------------------|-----------------------|------------------------------------------------------------------------------------------------|
|`WS2812_DI_PIN` |*Not defined* |The GPIO pin connected to the DI pin of the first LED in the chain |
|`WS2812_LED_COUNT` |*Not defined* |Number of LEDs in the WS2812 chain - automatically set when RGBLight or RGB Matrix is configured|
|`WS2812_TIMING` |`1250` |The total length of a bit (TH+TL) in nanoseconds |
|`WS2812_T1H` |`900` |The length of a "1" bit's high phase in nanoseconds |
|`WS2812_T0H` |`350` |The length of a "0" bit's high phase in nanoseconds |
|`WS2812_TRST_US` |`280` |The length of the reset phase in microseconds |
|`WS2812_BYTE_ORDER`|`WS2812_BYTE_ORDER_GRB`|The byte order of the RGB data |
### Timing Adjustment :id=timing-adjustment
Different versions of the addressable LEDs have differing requirements for the T<sub>RST</sub> period between frames.
The default setting is 280 µs, which should work for most cases, but this can be overridden in your config.h. e.g.:
The WS2812 LED communication protocol works by encoding a "1" bit with a long high pulse (T<sub>1</sub>H), and a "0" bit with a shorter pulse (T<sub>0</sub>H). The total cycle length of a bit is the same.
The "reset" pulse (T<sub>RST</sub>) latches the sent RGB data to all of the LEDs and denotes a completed "frame".
Some WS2812 variants have slightly different timing parameter requirements, which can be accounted for if necessary using the above `#define`s in your `config.h`.
### Byte Order :id=byte-order
Some WS2812 variants may have their color components in a different physical or logical order. For example, the WS2812B-2020 has physically swapped red and green LEDs, which causes the wrong color to be displayed, because the default order of the bytes sent over the wire is defined as GRB.
If you find your LED colors are consistently swapped, you may need to change the byte order by adding the following to your `config.h`:
```c
#define WS2812_TRST_US 80
#define WS2812_BYTE_ORDER WS2812_BYTE_ORDER_GRB
```
#### Byte Order
Where the byte order may be one of:
|Byte Order|Known Devices |
|----------|----------------------------|
|`GRB` |Most WS2812s, SK6812, SK6805|
|`RGB` |WS2812B-2020 |
|`BGR` |TM1812 |
Some variants of the WS2812 may have their color components in a different physical or logical order. For example, the WS2812B-2020 has physically swapped red and green LEDs, which causes the wrong color to be displayed, because the default order of the bytes sent over the wire is defined as GRB.
In this case, you can change the byte order by defining `WS2812_BYTE_ORDER` as one of the following values:
## Driver Configuration :id=driver-configuration
| Byte order | Known devices |
| --------------------------------- | ----------------------------- |
| `WS2812_BYTE_ORDER_GRB` (default) | Most WS2812's, SK6812, SK6805 |
| `WS2812_BYTE_ORDER_RGB` | WS2812B-2020 |
| `WS2812_BYTE_ORDER_BGR` | TM1812 |
Driver selection can be configured in `rules.mk` as `WS2812_DRIVER`, or in `info.json` as `ws2812.driver`. Valid values are `bitbang` (default), `i2c`, `spi`, `pwm`, `vendor`, or `custom`. See below for information on individual drivers.
### Bitbang Driver :id=bitbang-driver
### Bitbang
Default driver, the absence of configuration assumes this driver. To configure it, add this to your rules.mk:
This is the default WS2812 driver. It operates by "bit-banging" ie. directly toggling the GPIO.
Please note that on AVR devices, due to the tight timing requirements longer chains and/or heavy CPU loads may cause visible lag. Unfortunately this driver is usually the only option for AVR.
```make
WS2812_DRIVER = bitbang
```
!> This driver is not hardware accelerated and may not be performant on heavily loaded systems.
### I2C Driver :id=i2c-driver
A specialized driver mainly used for PS2AVRGB (Bootmapper Client) boards, which possess an ATtiny85 that handles the WS2812 LEDs.
```make
WS2812_DRIVER = i2c
```
The following `#define`s apply only to the `i2c` driver:
#### Adjusting bit timings
|Define |Default|Description |
|--------------------|-------|---------------------------------|
|`WS2812_I2C_ADDRESS`|`0xB0` |The I2C address of the ATtiny85. |
|`WS2812_I2C_TIMEOUT`|`100` |The I2C timeout, in milliseconds.|
The WS2812 LED communication topology depends on a serialized timed window. Different versions of the addressable LEDs have differing requirements for the timing parameters, for instance, of the SK6812.
You can tune these parameters through the definition of the following macros:
### PIO Driver :id=pio-driver
| Macro | Default | AVR | ARM |
| --------------- | ---------------------------- | ------------------ | ------------------ |
| `WS2812_TIMING` | `1250` | :heavy_check_mark: | :heavy_check_mark: |
| `WS2812_T0H` | `350` | :heavy_check_mark: | :heavy_check_mark: |
| `WS2812_T0L` | `WS2812_TIMING - WS2812_T0H` | | :heavy_check_mark: |
| `WS2812_T1H` | `900` | :heavy_check_mark: | :heavy_check_mark: |
| `WS2812_T1L` | `WS2812_TIMING - WS2812_T1H` | | :heavy_check_mark: |
This driver is RP2040-only, and leverages the onboard PIO (programmable I/O) system and DMA to offload processing from the CPU.
### I2C
Targeting boards where WS2812 support is offloaded to a 2nd MCU. Currently the driver is limited to AVR given the known consumers are ps2avrGB/BMC. To configure it, add this to your rules.mk:
The WS2812 PIO program uses one state machine, six instructions and one DMA interrupt handler callback. Due to the implementation the time resolution for this driver is 50 ns - any value not specified in this interval will be rounded to the next matching interval.
```make
WS2812_DRIVER = i2c
WS2812_DRIVER = vendor
```
Configure the hardware via your config.h:
```c
#define WS2812_I2C_ADDRESS 0xB0 // default: 0xB0
#define WS2812_I2C_TIMEOUT 100 // default: 100
### PWM Driver :id=pwm-driver
This driver is ARM-only, and leverages the onboard PWM peripheral and DMA to offload processing from the CPU.
```make
WS2812_DRIVER = pwm
```
### 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. `WS2812_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:
### SPI Driver :id=spi-driver
This driver is ARM-only, and leverages the onboard SPI peripheral and DMA to offload processing from the CPU. The DI pin **must** be connected to the MOSI pin on the MCU, and all other SPI pins **must** be left unused. This is also very dependent on your MCU's SPI peripheral clock speed, and may or may not be possible depending on the MCU selected.
```make
WS2812_DRIVER = spi
```
Configure the hardware via your config.h:
```c
#define WS2812_SPI_DRIVER SPID1 // default: SPID1
#define WS2812_SPI_MOSI_PAL_MODE 5 // MOSI pin "alternate function", see the respective datasheet for the appropriate values for your MCU. default: 5
#define WS2812_SPI_SCK_PIN B3 // Required for F072, may be for others -- SCK pin, see the respective datasheet for the appropriate values for your MCU. default: unspecified
#define WS2812_SPI_SCK_PAL_MODE 5 // SCK pin "alternate function", see the respective datasheet for the appropriate values for your MCU. default: 5
```
## ChibiOS/ARM Configuration :id=arm-configuration
You must also turn on the SPI feature in your halconf.h and mcuconf.h
The following defines apply only to ARM devices:
#### Circular Buffer Mode
Some boards may flicker while in the normal buffer mode. To fix this issue, circular buffer mode may be used to rectify the issue.
|Define |Default |Description |
|------------|------------------------------|---------------------------------------------------------------------------------|
|`WS2812_T1L`|`(WS2812_TIMING - WS2812_T1H)`|The length of a "1" bit's low phase in nanoseconds (bitbang and PIO drivers only)|
|`WS2812_T0L`|`(WS2812_TIMING - WS2812_T0H)`|The length of a "0" bit's low phase in nanoseconds (bitbang and PIO drivers only)|
By default, the circular buffer mode is disabled.
### Push-Pull and Open Drain :id=push-pull-open-drain
To enable this alternative buffer mode, place this into your `config.h` file:
```c
#define WS2812_SPI_USE_CIRCULAR_BUFFER
```
By default, the GPIO used for data transmission is configured as a *push-pull* output, meaning the pin is effectively always driven either to VCC or to ground.
#### Setting baudrate with divisor
To adjust the baudrate at which the SPI peripheral is configured, users will need to derive the target baudrate from the clock tree provided by STM32CubeMX.
For situations where the logic level voltage is lower than the power supply voltage, however, this can pose an issue. The solution is to configure the pin for *open drain* mode instead, and use a pullup resistor between the DI pin and VCC. In this mode, the MCU can only pull the GPIO *low*, or leave it floating. The pullup resistor is then responsible for pulling the line high, when the MCU is not driving the GPIO.
Only divisors of 2, 4, 8, 16, 32, 64, 128 and 256 are supported by hardware.
To configure the DI pin for open drain configuration, add the following to your `config.h`:
| Define | Default | Description |
| -------------------- | ------- | ----------------------------------- |
| `WS2812_SPI_DIVISOR` | `16` | SPI source clock peripheral divisor |
```c
#define WS2812_EXTERNAL_PULLUP
```
#### Testing Notes
### SPI Driver :id=arm-spi-driver
While not an exhaustive list, the following table provides the scenarios that have been partially validated:
Depending on the ChibiOS board configuration, you may need to enable SPI at the keyboard level. For STM32, this would look like:
| | SPI1 | SPI2 | SPI3 |
| ---- | ------------------------------------------- | --------------------------------------- | --------------------- |
| f072 | ? | B15 :heavy_check_mark: (needs SCK: B13) | 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: |
`halconf.h`:
```c
#define HAL_USE_SPI TRUE
```
`mcuconf.h`:
```c
#undef STM32_SPI_USE_SPI1
#define STM32_SPI_USE_SPI1 TRUE
```
*Other supported ChibiOS boards and/or pins may function, it will be highly chip and configuration dependent.*
The following `define`s apply only to the `spi` driver:
### PWM
|Define |Default |Description |
|--------------------------------|-------------|-------------------------------------------------------------------------------|
|`WS2812_SPI_DRIVER` |`SPID1` |The SPI driver to use |
|`WS2812_SPI_MOSI_PAL_MODE` |`5` |The MOSI pin alternative function to use |
|`WS2812_SPI_SCK_PIN` |*Not defined*|The SCK pin - required for F072 and possibly others |
|`WS2812_SPI_SCK_PAL_MODE` |`5` |The SCK pin alternative function to use - required for F072 and possibly others|
|`WS2812_SPI_DIVISOR` |`16` |The divisor used to adjust the baudrate |
|`WS2812_SPI_USE_CIRCULAR_BUFFER`|*Not defined*|Enable a circular buffer for improved rendering |
Targeting STM32 boards where WS2812 support is offloaded to an PWM timer and DMA stream. The advantage is that the use of DMA offloads processing of the WS2812 protocol from the MCU. To configure it, add this to your rules.mk:
#### Setting the Baudrate :id=arm-spi-baudrate
```make
WS2812_DRIVER = pwm
```
To adjust the SPI baudrate, you will need to derive the target baudrate from the clock tree provided by STM32CubeMX, and add the following to your `config.h`:
Configure the hardware via your config.h:
```c
#define WS2812_PWM_DRIVER PWMD2 // default: PWMD2
#define WS2812_PWM_CHANNEL 2 // default: 2
#define WS2812_PWM_PAL_MODE 2 // Pin "alternate function", see the respective datasheet for the appropriate values for your MCU. default: 2
//#define WS2812_PWM_COMPLEMENTARY_OUTPUT // Define for a complementary timer output (TIMx_CHyN); omit for a normal timer output (TIMx_CHy).
#define WS2812_DMA_STREAM STM32_DMA1_STREAM2 // DMA Stream for TIMx_UP, see the respective reference manual for the appropriate values for your MCU.
#define WS2812_DMA_CHANNEL 2 // DMA Channel for TIMx_UP, see the respective reference manual for the appropriate values for your MCU.
#define WS2812_DMAMUX_ID STM32_DMAMUX1_TIM2_UP // DMAMUX configuration for TIMx_UP -- only required if your MCU has a DMAMUX peripheral, see the respective reference manual for the appropriate values for your MCU.
#define WS2812_SPI_DIVISOR 16
```
Note that using a complementary timer output (TIMx_CHyN) is possible only for advanced-control timers (TIM1, TIM8, TIM20 on STM32), and the `STM32_PWM_USE_ADVANCED` option in mcuconf.h must be set to `TRUE`. Complementary outputs of general-purpose timers are not supported due to ChibiOS limitations.
Only divisors of 2, 4, 8, 16, 32, 64, 128 and 256 are supported on STM32 devices. Other MCUs may have similar constraints -- check the reference manual for your respective MCU for specifics.
#### Circular Buffer :id=arm-spi-circular-buffer
You must also turn on the PWM feature in your halconf.h and mcuconf.h
A circular buffer can be enabled if you experience flickering.
#### Testing Notes
To enable the circular buffer, add the following to your `config.h`:
While not an exhaustive list, the following table provides the scenarios that have been partially validated:
```c
#define WS2812_SPI_USE_CIRCULAR_BUFFER
```
| | Status |
| --------- | ------------------ |
| f072 | ? |
| f103 | :heavy_check_mark: |
| f303 | :heavy_check_mark: |
| f401/f411 | :heavy_check_mark: |
### PIO Driver :id=arm-pio-driver
*Other supported ChibiOS boards and/or pins may function, it will be highly chip and configuration dependent.*
The following `#define`s apply only to the PIO driver:
### PIO
|Define |Default |Description |
|---------------------|-------------|---------------------------------------|
|`WS2812_PIO_USE_PIO1`|*Not defined*|Use the PIO1 peripheral instead of PIO0|
Targeting Raspberry Pi RP2040 boards only where WS2812 support is offloaded to an dedicated PIO implementation. This offloads processing of the WS2812 protocol from the MCU to a dedicated PIO program using DMA transfers.
### PWM Driver :id=arm-pwm-driver
To configure it, add this to your rules.mk:
Depending on the ChibiOS board configuration, you may need to enable PWM at the keyboard level. For STM32, this would look like:
```make
WS2812_DRIVER = vendor
`halconf.h`:
```c
#define HAL_USE_PWM TRUE
```
Configure the hardware via your config.h:
`mcuconf.h`:
```c
#define WS2812_PIO_USE_PIO1 // Force the usage of PIO1 peripheral, by default the WS2812 implementation uses the PIO0 peripheral
#undef STM32_PWM_USE_TIM2
#define STM32_PWM_USE_TIM2 TRUE
```
The WS2812 PIO programm uses 1 state machine, 6 instructions and one DMA interrupt handler callback. Due to the implementation the time resolution for this drivers is 50ns, any value not specified in this interval will be rounded to the next matching interval.
The following `#define`s apply only to the `pwm` driver:
### Push Pull and Open Drain Configuration
The default configuration is a push pull on the defined pin.
This can be configured for bitbang, PWM and SPI.
|Define |Default |Description |
|---------------------------------|--------------------|------------------------------------------------------------------------------------------|
|`WS2812_PWM_DRIVER` |`PWMD2` |The PWM driver to use |
|`WS2812_PWM_CHANNEL` |`2` |The PWM channel to use |
|`WS2812_PWM_PAL_MODE` |`2` |The pin alternative function to use |
|`WS2812_DMA_STREAM` |`STM32_DMA1_STREAM2`|The DMA Stream for `TIMx_UP` |
|`WS2812_DMA_CHANNEL` |`2` |The DMA Channel for `TIMx_UP` |
|`WS2812_DMAMUX_ID` |*Not defined* |The DMAMUX configuration for `TIMx_UP` - only required if your MCU has a DMAMUX peripheral|
|`WS2812_PWM_COMPLEMENTARY_OUTPUT`|*Not defined* |Whether the PWM output is complementary (`TIMx_CHyN`) |
Note: This only applies to STM32 boards.
?> Using a complementary timer output (`TIMx_CHyN`) is possible only for advanced-control timers (1, 8 and 20 on STM32), and the `STM32_PWM_USE_ADVANCED` option in `mcuconf.h` must be set to `TRUE`. Complementary outputs of general-purpose timers are not supported due to ChibiOS limitations.
To configure the `WS2812_DI_PIN` to open drain configuration add this to your config.h file:
```c
#define WS2812_EXTERNAL_PULLUP
```
## API :id=api
### `void ws2812_setleds(rgb_led_t *ledarray, uint16_t number_of_leds)` :id=api-ws2812-setleds
Send RGB data to the WS2812 LED chain.
#### Arguments :id=api-ws2812-setleds-arguments
- `rgb_led_t *ledarray`
A pointer to the LED array.
- `uint16_t number_of_leds`
The length of the LED array.

Loading…
Cancel
Save