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.
|
# 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
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
from migen import *
|
from migen import *
|
||||||
from migen.genlib.misc import WaitTimer
|
from migen.genlib.misc import WaitTimer
|
||||||
|
|
||||||
from litex.soc.interconnect.csr import *
|
from litex.soc.interconnect.csr import *
|
||||||
|
from litex.soc.interconnect import wishbone
|
||||||
|
|
||||||
# Led Chaser ---------------------------------------------------------------------------------------
|
# Led Chaser ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -47,3 +50,161 @@ class LedChaser(Module, AutoCSR):
|
||||||
)
|
)
|
||||||
# Use PWM as Output Enable for pads.
|
# Use PWM as Output Enable for pads.
|
||||||
self.comb += If(~self.pwm.pwm, self.pads.eq(0))
|
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