parent
ac3ffe2683
commit
f9264ac454
@ -0,0 +1,214 @@ |
||||
#!/usr/bin/env python3 |
||||
|
||||
# |
||||
# neopixelengine.py |
||||
# |
||||
# NeoPixel protocol engine (wanna-be), see: |
||||
# http://www.adafruit.com/datasheets/WS2812.pdf |
||||
# https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ |
||||
# |
||||
# History: |
||||
# -------- |
||||
# 15.10.20/KQ Initial module export, test bench added |
||||
# 16.10.20/KQ Several submodules (incl. FSM) tested |
||||
# 17.10.20/KQ NeoPixel engine up & driving LEDs |
||||
# 18.10.20/KQ Cleanup & more 'array' |
||||
# 19.10.20/KQ Renamed from 'neopixelprotocol' |
||||
# 27.10.20/KQ Using AutoDoc etc(improved documentation generation) |
||||
# |
||||
|
||||
from migen import * |
||||
from litex.soc.interconnect.csr import AutoCSR, CSRStatus, CSRStorage, CSRField, CSRAccess |
||||
from litex.soc.integration.doc import AutoDoc, ModuleDoc |
||||
|
||||
class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
||||
""" |
||||
NeoPixelEngine class provides the protocol logic to drive NeoPixel LED strips |
||||
|
||||
Usage: |
||||
###### |
||||
|
||||
#. Fill NeoPixelEngine's local array of GRB values (Green/Red/Blue). |
||||
Load ``b24Data2Load`` with a 24-bit (GRB) value. |
||||
Indicate the offset (where to store) via writing to ``b8LoadOffset``. |
||||
Repeat for all offsets 'til end of array ... |
||||
|
||||
#. Indicate to NeoPixelEngine the actual no. of pixels used by setting up ``b8Len``. |
||||
|
||||
#. Finally, enable processing by setting ``bEnable`` to true (1). |
||||
|
||||
Inputs: |
||||
####### |
||||
|
||||
:b24Data2Load: New data to be loaded (24 bits) |
||||
|
||||
:b8LoadOffset: Offset (0..255) into b24GRBArray to load b24Data2Load to |
||||
|
||||
:b8Len: Length (0..255) of actual 24-bit data entries (i.e. # of NeoPixels) |
||||
|
||||
:bEnable: To enable running (after data preparation) |
||||
|
||||
Output: |
||||
####### |
||||
|
||||
:bDataPin: NeoPixel 'Din' pin output (wire to actual output pin ... ;) |
||||
""" |
||||
def __init__(self, n_LEDs=3): |
||||
# On Colorlight-5A-75B/Lattice ECP5-25 (@i7/4th gen.): |
||||
# 256 NeoPixel LEDs will use 95% of TRELLIS_SLICES & REQUIRE ether_net & ether_bone being DISABLED! Calc. time >2h |
||||
# 192 NeoPixel LEDs w/ ethernet/etherbone will use 95% of TRELLIS_SLICES & take approx. 2:20h to calculate |
||||
# 27 NeoPixels LEDs w/ ethernet/etherbone used for any tests will require 73% of TRELLIS_SLICES & take less than 0:10h to calculate |
||||
self.b24GRBArray = Array(Signal(24) for word24 in range(n_LEDs)) # Local 24-bit data Array |
||||
# Inputs |
||||
self.b24Data2Load = CSRStorage(24, reset_less=True, |
||||
fields=[CSRField("Data2Load", size=24, description="*Field*: 24-Bit value")], |
||||
description=""" |
||||
Load value (24-Bit G/R/B). |
||||
Use ``b8LoadOffset`` first to indicate array location where to store this value. |
||||
""") |
||||
self.b8LoadOffset = CSRStorage(8, reset_less=True, |
||||
fields=[CSRField("LoadOffset", size=8, description="*Field*: 8-Bit value (0..max)")], |
||||
description=""" |
||||
Offset into storage array for 24-bit G/R/B values. |
||||
Prepare this one first, then indicate value to store via ``b24Data2Load``. |
||||
""") |
||||
self.b8Len = CSRStorage(8, reset_less=True, |
||||
fields=[CSRField("Len", size=8, description="*Field*: 8-Bit value (0..max)")], |
||||
description=""" |
||||
No. of active (GRB) entries. |
||||
Indicate actual # of elements used (may be less than max!) |
||||
""") |
||||
self.bEnable = CSRStorage(1, reset_less=True, |
||||
fields=[CSRField("Enable", size=1, description="*Field*: bit", values=[ |
||||
("0", "DISABLED", "``NeoPixel`` protocol not active"), |
||||
("1", "ENABLED", "``NeoPixel`` protocol active"), |
||||
]) |
||||
], |
||||
description=""" |
||||
Enable free run (signal start & abort) |
||||
""") |
||||
|
||||
# Local data |
||||
self.b8Offset = Signal(8) # Array rover |
||||
self.b24GRB = Signal(24) # Current 24-bit data to send |
||||
self.b12PulseLen = Signal(12) # Current pulse length |
||||
self.b5Count24 = Signal(5) # 24-Bit counter |
||||
|
||||
# Output |
||||
self.bDataPin = Signal() # To be wired to data pin ... |
||||
|
||||
### |
||||
fsm = FSM(reset_state="IDLE") # FSM starts idling ... |
||||
self.submodules += fsm |
||||
|
||||
self.sync += self.b24GRBArray[self.b8LoadOffset.storage].eq(self.b24Data2Load.storage) # Loader is allways active! |
||||
|
||||
fsm.act("IDLE", |
||||
If((self.bEnable.storage==True) and (self.b8Len.storage > 0), |
||||
NextValue(self.b8Offset, 0), # Start @ 1st 24-bit data |
||||
NextValue(self.b24GRB, self.b24GRBArray[0]), # ... but load 1st right away! |
||||
NextValue(self.b5Count24,0), # Bit count 0..23 |
||||
NextState("PREPAREBIT") |
||||
) |
||||
) |
||||
|
||||
# Protocol: T0H=400ns/T0L=850ns, T1H=800ns/T1L=450ns, RST>50ยตs(>50000ns) |
||||
fsm.act("PREPAREBIT", |
||||
If(self.b24GRB[23], |
||||
NextValue(self.b12PulseLen,47), # Compensate for 1 state changes w/o action ...), |
||||
NextState("T1H") |
||||
).Else( |
||||
NextValue(self.b12PulseLen,23), # Compensate for 1 state changes w/o action ... |
||||
NextState("T0H") |
||||
) |
||||
) |
||||
fsm.act("T1H", |
||||
NextValue(self.bDataPin, 1), |
||||
NextValue(self.b12PulseLen, self.b12PulseLen-1), |
||||
If(self.b12PulseLen == 0, |
||||
NextValue(self.b12PulseLen, 24), # Compensate for 3 state changes w/o action ... |
||||
NextState("T1L") |
||||
) |
||||
) |
||||
fsm.act("T1L", |
||||
NextValue(self.bDataPin, 0), |
||||
NextValue(self.b12PulseLen, self.b12PulseLen-1), |
||||
If(self.b12PulseLen == 0, |
||||
NextValue(self.b5Count24, self.b5Count24 + 1), # Next bit (of GRB) |
||||
NextValue(self.b24GRB, self.b24GRB << 1), # Next bit (of GRB) |
||||
NextState("NEXTBIT") |
||||
) |
||||
) |
||||
|
||||
fsm.act("T0H", |
||||
NextValue(self.bDataPin, 1), |
||||
NextValue(self.b12PulseLen, self.b12PulseLen-1), |
||||
If(self.b12PulseLen == 0, |
||||
NextValue(self.b12PulseLen, 48), # Compensate for 3 state changes w/o action ... |
||||
NextState("T0L") |
||||
) |
||||
) |
||||
fsm.act("T0L", |
||||
NextValue(self.bDataPin, 0), |
||||
NextValue(self.b12PulseLen, self.b12PulseLen-1), |
||||
If(self.b12PulseLen == 0, |
||||
NextValue(self.b5Count24, self.b5Count24 + 1), # Next bit (of GRB) |
||||
NextValue(self.b24GRB, self.b24GRB << 1), # Next bit (of GRB) |
||||
NextState("NEXTBIT") |
||||
) |
||||
) |
||||
|
||||
fsm.act("NEXTBIT", |
||||
If(self.b5Count24 < 24, # Not yet done? |
||||
NextState("PREPAREBIT") |
||||
).Else( # GRB word finished. More to come? |
||||
NextValue(self.b5Count24,0), # Bit count reset for next word |
||||
NextValue(self.b8Offset, self.b8Offset + 1), # Prepare offset for later use |
||||
NextState("NEXTWORD") |
||||
) |
||||
) |
||||
fsm.act("NEXTWORD", |
||||
If((self.b8Offset < self.b8Len.storage) & (self.bEnable.storage==True), # Still more words to come (& no exit request)? |
||||
NextValue(self.b24GRB, self.b24GRBArray[self.b8Offset]), # Load in advance |
||||
NextState("PREPAREBIT") |
||||
).Else( |
||||
NextValue(self.b12PulseLen, 4095), # >50ยตs required (3000 not ok!) |
||||
NextState("RST") |
||||
) |
||||
) |
||||
fsm.act("RST", |
||||
NextValue(self.bDataPin, 0), |
||||
NextValue(self.b12PulseLen, self.b12PulseLen-1), |
||||
If(self.b12PulseLen == 0, |
||||
NextState("IDLE") |
||||
) |
||||
) |
||||
|
||||
def npe_testbench(npe): |
||||
print("----- npe testbench -----") |
||||
yield npe.b8LoadOffset.storage.eq(3) |
||||
yield |
||||
yield npe.b24Data2Load.storage.eq(0x100000) |
||||
yield |
||||
yield npe.b24GRBArray[0].eq(0x123456) |
||||
yield |
||||
yield npe.b24GRBArray[1].eq(0x223344) |
||||
yield |
||||
yield npe.b24GRBArray[2].eq(0x654321) |
||||
yield |
||||
yield npe.b8Len.storage.eq(3) |
||||
yield |
||||
yield npe.bEnable.storage.eq(True) |
||||
yield |
||||
# |
||||
for i in range(10000): # Send the whole data & restart ... |
||||
print(i,": ", sep="", end="") |
||||
print((yield npe.bDataPin)) # Actual pin to move |
||||
yield |
||||
if i == 5000: |
||||
yield npe.bEnable.storage.eq(True) # Enable quickest restart ... |
||||
yield |
||||
|
||||
if __name__ == "__main__": |
||||
npe = NeoPixelEngine(n_LEDs=3) |
||||
run_simulation(npe, npe_testbench(npe), vcd_name="npe.vcd") |