interfaces component

WS2812 LED Driver

Open in playground

A WS2812 addressable LED has no clock pin. One wire carries everything: 24 bits of color per LED, green then red then blue, most significant bit first. Every bit occupies the same fixed period. The wire is high at the start of each period, and the width of that high pulse is the data: short means 0, long means 1. After the last bit, holding the wire low for the latch gap tells the LED the frame is over.

The driver is a three-state machine around a 24-bit shift register. A start pulse latches grb and begins the send state. A phase counter walks each bit period. dout stays high while the count is under the width the current bit selects, then low for the rest. At each period boundary the register shifts left, so the outgoing bit always sits at the top. After bit zero the machine holds the line low through the gap state, and busy covers the whole frame including that gap.

The bit periods are generics. The shipped values keep the simulation short; the source header gives the 800 kHz values for a 50 MHz clock.

In the waveform, compare neighbouring pulses while bit_cnt_r counts down: seven cycles high for a 1, three for a 0, same period either way.

In the netlist, find the shift register row feeding one comparator: the width mux and the phase compare are the entire encoder.

The counters and single-wire framing here are built from first principles in Sequential Design Patterns.

-- WS2812 single-wire LED driver: 24 GRB bits per frame, each bit encoded
-- as pulse width on one wire, then a low latch gap. Shipped periods keep
-- simulation readable; at a real 50 MHz clock the 800 kHz stream needs
-- CYCLES_BIT=63, CYCLES_T0H=20, CYCLES_T1H=40 and CYCLES_RESET>=2500.
library ieee;
use ieee.std_logic_1164.all;

entity ws2812_led_driver is
  generic (
    CYCLES_BIT   : positive := 10;
    CYCLES_T0H   : positive := 3;
    CYCLES_T1H   : positive := 7;
    CYCLES_RESET : positive := 30
  );
  port (
    clk   : in  std_logic;
    rst   : in  std_logic;
    start : in  std_logic;
    grb   : in  std_logic_vector(23 downto 0);
    dout  : out std_logic;
    busy  : out std_logic
  );
end entity ws2812_led_driver;

architecture rtl of ws2812_led_driver is

  type state_t is (ST_IDLE, ST_SEND, ST_GAP);

  signal state_r   : state_t;
  signal shift_r   : std_logic_vector(23 downto 0);
  signal bit_cnt_r : integer range 0 to 23;
  signal phase_r   : integer range 0 to CYCLES_BIT - 1;
  signal gap_r     : integer range 0 to CYCLES_RESET - 1;
  signal width_s   : integer range 0 to CYCLES_BIT;

begin

  seq_p : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        state_r   <= ST_IDLE;
        shift_r   <= (others => '0');
        bit_cnt_r <= 0;
        phase_r   <= 0;
        gap_r     <= 0;
      else
        case state_r is
          when ST_IDLE =>
            if start = '1' then
              shift_r   <= grb;
              bit_cnt_r <= 23;
              phase_r   <= 0;
              state_r   <= ST_SEND;
            end if;
          when ST_SEND =>
            if phase_r = CYCLES_BIT - 1 then
              phase_r <= 0;
              if bit_cnt_r = 0 then
                gap_r   <= 0;
                state_r <= ST_GAP;
              else
                -- MSB first: the next bit slides into shift_r(23)
                shift_r   <= shift_r(22 downto 0) & '0';
                bit_cnt_r <= bit_cnt_r - 1;
              end if;
            else
              phase_r <= phase_r + 1;
            end if;
          when ST_GAP =>
            if gap_r = CYCLES_RESET - 1 then
              state_r <= ST_IDLE;
            else
              gap_r <= gap_r + 1;
            end if;
        end case;
      end if;
    end if;
  end process seq_p;

  -- The wire: high for the first T0H or T1H cycles of each bit period,
  -- low through the tail of the period and the whole latch gap
  width_s <= CYCLES_T1H when shift_r(23) = '1' else CYCLES_T0H;
  dout    <= '1' when state_r = ST_SEND and phase_r < width_s else '0';
  busy    <= '0' when state_r = ST_IDLE else '1';

end architecture rtl;
Waveform loading...