cores/led: Add initial WS2812/NeoPixel core (MMAPed).
This commit is contained in:
parent
c13be522ce
commit
67431f4109
|
@ -1,13 +1,16 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2020 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# Copyright (c) 2020-2021 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import math
|
||||
|
||||
from migen import *
|
||||
from migen.genlib.misc import WaitTimer
|
||||
|
||||
from litex.soc.interconnect.csr import *
|
||||
from litex.soc.interconnect import wishbone
|
||||
|
||||
# Led Chaser ---------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -47,3 +50,161 @@ class LedChaser(Module, AutoCSR):
|
|||
)
|
||||
# Use PWM as Output Enable for pads.
|
||||
self.comb += If(~self.pwm.pwm, self.pads.eq(0))
|
||||
|
||||
|
||||
# WS2812/NeoPixel ----------------------------------------------------------------------------------
|
||||
|
||||
class WS2812(Module):
|
||||
"""WS2812/NeoPixel Led Driver.
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
||||
│DI DO│ │DI DO│ │DI DO│ │DI DO│ │DI DO│ Next leds
|
||||
FPGA ───► ├───► ├───► ├───► ├───► ├───► or
|
||||
│ LED0 │ │ LED1 │ │ LED2 │ │ LED3 │ │ LED4 │ end of chain.
|
||||
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
|
||||
24-bit 24-bit 24-bit 24-bit 24-bit
|
||||
|
||||
WS2812/NeoPixel Leds are smart RGB Leds controlled over a simple one wire protocol:
|
||||
- Each Led will "digest" a 24-bit control word: (MSB) G-R-B (LSB).
|
||||
- Leds can be chained through DIN->DOUT connection.
|
||||
|
||||
Each control sequence is separated by a reset code: Line low for > 50us.
|
||||
Ones are transmitted as:
|
||||
┌─────┐
|
||||
│ T0H │ │ T0H = 400ns +-150ns
|
||||
│ │ T0L │ T0L = 800ns +-150ns
|
||||
└───────────┘
|
||||
Zeros are transmitted as:
|
||||
┌──────────┐
|
||||
│ T1H │ │ T1H = 850ns +-150ns
|
||||
│ │ T1L │ T1L = 450ns +-150ns
|
||||
└──────┘
|
||||
|
||||
Integration
|
||||
-----------
|
||||
|
||||
The core handles the WS2812 protocol and exposes the Led chain as an MMAPed peripheral:
|
||||
|
||||
32-bit
|
||||
00_GG_RR_BB
|
||||
┌───────────┐
|
||||
Base + 0 │ LED0 │
|
||||
├───────────┤
|
||||
Base + 4 │ LED1 │
|
||||
├───────────┤
|
||||
Base + 8 │ LED2 │
|
||||
└───────────┘
|
||||
... ...
|
||||
|
||||
It can be simply integrated in a LiteX SoC with:
|
||||
self.submodules.ws2812 = WS2812(platform.request("x"), nleds=32, sys_clk_freq=sys_clk_freq)
|
||||
self.bus.add_slave(name="ws2812", slave=self.ws2812.bus, region=SoCRegion(
|
||||
origin = 0x2000_0000,
|
||||
size = 32*4,
|
||||
))
|
||||
|
||||
Each Led can then be directly controlled from the Bus of the SoC.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pad : Signal, in
|
||||
FPGA DOut.
|
||||
nleds : int, in
|
||||
Number of Leds in the chain.
|
||||
sys_clk_freq: int, in
|
||||
System Clk Frequency.
|
||||
"""
|
||||
def __init__(self, pad, nleds, sys_clk_freq):
|
||||
# Memory.
|
||||
mem = Memory(32, nleds)
|
||||
port = mem.get_port()
|
||||
self.specials += mem, port
|
||||
|
||||
# Wishone Memory.
|
||||
self.submodules.wb_mem = wishbone.SRAM(
|
||||
mem_or_size = mem,
|
||||
read_only = False,
|
||||
bus = wishbone.Interface(data_width=32)
|
||||
)
|
||||
self.bus = self.wb_mem.bus
|
||||
|
||||
# Internal Signals.
|
||||
led_data = Signal(24)
|
||||
bit_count = Signal(8)
|
||||
led_count = Signal(int(math.log2(nleds)))
|
||||
|
||||
# Timings.
|
||||
trst = 75e-6
|
||||
t0h = 0.40e-6
|
||||
t0l = 0.85e-6
|
||||
t1h = 0.80e-6
|
||||
t1l = 0.45e-6
|
||||
|
||||
# Timers.
|
||||
t0h_timer = WaitTimer(int(t0h*sys_clk_freq))
|
||||
t0l_timer = WaitTimer(int(t0l*sys_clk_freq))
|
||||
self.submodules += t0h_timer, t0l_timer
|
||||
|
||||
t1h_timer = WaitTimer(int(t1h*sys_clk_freq))
|
||||
t1l_timer = WaitTimer(int(t1l*sys_clk_freq))
|
||||
self.submodules += t1h_timer, t1l_timer
|
||||
|
||||
trst_timer = WaitTimer(int(trst*sys_clk_freq))
|
||||
self.submodules += trst_timer
|
||||
|
||||
# FSM
|
||||
self.submodules.fsm = fsm = FSM(reset_state="RST")
|
||||
fsm.act("RST",
|
||||
trst_timer.wait.eq(1),
|
||||
If(trst_timer.done,
|
||||
NextValue(led_count, 0),
|
||||
NextState("LED-SHIFT")
|
||||
)
|
||||
)
|
||||
self.comb += port.adr.eq(led_count)
|
||||
fsm.act("LED-SHIFT",
|
||||
NextValue(bit_count, 24-1),
|
||||
NextValue(led_data, port.dat_r),
|
||||
NextValue(led_count, led_count + 1),
|
||||
If(led_count == (nleds-1),
|
||||
NextState("RST")
|
||||
).Else(
|
||||
NextState("BIT-TEST")
|
||||
)
|
||||
)
|
||||
fsm.act("BIT-TEST",
|
||||
If(led_data[-1] == 0,
|
||||
NextState("ZERO-SEND"),
|
||||
),
|
||||
If(led_data[-1] == 1,
|
||||
NextState("ONE-SEND"),
|
||||
),
|
||||
)
|
||||
fsm.act("ZERO-SEND",
|
||||
t0h_timer.wait.eq(1),
|
||||
t0l_timer.wait.eq(t0h_timer.done),
|
||||
pad.eq(~t0h_timer.done),
|
||||
If(t0l_timer.done,
|
||||
NextState("BIT-SHIFT")
|
||||
)
|
||||
)
|
||||
fsm.act("ONE-SEND",
|
||||
t1h_timer.wait.eq(1),
|
||||
t1l_timer.wait.eq(t1h_timer.done),
|
||||
pad.eq(~t1h_timer.done),
|
||||
If(t1l_timer.done,
|
||||
NextState("BIT-SHIFT")
|
||||
)
|
||||
)
|
||||
fsm.act("BIT-SHIFT",
|
||||
NextValue(led_data, Cat(Signal(), led_data)),
|
||||
NextValue(bit_count, bit_count - 1),
|
||||
If(bit_count == 0,
|
||||
NextState("LED-SHIFT")
|
||||
).Else(
|
||||
NextState("BIT-TEST")
|
||||
)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue