250 lines
14 KiB
Python
250 lines
14 KiB
Python
|
## pixel pusher!
|
||
|
import rp2
|
||
|
import array
|
||
|
import machine
|
||
|
import pin_defs
|
||
|
import dma_defs
|
||
|
import pio_code
|
||
|
import pio_defs
|
||
|
import micropython
|
||
|
from phosphor_gradient_14 import phosphor_gradient
|
||
|
from uctypes import addressof
|
||
|
|
||
|
## allows interrupts to throw errors
|
||
|
import micropython
|
||
|
micropython.alloc_emergency_exception_buf(100)
|
||
|
|
||
|
NOP = const(41) # 0x29 -- display on
|
||
|
SET_X = const(42) # 0x2A -- column select
|
||
|
SET_Y = const(43) # 0x2B -- row select
|
||
|
SET_COLOR = const(44) # 0x2C -- send data
|
||
|
X_MSB_OFFSET = const(1) # data from ADC comes 16 bits X | 16 bits Y, LSB order
|
||
|
Y_MSB_OFFSET = const(3)
|
||
|
|
||
|
COLOR_MASK = const(0x0F) ## 16 colors max
|
||
|
FRAME_MASK = const(0x0F) ## 16 colors max
|
||
|
|
||
|
_LOOPING = const(False)
|
||
|
_IRQ = const(False)
|
||
|
# pixel_debug = machine.Pin(27, machine.Pin.OUT)
|
||
|
|
||
|
class Pixel_Pusher():
|
||
|
|
||
|
@micropython.viper
|
||
|
def boop(self, color:int, frame:int):
|
||
|
"""Trigger me once per adc_reader frame update"""
|
||
|
# pixel_debug.high()
|
||
|
## set frame, color
|
||
|
machine.mem16[self.color_storage_address] = self.phosphors[color & COLOR_MASK]
|
||
|
self.stage_sample_data.registers[0] = self.frame_starts[frame & FRAME_MASK]
|
||
|
## reset counter lookup
|
||
|
self.pixel_frame_counter.registers[0] = self.frame_counter_lookup_address
|
||
|
## and go!
|
||
|
self.stage_sample_data.registers[7] = 1 ## transaction count trigger register
|
||
|
# pixel_debug.low()
|
||
|
|
||
|
@micropython.viper
|
||
|
def pixel_frame_interrupt_handler(self, dma_caller):
|
||
|
## even this is too much -- my guess is that the IRQs stack up during a GC or other system stupid thing
|
||
|
self.frame_done = True
|
||
|
|
||
|
def resume(self):
|
||
|
for d in self.allDMAs:
|
||
|
d.ctrl = d.ctrl | 1
|
||
|
self.stage_sample_data.config(trigger=True)
|
||
|
def pause(self):
|
||
|
for d in self.allDMAs:
|
||
|
d.ctrl = d.ctrl & ~1 ## clear enable bit
|
||
|
def deinit(self):
|
||
|
# print(self.allDMAs)
|
||
|
self.pixel_pusher_sm.active(0)
|
||
|
self.pause()
|
||
|
for d in self.allDMAs:
|
||
|
d.close()
|
||
|
|
||
|
def __init__(self, adc_reader):
|
||
|
## Consider deinitializing screen here? Or should that just always happen in code: risky! (@_@)
|
||
|
## Needs adc_reader b/c it needs to know where the samples are stored
|
||
|
self.frame_starts = adc_reader.frame_starts
|
||
|
self.adc_frame = adc_reader.current_frame
|
||
|
self.phosphors = phosphor_gradient
|
||
|
self.num_samples_per_frame = adc_reader.num_samples_per_frame
|
||
|
self.frame_done = False
|
||
|
self._init_PIO()
|
||
|
|
||
|
## Data storage for DMA trickery
|
||
|
## These three 32-bit numbers are formatted up to pass to the pixel_pusher_pio
|
||
|
## It takes care of setting the command bit when relevant, and repeating the data
|
||
|
## 120s are just placeholders for the X/Y coordinates
|
||
|
self.pixel_command_array = array.array("B",
|
||
|
[0, SET_X, 0, 120,
|
||
|
0, SET_Y, 0, 120,
|
||
|
0, SET_COLOR, 0xFF,0xFF,
|
||
|
0, NOP, 0, 0])
|
||
|
self.pixel_command_addr = array.array("L", [addressof(self.pixel_command_array)]) ## one element, for resetter
|
||
|
|
||
|
self.command_x = addressof(self.pixel_command_array) + 3 ## byte position in array
|
||
|
self.command_y = addressof(self.pixel_command_array) + 7
|
||
|
self.command_color = addressof(self.pixel_command_array) + 10
|
||
|
|
||
|
## Storage for one sample from ADC
|
||
|
self.one_sample_storage = bytearray(4)
|
||
|
self.one_sample_storage_address = addressof(self.one_sample_storage)
|
||
|
|
||
|
## Storage for the color
|
||
|
self.color_storage = bytearray(2)
|
||
|
## One way to set it
|
||
|
machine.mem16[addressof(self.color_storage)] = self.phosphors[15]
|
||
|
self.color_storage_address = addressof(self.color_storage)
|
||
|
|
||
|
## Strange array for counting purposes
|
||
|
## returns a count for stage_sample_data, num_samples_per_frame-1 times
|
||
|
## and then a 0, which will trigger and IRQ @ end of frame
|
||
|
## and then you can reset
|
||
|
self.frame_counter_lookup = array.array("L", [1]*(self.num_samples_per_frame-1))
|
||
|
self.frame_counter_lookup.append(0)
|
||
|
self.frame_counter_lookup_address = addressof(self.frame_counter_lookup)
|
||
|
|
||
|
## And here's the world's most convoluted DMA / PIO chain!
|
||
|
self.stage_sample_data = rp2.DMA() ## pulls one sample pair from memory into bit_flipper_pio
|
||
|
self.store_flipped_data = rp2.DMA() ## pulls bit-flipped pair and writes to sample storage
|
||
|
self.pixel_load_x = rp2.DMA() ## extracts X from sample storage, puts it in pixel command array
|
||
|
self.pixel_load_y = rp2.DMA() ## extracts Y ...
|
||
|
self.pixel_load_color = rp2.DMA() ## extracts color ...
|
||
|
self.pixel_command_to_screen = rp2.DMA() ## writes pixel command array to screen
|
||
|
self.pixel_command_resetter = rp2.DMA() ## resets command_to_screen after three commands
|
||
|
self.pixel_frame_counter = rp2.DMA() ## refreshes pixel command array address, stalls stage_sample_data at end of every frame
|
||
|
## by sending a zero read address. Restart by configuring these two.
|
||
|
self.allDMAs = [self.stage_sample_data, self.store_flipped_data, self.pixel_load_x , self.pixel_load_y , self.pixel_load_color, self.pixel_command_to_screen,
|
||
|
self.pixel_command_resetter , self.pixel_frame_counter ]
|
||
|
|
||
|
self.pixel_frame_counter_read_address = int(addressof(self.pixel_frame_counter.registers[0:]))
|
||
|
|
||
|
## Control defs:
|
||
|
## {'inc_read': 0, 'high_pri': 0, 'ring_sel': 0, 'size': 0,
|
||
|
## 'enable': 0, 'treq_sel': 0, 'sniff_en': 0, 'chain_to': 0,
|
||
|
## 'inc_write': 0, 'ring_size': 0, 'bswap': 0, 'IRQ_quiet': 0}
|
||
|
self.stage_sample_data.ctrl = self.stage_sample_data.pack_ctrl(default = 0,
|
||
|
size = dma_defs.SIZE_4BYTES,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.DREQ_PIO0_TX2, ## pace to TX FIFO
|
||
|
chain_to = self.store_flipped_data.channel_id,
|
||
|
IRQ_quiet = 1,
|
||
|
inc_read = 1
|
||
|
)
|
||
|
self.stage_sample_data.config(count = 1,
|
||
|
read = self.frame_starts[0], ## initial value, set this to start of frame to begin chain
|
||
|
write = pio_defs.PIO0_BASE + pio_defs.TXF2_OFFSET
|
||
|
)
|
||
|
if _IRQ:
|
||
|
self.stage_sample_data.irq(handler=self.pixel_frame_interrupt_handler, hard=True)
|
||
|
## when one-shot, don't need to interrupt itself.
|
||
|
|
||
|
|
||
|
## The bit-flipper PIO is in the middle here.
|
||
|
## It's PIO 0, 2
|
||
|
|
||
|
self.store_flipped_data.ctrl = self.store_flipped_data.pack_ctrl(default = 0,
|
||
|
size = dma_defs.SIZE_4BYTES,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.DREQ_PIO0_RX2, ## on PIO
|
||
|
chain_to = self.pixel_load_x.channel_id,
|
||
|
IRQ_quiet = 1
|
||
|
)
|
||
|
self.store_flipped_data.config(count = 1,
|
||
|
read = pio_defs.PIO0_BASE + pio_defs.RXF2_OFFSET,
|
||
|
write = self.one_sample_storage_address)
|
||
|
######
|
||
|
|
||
|
self.pixel_load_x.ctrl = self.pixel_load_x.pack_ctrl(default = 0,
|
||
|
size = dma_defs.SIZE_1BYTE,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.TREQ_PERMANENT,
|
||
|
chain_to = self.pixel_load_y.channel_id,
|
||
|
IRQ_quiet = 1
|
||
|
)
|
||
|
self.pixel_load_x.config(count = 1,
|
||
|
read = self.one_sample_storage_address + X_MSB_OFFSET,
|
||
|
write = self.command_x)
|
||
|
|
||
|
self.pixel_load_y.ctrl = self.pixel_load_y.pack_ctrl(default = 0,
|
||
|
size = dma_defs.SIZE_1BYTE,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.TREQ_PERMANENT,
|
||
|
chain_to = self.pixel_load_color.channel_id,
|
||
|
IRQ_quiet = 1
|
||
|
)
|
||
|
self.pixel_load_y.config(count = 1,
|
||
|
read = self.one_sample_storage_address + Y_MSB_OFFSET,
|
||
|
write = self.command_y)
|
||
|
|
||
|
self.pixel_load_color.ctrl = self.pixel_load_color.pack_ctrl(default = 0,
|
||
|
bswap = 1,
|
||
|
size = dma_defs.SIZE_2BYTES,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.TREQ_PERMANENT,
|
||
|
chain_to = self.pixel_command_to_screen.channel_id,
|
||
|
IRQ_quiet = 1
|
||
|
)
|
||
|
self.pixel_load_color.config(count = 1,
|
||
|
read = self.color_storage_address,
|
||
|
write = self.command_color)
|
||
|
|
||
|
self.pixel_command_to_screen.ctrl = self.pixel_command_to_screen.pack_ctrl(default = 0,
|
||
|
bswap = 1,
|
||
|
inc_read = 1,
|
||
|
size = dma_defs.SIZE_4BYTES,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.DREQ_PIO1_TX0,
|
||
|
chain_to = self.pixel_command_resetter.channel_id,
|
||
|
IRQ_quiet = 1,
|
||
|
ring_sel = 0, # ring on read
|
||
|
ring_size = 4 # 2**4 = 16 bytes = 4 transfers
|
||
|
)
|
||
|
self.pixel_command_to_screen.config(count = 3,
|
||
|
read = addressof(self.pixel_command_array),
|
||
|
write = pio_defs.PIO1_BASE + pio_defs.TXF0_OFFSET) ## PIO sm(4)
|
||
|
|
||
|
|
||
|
self.pixel_command_resetter.ctrl = self.pixel_command_resetter.pack_ctrl(default = 0,
|
||
|
size = dma_defs.SIZE_4BYTES,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.TREQ_PERMANENT,
|
||
|
IRQ_quiet = 1,
|
||
|
chain_to = self.pixel_frame_counter.channel_id
|
||
|
)
|
||
|
self.pixel_command_resetter.config(count = 1,
|
||
|
write = addressof(self.pixel_command_to_screen.registers[0:]), ## set read address back to top
|
||
|
read = addressof(self.pixel_command_addr))
|
||
|
|
||
|
self.pixel_frame_counter.ctrl = self.pixel_frame_counter.pack_ctrl(default = 0,
|
||
|
inc_read = 1,
|
||
|
size = dma_defs.SIZE_4BYTES,
|
||
|
enable = 1,
|
||
|
treq_sel = dma_defs.TREQ_PERMANENT,
|
||
|
IRQ_quiet = 1,
|
||
|
chain_to = self.pixel_frame_counter.channel_id
|
||
|
## ends the chain. reset the frame_counter_lookup to restart.
|
||
|
)
|
||
|
self.pixel_frame_counter.config(count = 1,
|
||
|
write = addressof(self.stage_sample_data.registers[7:]), ## count trigger register
|
||
|
read = addressof(self.frame_counter_lookup))
|
||
|
|
||
|
|
||
|
def _init_PIO(self):
|
||
|
## same pins shared with
|
||
|
self.sck_pin = machine.Pin(pin_defs.sck, machine.Pin.OUT)
|
||
|
self.data_pin = machine.Pin(pin_defs.data, machine.Pin.OUT)
|
||
|
self.dc_pin = machine.Pin(pin_defs.dc, machine.Pin.OUT)
|
||
|
|
||
|
## PIO 1, 0
|
||
|
self.pixel_pusher_sm = rp2.StateMachine(4, pio_code.handle_screen_command, freq=250_000_000,
|
||
|
out_base=self.data_pin, set_base=self.dc_pin, sideset_base=self.sck_pin)
|
||
|
self.pixel_pusher_sm.active(1)
|
||
|
|
||
|
## PIO 0, 2
|
||
|
self.bit_flipper_sm = rp2.StateMachine(2, pio_code.bit_flipper_pio, freq=250_000_000)
|
||
|
self.bit_flipper_sm.restart() ## flush buffers? belt and suspenders.
|
||
|
self.bit_flipper_sm.active(1)
|
||
|
|