neorv32/docs/datasheet/soc_neoled.adoc

204 lines
11 KiB
Plaintext
Raw Permalink Normal View History

2024-02-24 08:25:27 +00:00
<<<
:sectnums:
==== Smart LED Interface (NEOLED)
[cols="<3,<3,<4"]
[frame="topbot",grid="none"]
|=======================
| Hardware source file(s): | neorv32_neoled.vhd |
| Software driver file(s): | neorv32_neoled.c |
| | neorv32_neoled.h |
| Top entity port: | `neoled_o` | 1-bit serial data output
| Configuration generics: | `IO_NEOLED_EN` | implement NEOLED controller when `true`
| | `IO_NEOLED_TX_FIFO` | TX FIFO depth, has to be a power of 2, min 1
| CPU interrupts: | fast IRQ channel 9 | configurable NEOLED data FIFO interrupt (see <<_processor_interrupts>>)
|=======================
**Overview**
The NEOLED module provides a dedicated interface for "smart RGB LEDs" like WS2812, WS2811 or any other compatible
LEDs. These LEDs provide a single-wire interface that uses an asynchronous serial protocol for transmitting color
data. Using the NEOLED module allows CPU-independent operation of an arbitrary number of smart LEDs. A configurable data
buffer (FIFO) allows to utilize block transfer operation without requiring the CPU.
[NOTE]
The NEOLED interface is compatible to the "Adafruit Industries NeoPixel(TM)" products, which feature
WS2812 (or older WS2811) smart LEDs. Other LEDs might be compatible as well when adjusting the controller's programmable
timing configuration.
The interface provides a single 1-bit output `neoled_o` to drive an arbitrary number of cascaded LEDs. Since the
NEOLED module provides 24-bit and 32-bit operating modes, a mixed setup with RGB LEDs (24-bit color)
and RGBW LEDs (32-bit color including a dedicated white LED chip) is possible.
**Theory of Operation**
The NEOLED modules provides two accessible interface registers: the control register `CTRL` and the write-only
TX data register `DATA`. The NEOLED module is globally enabled via the control register's
`NEOLED_CTRL_EN` bit. Clearing this bit will terminate any current operation, clear the TX buffer, reset the module
and set the `neoled_o` output to zero. The precise timing (e.g. implementing the **WS2812** protocol) and transmission
mode are fully programmable via the `CTRL` register to provide maximum flexibility.
**RGB / RGBW Configuration**
NeoPixel(TM) LEDs are available in two "color" version: LEDs with three chips providing RGB color and LEDs with
four chips providing RGB color plus a dedicated white LED chip (= RGBW). Since the intensity of every
LED chip is defined via an 8-bit value the RGB LEDs require a frame of 24-bit per module and the RGBW
LEDs require a frame of 32-bit per module.
The data transfer quantity of the NEOLED module can be programmed via the `NEOLED_MODE_EN` control
register bit. If this bit is cleared, the NEOLED interface operates in 24-bit mode and will transmit bits `23:0` of
the data written to `DATA` to the LEDs. If `NEOLED_MODE_EN` is set, the NEOLED interface operates in 32-bit
mode and will transmit bits `31:0` of the data written to `DATA` to the LEDs.
The mode bit can be reconfigured before writing a new data word to `DATA` in order to support an arbitrary setup/mixture
of RGB and RGBW LEDs.
**Protocol**
The interface of the WS2812 LEDs uses an 800kHz carrier signal. Data is transmitted in a serial manner
starting with LSB-first. The intensity for each R, G & B (& W) LED chip (= color code) is defined via an 8-bit
value. The actual data bits are transferred by modifying the duty cycle of the signal (the timings for the
WS2812 are shown below). A RESET command is "send" by pulling the data line LOW for at least 50μs.
.WS2812 bit-level protocol - taken from the "Adafruit NeoPixel(TM) Überguide"
image::neopixel.png[align=center]
.WS2812 interface timing
[cols="<2,<2,<6"]
[grid="all"]
|=======================
| T~total~ (T~carrier~) | 1.25μs +/- 300ns | period for a single bit
| T~0H~ | 0.4μs +/- 150ns | high-time for sending a `1`
| T~0L~ | 0.8μs +/- 150ns | low-time for sending a `1`
| T~1H~ | 0.85μs +/- 150ns | high-time for sending a `0`
| T~1L~ | 0.45μs +/- 150 ns | low-time for sending a `0`
| RESET | Above 50μs | low-time for sending a RESET command
|=======================
**Timing Configuration**
The basic carrier frequency (800kHz for the WS2812 LEDs) is configured via a 3-bit main clock prescaler
(`NEOLED_CTRL_PRSC*`, see table below) that scales the main processor clock f~main~ and a 5-bit cycle
multiplier `NEOLED_CTRL_T_TOT_*`.
.NEOLED Prescaler Configuration
[cols="<4,^1,^1,^1,^1,^1,^1,^1,^1"]
[options="header",grid="rows"]
|=======================
| **`NEOLED_CTRL_PRSCx`** | `0b000` | `0b001` | `0b010` | `0b011` | `0b100` | `0b101` | `0b110` | `0b111`
| Resulting `clock_prescaler` | 2 | 4 | 8 | 64 | 128 | 1024 | 2048 | 4096
|=======================
The duty-cycles (or more precisely: the high- and low-times for sending either a '1' bit or a '0' bit) are
defined via the 5-bit `NEOLED_CTRL_T_ONE_H_*` and `NEOLED_CTRL_T_ZERO_H_*` values, respectively. These programmable
timing constants allow to adapt the interface for a wide variety of smart LED protocol (for example WS2812 vs.
WS2811).
**Timing Configuration - Example (WS2812)**
Generate the base clock f~TX~ for the NEOLED TX engine:
* processor clock f~main~ = 100 MHz
* `NEOLED_CTRL_PRSCx` = `0b001` = f~main~ / 4
_**f~TX~**_ = _f~main~[Hz]_ / `clock_prescaler` = 100MHz / 4 = 25MHz
_**T~TX~**_ = 1 / _**f~TX~**_ = 40ns
Generate carrier period (T~carrier~) and *high-times* (duty cycle) for sending `0` (T~0H~) and `1` (T~1H~) bits:
* `NEOLED_CTRL_T_TOT` = `0b11110` (= decimal 30)
* `NEOLED_CTRL_T_ZERO_H` = `0b01010` (= decimal 10)
* `NEOLED_CTRL_T_ONE_H` = `0b10100` (= decimal 20)
_**T~carrier~**_ = _**T~TX~**_ * `NEOLED_CTRL_T_TOT` = 40ns * 30 = 1.4µs
_**T~0H~**_ = _**T~TX~**_ * `NEOLED_CTRL_T_ZERO_H` = 40ns * 10 = 0.4µs
_**T~1H~**_ = _**T~TX~**_ * `NEOLED_CTRL_T_ONE_H` = 40ns * 20 = 0.8µs
[TIP]
The NEOLED SW driver library (`neorv32_neoled.h`) provides a simplified configuration
function that configures all timing parameters for driving WS2812 LEDs based on the processor
clock frequency.
**TX Data FIFO**
The interface features a configurable TX data buffer (a FIFO) to allow more CPU-independent operation. The buffer
depth is configured via the `IO_NEOLED_TX_FIFO` top generic (default = 1 entry). The FIFO size configuration can be
read via the `NEOLED_CTRL_BUFS_x` control register bits, which result log2(_IO_NEOLED_TX_FIFO_).
When writing data to the `DATA` register the data is automatically written to the TX buffer. Whenever
data is available in the buffer the serial transmission engine will take and transmit it to the LEDs.
The data transfer size (`NEOLED_MODE_EN`) can be modified at any time since this control register bit is also buffered
in the FIFO. This allows an arbitrary mix of RGB and RGBW LEDs in the chain.
Software can check the FIFO fill level via the control register's `NEOLED_CTRL_TX_EMPTY`, `NEOLED_CTRL_TX_HALF`
and `NEOLED_CTRL_TX_FULL` flags. The `NEOLED_CTRL_TX_BUSY` flags provides additional information if the the serial
transmit engine is still busy sending data.
[WARNING]
Please note that the timing configurations (`NEOLED_CTRL_PRSCx`, `NEOLED_CTRL_T_TOT_x`,
`NEOLED_CTRL_T_ONE_H_x` and `NEOLED_CTRL_T_ZERO_H_x`) are **NOT** stored to the buffer. Changing
these value while the buffer is not empty or the TX engine is still busy will cause data corruption.
**Strobe Command ("RESET")**
According to the WS2812 specs the data written to the LED's shift registers is strobed to the actual PWM driver
registers when the data line is low for 50μs ("RESET" command, see table above). This can be implemented
using busy-wait for at least 50μs. Obviously, this concept wastes a lot of processing power.
To circumvent this, the NEOLED module provides an option to automatically issue an idle time for creating the RESET
command. If the `NEOLED_CTRL_STROBE` control register bit is set, _all_ data written to the data FIFO (via `DATA`,
the actually written data is irrelevant) will trigger an idle phase (`neoled_o` = zero) of 127 periods (= _**T~carrier~**_).
This idle time will cause the LEDs to strobe the color data into the PWM driver registers.
Since the `NEOLED_CTRL_STROBE` flag is also buffered in the TX buffer, the RESET command is treated just as another
data word being written to the TX buffer making busy wait concepts obsolete and allowing maximum refresh rates.
**NEOLED Interrupt**
The NEOLED modules features a single interrupt that triggers based on the current TX buffer fill level.
The interrupt can only become pending if the NEOLED module is enabled. The specific interrupt condition
is configured via the `NEOLED_CTRL_IRQ_CONF` bit in the unit's control register.
If `NEOLED_CTRL_IRQ_CONF` is set, the module's interrupt is generated whenever the TX FIFO is less than half-full.
In this case software can write up to `IO_NEOLED_TX_FIFO`/2 new data words to `DATA` without checking the FIFO
status flags. If `NEOLED_CTRL_IRQ_CONF` is cleared, an interrupt is generated when the TX FIFO is empty.
One the NEOLED interrupt has been triggered and became pending, it has to explicitly cleared again by
writing zero to according <<_mip>> CSR bit.
**Register Map**
.NEOLED register map (`struct NEORV32_NEOLED`)
[cols="<2,<1,<5,^1,<5"]
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
.13+<| `0xfffffd00` .13+<| `CTRL` <|`0` `NEOLED_CTRL_EN` ^| r/w <| NEOLED enable
<|`1` `NEOLED_CTRL_MODE` ^| r/w <| data transfer size; `0`=24-bit; `1`=32-bit
<|`2` `NEOLED_CTRL_STROBE` ^| r/w <| `0`=send normal color data; `1`=send RESET command on data write access
<|`5:3` `NEOLED_CTRL_PRSC2 : NEOLED_CTRL_PRSC0` ^| r/w <| 3-bit clock prescaler, bit 0
<|`9:6` `NEOLED_CTRL_BUFS3 : NEOLED_CTRL_BUFS0` ^| r/- <| 4-bit log2(_IO_NEOLED_TX_FIFO_)
<|`14:10` `NEOLED_CTRL_T_TOT_4 : NEOLED_CTRL_T_TOT_0` ^| r/w <| 5-bit pulse clock ticks per total single-bit period (T~total~)
<|`19:15` `NEOLED_CTRL_T_ZERO_H_4 : NEOLED_CTRL_T_ZERO_H_0` ^| r/w <| 5-bit pulse clock ticks per high-time for sending a zero-bit (T~0H~)
<|`24:20` `NEOLED_CTRL_T_ONE_H_4 : NEOLED_CTRL_T_ONE_H_0` ^| r/w <| 5-bit pulse clock ticks per high-time for sending a one-bit (T~1H~)
<|`27` `NEOLED_CTRL_IRQ_CONF` ^| r/w <| TX FIFO interrupt configuration: `0`=IRQ if FIFO is empty, `1`=IRQ if FIFO is less than half-full
<|`28` `NEOLED_CTRL_TX_EMPTY` ^| r/- <| TX FIFO is empty
<|`29` `NEOLED_CTRL_TX_HALF` ^| r/- <| TX FIFO is _at least_ half full
<|`30` `NEOLED_CTRL_TX_FULL` ^| r/- <| TX FIFO is full
<|`31` `NEOLED_CTRL_TX_BUSY` ^| r/- <| TX serial engine is busy when set
| `0xfffffd04` | `DATA` <|`31:0` / `23:0` ^| -/w <| TX data (32- or 24-bit, depending on _NEOLED_CTRL_MODE_ bit)
|=======================