113 lines
4.1 KiB
Python
113 lines
4.1 KiB
Python
|
import gc
|
||
|
import time
|
||
|
import gc9a01
|
||
|
import uctypes
|
||
|
import machine
|
||
|
import _thread
|
||
|
|
||
|
## Badge-specific classes
|
||
|
from codec import Codec
|
||
|
from screen import Screen
|
||
|
from waveform import Waveform
|
||
|
from adc_reader import ADC_Reader
|
||
|
from pixel_pusher import Pixel_Pusher
|
||
|
|
||
|
## Misc helpers and defines
|
||
|
import dma_defs
|
||
|
import pin_defs
|
||
|
|
||
|
class Vectorscope():
|
||
|
|
||
|
def __init__(self, screen_running = False):
|
||
|
|
||
|
## Couple buttons if you want to play with them
|
||
|
# self.audio_shutdown_pin = machine.Pin(pin_defs.audio_shutdown, machine.Pin.OUT, value=1)
|
||
|
self.user_button = machine.Pin(pin_defs.user_button, machine.Pin.IN)
|
||
|
|
||
|
## Turn up the heat!
|
||
|
machine.freq(250_000_000)
|
||
|
|
||
|
if not screen_running:
|
||
|
## We actually only use the raster screen for the init routine.
|
||
|
## So don't need to re-init if coming from the main menu
|
||
|
self.screen = Screen()
|
||
|
self.screen.tft.fill(gc9a01.BLACK)
|
||
|
## deinit here since we don't need screen around
|
||
|
self.screen.deinit()
|
||
|
gc.collect()
|
||
|
|
||
|
## start up I2S state machines
|
||
|
self.codec = Codec()
|
||
|
gc.collect()
|
||
|
|
||
|
## Fire up the I2S output feeder
|
||
|
self.wave = Waveform()
|
||
|
gc.collect()
|
||
|
|
||
|
## sets up memory, DMAs to continuously read in ADC data into a 16-stage buffer
|
||
|
self.adc_reader = ADC_Reader()
|
||
|
gc.collect()
|
||
|
|
||
|
## automatically blits memory out to screen
|
||
|
## needs adc_reader b/c needs to know where samples go
|
||
|
## this is the real house of cards...
|
||
|
self.pixel_pusher = Pixel_Pusher(self.adc_reader)
|
||
|
gc.collect()
|
||
|
|
||
|
## start up the phosphor effect feeder on the other core,
|
||
|
## b/c it's a ridiculous CPU hog with precise timing requirements
|
||
|
self.kill_phosphor = False
|
||
|
_thread.start_new_thread(self.phosphor, [()])
|
||
|
|
||
|
|
||
|
|
||
|
def phosphor(self, thread_callback_stuff):
|
||
|
previous_frame = self.adc_reader.current_frame
|
||
|
## end of frame counter for pixel pusher, used to trigger next frame
|
||
|
end_of_pixel_counter = uctypes.addressof(self.pixel_pusher.frame_counter_lookup)+1024*4
|
||
|
while not self.kill_phosphor:
|
||
|
## wait for ADC frame change to sync up
|
||
|
while self.adc_reader.current_frame == previous_frame:
|
||
|
pass ## (@_@) this could be an async wait: should have ~10 ms of CPU time
|
||
|
previous_frame = self.adc_reader.current_frame
|
||
|
|
||
|
## While reading this ADC frame, push out all the others to the screen
|
||
|
## starting with the oldest frame,
|
||
|
## i.e. the next one in line. (circular buffer and all that.)
|
||
|
frame_counter = (self.adc_reader.current_frame + 1) & 0x0F
|
||
|
phosphor_counter = 1 ## this is the dimmest / off phosphor level
|
||
|
for i in range(15):
|
||
|
## Start off a frame's worth of pixels off to the screen
|
||
|
self.pixel_pusher.boop(phosphor_counter, (frame_counter+i) & 0x0F)
|
||
|
## next brighter color up next
|
||
|
phosphor_counter = phosphor_counter + 1
|
||
|
## wait for pixel frame to finish -- this is happening in a DMA/PIO chain asynchronously
|
||
|
while self.pixel_pusher.pixel_frame_counter.read != end_of_pixel_counter:
|
||
|
pass ## this one should be a busy-wait.
|
||
|
## It's on the order of microseconds, depending on screen speed.
|
||
|
|
||
|
gc.collect() ## doing this preemptively here where we have time prevents it from happening when we don't want
|
||
|
## One gc.collect per 35 ms is excessive. But it makes the beast happy, and we're stalling anyway.
|
||
|
_thread.exit()
|
||
|
|
||
|
def deinit(self):
|
||
|
|
||
|
self.kill_phosphor = True
|
||
|
machine.freq(125_000_000)
|
||
|
|
||
|
self.pixel_pusher.deinit()
|
||
|
self.adc_reader.deinit()
|
||
|
self.wave.deinit()
|
||
|
self.codec.deinit()
|
||
|
|
||
|
## doesn't seem to work.
|
||
|
## Get OS Error 16 on next run
|
||
|
## brute force... grrr...
|
||
|
machine.reset()
|
||
|
|
||
|
|
||
|
def call_out(self):
|
||
|
pass
|
||
|
|
||
|
|