sc7-default-firmware/vectorscope.py

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