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
 | |
| 
 | |
| 
 |