commit efb2b80b25d0caecb4d2e9ad77e373b8557746fa Author: true Date: Fri Nov 3 09:47:33 2023 -0700 Initial commit of original SC7 code diff --git a/A.py b/A.py new file mode 100644 index 0000000..556f6f8 --- /dev/null +++ b/A.py @@ -0,0 +1,34 @@ + +from vectorscope import Vectorscope +import vectoros +import keyboardcb +import keyleds +import asyncio + +import random_walk + +_abort=False + +async def random_walker(v): + ## Minimal example + global _abort + r = random_walk.RW(v) + x,y = 0,0 + while _abort==False: + x,y = r.random_walk(x,y) + ## this is important -- it yields to the key scanner + await asyncio.sleep_ms(10) + +## Below here is boilerplate. +def do_abort(key): + global _abort + _abort=True + +from vos_state import vos_state + +async def slot_main(v): + global _abort + # watch the keys -- you can define your own callbacks here + mykeys = keyboardcb.KeyboardCB( {keyleds.KEY_MENU: do_abort} ) + await random_walker(v) + print("OK done") diff --git a/B.py b/B.py new file mode 100644 index 0000000..66ee7af --- /dev/null +++ b/B.py @@ -0,0 +1,63 @@ + +import math +import time + +from vectorscope import Vectorscope + +import vectoros +import keyboardcb +import keyleds +import asyncio + + +_abort=False +_xscale=1 +_yscale=1 + +async def kminimal_example(v): + ## Minimal example with keys + global _abort, _xscale, _yscale + while _abort==False: + for i in range(360): + scale=1 + if _abort: + print("Get out!") + break + v.wave.constantX(int(math.cos(i * math.pi / 180 * 5*_xscale) * 10000)) + v.wave.constantY(int(math.sin(i * math.pi / 180 * 5*_yscale)* 10000)) + await asyncio.sleep_ms(10) + + +def do_abort(key): + global _abort + _abort=True + +def do_xscale(key): + global _xscale + _xscale+=1 + if _xscale>6: + _xscale=1 + + +def do_yscale(key): + global _yscale + _yscale+=1 + if _yscale>6: + _yscale=1 + + + +from vos_state import vos_state + +async def slot_main(v): + global _abort,_continue +# So... Press D (or whatever is configured) and note the message below. Press Range to start the demo +# The demo will run until you press Menu. LEVEL/RANGE will change frequency of X and Y in steps +# Note that if you don't yield occasionaly, you don't get key scanning + + # watch the keys + mykeys=keyboardcb.KeyboardCB({ keyleds.KEY_LEVEL: do_xscale, keyleds.KEY_RANGE: do_yscale, keyleds.KEY_MENU: do_abort}) + + + await kminimal_example(v) + print("OK done") diff --git a/C.py b/C.py new file mode 100644 index 0000000..556f6f8 --- /dev/null +++ b/C.py @@ -0,0 +1,34 @@ + +from vectorscope import Vectorscope +import vectoros +import keyboardcb +import keyleds +import asyncio + +import random_walk + +_abort=False + +async def random_walker(v): + ## Minimal example + global _abort + r = random_walk.RW(v) + x,y = 0,0 + while _abort==False: + x,y = r.random_walk(x,y) + ## this is important -- it yields to the key scanner + await asyncio.sleep_ms(10) + +## Below here is boilerplate. +def do_abort(key): + global _abort + _abort=True + +from vos_state import vos_state + +async def slot_main(v): + global _abort + # watch the keys -- you can define your own callbacks here + mykeys = keyboardcb.KeyboardCB( {keyleds.KEY_MENU: do_abort} ) + await random_walker(v) + print("OK done") diff --git a/D.py b/D.py new file mode 100644 index 0000000..556f6f8 --- /dev/null +++ b/D.py @@ -0,0 +1,34 @@ + +from vectorscope import Vectorscope +import vectoros +import keyboardcb +import keyleds +import asyncio + +import random_walk + +_abort=False + +async def random_walker(v): + ## Minimal example + global _abort + r = random_walk.RW(v) + x,y = 0,0 + while _abort==False: + x,y = r.random_walk(x,y) + ## this is important -- it yields to the key scanner + await asyncio.sleep_ms(10) + +## Below here is boilerplate. +def do_abort(key): + global _abort + _abort=True + +from vos_state import vos_state + +async def slot_main(v): + global _abort + # watch the keys -- you can define your own callbacks here + mykeys = keyboardcb.KeyboardCB( {keyleds.KEY_MENU: do_abort} ) + await random_walker(v) + print("OK done") diff --git a/adc_reader.py b/adc_reader.py new file mode 100644 index 0000000..55b8b39 --- /dev/null +++ b/adc_reader.py @@ -0,0 +1,146 @@ +from uctypes import addressof +import array +import rp2 +import time +import machine +import dma_defs +import pio_defs + +# read_debug = machine.Pin(26, machine.Pin.OUT) + +num_frames = const(16) +num_samples_per_frame = const(1024) +bytes_per_sample = const(4) +audio_data_length = const(num_frames * num_samples_per_frame * bytes_per_sample) + +class ADC_Reader(): + + def __init__(self): + + self.num_samples_per_frame = num_samples_per_frame + ## Setup storage for frames of samples + self.audio_data = bytearray(audio_data_length) + ## and the individual frames start here + self.frame_starts = array.array("L", [addressof(self.audio_data) + i*num_samples_per_frame*bytes_per_sample for i in range(num_frames)]) + self.current_frame = 0 + ## finish up init + self.align_frame_lookup_address() + self.config_dmas() + + def init(self): + self.__init__() + + def deinit(self): + """unwind all of the above""" + self.audio_read_control.ctrl = 0 + time.sleep_ms(10) ## (@_@) replace with asyncio when necessary + self.audio_read_transfer.ctrl = 0 + self.audio_read_control.close() + time.sleep_ms(10) ## (@_@) replace with asyncio when necessary + self.audio_read_transfer.close() + + def pause(self): + self.audio_read_transfer.ctrl &= ~(1) ## mask enable bit + time.sleep_ms(10) + self.audio_read_control.ctrl &= ~(1) ## mask enable bit + + def resume(self): + self.audio_read_control.ctrl |= 1 ## set enable bit + time.sleep_ms(10) + self.audio_read_transfer.ctrl |= 1 ## set enable bit + self.audio_read_transfer.config(trigger=True) + + @micropython.viper + def audio_read_frame_interrupt(self, calling_dma): + """fires off once per frame, in parallel with audio_read_control""" + # read_debug.high() + self.current_frame = (int(self.current_frame)+1) & 0x0F + ## setup frames/colors and trigger pixel pusher here + ## want it to go through one cycle -- 15 frames at 15 colors. + + # read_debug.low() + + def config_dmas(self): + self.audio_read_transfer = rp2.DMA() + #print("self.audio_read_transfer") + #print(self.audio_read_transfer) + self.audio_read_control = rp2.DMA() + #print("self.audio_read_control") + #print(self.audio_read_control) + self.audio_read_transfer.ctrl = self.audio_read_transfer.pack_ctrl(default = 0, + size = dma_defs.SIZE_4BYTES, + enable = 1, + treq_sel = dma_defs.DREQ_PIO0_RX0, + inc_write = 1, + chain_to = self.audio_read_control.channel_id + ) + self.audio_read_transfer.config(count = num_samples_per_frame, + read = pio_defs.PIO0_BASE + pio_defs.RXF0_OFFSET) + self.audio_read_transfer.irq(handler = self.audio_read_frame_interrupt) + # manual trigger + # self.audio_read_transfer.config(write = audio_data, trigger=True) + + self.audio_read_control.ctrl = self.audio_read_control.pack_ctrl(default = 0, + size = dma_defs.SIZE_4BYTES, + enable = 1, + treq_sel = dma_defs.TREQ_PERMANENT, + IRQ_quiet = 1, + chain_to = self.audio_read_transfer.channel_id, + inc_read = 1, + ring_size = 6) ## 16 addresses * 4 bytes + ## feed write addresses of each frame start when done with a frame + self.audio_read_control.config(write = addressof(self.audio_read_transfer.registers[1:]), + read = self.frame_lookup_address, + count = 1, + trigger = True) + + + def align_frame_lookup_address(self): + ## Need to align this for DMA to enable ring looping over frame starts. + ## each address is 4 bytes, need 16 of them: 2+4 = 6 bits clear -- align to nearest 0b11000000 = C0 + ## with 64 bytes to be stored, need 128 to guarantee clear (2x num_frames * 4 bytes) + ## Make an aligned lookup table for DMA porpoises + self.frame_lookup = bytearray(num_frames * 4 * 2) + self.frame_lookup_address = addressof(self.frame_lookup) + + ## spool forward to nearest /64 block : frame_lookup_address is now ready to use + while self.frame_lookup_address != self.frame_lookup_address & (0xFFFFFFC0): + self.frame_lookup_address = self.frame_lookup_address + 1 + + ## copy frame starts over into lookup LSB first + ## could probably do this with struct.pack()? + tempaddress = self.frame_lookup_address + for f in self.frame_starts: + machine.mem8[tempaddress] = f & 0xFF + machine.mem8[tempaddress+1] = (f & 0xFF00) >> 8 + machine.mem8[tempaddress+2] = (f & 0xFF0000) >> 16 + machine.mem8[tempaddress+3] = (f & 0xFF000000) >> 24 + tempaddress = tempaddress + 4 + + + #################################### + ## Residual useful debugging stuff + #################################### + + def debug_print_frames(self,n): + for j in range(16): + print(f'############## {j} ################') + for i in range(n): + dma_defs.print_friendly(machine.mem32[self.frame_starts[j]+4*i]) + + def dma_frame_diagnostics(self): + current_write_addr = self.audio_read_transfer.write + current_frame_pointer = self.audio_read_control.read + current_frame = (current_frame_pointer - self.frame_lookup_address) // 4 + current_frame_start_addr = machine.mem32[current_frame_pointer] + print("Address of frame start pointer") + print(hex(current_frame_pointer)) + print("Address of frame start") + print(hex(current_frame_start_addr)) + print(f"Frame number: {current_frame}") + print("Address of current datapoint") + print(hex(current_write_addr)) + + + + diff --git a/aiorepl.py b/aiorepl.py new file mode 100644 index 0000000..e75fc8b --- /dev/null +++ b/aiorepl.py @@ -0,0 +1,186 @@ +# MIT license; Copyright (c) 2022 Jim Mussared + +import micropython +from micropython import const +import re +import sys +import time +import asyncio + +# Import statement (needs to be global, and does not return). +_RE_IMPORT = re.compile("^import ([^ ]+)( as ([^ ]+))?") +_RE_FROM_IMPORT = re.compile("^from [^ ]+ import ([^ ]+)( as ([^ ]+))?") +# Global variable assignment. +_RE_GLOBAL = re.compile("^([a-zA-Z0-9_]+) ?=[^=]") +# General assignment expression or import statement (does not return a value). +_RE_ASSIGN = re.compile("[^=]=[^=]") + +# Command hist (One reserved slot for the current command). +_HISTORY_LIMIT = const(5 + 1) + + +async def execute(code, g, s): + if not code.strip(): + return + + try: + if "await " in code: + # Execute the code snippet in an async context. + if m := _RE_IMPORT.match(code) or _RE_FROM_IMPORT.match(code): + code = "global {}\n {}".format(m.group(3) or m.group(1), code) + elif m := _RE_GLOBAL.match(code): + code = "global {}\n {}".format(m.group(1), code) + elif not _RE_ASSIGN.search(code): + code = "return {}".format(code) + + code = """ +import uasyncio as asyncio +async def __code(): + {} + +__exec_task = asyncio.create_task(__code()) +""".format( + code + ) + + async def kbd_intr_task(exec_task, s): + while True: + if ord(await s.read(1)) == 0x03: + exec_task.cancel() + return + + l = {"__exec_task": None} + exec(code, g, l) + exec_task = l["__exec_task"] + + # Concurrently wait for either Ctrl-C from the stream or task + # completion. + intr_task = asyncio.create_task(kbd_intr_task(exec_task, s)) + + try: + try: + return await exec_task + except asyncio.CancelledError: + pass + finally: + intr_task.cancel() + try: + await intr_task + except asyncio.CancelledError: + pass + else: + # Excute code snippet directly. + try: + try: + micropython.kbd_intr(3) + try: + return eval(code, g) + except SyntaxError: + # Maybe an assignment, try with exec. + return exec(code, g) + except KeyboardInterrupt: + pass + finally: + micropython.kbd_intr(-1) + + except Exception as err: + print("{}: {}".format(type(err).__name__, err)) + + +# REPL task. Invoke this with an optional mutable globals dict. +async def task(g=None, prompt="--> "): + print("Starting asyncio REPL...") + if g is None: + g = __import__("__main__").__dict__ + try: + micropython.kbd_intr(-1) + s = asyncio.StreamReader(sys.stdin) + # clear = True + hist = [None] * _HISTORY_LIMIT + hist_i = 0 # Index of most recent entry. + hist_n = 0 # Number of history entries. + c = 0 # ord of most recent character. + t = 0 # timestamp of most recent character. + while True: + hist_b = 0 # How far back in the history are we currently. + sys.stdout.write(prompt) + cmd = "" + while True: + b = await s.read(1) + pc = c # save previous character + c = ord(b) + pt = t # save previous time + t = time.ticks_ms() + if c < 0x20 or c > 0x7E: + if c == 0x0A: + # LF + # If the previous character was also LF, and was less + # than 20 ms ago, this was likely due to CRLF->LFLF + # conversion, so ignore this linefeed. + if pc == 0x0A and time.ticks_diff(t, pt) < 20: + continue + sys.stdout.write("\n") + if cmd: + # Push current command. + hist[hist_i] = cmd + # Increase history length if possible, and rotate ring forward. + hist_n = min(_HISTORY_LIMIT - 1, hist_n + 1) + hist_i = (hist_i + 1) % _HISTORY_LIMIT + + result = await execute(cmd, g, s) + if result is not None: + sys.stdout.write(repr(result)) + sys.stdout.write("\n") + break + elif c == 0x08 or c == 0x7F: + # Backspace. + if cmd: + cmd = cmd[:-1] + sys.stdout.write("\x08 \x08") + elif c == 0x02: + # Ctrl-B + continue + elif c == 0x03: + # Ctrl-C + if pc == 0x03 and time.ticks_diff(t, pt) < 120: #was 20 + # Two very quick Ctrl-C (faster than a human + # typing) likely means mpremote trying to + # escape. + asyncio.new_event_loop() + return + sys.stdout.write("\n") + break + elif c == 0x04: + # Ctrl-D + sys.stdout.write("\n") + # Shutdown asyncio. + asyncio.new_event_loop() + return + elif c == 0x1B: + # Start of escape sequence. + key = await s.read(2) + if key in ("[A", "[B"): + # Stash the current command. + hist[(hist_i - hist_b) % _HISTORY_LIMIT] = cmd + # Clear current command. + b = "\x08" * len(cmd) + sys.stdout.write(b) + sys.stdout.write(" " * len(cmd)) + sys.stdout.write(b) + # Go backwards or forwards in the history. + if key == "[A": + hist_b = min(hist_n, hist_b + 1) + else: + hist_b = max(0, hist_b - 1) + # Update current command. + cmd = hist[(hist_i - hist_b) % _HISTORY_LIMIT] + sys.stdout.write(cmd) + else: + # sys.stdout.write("\\x") + # sys.stdout.write(hex(c)) + pass + else: + sys.stdout.write(b) + cmd += b + finally: + micropython.kbd_intr(3) diff --git a/codec.py b/codec.py new file mode 100644 index 0000000..884a72e --- /dev/null +++ b/codec.py @@ -0,0 +1,64 @@ +from machine import Pin, SPI, PWM, I2C, I2S, PWM, freq +import time +import rp2 +import machine +import pin_defs +import pio_defs +import pio_code + +## I2C bus address +ak4619_addr = const(0x10) +READ_FIFO = pio_defs.PIO0_BASE + pio_defs.RXF0_OFFSET ## 0, 0 +WRITE_FIFO = pio_defs.PIO0_BASE + pio_defs.TXF1_OFFSET ## 0, 1 + +class Codec(): + def __init__(self): + # RPi I2S doesn't have master clock -- we make one with PWM + # 125000000 / 16 = 7.8125 MHz master clock + # 250_000_000 / 16 = 7.8125 MHz master clock + self.MASTER_CLOCK_HZ = freq() // 32 + # fs sample clock = MCLK / 256 = 30517.578 Hz + self.mclk = Pin(pin_defs.mclk, Pin.OUT) + self.mclk_pwm = PWM(self.mclk, freq=self.MASTER_CLOCK_HZ, duty_u16=32768) + + self.bit_clock = Pin(pin_defs.bit_clock, Pin.OUT, value=0) ## fs / 32 + self.lr_clock = Pin(pin_defs.lr_clock, Pin.OUT, value=0) ## fs + self.sd_in = Pin(pin_defs.sd_in, Pin.IN) ## In 3/4 (Y=L, X=R) + self.sd_out = Pin(pin_defs.sd_out, Pin.OUT, value=0) ## Out 1/2 (Y=L, X=R) + self.pdn = Pin(pin_defs.pdn, Pin.OUT, value=0) ## Power down on low + + self.i2s_read_sm = rp2.StateMachine(0, pio_code.i2s_read_pio, freq=self.MASTER_CLOCK_HZ // 2, sideset_base=self.bit_clock, in_base=self.sd_in) + self.i2s_write_sm = rp2.StateMachine(1, pio_code.i2s_write_pio, freq=self.MASTER_CLOCK_HZ // 2, out_base=self.sd_out) + self.start() + + def deinit(self): + self.i2s_read_sm.active(0) + self.i2s_write_sm.active(0) + self.mclk_pwm.deinit() + self.pdn.value(0) + + ## Manually resync the PIO state machines so they run in cycle + def start(self): + self.mclk_pwm = PWM(self.mclk, freq=self.MASTER_CLOCK_HZ, duty_u16=32768) + machine.mem32[pio_defs.PIO0_BASE] = (0b1111 << pio_defs.CLKDIV_RESTART_BIT) ## reset all clock dividers + machine.mem32[pio_defs.PIO0_BASE] = (0b1111 << pio_defs.SM_ENABLE_BIT) ## enable all simultaneously to sync up + self.config_i2c() + + def config_i2c(self): + self.i2c = I2C(0, scl=Pin(pin_defs.i2c_scl), sda=Pin(pin_defs.i2c_sda), freq=400_000) + ## Initialize AK4619 registers + ## PDN starts 0, when goes high, enters config mode after 10 ms -- datasheet + self.pdn.value(1) + time.sleep_ms(10) + ## Config for 16-bit samples & 16-bit slots + self.i2c.writeto(ak4619_addr, bytes([0x01, 0x3A])) # MSB Justified, 16 bit slot and read data on falling bit-clock + self.i2c.writeto(ak4619_addr, bytes([0x02, 0x0A])) # 16 bit data on in and out + ## Config single-ended input on 3 & 4 + self.i2c.writeto(ak4619_addr, bytes([0x0B, 0x05])) + ## slow ADC filters -- need to look into this later? + self.i2c.writeto(ak4619_addr, bytes([0x0A, 0x22])) + self.i2c.writeto(ak4619_addr, bytes([0x0D, 0x06])) + self.i2c.writeto(ak4619_addr, bytes([0x00, 0x37])) ## Power on, release reset bit + + + diff --git a/colors.py b/colors.py new file mode 100644 index 0000000..a226fd3 --- /dev/null +++ b/colors.py @@ -0,0 +1,24 @@ +from gc9a01 import color565 +import gc9a01 as lcd +import phosphor_gradient_14 + +PHOSPHOR = phosphor_gradient_14.phosphor_gradient + +PHOSPHOR_BRIGHT=PHOSPHOR[15] +PHOSPHOR_DARK=PHOSPHOR[12] +PHOSPHOR_BG=PHOSPHOR[0] + +BLACK=lcd.BLACK +WHITE=lcd.WHITE +RED=lcd.RED +GREEN=lcd.GREEN +BLUE=lcd.BLUE +CYAN=lcd.CYAN +MAGENTA=lcd.MAGENTA +YELLOW=lcd.YELLOW + +GRAY=color565(0x80,0x80,0x80) + +def rgb(r,g,b): + return color565(r,g,b) + diff --git a/dds.py b/dds.py new file mode 100644 index 0000000..acb4cfb --- /dev/null +++ b/dds.py @@ -0,0 +1,109 @@ +import array +import generate_wavetables + +NUM_SAMPLES = const(256) +ACCUMULATOR_BITS = const(16) + +X_CHANNEL = const(0) +Y_CHANNEL = const(1) + +WAVEFORM_SINE = const(0) +WAVEFORM_SQUARE = const(1) +WAVEFORM_SAWTOOTH = const(2) +WAVEFORM_TRIANGLE = const(3) + + +class DDS(): + + def __init__(self, vectorscope): + """ pass it an instance of the Vectorscope module from main, it needs to write to the DMA audio registers""" + self.wave = vectorscope.wave + + self.waveform = ["sine", "sine"] + self.increment = [51000, 25000] + self.amplitude = [1, 1] ## set this infrequently? -- it's an expensive calculation + self.phase = [0, 0] + self.phase_increment = [0, 0] + self.index = [0, 0] + self.accumulator = [0, 0] + self.samplesX = [0]*NUM_SAMPLES + self.samplesY = [0]*NUM_SAMPLES + self.samples = [self.samplesX, self.samplesY] + + self.base_waveforms = {} + self.base_waveforms["sine"] = generate_wavetables.sine() + self.base_waveforms["square"] = generate_wavetables.square() + self.base_waveforms["sawtooth"] = generate_wavetables.sawtooth() + self.base_waveforms["triangle"] = generate_wavetables.triangle() + + ## init + self.waves = [[0]*256, [0]*256] + self.recalculate_waveforms() ## have to do this every time you change parameters. + + def recalculate_waveforms(self): ## select 0=X, 1=Y + for i in [0,1]: + self.waves[i] = [int(self.amplitude[i]*x) for x in self.base_waveforms[self.waveform[i]]] + + def initial_wait_for_buffer_sync(self): + self.wave.outBuffer_ready = False ## mark seen + while not self.wave.outBuffer_ready: + pass ## (@_@) should be async wait + + @micropython.viper + def do_dds(self): + for i in [0,1]: ## X , Y + self.phase[i] = int(self.phase[i]) + int(self.phase_increment[i]) + for s in range(NUM_SAMPLES): + self.index[i] = int(self.phase[i]) + (int(self.accumulator[i]) >> ACCUMULATOR_BITS) & 0xFF ## roll over sample bits + self.accumulator[i] = (int(self.accumulator[i]) + int(self.increment[i])) & 0xFFFFFF ## sample bits + accumulator bits + self.samples[i][s] = self.waves[i][self.index[i]] + + def populate_buffer(self): + while not self.wave.outBuffer_ready: + pass ## (@_@) should be async wait + self.wave.packX(self.samplesX) + self.wave.packY(self.samplesY) + self.wave.outBuffer_ready = False + + +if __name__ == "__main__": + import vectorscope + import machine + userbutton = machine.Pin(19, machine.Pin.IN) + + v = vectorscope.Vectorscope() + d = DDS(v) + d.amplitude=[0.5, 0.5] + d.recalculate_waveforms() + ## but you can also write them yourself directly: 16-bit signed, 256 values + d.waves[0] = [0]*256 + ## but it gets overwritten if you recalculate_waveforms() + d.recalculate_waveforms() + + + def go(n=200): + for i in range(n): + d.do_dds() + d.populate_buffer() + d.waves[0] = d.base_waveforms["square"] + d.recalculate_waveforms() + + + for i in range(n): + d.do_dds() + d.populate_buffer() + + go() + + + + + + + + + + + + + diff --git a/dma_defs.py b/dma_defs.py new file mode 100644 index 0000000..7ef9b6e --- /dev/null +++ b/dma_defs.py @@ -0,0 +1,171 @@ +import machine +from uctypes import BF_POS, BF_LEN, UINT32, BFUINT32, struct, addressof + +## configure DMA +BASE = const(0x50000000) +CHAN_WIDTH = const(0x40) +CHAN_COUNT = const(12) + +def dma_num_tempy(x): + return BASE + CHAN_WIDTH * x +## ex: dma_num(7) + +def hexmem(x): + return hex(machine.mem32[x]) + +def hexaddr(x): + return hex(addressof(x)) + +def print_friendly(u32): + a = (u32 & 0xFF000000) >> 24 + b = (u32 & 0x00FF0000) >> 16 + c = (u32 & 0x0000FF00) >> 8 + d = (u32 & 0x000000FF) + print(f'{a:#010b} {b:#010b} {c:#010b} {d:#010b}') + +def dma_scan(): + used = [] + for i in range(12): + ctrl_reg_contents = machine.mem32[dma_num_tempy(i)+0x10] + if ctrl_reg_contents != 0: + used.append(i) + print(f"{i:02}: {ctrl_reg_contents:#034b}") + print(f"Used DMAs: {used}") + +def dma_debug(dma): + base=addressof(dma.registers) + print(f"READ: {dma.registers[0]:x} WRITE: {dma.registers[1]:x} COUNT next: {machine.mem32[base+0x804]} current: {machine.mem32[base+0x08]}") + print(dma.unpack_ctrl(dma.ctrl)) + print("\n") + + +## Control fields, packed into a nice structure +## cribbed from rp2040_pio_dma.py +## Thanks to https://github.com/benevpi/MicroPython_PIO_Music_DMA/blob/main/rp2040_pio_dma.py +DMA_CTRL_FIELDS = { + "AHB_ERROR": 31<0: + self.cursor=self.cursor-1 + else: + if self.dispmenu>0: + self.dispmenu=self.dispmenu-1 + await self.menu_update() + + if key==keyleds.JOY_DN: + if self.cursor=0: + await asyncio.sleep(0) + + if self.clear_after: + screen.clear() + + if (self._extjoy): + self.joy.detach() + + + + + diff --git a/menudemo.py b/menudemo.py new file mode 100644 index 0000000..2605b26 --- /dev/null +++ b/menudemo.py @@ -0,0 +1,79 @@ +import joystick +from menu import * # bad habit but makes our menu definition nice +from vos_state import vos_state +import blinker +import vectoros +import colors +import gc + +# run the sketch demo +def runsketch(arg): + vos_state.show_menu=False # get the menu of the way + vectoros.launch_task('sketch') + return EXIT + +def gfxdemo(arg): + vos_state.show_menu=False # get the menu of the way + vectoros.launch_task('screen') # launch + return EXIT + +def testdemo(arg): + import tester2 + vectoros.vectoros_shutdown() + tester2.main() # this never returns but it does reboot + + +# the main vector scope demo +def run_demo(arg): + vos_state.show_menu=False + vectoros.launch_task('demo') + # we never come back + return EXIT + +def reboot(arg): + if arg==False: + vectoros.reset() + else: + vectoros.soft_reset() + +# handle slots +def abcd(key): + if vos_state.show_menu: + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,f"Menu key {key}") + kdict={ keyleds.KEY_A: 'A', keyleds.KEY_B: 'B', keyleds.KEY_C: 'C', keyleds.KEY_D: 'D'} + await vectoros.launch_vecslot("slot"+kdict[key]) + + +# I really didn't want this to be async but it seems like do_menu must have an await +# and run rarely returns when you have a lot going on +async def vos_main(): +# you do NOT have to use with here +# but if you don't you have to worry about the menu controller's joystick instance going out of scope yourself +# or just make everything global -- the menu is smart enough to not listen to events when it is not active +# note: m_back and m_exit were imported from menu + while True: # since this is the main menu, we don't really every quit + print("creating slotkey") + slotkey=keyboardcb.KeyboardCB(abcd,keyleds.KEY_ABCD) + with Menu(clear_after=True,fg_color=colors.PHOSPHOR_DARK,bg_color=colors.PHOSPHOR_BG, + cursor_bg=colors.PHOSPHOR_BG, cursor_fg=colors.PHOSPHOR_BRIGHT) as amenu: + # submenu=[["Test", testdemo, 0],["Previous",m_back,None],["Abort",m_exit,None],["Reset CPU",reboot,False]] + mainmenu=[["Demo",run_demo,None],["Sketch", runsketch, 0],["GFX",gfxdemo,0 ] ] + # ["Test Menu",SUBMENU,submenu]] +# comment next line for default font + amenu.set_font("*") # set default vector font + #amenu.set_callback(menu_custom) + await amenu.do_menu(mainmenu) +# screen.text(40,80,"menu done") + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,f"Menu waiting {vos_state.show_menu}") + while vos_state.show_menu==False: # wait until we have to be seen again + await asyncio.sleep_ms(0) + + +def main(): + asyncio.run(vos_main()) + # this never runs + +if __name__=="__main__": + import vectoros + vectoros.run() + diff --git a/minimal_example.py b/minimal_example.py new file mode 100644 index 0000000..19b0c0a --- /dev/null +++ b/minimal_example.py @@ -0,0 +1,20 @@ + +import math +import time + +from vectorscope import Vectorscope +from random_walk import RW + + + +def minimal_example(v): + ## Minimal example + for i in range(200): + v.wave.constantX(int(math.cos(i * math.pi / 180 * 5) * 10000)) + v.wave.constantY(int(math.sin(i * math.pi / 180 * 5)* 10000)) + time.sleep_ms(10) + +async def slot_main(v): + minimal_example(v) + + \ No newline at end of file diff --git a/phosphor_gradient_14.py b/phosphor_gradient_14.py new file mode 100644 index 0000000..b5b95a3 --- /dev/null +++ b/phosphor_gradient_14.py @@ -0,0 +1,10 @@ +import gc9a01 +import struct +## https://colordesigner.io/gradient-generator + +_gradient_rgb = [ (10, 15, 10),(10, 15, 10), (15, 22, 15), (19, 31, 20), (24, 47, 26), (28, 64, 32), (31, 82, 39), + (34, 100, 45), (37, 118, 51), (38, 138, 57), (40, 157, 63), (41, 177, 68), + (41, 198, 74), (41, 219, 80), (41, 240, 85), (150, 255, 200) ] + +phosphor_gradient = [gc9a01.color565(r,g,b) for r,g,b in _gradient_rgb] + diff --git a/pin_defs.py b/pin_defs.py new file mode 100644 index 0000000..7c92ad1 --- /dev/null +++ b/pin_defs.py @@ -0,0 +1,40 @@ +## Hardware GPIO # pin defines + +## Screen pins +sck = const(2) +data = const(3) +reset = const(4) +dc = const(5) + +## there are a number of libs that require pins that we don't provide +## pass them the on-board LED. It's unlikely to hurt. +throwaway = const(25) +debug = const(27) + +user_button = const(19) +audio_shutdown = const(22) + +## Codec pins +## clocks +mclk = const(7) +bit_clock = const(8) +lr_clock = const(9) +## data +sd_in = const(13) ## In 3/4 (Y=L, X=R) +sd_out = const(10) ## Out 1/2 (Y=L, X=R) +## config +pdn = const(6) +i2c_scl = const(21) +i2c_sda = const(20) + + + + +## some globally useful pin inits +# debug_pin = machine.Pin(debug, machine.Pin.OUT, value=0) +# audio_shutdown_pin = machine.Pin(audio_shutdown, machine.Pin.OUT, value=1) +# user_button = machine.Pin(user_button, machine.Pin.IN) + + +#sd_in=Pin(13, Pin.IN) ## In 3/4 (Y=L, X=R) +#sd_out=Pin(10, Pin.OUT, value=0) ## Out 1/2 (Y=L, X=R) diff --git a/pio_code.py b/pio_code.py new file mode 100644 index 0000000..c70d927 --- /dev/null +++ b/pio_code.py @@ -0,0 +1,124 @@ +import rp2 + +###################################################### +## Define I2S PIO machines +## 16-bit slots, data +## read on falling edge of bit clock +###################################################### + +@rp2.asm_pio(sideset_init=[rp2.PIO.OUT_LOW]*2, fifo_join=rp2.PIO.JOIN_RX) +## bitclock and LR clock on the sidesets, respectively +def i2s_read_pio(): + nop()[3] ## delay to sync up with write PIO -- empirically determined + wrap_target() + + set(y,14) .side(0b00)[1] ## send 32 bits per LR read + label("L") + in_(pins, 1) .side(0b01)[1] ## Left, bits + jmp(y_dec, "L") .side(0b00)[1] + in_(pins, 1) .side(0b11)[1] + + set(y,14) .side(0b10)[1] + label("R") + in_(pins,1) .side(0b11)[1] ## right, bits + jmp(y_dec, "R") .side(0b10)[1] + in_(pins,1) .side(0b01) + + push(noblock) ## noblock keeps PIOs in sync + wrap() + +## 10 + +@rp2.asm_pio(out_init=rp2.PIO.OUT_LOW, fifo_join=rp2.PIO.JOIN_TX) +## runs lockstep with read pio, spits out data when it's got it, zeros otherwise +def i2s_write_pio(): + wrap_target() + + pull(noblock) ## noblock keeps PIOs in sync + set(y,14) ## send 16 bits per LR clock + label("L") + out(pins,1) [1] ## Left, bits + jmp(y_dec, "L") [1] + out(pins,1) [1] + + set(y,14) [1] + label("R") + out(pins,1) [1] + jmp(y_dec, "R") [1] + out(pins,1) [1] + wrap() + +## 19 + +@rp2.asm_pio(in_shiftdir=1, out_shiftdir=1) +def bit_flipper_pio(): + ## takes in two 16-bit as signed/unsigned integers + ## converts them by flipping MSBit of each 16-bitter + ## tailored for reading X/Y channel from DAC and fitting + ## the result on the (unsigned 8bit) screen + pull() + set(y,1) + label("twice") + + in_(osr, 15) ## copy 15 LSB unchanged to output + out(null, 15) ## discard 15 bits from OSR to keep pace + + mov(osr, invert(osr)) ## inverted what's left + in_(osr, 1) ## copy out one inverted bit + out(null, 1) ## drop that bit from OSR + mov(osr, invert(osr)) ## reinvert for the next word + + jmp(y_dec, "twice") + push() + +## 29 + + + +######################################################## +## Pixel Pusher: Handle Screen Command +## Takes commands and data +## sends it to the screen pretty fast +## Format: +# 0 2A 0 X +# 0 2B 0 Y +# 0 2C C1 C2 +## Screen expects 2A 0 X 0 X 2B 0 Y 0 Y 2C C1 C2 +## with the commands framed by the DC line going low +## This PIO ignores the first byte, +## transmits the second, framed by DC, +## then doubles the second pair +## This means it actually transmits C1 C2 C1 C2, but +## the screen doesn't seem to care (hack, hack!) +######################################################## + +@rp2.asm_pio(out_init=rp2.PIO.OUT_LOW, set_init=rp2.PIO.OUT_HIGH, + sideset_init=rp2.PIO.OUT_LOW, autopull=True, autopush=True) +def handle_screen_command(): + pull(block).side(0) + out(null, 8).side(0) ## drop initial zeros + + set(pins, 0).side(0) ## DC set low, CMD mode + + set(y,7).side(0) ## send 8 command bits + label("send_cmd") + out(pins, 1).side(0) + jmp(y_dec, "send_cmd").side(1) + + set(pins, 1).side(0) ## DC set high, data mode + + mov(x, osr).side(0) ## copy next bytes over, b/c going to send them twice + mov(osr, x).side(0) ## copy next bytes over, b/c going to send them twice + set(y,15).side(0) ## send 0, X + label("one_time") + out(pins, 1).side(0) + jmp(y_dec, "one_time").side(1) + + mov(osr,x).side(0) ## copy bytes back + + set(y,15).side(0) ## re-send 0, X + label("one_more_time") + out(pins, 1).side(0) + jmp(y_dec, "one_more_time").side(1) + +# 16 diff --git a/pio_defs.py b/pio_defs.py new file mode 100644 index 0000000..0dbf290 --- /dev/null +++ b/pio_defs.py @@ -0,0 +1,39 @@ + + +PIO0_BASE = const(0x50200000) +PIO1_BASE = const(0x50300000) + +CLKDIV_RESTART_BIT = const(8) +SM_ENABLE_BIT = const(0) + +FSTAT_OFFSET = const(0x004) +FDEBUG_OFFSET = const(0x008) + +TXF0_OFFSET = const(0x010) +TXF1_OFFSET = const(0x014) +TXF2_OFFSET = const(0x018) +TXF3_OFFSET = const(0x01C) + +RXF0_OFFSET = const(0x020) +RXF1_OFFSET = const(0x024) +RXF2_OFFSET = const(0x028) +RXF3_OFFSET = const(0x02C) + +## Direct write-only access to instruction memory. +INSTR_MEM0 = const(0x048) +INSTR_MEM1 = const(0x04C) +# etc. +INSTR_MEM31 = const(0x0C4) + +SM0_CLKDIV = const(0x0C8) +SM1_CLKDIV = const(0x0E0) +SM2_CLKDIV = const(0x0F8) +SM3_CLKDIV = const(0x110) +## Frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256) + + + + + + + diff --git a/pixel_pusher.py b/pixel_pusher.py new file mode 100644 index 0000000..658bfe8 --- /dev/null +++ b/pixel_pusher.py @@ -0,0 +1,249 @@ +## 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) + diff --git a/pl_earth.jpg b/pl_earth.jpg new file mode 100644 index 0000000..7e23ab5 Binary files /dev/null and b/pl_earth.jpg differ diff --git a/pl_jupiter.jpg b/pl_jupiter.jpg new file mode 100644 index 0000000..0f4ac7f Binary files /dev/null and b/pl_jupiter.jpg differ diff --git a/pl_mars.jpg b/pl_mars.jpg new file mode 100644 index 0000000..718294c Binary files /dev/null and b/pl_mars.jpg differ diff --git a/pl_mercury.jpg b/pl_mercury.jpg new file mode 100644 index 0000000..21eca2b Binary files /dev/null and b/pl_mercury.jpg differ diff --git a/pl_moon.jpg b/pl_moon.jpg new file mode 100644 index 0000000..41eef81 Binary files /dev/null and b/pl_moon.jpg differ diff --git a/pl_neptune.jpg b/pl_neptune.jpg new file mode 100644 index 0000000..5646e6a Binary files /dev/null and b/pl_neptune.jpg differ diff --git a/pl_saturn.jpg b/pl_saturn.jpg new file mode 100644 index 0000000..bb876e8 Binary files /dev/null and b/pl_saturn.jpg differ diff --git a/pl_uranus.jpg b/pl_uranus.jpg new file mode 100644 index 0000000..a3a47fa Binary files /dev/null and b/pl_uranus.jpg differ diff --git a/pl_venus.jpg b/pl_venus.jpg new file mode 100644 index 0000000..67d13ab Binary files /dev/null and b/pl_venus.jpg differ diff --git a/random_walk.py b/random_walk.py new file mode 100644 index 0000000..4744030 --- /dev/null +++ b/random_walk.py @@ -0,0 +1,24 @@ +import random +import time + +class RW(): + def __init__(self, vectorscope, scale=5000, iterations=1000, delay=10): + self.scale = scale + self.v = vectorscope + self.delay = delay + self.iterations=iterations + + def random_walk(self, x, y): + x = x + random.randint(-self.scale,self.scale) + y = y + random.randint(-self.scale,self.scale) + self.v.wave.point(x,y) + return x,y + + def go(self): + x,y = 0,0 + for i in range(self.iterations): + x,y = self.random_walk(x,y) + time.sleep_ms(self.delay) + + + diff --git a/romans.py b/romans.py new file mode 100644 index 0000000..2f5f3c4 --- /dev/null +++ b/romans.py @@ -0,0 +1,208 @@ +WIDTH = 32 +HEIGHT = 32 +FIRST = 0x20 +LAST = 0x7f + +_font =\ +b'\x00\x4a\x5a\x08\x4d\x57\x52\x46\x52\x54\x20\x52\x52\x59\x51'\ +b'\x5a\x52\x5b\x53\x5a\x52\x59\x05\x4a\x5a\x4e\x46\x4e\x4d\x20'\ +b'\x52\x56\x46\x56\x4d\x0b\x48\x5d\x53\x42\x4c\x62\x20\x52\x59'\ +b'\x42\x52\x62\x20\x52\x4c\x4f\x5a\x4f\x20\x52\x4b\x55\x59\x55'\ +b'\x1a\x48\x5c\x50\x42\x50\x5f\x20\x52\x54\x42\x54\x5f\x20\x52'\ +b'\x59\x49\x57\x47\x54\x46\x50\x46\x4d\x47\x4b\x49\x4b\x4b\x4c'\ +b'\x4d\x4d\x4e\x4f\x4f\x55\x51\x57\x52\x58\x53\x59\x55\x59\x58'\ +b'\x57\x5a\x54\x5b\x50\x5b\x4d\x5a\x4b\x58\x1f\x46\x5e\x5b\x46'\ +b'\x49\x5b\x20\x52\x4e\x46\x50\x48\x50\x4a\x4f\x4c\x4d\x4d\x4b'\ +b'\x4d\x49\x4b\x49\x49\x4a\x47\x4c\x46\x4e\x46\x50\x47\x53\x48'\ +b'\x56\x48\x59\x47\x5b\x46\x20\x52\x57\x54\x55\x55\x54\x57\x54'\ +b'\x59\x56\x5b\x58\x5b\x5a\x5a\x5b\x58\x5b\x56\x59\x54\x57\x54'\ +b'\x22\x45\x5f\x5c\x4f\x5c\x4e\x5b\x4d\x5a\x4d\x59\x4e\x58\x50'\ +b'\x56\x55\x54\x58\x52\x5a\x50\x5b\x4c\x5b\x4a\x5a\x49\x59\x48'\ +b'\x57\x48\x55\x49\x53\x4a\x52\x51\x4e\x52\x4d\x53\x4b\x53\x49'\ +b'\x52\x47\x50\x46\x4e\x47\x4d\x49\x4d\x4b\x4e\x4e\x50\x51\x55'\ +b'\x58\x57\x5a\x59\x5b\x5b\x5b\x5c\x5a\x5c\x59\x07\x4d\x57\x52'\ +b'\x48\x51\x47\x52\x46\x53\x47\x53\x49\x52\x4b\x51\x4c\x0a\x4b'\ +b'\x59\x56\x42\x54\x44\x52\x47\x50\x4b\x4f\x50\x4f\x54\x50\x59'\ +b'\x52\x5d\x54\x60\x56\x62\x0a\x4b\x59\x4e\x42\x50\x44\x52\x47'\ +b'\x54\x4b\x55\x50\x55\x54\x54\x59\x52\x5d\x50\x60\x4e\x62\x08'\ +b'\x4a\x5a\x52\x4c\x52\x58\x20\x52\x4d\x4f\x57\x55\x20\x52\x57'\ +b'\x4f\x4d\x55\x05\x45\x5f\x52\x49\x52\x5b\x20\x52\x49\x52\x5b'\ +b'\x52\x07\x4e\x56\x53\x57\x52\x58\x51\x57\x52\x56\x53\x57\x53'\ +b'\x59\x51\x5b\x02\x45\x5f\x49\x52\x5b\x52\x05\x4e\x56\x52\x56'\ +b'\x51\x57\x52\x58\x53\x57\x52\x56\x02\x47\x5d\x5b\x42\x49\x62'\ +b'\x11\x48\x5c\x51\x46\x4e\x47\x4c\x4a\x4b\x4f\x4b\x52\x4c\x57'\ +b'\x4e\x5a\x51\x5b\x53\x5b\x56\x5a\x58\x57\x59\x52\x59\x4f\x58'\ +b'\x4a\x56\x47\x53\x46\x51\x46\x04\x48\x5c\x4e\x4a\x50\x49\x53'\ +b'\x46\x53\x5b\x0e\x48\x5c\x4c\x4b\x4c\x4a\x4d\x48\x4e\x47\x50'\ +b'\x46\x54\x46\x56\x47\x57\x48\x58\x4a\x58\x4c\x57\x4e\x55\x51'\ +b'\x4b\x5b\x59\x5b\x0f\x48\x5c\x4d\x46\x58\x46\x52\x4e\x55\x4e'\ +b'\x57\x4f\x58\x50\x59\x53\x59\x55\x58\x58\x56\x5a\x53\x5b\x50'\ +b'\x5b\x4d\x5a\x4c\x59\x4b\x57\x06\x48\x5c\x55\x46\x4b\x54\x5a'\ +b'\x54\x20\x52\x55\x46\x55\x5b\x11\x48\x5c\x57\x46\x4d\x46\x4c'\ +b'\x4f\x4d\x4e\x50\x4d\x53\x4d\x56\x4e\x58\x50\x59\x53\x59\x55'\ +b'\x58\x58\x56\x5a\x53\x5b\x50\x5b\x4d\x5a\x4c\x59\x4b\x57\x17'\ +b'\x48\x5c\x58\x49\x57\x47\x54\x46\x52\x46\x4f\x47\x4d\x4a\x4c'\ +b'\x4f\x4c\x54\x4d\x58\x4f\x5a\x52\x5b\x53\x5b\x56\x5a\x58\x58'\ +b'\x59\x55\x59\x54\x58\x51\x56\x4f\x53\x4e\x52\x4e\x4f\x4f\x4d'\ +b'\x51\x4c\x54\x05\x48\x5c\x59\x46\x4f\x5b\x20\x52\x4b\x46\x59'\ +b'\x46\x1d\x48\x5c\x50\x46\x4d\x47\x4c\x49\x4c\x4b\x4d\x4d\x4f'\ +b'\x4e\x53\x4f\x56\x50\x58\x52\x59\x54\x59\x57\x58\x59\x57\x5a'\ +b'\x54\x5b\x50\x5b\x4d\x5a\x4c\x59\x4b\x57\x4b\x54\x4c\x52\x4e'\ +b'\x50\x51\x4f\x55\x4e\x57\x4d\x58\x4b\x58\x49\x57\x47\x54\x46'\ +b'\x50\x46\x17\x48\x5c\x58\x4d\x57\x50\x55\x52\x52\x53\x51\x53'\ +b'\x4e\x52\x4c\x50\x4b\x4d\x4b\x4c\x4c\x49\x4e\x47\x51\x46\x52'\ +b'\x46\x55\x47\x57\x49\x58\x4d\x58\x52\x57\x57\x55\x5a\x52\x5b'\ +b'\x50\x5b\x4d\x5a\x4c\x58\x0b\x4e\x56\x52\x4f\x51\x50\x52\x51'\ +b'\x53\x50\x52\x4f\x20\x52\x52\x56\x51\x57\x52\x58\x53\x57\x52'\ +b'\x56\x0d\x4e\x56\x52\x4f\x51\x50\x52\x51\x53\x50\x52\x4f\x20'\ +b'\x52\x53\x57\x52\x58\x51\x57\x52\x56\x53\x57\x53\x59\x51\x5b'\ +b'\x03\x46\x5e\x5a\x49\x4a\x52\x5a\x5b\x05\x45\x5f\x49\x4f\x5b'\ +b'\x4f\x20\x52\x49\x55\x5b\x55\x03\x46\x5e\x4a\x49\x5a\x52\x4a'\ +b'\x5b\x14\x49\x5b\x4c\x4b\x4c\x4a\x4d\x48\x4e\x47\x50\x46\x54'\ +b'\x46\x56\x47\x57\x48\x58\x4a\x58\x4c\x57\x4e\x56\x4f\x52\x51'\ +b'\x52\x54\x20\x52\x52\x59\x51\x5a\x52\x5b\x53\x5a\x52\x59\x37'\ +b'\x45\x60\x57\x4e\x56\x4c\x54\x4b\x51\x4b\x4f\x4c\x4e\x4d\x4d'\ +b'\x50\x4d\x53\x4e\x55\x50\x56\x53\x56\x55\x55\x56\x53\x20\x52'\ +b'\x51\x4b\x4f\x4d\x4e\x50\x4e\x53\x4f\x55\x50\x56\x20\x52\x57'\ +b'\x4b\x56\x53\x56\x55\x58\x56\x5a\x56\x5c\x54\x5d\x51\x5d\x4f'\ +b'\x5c\x4c\x5b\x4a\x59\x48\x57\x47\x54\x46\x51\x46\x4e\x47\x4c'\ +b'\x48\x4a\x4a\x49\x4c\x48\x4f\x48\x52\x49\x55\x4a\x57\x4c\x59'\ +b'\x4e\x5a\x51\x5b\x54\x5b\x57\x5a\x59\x59\x5a\x58\x20\x52\x58'\ +b'\x4b\x57\x53\x57\x55\x58\x56\x08\x49\x5b\x52\x46\x4a\x5b\x20'\ +b'\x52\x52\x46\x5a\x5b\x20\x52\x4d\x54\x57\x54\x17\x47\x5c\x4b'\ +b'\x46\x4b\x5b\x20\x52\x4b\x46\x54\x46\x57\x47\x58\x48\x59\x4a'\ +b'\x59\x4c\x58\x4e\x57\x4f\x54\x50\x20\x52\x4b\x50\x54\x50\x57'\ +b'\x51\x58\x52\x59\x54\x59\x57\x58\x59\x57\x5a\x54\x5b\x4b\x5b'\ +b'\x12\x48\x5d\x5a\x4b\x59\x49\x57\x47\x55\x46\x51\x46\x4f\x47'\ +b'\x4d\x49\x4c\x4b\x4b\x4e\x4b\x53\x4c\x56\x4d\x58\x4f\x5a\x51'\ +b'\x5b\x55\x5b\x57\x5a\x59\x58\x5a\x56\x0f\x47\x5c\x4b\x46\x4b'\ +b'\x5b\x20\x52\x4b\x46\x52\x46\x55\x47\x57\x49\x58\x4b\x59\x4e'\ +b'\x59\x53\x58\x56\x57\x58\x55\x5a\x52\x5b\x4b\x5b\x0b\x48\x5b'\ +b'\x4c\x46\x4c\x5b\x20\x52\x4c\x46\x59\x46\x20\x52\x4c\x50\x54'\ +b'\x50\x20\x52\x4c\x5b\x59\x5b\x08\x48\x5a\x4c\x46\x4c\x5b\x20'\ +b'\x52\x4c\x46\x59\x46\x20\x52\x4c\x50\x54\x50\x16\x48\x5d\x5a'\ +b'\x4b\x59\x49\x57\x47\x55\x46\x51\x46\x4f\x47\x4d\x49\x4c\x4b'\ +b'\x4b\x4e\x4b\x53\x4c\x56\x4d\x58\x4f\x5a\x51\x5b\x55\x5b\x57'\ +b'\x5a\x59\x58\x5a\x56\x5a\x53\x20\x52\x55\x53\x5a\x53\x08\x47'\ +b'\x5d\x4b\x46\x4b\x5b\x20\x52\x59\x46\x59\x5b\x20\x52\x4b\x50'\ +b'\x59\x50\x02\x4e\x56\x52\x46\x52\x5b\x0a\x4a\x5a\x56\x46\x56'\ +b'\x56\x55\x59\x54\x5a\x52\x5b\x50\x5b\x4e\x5a\x4d\x59\x4c\x56'\ +b'\x4c\x54\x08\x47\x5c\x4b\x46\x4b\x5b\x20\x52\x59\x46\x4b\x54'\ +b'\x20\x52\x50\x4f\x59\x5b\x05\x48\x59\x4c\x46\x4c\x5b\x20\x52'\ +b'\x4c\x5b\x58\x5b\x0b\x46\x5e\x4a\x46\x4a\x5b\x20\x52\x4a\x46'\ +b'\x52\x5b\x20\x52\x5a\x46\x52\x5b\x20\x52\x5a\x46\x5a\x5b\x08'\ +b'\x47\x5d\x4b\x46\x4b\x5b\x20\x52\x4b\x46\x59\x5b\x20\x52\x59'\ +b'\x46\x59\x5b\x15\x47\x5d\x50\x46\x4e\x47\x4c\x49\x4b\x4b\x4a'\ +b'\x4e\x4a\x53\x4b\x56\x4c\x58\x4e\x5a\x50\x5b\x54\x5b\x56\x5a'\ +b'\x58\x58\x59\x56\x5a\x53\x5a\x4e\x59\x4b\x58\x49\x56\x47\x54'\ +b'\x46\x50\x46\x0d\x47\x5c\x4b\x46\x4b\x5b\x20\x52\x4b\x46\x54'\ +b'\x46\x57\x47\x58\x48\x59\x4a\x59\x4d\x58\x4f\x57\x50\x54\x51'\ +b'\x4b\x51\x18\x47\x5d\x50\x46\x4e\x47\x4c\x49\x4b\x4b\x4a\x4e'\ +b'\x4a\x53\x4b\x56\x4c\x58\x4e\x5a\x50\x5b\x54\x5b\x56\x5a\x58'\ +b'\x58\x59\x56\x5a\x53\x5a\x4e\x59\x4b\x58\x49\x56\x47\x54\x46'\ +b'\x50\x46\x20\x52\x53\x57\x59\x5d\x10\x47\x5c\x4b\x46\x4b\x5b'\ +b'\x20\x52\x4b\x46\x54\x46\x57\x47\x58\x48\x59\x4a\x59\x4c\x58'\ +b'\x4e\x57\x4f\x54\x50\x4b\x50\x20\x52\x52\x50\x59\x5b\x14\x48'\ +b'\x5c\x59\x49\x57\x47\x54\x46\x50\x46\x4d\x47\x4b\x49\x4b\x4b'\ +b'\x4c\x4d\x4d\x4e\x4f\x4f\x55\x51\x57\x52\x58\x53\x59\x55\x59'\ +b'\x58\x57\x5a\x54\x5b\x50\x5b\x4d\x5a\x4b\x58\x05\x4a\x5a\x52'\ +b'\x46\x52\x5b\x20\x52\x4b\x46\x59\x46\x0a\x47\x5d\x4b\x46\x4b'\ +b'\x55\x4c\x58\x4e\x5a\x51\x5b\x53\x5b\x56\x5a\x58\x58\x59\x55'\ +b'\x59\x46\x05\x49\x5b\x4a\x46\x52\x5b\x20\x52\x5a\x46\x52\x5b'\ +b'\x0b\x46\x5e\x48\x46\x4d\x5b\x20\x52\x52\x46\x4d\x5b\x20\x52'\ +b'\x52\x46\x57\x5b\x20\x52\x5c\x46\x57\x5b\x05\x48\x5c\x4b\x46'\ +b'\x59\x5b\x20\x52\x59\x46\x4b\x5b\x06\x49\x5b\x4a\x46\x52\x50'\ +b'\x52\x5b\x20\x52\x5a\x46\x52\x50\x08\x48\x5c\x59\x46\x4b\x5b'\ +b'\x20\x52\x4b\x46\x59\x46\x20\x52\x4b\x5b\x59\x5b\x0b\x4b\x59'\ +b'\x4f\x42\x4f\x62\x20\x52\x50\x42\x50\x62\x20\x52\x4f\x42\x56'\ +b'\x42\x20\x52\x4f\x62\x56\x62\x02\x4b\x59\x4b\x46\x59\x5e\x0b'\ +b'\x4b\x59\x54\x42\x54\x62\x20\x52\x55\x42\x55\x62\x20\x52\x4e'\ +b'\x42\x55\x42\x20\x52\x4e\x62\x55\x62\x05\x4a\x5a\x52\x44\x4a'\ +b'\x52\x20\x52\x52\x44\x5a\x52\x02\x49\x5b\x49\x62\x5b\x62\x07'\ +b'\x4e\x56\x53\x4b\x51\x4d\x51\x4f\x52\x50\x53\x4f\x52\x4e\x51'\ +b'\x4f\x11\x49\x5c\x58\x4d\x58\x5b\x20\x52\x58\x50\x56\x4e\x54'\ +b'\x4d\x51\x4d\x4f\x4e\x4d\x50\x4c\x53\x4c\x55\x4d\x58\x4f\x5a'\ +b'\x51\x5b\x54\x5b\x56\x5a\x58\x58\x11\x48\x5b\x4c\x46\x4c\x5b'\ +b'\x20\x52\x4c\x50\x4e\x4e\x50\x4d\x53\x4d\x55\x4e\x57\x50\x58'\ +b'\x53\x58\x55\x57\x58\x55\x5a\x53\x5b\x50\x5b\x4e\x5a\x4c\x58'\ +b'\x0e\x49\x5b\x58\x50\x56\x4e\x54\x4d\x51\x4d\x4f\x4e\x4d\x50'\ +b'\x4c\x53\x4c\x55\x4d\x58\x4f\x5a\x51\x5b\x54\x5b\x56\x5a\x58'\ +b'\x58\x11\x49\x5c\x58\x46\x58\x5b\x20\x52\x58\x50\x56\x4e\x54'\ +b'\x4d\x51\x4d\x4f\x4e\x4d\x50\x4c\x53\x4c\x55\x4d\x58\x4f\x5a'\ +b'\x51\x5b\x54\x5b\x56\x5a\x58\x58\x11\x49\x5b\x4c\x53\x58\x53'\ +b'\x58\x51\x57\x4f\x56\x4e\x54\x4d\x51\x4d\x4f\x4e\x4d\x50\x4c'\ +b'\x53\x4c\x55\x4d\x58\x4f\x5a\x51\x5b\x54\x5b\x56\x5a\x58\x58'\ +b'\x08\x4d\x59\x57\x46\x55\x46\x53\x47\x52\x4a\x52\x5b\x20\x52'\ +b'\x4f\x4d\x56\x4d\x16\x49\x5c\x58\x4d\x58\x5d\x57\x60\x56\x61'\ +b'\x54\x62\x51\x62\x4f\x61\x20\x52\x58\x50\x56\x4e\x54\x4d\x51'\ +b'\x4d\x4f\x4e\x4d\x50\x4c\x53\x4c\x55\x4d\x58\x4f\x5a\x51\x5b'\ +b'\x54\x5b\x56\x5a\x58\x58\x0a\x49\x5c\x4d\x46\x4d\x5b\x20\x52'\ +b'\x4d\x51\x50\x4e\x52\x4d\x55\x4d\x57\x4e\x58\x51\x58\x5b\x08'\ +b'\x4e\x56\x51\x46\x52\x47\x53\x46\x52\x45\x51\x46\x20\x52\x52'\ +b'\x4d\x52\x5b\x0b\x4d\x57\x52\x46\x53\x47\x54\x46\x53\x45\x52'\ +b'\x46\x20\x52\x53\x4d\x53\x5e\x52\x61\x50\x62\x4e\x62\x08\x49'\ +b'\x5a\x4d\x46\x4d\x5b\x20\x52\x57\x4d\x4d\x57\x20\x52\x51\x53'\ +b'\x58\x5b\x02\x4e\x56\x52\x46\x52\x5b\x12\x43\x61\x47\x4d\x47'\ +b'\x5b\x20\x52\x47\x51\x4a\x4e\x4c\x4d\x4f\x4d\x51\x4e\x52\x51'\ +b'\x52\x5b\x20\x52\x52\x51\x55\x4e\x57\x4d\x5a\x4d\x5c\x4e\x5d'\ +b'\x51\x5d\x5b\x0a\x49\x5c\x4d\x4d\x4d\x5b\x20\x52\x4d\x51\x50'\ +b'\x4e\x52\x4d\x55\x4d\x57\x4e\x58\x51\x58\x5b\x11\x49\x5c\x51'\ +b'\x4d\x4f\x4e\x4d\x50\x4c\x53\x4c\x55\x4d\x58\x4f\x5a\x51\x5b'\ +b'\x54\x5b\x56\x5a\x58\x58\x59\x55\x59\x53\x58\x50\x56\x4e\x54'\ +b'\x4d\x51\x4d\x11\x48\x5b\x4c\x4d\x4c\x62\x20\x52\x4c\x50\x4e'\ +b'\x4e\x50\x4d\x53\x4d\x55\x4e\x57\x50\x58\x53\x58\x55\x57\x58'\ +b'\x55\x5a\x53\x5b\x50\x5b\x4e\x5a\x4c\x58\x11\x49\x5c\x58\x4d'\ +b'\x58\x62\x20\x52\x58\x50\x56\x4e\x54\x4d\x51\x4d\x4f\x4e\x4d'\ +b'\x50\x4c\x53\x4c\x55\x4d\x58\x4f\x5a\x51\x5b\x54\x5b\x56\x5a'\ +b'\x58\x58\x08\x4b\x58\x4f\x4d\x4f\x5b\x20\x52\x4f\x53\x50\x50'\ +b'\x52\x4e\x54\x4d\x57\x4d\x11\x4a\x5b\x58\x50\x57\x4e\x54\x4d'\ +b'\x51\x4d\x4e\x4e\x4d\x50\x4e\x52\x50\x53\x55\x54\x57\x55\x58'\ +b'\x57\x58\x58\x57\x5a\x54\x5b\x51\x5b\x4e\x5a\x4d\x58\x08\x4d'\ +b'\x59\x52\x46\x52\x57\x53\x5a\x55\x5b\x57\x5b\x20\x52\x4f\x4d'\ +b'\x56\x4d\x0a\x49\x5c\x4d\x4d\x4d\x57\x4e\x5a\x50\x5b\x53\x5b'\ +b'\x55\x5a\x58\x57\x20\x52\x58\x4d\x58\x5b\x05\x4a\x5a\x4c\x4d'\ +b'\x52\x5b\x20\x52\x58\x4d\x52\x5b\x0b\x47\x5d\x4a\x4d\x4e\x5b'\ +b'\x20\x52\x52\x4d\x4e\x5b\x20\x52\x52\x4d\x56\x5b\x20\x52\x5a'\ +b'\x4d\x56\x5b\x05\x4a\x5b\x4d\x4d\x58\x5b\x20\x52\x58\x4d\x4d'\ +b'\x5b\x09\x4a\x5a\x4c\x4d\x52\x5b\x20\x52\x58\x4d\x52\x5b\x50'\ +b'\x5f\x4e\x61\x4c\x62\x4b\x62\x08\x4a\x5b\x58\x4d\x4d\x5b\x20'\ +b'\x52\x4d\x4d\x58\x4d\x20\x52\x4d\x5b\x58\x5b\x27\x4b\x59\x54'\ +b'\x42\x52\x43\x51\x44\x50\x46\x50\x48\x51\x4a\x52\x4b\x53\x4d'\ +b'\x53\x4f\x51\x51\x20\x52\x52\x43\x51\x45\x51\x47\x52\x49\x53'\ +b'\x4a\x54\x4c\x54\x4e\x53\x50\x4f\x52\x53\x54\x54\x56\x54\x58'\ +b'\x53\x5a\x52\x5b\x51\x5d\x51\x5f\x52\x61\x20\x52\x51\x53\x53'\ +b'\x55\x53\x57\x52\x59\x51\x5a\x50\x5c\x50\x5e\x51\x60\x52\x61'\ +b'\x54\x62\x02\x4e\x56\x52\x42\x52\x62\x27\x4b\x59\x50\x42\x52'\ +b'\x43\x53\x44\x54\x46\x54\x48\x53\x4a\x52\x4b\x51\x4d\x51\x4f'\ +b'\x53\x51\x20\x52\x52\x43\x53\x45\x53\x47\x52\x49\x51\x4a\x50'\ +b'\x4c\x50\x4e\x51\x50\x55\x52\x51\x54\x50\x56\x50\x58\x51\x5a'\ +b'\x52\x5b\x53\x5d\x53\x5f\x52\x61\x20\x52\x53\x53\x51\x55\x51'\ +b'\x57\x52\x59\x53\x5a\x54\x5c\x54\x5e\x53\x60\x52\x61\x50\x62'\ +b'\x17\x46\x5e\x49\x55\x49\x53\x4a\x50\x4c\x4f\x4e\x4f\x50\x50'\ +b'\x54\x53\x56\x54\x58\x54\x5a\x53\x5b\x51\x20\x52\x49\x53\x4a'\ +b'\x51\x4c\x50\x4e\x50\x50\x51\x54\x54\x56\x55\x58\x55\x5a\x54'\ +b'\x5b\x51\x5b\x4f\x22\x4a\x5a\x4a\x46\x4a\x5b\x4b\x5b\x4b\x46'\ +b'\x4c\x46\x4c\x5b\x4d\x5b\x4d\x46\x4e\x46\x4e\x5b\x4f\x5b\x4f'\ +b'\x46\x50\x46\x50\x5b\x51\x5b\x51\x46\x52\x46\x52\x5b\x53\x5b'\ +b'\x53\x46\x54\x46\x54\x5b\x55\x5b\x55\x46\x56\x46\x56\x5b\x57'\ +b'\x5b\x57\x46\x58\x46\x58\x5b\x59\x5b\x59\x46\x5a\x46\x5a\x5b'\ +b'' + +_index =\ +b'\x00\x00\x03\x00\x16\x00\x23\x00\x3c\x00\x73\x00\xb4\x00\xfb'\ +b'\x00\x0c\x01\x23\x01\x3a\x01\x4d\x01\x5a\x01\x6b\x01\x72\x01'\ +b'\x7f\x01\x86\x01\xab\x01\xb6\x01\xd5\x01\xf6\x01\x05\x02\x2a'\ +b'\x02\x5b\x02\x68\x02\xa5\x02\xd6\x02\xef\x02\x0c\x03\x15\x03'\ +b'\x22\x03\x2b\x03\x56\x03\xc7\x03\xda\x03\x0b\x04\x32\x04\x53'\ +b'\x04\x6c\x04\x7f\x04\xae\x04\xc1\x04\xc8\x04\xdf\x04\xf2\x04'\ +b'\xff\x04\x18\x05\x2b\x05\x58\x05\x75\x05\xa8\x05\xcb\x05\xf6'\ +b'\x05\x03\x06\x1a\x06\x27\x06\x40\x06\x4d\x06\x5c\x06\x6f\x06'\ +b'\x88\x06\x8f\x06\xa8\x06\xb5\x06\xbc\x06\xcd\x06\xf2\x06\x17'\ +b'\x07\x36\x07\x5b\x07\x80\x07\x93\x07\xc2\x07\xd9\x07\xec\x07'\ +b'\x05\x08\x18\x08\x1f\x08\x46\x08\x5d\x08\x82\x08\xa7\x08\xcc'\ +b'\x08\xdf\x08\x04\x09\x17\x09\x2e\x09\x3b\x09\x54\x09\x61\x09'\ +b'\x76\x09\x89\x09\xda\x09\xe1\x09\x32\x0a\x63\x0a' + +FONT = memoryview(_font) +INDEX = memoryview(_index) + diff --git a/screen.py b/screen.py new file mode 100644 index 0000000..2b2ef8e --- /dev/null +++ b/screen.py @@ -0,0 +1,74 @@ +from machine import Pin, SPI, SoftSPI +import pin_defs +import gc9a01 +import gc +import rp2 + +class Screen(): + def __init__(self, softSPI=False): + self.softSPI = softSPI + self.sck = Pin(pin_defs.sck, Pin.OUT) + self.data = Pin(pin_defs.data, Pin.OUT) + self.dc = Pin(pin_defs.dc, Pin.OUT) + self.init() + + def deinit(self): + self.spi.deinit() + ## this does not appear to de-initialize the DMAs -- the enable bits are still set + del(self.spi) + del(self.tft) + gc.collect() + + def init(self): + if self.softSPI: + self.spi = SoftSPI(baudrate=10_000_000, sck=self.sck, mosi=self.data, miso=Pin(pin_defs.throwaway)) + else: + self.spi = SPI(0, baudrate=40_000_000, sck=self.sck, mosi=self.data) + + self.tft = gc9a01.GC9A01(self.spi, 240, 240, + reset = Pin(pin_defs.reset, Pin.OUT), + cs = Pin(pin_defs.throwaway, Pin.OUT), ## not used, grounded on board + dc = self.dc, + backlight = Pin(pin_defs.throwaway, Pin.OUT), ## not used, always on + rotation = 0) + self.tft.init() + self.tft.fill(gc9a01.color565(10,15,10)) + + +if __name__ == "__main__": + + import time + import random + + ## instantiate and init + s = Screen() + + ## For everything you can do with the GC9A01 library: + ## https://github.com/russhughes/gc9a01_mpy + + s.tft.fill(gc9a01.BLUE) + time.sleep_ms(500) + s.tft.fill(gc9a01.YELLOW) + time.sleep_ms(500) + s.tft.fill(gc9a01.BLACK) + + phosphor_bright = gc9a01.color565(120, 247, 180) + phosphor_dark = gc9a01.color565(45, 217, 80) + + ## better graphic demo should go here + for i in range(200): + x1 = random.randint(0, 240) + x2 = random.randint(0, 240) + y1 = random.randint(0, 240) + y2 = random.randint(0, 240) + + if i % 2: + s.tft.line(x1, y1, x2, y2, phosphor_bright) + else: + s.tft.line(x1, y1, x2, y2, phosphor_dark) + time.sleep_ms(20) + + + + + diff --git a/screennorm.py b/screennorm.py new file mode 100644 index 0000000..4519dab --- /dev/null +++ b/screennorm.py @@ -0,0 +1,125 @@ +import gc9a01 +from machine import Pin, SPI +import vga1_16x32 as font +import gc +import romans as deffont + +class ScreenNorm: + """ + A light wrapper around the gc9a01 screen. + + Attributes: + _spi (SPI): The SPI interface. + tft (gc9a01.GC9A01): The TFT display. + """ + + def __init__(self): + """ + The constructor for ScreenNorm class. + """ + + self._spi = SPI(0, baudrate=40000000, sck=Pin(2, Pin.OUT), mosi=Pin(3, Pin.OUT), miso=Pin(20,Pin.IN)) + self.wake() + + def get_font(self): + """ + Get the normal font + + Returns: + font object + """ + return font + + def get_vfont(self): + """ + Get the default vector font (romans) + """ + return deffont + + def wake(self): + """ + Method to wake up the display. + """ + + self.tft = gc9a01.GC9A01(self._spi, 240, 240, + reset=Pin(4, Pin.OUT), + cs=Pin(26, Pin.OUT), + dc=Pin(5, Pin.OUT), + backlight=Pin(27, Pin.OUT), + rotation=0) + + self.tft.init() + + def idle(self): + """ + Method to get the display out of the way for another screen helper. + """ + + self._spi.deinit() + self.tft=None + + def jpg(self,filename): + """ + Method to show a jpg on the display. + + Args: + filename (str): The name of the jpg file. + """ + + if self.tft!=None: + gc.collect() + self.tft.jpg(filename,0,0,1) + + def text(self,x,y,txt,fg_color=gc9a01.color565(45, 217, 80),bg_color=gc9a01.color565(10,15,10)): + """ + Method to draw text on the display. + + Args: + x (int): The x-coordinate of the top left corner of the text. + y (int): The y-coordinate of the top left corner of the text. + txt (str): The text to draw. + fg_color (int): The foreground color. Default is gc9a01.color565(243,191,16). + bg_color (int): The background color. Default is gc9a01.color565(26,26,26). + """ + + if self.tft!=None: + self.tft.text(font,txt,x,y,fg_color,bg_color) + + def text_font(self,font,x,y,txt,fg_color=gc9a01.color565(45, 217, 80),scale=1.0): + """ + Method to draw text with a font. + + Args: + font (font or None): The font to use (or use default font) + x,y (int): X and Y coordinate + txt (str): String + fg_color (int) Foreground color (note: no background color -- always transparent) + """ + if self.tft!=None: + if font==None: + font=deffont + self.tft.draw(font,txt,x,y,fg_color,scale) + + def clear(self,color=0): + """ + Method to clear the display with a color. + + Args: + color (int): The background color. Default is 0. + """ + + if self.tft!=None: + self.tft.fill_rect(0,0,240,240,color) + + def pixel(self,x,y,color): + """ + Method to set a pixel on the display. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + color (int): The color of the pixel. + """ + + if self.tft!=None: + self.tft.pixel(x,y,color) diff --git a/screentest.py b/screentest.py new file mode 100644 index 0000000..118eea8 --- /dev/null +++ b/screentest.py @@ -0,0 +1,60 @@ +import screennorm +import keyboardcb +import keyleds +import vectoros +import gc +import asyncio + +screen=screennorm.ScreenNorm() + + +exit_flag=False + +def text_overlay(): + screen.text(60,25,"SUPERCON") + screen.text(85,190,"2023") + +def back(key): + screen.jpg("bluemarble.jpg") # button A globe + text_overlay() + +def fwd(key): + screen.jpg("wrencher.jpg") # button B wrencher + text_overlay() + +def menu(key): # menu -bail out + global exit_flag + exit_flag=True + +def startlcd(key): # button D - start LCD + if screen.tft==None: + screen.wake() + back(None) + +def stoplcd(key): # button C stop LCD + if screen.tft!=None: + screen.clear() + screen.idle() + +async def vos_main(): + global exit_flag + keys=keyboardcb.KeyboardCB({keyleds.KEY_A: back, keyleds.KEY_B: fwd, keyleds.KEY_C: stoplcd, keyleds.KEY_D: startlcd, + keyleds.KEY_MENU: menu}) + back(None) + while exit_flag==False: + await asyncio.sleep_ms(500) + if vectoros.vectoros_active==False: + gc.collect() +# stop listening for keys + keys.detach() + exit_flag=False # next time + from vos_state import vos_state + vos_state.show_menu=True + +def main(): + asyncio.run(vos_main()) + + +if __name__=="__main__": + keyboardcb.KeyboardCB.run(100) + main() diff --git a/sketch.py b/sketch.py new file mode 100644 index 0000000..cd8c4fc --- /dev/null +++ b/sketch.py @@ -0,0 +1,169 @@ +import screennorm +import keyboardcb +import keyleds +import joystick +import vectoros +from vos_state import vos_state +import gc9a01 + +# little etch-a-sketch demo + +pendown = True # start off with pen down +color = gc9a01.BLACK # start off black + +# To maintain the cursor we need a bit map which is slow +# so less is more. Also pixels are tiny +# so 40 is a good compromise (40x40 drawing area) +SIZE=40 +PIXSIZE=240//SIZE # size of each "pixel" + +# start cursor in the middle +cursor_x = (SIZE+1)//2 +cursor_y = (SIZE+1)//2 + +# This will be the backing store for the screen +model=[] + +stopflag=False # stop when true + +# clear the mdoel with color c (does not draw; see cursor) +def fill_model(c): + global model + model=[[c for i in range(SIZE)] for j in range(SIZE)] + + + +# update screen and overlay cursor (or just cursor area if blit==False) +def cursor(blit=True): + global model + global cursor_x + global cursor_y + global PIXSIZE + global pendown + ccolor=gc9a01.color565(0xC0,0xFF,0x80) + if pendown: + ccolor=gc9a01.color565(0xFF,0x80,0x80) + if blit: + for x in range(SIZE): + for y in range(SIZE): + screen.tft.fill_rect(x*PIXSIZE,y*PIXSIZE,PIXSIZE,PIXSIZE,model[x][y]) + else: # only draw around the cursor + for x in range(-1,2): + for y in range(-1,2): + nx=cursor_x+x + ny=cursor_y+y + screen.tft.fill_rect(nx*PIXSIZE,ny*PIXSIZE,PIXSIZE,PIXSIZE,model[nx][ny]) + screen.tft.fill_rect(cursor_x*PIXSIZE,cursor_y*PIXSIZE,PIXSIZE,PIXSIZE,ccolor) + +# flip pen/up down on joystick button (non-repeating) +def joybtn(key): + global pendown + pendown=not pendown + cursor(False) # update cursor color + +# normal joystick commands +def joycmd(key): + global pendown, cursor_x, cursor_y + x=0 + y=0 + if key==keyleds.JOY_N: + y=-1 + elif key==keyleds.JOY_S: + y=1 + elif key==keyleds.JOY_E: + x=1 + elif key==keyleds.JOY_W: + x=-1 + elif key==keyleds.JOY_NW: + y=-1 + x=-1 + elif key==keyleds.JOY_SW: + y=1 + x=-1 + elif key==keyleds.JOY_NE: + y=-1 + x=1 + elif key==keyleds.JOY_SE: + y=1 + x=1 + if x!=0 or y!=0: + newx=cursor_x+x + if newx>=0 and newx=0 and newycls.gc_delay: + ctr=0 + gc.collect() + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"Timer exit") + + @classmethod + def cancel(cls): + """ + Class method to cancel the polling task. + """ + cls._cancel=True + cls.task.cancel() + + @classmethod + async def _tick(cls): + """ + Class method to tick the timer. + + This method ticks the timer and calls the appropriate callback function for each timer event. + """ + + for item in cls.clients: + cls.clients[item][1]+=1 + if cls.clients[item][0]<=cls.clients[item][1]: + cls.clients[item][1]=0 + cls.current_id=item + try: + await cls.clients[item][2]() + except TypeError: + pass + try: + if cls.clients[item][3]: + del cls.clients[item] + except Exception: + pass + cls.current_id=None + + @classmethod + def remove_timer(cls,id): + """ + Class method to remove a timer. + + Args: + id (int): The id of the timer to remove. + """ + if id in cls.clients: + cls._next_id=id # try to recycle timer IDs (note we search "up" if one is in use already) + del cls.clients[id] + + @classmethod + def add_timer(cls,ticks, callback, oneshot=False): + """ + Class method to add a timer. + + Args: + ticks (int): The number of ticks before the timer triggers. + callback (function): The function to call when the timer triggers. + oneshot (bool): A flag to indicate if the timer should only trigger once. Default is False. + + Returns: + int: The id of the added timer. + """ + + # we search for a free timerID starting from _next_id + # normally that means we get an ascending number + # but if you delete a timer ID we will reuse it unless + # you delete two or more without creating any + # then you may "leak" lower number IDs but you + # should run out of resources before you run out of IDs + # anyway This is just a hedge for the case where you + # constantly create/remove a timer + while cls._next_id in cls.clients: + cls._next_id+=1 + + rv=cls._next_id + cls.clients[cls._next_id]=[ticks,0,callback,oneshot] + cls._next_id+=1 + + return rv + + def __init__(self,ticks,paused=False,oneshot=False): + """ + The constructor for Timer class. + + Args: + ticks (int): The number of ticks before the timer triggers. + paused (bool): A flag to indicate if the timer should start paused. Default is False. + oneshot (bool): A flag to indicate if the timer should only trigger once. Default is False. + """ + + self.ticks=ticks + self.paused=paused + self.oneshot=oneshot + + if paused: + self.id=None + else: + self.id=self.add_timer(ticks,self.action,oneshot) + + def __enter__(self): + return + + def __exit__(self,exc_type,exc_val,exc_tb): + if self.id!=None: + self.remove_timer(id) + + def action(self): + print("unhandled action") + + def pause(self): + if self.id==None: + return + + try: + self.remove_timer(self.id) + except Exception: + pass + + finally: + self.id=None + + def resume(self): + try: + self.remove_timer(self.id) + except Exception: + pass + + self.id=self.add_timer(self.ticks,self.self.action,True,self.oneshot) + + return self.id + +if __name__=="__main__": + import gc + onesecid=0 + + Timer.gc_delay=500 + + class Test(Timer): + def __init__(self): + super().__init__(20) + self.ct=0 + + def action(self): + self.ct=self.ct+1 + print("**",self.ct) + if (self.ct>=10): + self.pause() + + + twosec = Test() + + + def callback1sec(): + """ + Function to print a message every second. + """ + + print("Tick 1") + + def callback5sec(): + """ + Function to print a message and the amount of free memory every 5 seconds. + """ + + print("Tick 5",gc.mem_free()) + + def once(): + """ + Function to print a message and remove the 1 second tick. This function only runs once. + """ + + global onesecid + print("I only run once",onesecid) + Timer.remove_timer(onesecid) + + async def acallback_worker(): + """ + Asynchronous function to print a start and end message with a delay of 5 seconds. + """ + + print("async start") + await asyncio.sleep(5) + print("async done") + + # This is an async callback. However, the timer will stall until it completes + # so we just kick off a new task to run in the background and then the timer does its thing + async def acallback(): + asyncio.create_task(acallback_worker()) + + async def amain(): + """ + Asynchronous main function to run the timer and add several tasks to it. + """ + + global onesecid + Timer.run() + + + onesecid=Timer.add_timer(10,callback1sec) # every one second + Timer.add_timer(50,callback5sec) # 5 second synchronous repeating + Timer.add_timer(100,once,True) # one shot and remove the 1 second tick + Timer.add_timer(55,acallback,True) # one shot + + while True: + await asyncio.sleep(0) + + asyncio.run(amain()) + + diff --git a/vectoros.py b/vectoros.py new file mode 100644 index 0000000..1f511d4 --- /dev/null +++ b/vectoros.py @@ -0,0 +1,364 @@ +import aiorepl +import asyncio +import keyboardio +import screennorm +import timer +from vos_launch import launch_list, auto_launch_list, auto_launch_repl, key_scan_rate, gc_thread_rate, timer_base_rate, vectorscope_slots +from vos_state import vos_state +import gc +from machine import RTC +from machine import reset as m_reset, soft_reset as m_soft_reset +import vos_debug +from vectorscope import Vectorscope + + +_VERSION=20231001 + +_screen=screennorm.ScreenNorm() + + +async def _sleeper(): + """ + Asynchronous function to sleep forever. + + This function runs a loop that sleeps for 2 seconds and then collects garbage. + """ + + while True: + await asyncio.sleep(2) + if vos_state.gc_suspend==False: + gc.collect() + +def reset(): + m_reset() + +def soft_reset(): + m_soft_reset() + + +def sleep_forever(): + """ + Function to let non async function wait forever. + """ + + asyncio.run(_sleeper()) + +async def _delayer(n): + """ + Asynchronous function to delay for n seconds. + + Args: + n (int): The number of milliseconds to delay. + """ + + await asyncio.sleep_ms(n) + +def sleep(n): + """ + Function to delay for n seconds. + + Args: + n (int): The number of milliseconds to delay. + """ + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_WARNING,"sleep called which uses asyncio.run") + asyncio.run(_delayer(n)) + +def get_screen(): + """ + Function to get the one screen object. + + Returns: + ScreenNorm: The screen object. + """ + + global _screen + return _screen + +async def launch_repl(): + """ + Asynchronous function to start a repl. + """ + + vos_state.task_dict['$repl']=asyncio.create_task(aiorepl.task()) + +async def launch(task_tag): + """ + Asynchronous function to launch a program by its tag. + + Args: + task_tag (str): The tag of the program to launch. + + Returns: + Task: The created task. + """ + if vos_state.gc_suspend==False: + gc.collect() + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,f"launching {task_tag}") + mod=__import__(launch_list[task_tag]) + try: + fn=getattr(mod,'vos_main') + except Exception: + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_WARNING,f"launch could not find vos_main for {task_tag}. Trying main()") + fn=getattr(mod,'main') + try: + await fn() + except TypeError: + pass + + + +def launch_task(task_tag): + """ + Function to create async task to launch. + + Args: + task_tag (str): The tag of the program to launch. + + Returns: + Task: The created task. + """ + vos_state.task_dict[task_tag]=asyncio.create_task(launch(task_tag)) + + +async def launch_vecslot(slot): + import vectorscope + get_screen().idle() + gc.collect() + vos_state.gc_suspend=True + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,f"Launching slot {slot}:{vectorscope_slots[slot]}") + mod=__import__(vectorscope_slots[slot]) + fn=getattr(mod,'slot_main') + v = Vectorscope(screen_running = True) + try: + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"launching") + await fn(v) + except TypeError: + pass + except Exception as e: + print("launch exception",e) + finally: + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"Slot done, reboot!") + # await asyncio.sleep(5) + reset() + + +def remove_task(t): + """ + Function to remove a task. Note that this does not stop the task, just takes it out of the list + + Args: + t (str): The tag of the task to remove. + """ + + try: + vos_state.task_dict.pop(t) + except Exception: + pass + +_gc_exit=False + +async def _gc_thread(ms): + """ + Asynchronous function for optional thread to garbage collect occasionally. + + Args: + ms (int): The number of milliseconds to sleep between garbage collections. + """ + global _gc_exit + while _gc_exit==False: + await asyncio.sleep_ms(ms) + if vos_state.gc_suspend: + continue + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"Garbage collection starting") + gc.collect() + gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"Garbage collection thread exit") + + +def vectoros_active(): + """ + Function to test if we are active. + + Returns: + bool: True if active, False otherwise. + """ + + return vos_state.active + +_vectoros_runafter=None + +def set_global_exception(): + """ + Function to catch exceptions. + """ + def handle_exception(loop,context): + import sys + if context["exception"].args[0]=="__vectoros-exit__": + vos_state.run_after=context["exception"].args[1] + else: + sys.print_exception(context["exception"]) + sys.exit() + + + loop=asyncio.get_event_loop() + loop.set_exception_handler(handle_exception) + +async def vectoros_startup(autolaunch=True): + """ + Function to start services (called by main) + + Args: + autolaunch (bool): True to launch autostart programs (default) + """ + global _VERSION + vos_state.version=_VERSION + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,f"VectorOS {_VERSION} starting",RTC().datetime()) +# honor gc thread request + if gc_thread_rate!=0: + gc.disable() + vos_state.task_dict['$gc']=asyncio.create_task(_gc_thread(gc_thread_rate)) +# spin up system-level keyboard polling (see keyboardcb and joystick) + if key_scan_rate!=0: + vos_state.task_dict['$key']=keyboardio.KeyboardIO.run(key_scan_rate) +# spin up timer infrastructure + if timer_base_rate!=0: + vos_state.task_dict['$timer']=timer.Timer.run() +# at this point, we consider ourselves running + vos_state.active=True +# launch repl if requested + if autolaunch: + if (auto_launch_repl): + await launch_repl() +# run auto launch stuff from vos_launch + for t in auto_launch_list: + vos_state.task_dict[t]=asyncio.create_task(launch(t)) + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"VectorOS started",RTC().datetime()) + + +# shut down our service (but not apps) +def vectoros_shutdown(deactivate=True): + """ + Function to shut down vectoros services + """ + global _screen, _gc_exit + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"VectorOS shutting down",RTC().datetime()) + _screen.idle() + _screen=None + keyboardio.KeyboardIO.cancel() + del vos_state.task_dict['$key'] + timer.Timer.cancel() + del vos_state.task_dict['$timer'] + _gc_exit=True +# vos_state.task_dict['$gc'].cancel() + del vos_state.task_dict['$gc'] + for x in vos_state.task_dict: + vos_state.task_dict[x].cancel() + if (deactivate): + vos_state.active=False + asyncio.get_event_loop().set_exception_handler(None) + gc.collect() + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"VectorOS shut down",RTC().datetime()) + +def ext_run(cmd): + """ + Function to exit VectorOS and run a program + """ + gc.collect() + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,f"external run {cmd}") + raise Exception("__vectoros-exit__",cmd) + + + +# get all set up +async def main(): + """ + Asynchronous main function to set up the system. + + This function sets up exception handling, starts the garbage collection thread, keyboard polling, and timer infrastructure, + launches the repl if requested, imports modules from vos_launch, and runs auto launch stuff from vos_launch. + """ +# catch any strange exceptions + set_global_exception() + await vectoros_startup() + await _sleeper() + +# This is the main entry point that starts everything +def run(): + """ + Function to run the main function. + + This function runs the main function and resets the event loop. + """ + global _vectoros_runafter, _vectoros_runaftermod + cmd=None + while True: + try: + asyncio.run(main()) # do not create tasks before this point! + except SystemExit as se: + break # catch sys.exit + except Exception as e: + print(e) # shouldn't really run + print(hasattr(e,'arg')) + if hasattr(e,'arg'): + if e.arg[0]=='__vectoros-exit__': + _vectoros_runafter=e.args[1] + else: + print(e) + else: + print(e) +# This code launches a program after vectoros shutdown +# The "single core" method works more reliably but if it is +# in the second core causes stack errors (vos_state._xthreading==0) +# The vos_state._xthreading==1 case works for multicore but +# requrires a small server on the main core +#This mostly works in single core mode + finally: + if vos_state._xthreading==0: # single core mode + if vos_state.run_after != None: # as we exit, set up any pending command + cmd=vos_state.run_after + vos_state.run_after=None + else: + vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"reset event loop") + asyncio.new_event_loop() + if cmd!=None: + vectoros_shutdown() + vos_state.gc_suspend=True + asyncio.sleep_ms(250) # hope the gc thread dies if it hasn't already + import sys + del sys.modules['screennorm'] + del sys.modules['aiorepl'] + del sys.modules['romans'] + del sys.modules['vga1_16x32'] + gc.collect() + exec(cmd) + else: # multicore mode (requires start w/split_vos.py) + if vos_state.run_after != None: # as we exit, set up any pending command + print("core 1 passing back to zero") + import sys + del sys.modules['screennorm'] + del sys.modules['aiorepl'] + del sys.modules['romans'] + del sys.modules['vga1_16x32'] + del sys.modules['keyboardio'] + del sys.modules['timer'] + vos_state.gc_suspend=True + asyncio.sleep_ms(250) # hope the gc thread dies if it hasn't already + gc.collect() + vectoros_shutdown(False) + gc.collect() + else: + print("Resetting event loop") + asyncio.new_event_loop() + if vos_state._xthreading==1: + gc.collect() + vos_state.active=False + else: + pass + +if __name__=="__main__": + run() + + + + + diff --git a/vectorscope.py b/vectorscope.py new file mode 100644 index 0000000..6ea6463 --- /dev/null +++ b/vectorscope.py @@ -0,0 +1,112 @@ +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 + + diff --git a/vga1_16x32.py b/vga1_16x32.py new file mode 100644 index 0000000..849069d --- /dev/null +++ b/vga1_16x32.py @@ -0,0 +1,104 @@ +WIDTH = 16 +HEIGHT = 32 +FIRST = 0x20 +LAST = 0x7f +_FONT = \ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x7f\xfe\x7f\xfe\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x7f\xfe\x7f\xfe\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x01\x80\x01\x80\x0f\xf0\x0f\xf0\x39\x9c\x39\x9c\x71\x8e\x71\x8e\x71\x80\x71\x80\x39\x80\x39\x80\x0f\xf0\x0f\xf0\x01\x9c\x01\x9c\x01\x8e\x01\x8e\x71\x8e\x71\x8e\x39\x9c\x39\x9c\x0f\xf0\x0f\xf0\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x1c\x1e\x1c\x1e\x38\x1e\x38\x00\x70\x00\x70\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x0e\x3c\x0e\x3c\x1c\x3c\x1c\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x07\xc0\x1c\x70\x1c\x70\x38\x38\x38\x38\x1c\x70\x1c\x70\x07\xc0\x07\xc0\x0f\xce\x0f\xce\x38\xfc\x38\xfc\x70\x78\x70\x78\x70\x78\x70\x78\x38\xfc\x38\xfc\x0f\xce\x0f\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x03\x80\x03\x80\x01\xc0\x01\xc0\x00\xe0\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x07\x00\x03\x80\x03\x80\x01\xc0\x01\xc0\x00\xe0\x00\xe0\x00\xe0\x00\xe0\x00\xe0\x00\xe0\x00\xe0\x00\xe0\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x38\x0e\x38\x03\xe0\x03\xe0\x3f\xfe\x3f\xfe\x03\xe0\x03\xe0\x0e\x38\x0e\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x3f\xfe\x3f\xfe\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc0\x03\xc0\x03\xc0\x03\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfe\x3f\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc0\x03\xc0\x03\xc0\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x1c\x00\x38\x00\x38\x00\x70\x00\x70\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x0e\x00\x0e\x00\x1c\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xe0\x07\xe0\x1c\x38\x1c\x38\x38\x3c\x38\x3c\x38\x7c\x38\x7c\x38\xdc\x38\xdc\x39\x9c\x39\x9c\x3b\x1c\x3b\x1c\x3e\x1c\x3e\x1c\x3c\x1c\x3c\x1c\x1c\x38\x1c\x38\x07\xe0\x07\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x03\xc0\x03\xc0\x0f\xc0\x0f\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x0f\xf0\x38\x1c\x38\x1c\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x1c\x00\x1c\x00\x70\x00\x70\x01\xc0\x01\xc0\x07\x00\x07\x00\x1c\x00\x1c\x00\x38\x00\x38\x00\x3f\xfe\x3f\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x0f\xf0\x38\x1c\x38\x1c\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x1c\x00\x1c\x01\xf0\x01\xf0\x00\x1c\x00\x1c\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x38\x1c\x38\x1c\x0f\xf0\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x03\xf0\x03\xf0\x07\x70\x07\x70\x0e\x70\x0e\x70\x1c\x70\x1c\x70\x38\x70\x38\x70\x3f\xfc\x3f\xfc\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfe\x3f\xfe\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3f\xf0\x3f\xf0\x00\x1c\x00\x1c\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x38\x1c\x38\x1c\x0f\xf0\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x00\x1c\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf8\x3f\xf8\x00\x38\x00\x38\x00\x38\x00\x38\x00\x70\x00\x70\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x0e\x1c\x0e\x07\xfe\x07\xfe\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x1c\x00\x1c\x0f\xf0\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x80\x03\x80\x03\x80\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x80\x03\x80\x03\x80\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x80\x03\x80\x03\x80\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x80\x03\x80\x03\x80\x03\x80\x07\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x0e\x00\x0e\x00\x1c\x00\x1c\x00\x0e\x00\x0e\x00\x07\x00\x07\x00\x03\x80\x03\x80\x01\xc0\x01\xc0\x00\xe0\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfc\x3f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfc\x3f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x07\x00\x03\x80\x03\x80\x01\xc0\x01\xc0\x00\xe0\x00\xe0\x00\x70\x00\x70\x00\x38\x00\x38\x00\x70\x00\x70\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xe0\x07\xe0\x1c\x38\x1c\x38\x38\x1c\x38\x1c\x00\x38\x00\x38\x00\x70\x00\x70\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x0f\xf0\x38\x1c\x38\x1c\x70\x0e\x70\x0e\x71\xfe\x71\xfe\x73\x8e\x73\x8e\x73\x8e\x73\x8e\x73\x8e\x73\x8e\x71\xfc\x71\xfc\x70\x00\x70\x00\x38\x00\x38\x00\x0f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc0\x03\xc0\x07\xe0\x07\xe0\x0e\x70\x0e\x70\x1c\x38\x1c\x38\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x3f\xfc\x3f\xfc\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x1c\x38\x1c\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x1c\x38\x1c\x3f\xf0\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x1c\x38\x1c\x3f\xf0\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfc\x3f\xfc\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3f\xe0\x3f\xe0\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3f\xfc\x3f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfc\x3f\xfc\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3f\xe0\x3f\xe0\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x3e\x38\x3e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x3f\xfe\x3f\xfe\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x1c\x1c\x1c\x1c\x0e\x38\x0e\x38\x03\xe0\x03\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x38\x1c\x38\x1c\x70\x1c\x70\x1c\xe0\x1c\xe0\x1d\xc0\x1d\xc0\x1f\x80\x1f\x80\x1f\x80\x1f\x80\x1d\xc0\x1d\xc0\x1c\xe0\x1c\xe0\x1c\x70\x1c\x70\x1c\x38\x1c\x38\x1c\x1c\x1c\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3f\xfc\x3f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x78\x1e\x78\x1e\x7c\x3e\x7c\x3e\x7e\x7e\x7e\x7e\x77\xee\x77\xee\x73\xce\x73\xce\x71\x8e\x71\x8e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x0e\x38\x0e\x3c\x0e\x3c\x0e\x3e\x0e\x3e\x0e\x3f\x0e\x3f\x0e\x3b\x8e\x3b\x8e\x39\xce\x39\xce\x38\xee\x38\xee\x38\x7e\x38\x7e\x38\x3e\x38\x3e\x38\x1e\x38\x1e\x38\x0e\x38\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x1c\x38\x1c\x3f\xf0\x3f\xf0\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\xee\x38\xee\x1c\x7c\x1c\x7c\x07\xf8\x07\xf8\x00\x1c\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x1c\x38\x1c\x3f\xf0\x3f\xf0\x38\xe0\x38\xe0\x38\x70\x38\x70\x38\x38\x38\x38\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x0f\xf0\x38\x1c\x38\x1c\x70\x0e\x70\x0e\x70\x00\x70\x00\x38\x00\x38\x00\x0f\xf0\x0f\xf0\x00\x1c\x00\x1c\x00\x0e\x00\x0e\x70\x0e\x70\x0e\x38\x1c\x38\x1c\x0f\xf0\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfe\x3f\xfe\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x1c\x38\x1c\x38\x0e\x70\x0e\x70\x07\xe0\x07\xe0\x03\xc0\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x70\x0e\x71\x8e\x71\x8e\x73\xce\x73\xce\x77\xee\x77\xee\x3e\x7c\x3e\x7c\x1c\x38\x1c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x1c\x38\x1c\x38\x0e\x70\x0e\x70\x07\xe0\x07\xe0\x03\xc0\x03\xc0\x07\xe0\x07\xe0\x0e\x70\x0e\x70\x1c\x38\x1c\x38\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x0e\x38\x0e\x38\x07\x70\x07\x70\x03\xe0\x03\xe0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfe\x3f\xfe\x00\x1c\x00\x1c\x00\x38\x00\x38\x00\x70\x00\x70\x00\xe0\x00\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x0e\x00\x0e\x00\x1c\x00\x1c\x00\x3f\xfe\x3f\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x1c\x00\x0e\x00\x0e\x00\x07\x00\x07\x00\x03\x80\x03\x80\x01\xc0\x01\xc0\x00\xe0\x00\xe0\x00\x70\x00\x70\x00\x38\x00\x38\x00\x1c\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x03\xc0\x03\xc0\x07\xe0\x07\xe0\x0e\x70\x0e\x70\x1c\x38\x1c\x38\x38\x1c\x38\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x07\x00\x07\x00\x03\x80\x03\x80\x01\xc0\x01\xc0\x00\xe0\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf8\x0f\xf8\x00\x0e\x00\x0e\x0f\xfe\x0f\xfe\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x0f\xfe\x0f\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x3f\xf8\x3f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf8\x0f\xf8\x38\x0e\x38\x0e\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x0e\x38\x0e\x0f\xf8\x0f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x07\xfe\x07\xfe\x1c\x0e\x1c\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x0f\xfe\x0f\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf8\x0f\xf8\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x3f\xfe\x3f\xfe\x38\x00\x38\x00\x38\x00\x38\x00\x0f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00\xf8\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x0f\xf0\x0f\xf0\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf8\x0f\xf8\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x0f\xfe\x0f\xfe\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x1f\xf8\x1f\xf8\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x3b\xf8\x3b\xf8\x3c\x0e\x3c\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x00\x70\x00\x70\x00\x70\x00\x00\x00\x00\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\x70\x00\xe0\x00\xe0\x0f\x80\x0f\x80\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x38\x0e\x38\x0e\x70\x0e\x70\x0e\xe0\x0e\xe0\x0f\xc0\x0f\xc0\x0e\xe0\x0e\xe0\x0e\x70\x0e\x70\x0e\x38\x0e\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3e\x78\x3e\x78\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x39\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xe0\x3f\xe0\x38\x38\x38\x38\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x07\xf0\x1c\x1c\x1c\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x07\xf0\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x1c\x38\x1c\x3f\xf0\x3f\xf0\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xfe\x07\xfe\x1c\x0e\x1c\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x1c\x0e\x1c\x0e\x07\xfe\x07\xfe\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x3f\xf0\x38\x1c\x38\x1c\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x0f\xfc\x38\x00\x38\x00\x38\x00\x38\x00\x0f\xf8\x0f\xf8\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x1f\xf8\x1f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x1f\xfc\x1f\xfc\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x38\x1c\x0f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x0e\x70\x0e\x38\x1c\x38\x1c\x1c\x38\x1c\x38\x0e\x70\x0e\x70\x07\xe0\x07\xe0\x03\xc0\x03\xc0\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x38\x0e\x39\xce\x39\xce\x3b\xee\x3b\xee\x1f\x7c\x1f\x7c\x0e\x38\x0e\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x38\x1c\x38\x0e\x70\x0e\x70\x07\xe0\x07\xe0\x03\xc0\x03\xc0\x07\xe0\x07\xe0\x0e\x70\x0e\x70\x1c\x38\x1c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x38\x0e\x38\x0e\x1c\x1c\x1c\x1c\x0e\x38\x0e\x38\x07\x70\x07\x70\x03\xe0\x03\xe0\x01\xc0\x01\xc0\x03\x80\x03\x80\x07\x00\x07\x00\x0e\x00\x0e\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfe\x3f\xfe\x00\x1c\x00\x1c\x00\x70\x00\x70\x01\xc0\x01\xc0\x07\x00\x07\x00\x1c\x00\x1c\x00\x3f\xfe\x3f\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00\xf8\x01\xc0\x01\xc0\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x1e\x00\x1e\x00\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x03\x80\x01\xc0\x01\xc0\x00\xf8\x00\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x1f\x00\x03\x80\x03\x80\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x00\x78\x00\x78\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x03\x80\x03\x80\x1f\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x9e\x07\x9e\x3c\xf0\x3c\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xc0\x07\x70\x07\x70\x1c\x1c\x1c\x1c\x70\x07\x70\x07\x70\x07\x70\x07\x7f\xff\x7f\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + +FONT = memoryview(_FONT) + diff --git a/vos_debug.py b/vos_debug.py new file mode 100644 index 0000000..75696f7 --- /dev/null +++ b/vos_debug.py @@ -0,0 +1,17 @@ + + +DEBUG_LEVEL_SILENT=-1 +DEBUG_LEVEL_SEVERE=0 +DEBUG_LEVEL_ERROR=10 +DEBUG_LEVEL_WARNING=20 +DEBUG_LEVEL_INFO=30 + +from vos_launch import debug_level + +def debug_print(level,*args): + """ + Function to print debug messages that are lower than debug level (set in vos_launch.py) + """ + if level<=debug_level: + return print(*args) + \ No newline at end of file diff --git a/vos_launch.py b/vos_launch.py new file mode 100644 index 0000000..6892840 --- /dev/null +++ b/vos_launch.py @@ -0,0 +1,40 @@ +# list of things to launch from vectoros + +# dictionary of tags to imports (will look for .vos_main() or .main() here) +launch_list={ "menu": "supercon_menu", 'sketch': 'sketch', "demo": "examples", "planets":"planets", "lissajous":"lissajous"} + +# list what you want to start auto (maybe just one thing?) need tag +auto_launch_list=["menu"] + +vectorscope_slots={"slotA": "A", "slotB": "B", "slotC": "C", "slotD": "D"} + +auto_launch_repl=False # to get out: import sys followed by sys.exit() + +key_scan_rate = 100 # how often to scan the keyboard globally (ms; 0 to do it yourself) + +# how often to garbage collect +# if you set this to zero and do nothing else +# garbage collection will be automatic as usual and before new tasks launch +gc_thread_rate = 5000 + +# Base rate for the timer (ms) +timer_base_rate=100 + +# Debug level (messages must be < this level to print) +# That is, at level 0 only level 0 messages print +# at level 1 then level 1 and level 0 messages print +# Set level to -1 to stop all messages (assuming you only call debug_print with positive values) + +# if you want to use symbols for debug level, these are defined in vos_debug: +DEBUG_LEVEL_SILENT=-1 +DEBUG_LEVEL_SEVERE=0 +DEBUG_LEVEL_ERROR=10 +DEBUG_LEVEL_WARNING=20 +DEBUG_LEVEL_INFO=30 + +debug_level=DEBUG_LEVEL_INFO + + +if __name__=="__main__": + import vectoros + vectoros.run() diff --git a/vos_state.py b/vos_state.py new file mode 100644 index 0000000..a282282 --- /dev/null +++ b/vos_state.py @@ -0,0 +1,18 @@ +# state for vector os + +class vos_state: + task_dict={} # tasks created (not removed, though, unless you do it yourself + active=False # are we active? + gc_suspend=False # do you want to suspend the gc? + show_menu=True # Used to restart main menu + run_after=None # Run after OS is done + version=None # Version (filled in on init) + _xthreading=0 + + +# put any globals you want for your application here (nice to put them "in" something like a dictionary + +# global my_task_vars={"exit_flag": False, "rate": 200} + + + \ No newline at end of file diff --git a/waveform.py b/waveform.py new file mode 100644 index 0000000..2e36fe7 --- /dev/null +++ b/waveform.py @@ -0,0 +1,132 @@ +import dma_defs +import pin_defs +import codec +import math +import struct +import time +import array +import rp2 +import machine +from uctypes import addressof + + +## Set up sample memory arrays + +_DEBUG = const(False) + +class Waveform(): + def __init__(self, num_samples_per_frame=256): + + self.outBuffer_ready = False ## flag raised when a sample gets stored and the outBuffers can be written to + ## your code needs to clear this from outside, then wait for next True to sync up + ## at 30 kHz it's about every 38 ms. + self.num_samples = num_samples_per_frame + self.outBufferX = bytearray(self.num_samples * 2) ## 2 bytes, 1 channel + self.outBufferY = bytearray(self.num_samples * 2) + self.outBuffer = bytearray(self.num_samples * 4) ## interleave + self.outBuffer_addr = array.array("L", [addressof(self.outBuffer)]) + + ## This DMA takes the whole block of samples and feeds them off to + ## the codec's write PIO state machine + ## It fires off an IRQ (feed_dac_irq_handler) when it's done with a round + ## feed_dac_irq_handler interleaves the two sample channels and queues them up + self.feed_dac_transfer = rp2.DMA() + ## And this DMA is chained to the above, resets it back to the beginning + self.feed_dac_control = rp2.DMA() + ## 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.feed_dac_transfer.ctrl = self.feed_dac_transfer.pack_ctrl(default = 0, + size = dma_defs.SIZE_4BYTES, + enable = 1, + treq_sel = dma_defs.DREQ_PIO0_TX1, + chain_to = self.feed_dac_control.channel_id, + bswap = 1, + IRQ_quiet = 0, + inc_read = 1) + self.feed_dac_transfer.config(count = self.num_samples, write = codec.WRITE_FIFO) + self.feed_dac_transfer.irq(handler=self.feed_dac_irq_handler, hard=False) + + self.feed_dac_control.ctrl = self.feed_dac_control.pack_ctrl(default = 0, + size = dma_defs.SIZE_4BYTES, ## addresses + enable = 1, + chain_to = self.feed_dac_control.channel_id, ## no chain + treq_sel = dma_defs.TREQ_PERMANENT, ## always on + IRQ_quiet = 1) + self.feed_dac_control.config(count = 1, + write = self.feed_dac_transfer.registers[15:], + read = addressof(self.outBuffer_addr), + trigger = True) + if _DEBUG: + self.debug_pin = machine.Pin(28, machine.Pin.OUT, value=0) + + def init(self): + self.__init__() + + def deinit(self): + """unwind all of the above""" + print("(@_@) TODO: verify DMA shuts down") + self.feed_dac_control.ctrl = 0 + time.sleep_ms(10) ## (@_@) replace with asyncio when necessary + self.feed_dac_transfer.ctrl = 0 + self.feed_dac_control.close() + time.sleep_ms(10) ## (@_@) replace with asyncio when necessary + self.feed_dac_transfer.close() + + ## 175 us? not horrible -- roughly 6 sample frames, have 256 + @micropython.viper + def interleave_buffers(self): + bufX_p = ptr16(self.outBufferX) + bufY_p = ptr16(self.outBufferY) + out_buffer_p = ptr32(self.outBuffer) + num_samples = int(self.num_samples) + for i in range(num_samples): + out_buffer_p[i] = (bufY_p[i] << 16) + bufX_p[i] + + @micropython.viper + def feed_dac_irq_handler(self, calling_dma): + ## this is where the magic happens + ## roughly every 8.5 ms @ 256 samples + if _DEBUG: + self.debug_pin.high() + self.interleave_buffers() + self.outBuffer_ready = True + ## Ping the user to load up their data + if _DEBUG: + self.debug_pin.low() + + ## Get data into the buffers + @micropython.viper + def _constant(self, value:int, buffer): + buffer_p = ptr8(buffer) + num_samples = int(self.num_samples) + for i in range(num_samples): + buffer_p[2*i+0] = (value & 0xFF00) >> 8 + buffer_p[2*i+1] = value & 0x00FF + + @micropython.viper + def _pack_wave(self, wavelist, buffer): + """takes a list of integers, +/- 15 bits worth, and packs it into a buffer""" + buffer_p = ptr8(buffer) + num_samples = int(self.num_samples) + for i in range(num_samples): + sample = int(wavelist[i]) + buffer_p[2*i+0] = (sample & 0xFF00) >> 8 + buffer_p[2*i+1] = sample & 0x00FF + + ## Convenience functions + def packX(self, wavelist): + self._pack_wave(wavelist, self.outBufferX) + def packY(self, wavelist): + self._pack_wave(wavelist, self.outBufferY) + def constantX(self, value:int): + self._constant(value, self.outBufferX) + def constantY(self, value:int): + self._constant(value, self.outBufferY) + def point(self, x:int, y:int): + self._constant(x, self.outBufferX) + self._constant(y, self.outBufferY) + + +