Initial commit of original SC7 code

This commit is contained in:
true 2023-11-03 09:47:33 -07:00
commit efb2b80b25
55 changed files with 4405 additions and 0 deletions

34
A.py Normal file
View File

@ -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")

63
B.py Normal file
View File

@ -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")

34
C.py Normal file
View File

@ -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")

34
D.py Normal file
View File

@ -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")

146
adc_reader.py Normal file
View File

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

186
aiorepl.py Normal file
View File

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

64
codec.py Normal file
View File

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

24
colors.py Normal file
View File

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

109
dds.py Normal file
View File

@ -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()

171
dma_defs.py Normal file
View File

@ -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<<BF_POS | 1<<BF_LEN | BFUINT32,
"READ_ERROR": 30<<BF_POS | 1<<BF_LEN | BFUINT32,
"WRITE_ERROR": 29<<BF_POS | 1<<BF_LEN | BFUINT32,
"BUSY": 24<<BF_POS | 1<<BF_LEN | BFUINT32,
"SNIFF_EN": 23<<BF_POS | 1<<BF_LEN | BFUINT32,
"BSWAP": 22<<BF_POS | 1<<BF_LEN | BFUINT32,
"IRQ_QUIET": 21<<BF_POS | 1<<BF_LEN | BFUINT32,
"TREQ_SEL": 15<<BF_POS | 6<<BF_LEN | BFUINT32,
"CHAIN_TO": 11<<BF_POS | 4<<BF_LEN | BFUINT32,
"RING_SEL": 10<<BF_POS | 1<<BF_LEN | BFUINT32,
"RING_SIZE": 6<<BF_POS | 4<<BF_LEN | BFUINT32,
"INCR_WRITE": 5<<BF_POS | 1<<BF_LEN | BFUINT32,
"INCR_READ": 4<<BF_POS | 1<<BF_LEN | BFUINT32,
"DATA_SIZE": 2<<BF_POS | 2<<BF_LEN | BFUINT32,
"HIGH_PRIORITY":1<<BF_POS | 1<<BF_LEN | BFUINT32,
"EN": 0<<BF_POS | 1<<BF_LEN | BFUINT32
}
## Control register bit positions
## Configuring DMA is essentially a matter of going through this list, making a choice for each bit, and then writing the result to the CTRL register
BUSY_BIT = const(24) ## flag, readme
SNIFF_ENABLE_BIT = const(23) ## flag
BYTE_SWAP_BIT = const(22) ## flag
IRQ_QUIET_BIT = const(21) ## flag to quiet IRQ
TREQ_SEL_BIT = const(15) ## request source
CHAIN_TO_BIT = const(11) ## chain dest
RING_RW_SEL_BIT = const(10) ## ring on read (0) or write (1)
RING_SIZE_BIT = const(6) ## if ring, how many bytes: 2**n
INC_WRITE_BIT = const(5) ## flag
INC_READ_BIT = const(4) ## flag
DATA_SIZE_BIT = const(2) ## 2**n bytes (1, 2, 4)
HIGH_PRIORITY_BIT = const(1) ## scheduling priority
ENABLE_BIT = const(0) ## will it run
## For use with DATA_SIZE_BIT
SIZE_1BYTE = const(0x0)
SIZE_2BYTES = const(0x1)
SIZE_4BYTES = const(0x2)
## DMA Config register offsets
##
## The aliases are very rich.
## You can always write to one of these 4 and it will _never_ trigger
READ_ADDR = const(0x00)
WRITE_ADDR = const(0x04)
TRANS_COUNT = const(0x08)
CTRL = const(0x10)
## Writing to one of these will _always_ trigger
READ_ADDR_TRIG = const(0x3C)
WRITE_ADDR_TRIG = const(0x2C)
TRANS_COUNT_TRIG = const(0x1C)
CTRL_TRIG = const(0x0C)
## And here are the aliases listed out in order for when you need
## to configure more than 1 byte in a row
READ_ADDR_0 = const(0x00)
WRITE_ADDR_0 = const(0x04)
TRANS_COUNT_0 = const(0x08)
CTRL_0 = const(0x0C)
CTRL_1 = const(0x10)
READ_ADDR_1 = const(0x14)
WRITE_ADDR_1 = const(0x18)
TRANS_COUNT_1 = const(0x1C)
CTRL_2 = const(0x20)
TRANS_COUNT_2 = const(0x24)
READ_ADDR_2 = const(0x28)
WRITE_ADDR_2 = const(0x2C)
CTRL_3 = const(0x30)
WRITE_ADDR_3 = const(0x34)
TRANS_COUNT_3 = const(0x38)
READ_ADDR_3 = const(0x3C)
## List of all Data Request and Trigger Requests
DREQ_PIO0_TX0 = const(0x00)
DREQ_PIO0_TX1 = const(0x01)
DREQ_PIO0_TX2 = const(0x02)
DREQ_PIO0_TX3 = const(0x03)
DREQ_PIO0_RX0 = const(0x04)
DREQ_PIO0_RX1 = const(0x05)
DREQ_PIO0_RX2 = const(0x06)
DREQ_PIO0_RX3 = const(0x07)
DREQ_PIO1_TX0 = const(0x08)
DREQ_PIO1_TX1 = const(0x09)
DREQ_PIO1_TX2 = const(0x0A)
DREQ_PIO1_TX3 = const(0x0B)
DREQ_PIO1_RX0 = const(0x0C)
DREQ_PIO1_RX1 = const(0x0D)
DREQ_PIO1_RX2 = const(0x0E)
DREQ_PIO1_RX3 = const(0x0F)
DREQ_SPI0_TX = const(0x10)
DREQ_SPI0_RX = const(0x11)
DREQ_SPI1_TX = const(0x12)
DREQ_SPI1_RX = const(0x13)
DREQ_UART0_TX = const(0x14)
DREQ_UART0_RX = const(0x15)
DREQ_UART1_TX = const(0x16)
DREQ_UART1_RX = const(0x17)
DREQ_PWM_WRAP0 = const(0x18)
DREQ_PWM_WRAP1 = const(0x19)
DREQ_PWM_WRAP2 = const(0x1A)
DREQ_PWM_WRAP3 = const(0x1B)
DREQ_PWM_WRAP4 = const(0x1C)
DREQ_PWM_WRAP5 = const(0x1D)
DREQ_PWM_WRAP6 = const(0x1E)
DREQ_PWM_WRAP7 = const(0x1F)
DREQ_I2C0_TX = const(0x20)
DREQ_I2C0_RX = const(0x21)
DREQ_I2C1_TX = const(0x22)
DREQ_I2C1_RX = const(0x23)
DREQ_ADC = const(0x24)
DREQ_XIP_STREAM = const(0x25)
DREQ_XIP_SSITX = const(0x26)
DREQ_XIP_SSIRX = const(0x27)
TREQ_TMR0 = const(0x3B)
TREQ_TMR1 = const(0x3C)
TREQ_TMR2 = const(0x3D)
TREQ_TMR3 = const(0x3E)
TREQ_PERMANENT = const(0x3F)

106
examples.py Normal file
View File

@ -0,0 +1,106 @@
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)
def static_buffer_example(v):
## Example of more complicated, repetitive waveform
## v.wave has two buffers of 256 samples for putting sample-wise data into:
## v.wave.outBufferX and outBufferY. These are packed 16 bits each, LSB first
## To make your life easier, v.wave.packX() will put a list of 16-bit ints there for you
ramp = range(-2**15, 2**15, 2**8)
v.wave.packX(ramp)
sine = [int(math.sin(2*x*math.pi/256)*16_000) for x in range(256)]
v.wave.packY(sine)
time.sleep_ms(1000)
## That discontinuity and wobble is real --
## that's what happens when you try to push around a real DAC that's bandwidth-limited.
def animated_buffer_example(v):
## To animate, you need to clear v.wave.outBuffer_ready and wait for it to go true
## Each output buffer frame has 256 samples, so takes ~8.5 ms at 30 kHz
ramp = range(-2**15, 2**15, 2**8)
v.wave.packX(ramp)
v.wave.outBuffer_ready = False
for i in range(200):
sine = [int(math.sin((50*i)+2*x*math.pi/256)*16_000) for x in range(256)]
while not v.wave.outBuffer_ready:
pass
v.wave.packY(sine)
v.wave.outBuffer_ready = False
## Any stuck pixels you see are a figment of your imagination. :)
## Or a desperate call for a pull request. Your call.
def random_walk_example(v):
## Example with a class, makes it tweakable on the command line
## because half the fun here is live coding and experimentation
r = RW(v.wave)
# print(dir(r))
r.scale = 1000
r.delay = 5
r.go()
def vos_main():
import vos_state,vectoros,gc,vos_debug, asyncio
from vos_debug import debug_print as debug
vectoros.get_screen().idle()
gc.collect()
vos_state.gc_suspend=True
asyncio.sleep(4)
v = Vectorscope()
minimal_example(v)
await asyncio.sleep(5)
static_buffer_example(v)
await asyncio.sleep(5)
animated_buffer_example(v)
random_walk_example(v)
debug(vos_debug.DEBUG_LEVEL_INFO,"Demo done, reboot!")
vectoros.reset()
if __name__ == "__main__":
v = Vectorscope()
minimal_example(v)
static_buffer_example(v)
animated_buffer_example(v)
random_walk_example(v)
v.deinit()
## before you reload, you have to deinitialize all of the DMA
## machines, else you get an error OS: 16

70
generate_wavetables.py Normal file
View File

@ -0,0 +1,70 @@
## This file generates headers with lookup tables for various waveforms
## Add your own.
import math
## phase is in degrees: 360 is a full cycle, and this is probably what you want
def phaseSteps(maxPhase, length=256):
steps = range(0, length)
steps = [1.0*x/length * 2.0*math.pi * (maxPhase/360.0) for x in steps]
return steps
def sine(maxPhase=360, length=256):
wave = [math.sin(x) for x in phaseSteps(maxPhase, length)]
return scaleAndRound(wave)
def sawtooth(maxPhase=360, length=256):
wave = [x for x in range(length)]
return scaleAndRound(wave)
def square(maxPhase=360, length=256):
wave = [0]*(length//2)
wave.extend([1]*(length//2))
return scaleAndRound(wave)
def triangle(maxPhase=360, length=256):
wave = [x for x in range(length//2)]
wave.extend([length//2 - x for x in range(length//2)])
return scaleAndRound(wave)
def bandlimitedSawtooth(numberPartials, maxPhase=360, length=256):
wave = [0]*length
sign = 1.0
for k in range(1, numberPartials+1):
phases = phaseSteps(maxPhase*k, length)
for i in range(length):
wave[i] += sign * math.sin(phases[i]) / k
sign = sign * -1
return scaleAndRound(wave)
def bandlimitedSquare(numberPartials, maxPhase=360, length=256):
wave = [0]*length
for k in range(1, numberPartials*2, 2):
phases = phaseSteps(maxPhase*k, length)
for i in range(length):
wave[i] += math.sin(phases[i]) / k
return scaleAndRound(wave)
def bandlimitedTriangle(numberPartials, maxPhase=360, length=256):
wave = [0]*length
sign = 1.0
for k in range(1, numberPartials*2, 2):
phases = phaseSteps(maxPhase*k, length)
for i in range(length):
wave[i] += sign * math.sin(phases[i]) / k**2
sign = sign * -1
return scaleAndRound(wave)
def scaleAndRound(data, scale=2**16-1, signedInt=True):
data = [0.0+x-min(data) for x in data]
data = [1.0*x/max(data)*scale for x in data]
data = [int(round(x)) for x in data]
if signedInt:
data = [int(x-(scale+1)//2) for x in data]
return(data)
if __name__ == "__main__":
sawtooth_sample = sawtooth(7)

42
joystick.py Normal file
View File

@ -0,0 +1,42 @@
import keyleds
import keyboardcb
class Joystick(keyboardcb.KeyboardCB):
"""
A joystick class that inherits from KeyboardCB. It has a default filter and repeat set.
It also knows how to replace chords like JOY_N + JOY_E to JOY_NE.
Attributes:
callback (dict): A dictionary of callback functions or a single callback.
single (bool): A flag to indicate if only single key press events should be handled.
attach (bool): A flag to indicate if the joystick should be attached during initialization.
"""
def __init__(self, callback={}, single_key_mode=False, attach=True):
"""
The constructor for Joystick class.
Args:
callback (dict): A dictionary of callback functions. Default is an empty dictionary.
single_key_mode (bool): A flag to indicate if only single key press events should be handled. Default is False.
attach (bool): A flag to indicate if the joystick should be attached during initialization. Default is True.
"""
super().__init__(callback, keyleds.JOY_ALL, single_key_mode, attach=attach)
async def key(self, b):
"""
Function to handle key press events.
This function generates synthetic keys and dispatches callbacks as usual.
Args:
b (int): The keycodes of the pressed keys (list).
"""
# generate synthetic keys
b1 = keyboardcb.replace_chord(b, [keyleds.JOY_N, keyleds.JOY_E], keyleds.JOY_NE)
b1 = keyboardcb.replace_chord(b1, [keyleds.JOY_N, keyleds.JOY_W], keyleds.JOY_NW)
b1 = keyboardcb.replace_chord(b1, [keyleds.JOY_S, keyleds.JOY_E], keyleds.JOY_SE)
b1 = keyboardcb.replace_chord(b1, [keyleds.JOY_S, keyleds.JOY_W], keyleds.JOY_SW)
await super().key(b1) # dispatch callbacks as usual

121
keyboardcb.py Normal file
View File

@ -0,0 +1,121 @@
import keyleds
import keyboardio
import asyncio
class KeyboardCB(keyboardio.KeyboardIO):
"""
A class that provides a single callback for a key or group of keys.
It can use a single callback or a dictionary to have different callbacks for different keys.
It can also filter repeating keys
Attributes:
callback (dict or function): A dictionary of callback functions or a single callback function.
filter (list): A list of keys to filter (default none, or use keys from callback when callback is a dictionary)
single_key_mode (bool): A flag to indicate if only single key press events should be handled.
attach (bool): A flag to indicate if the keyboard should be attached during initialization.
active (bool): A flag to indicate if the keyboard is active.
"""
def __init__(self, callback={}, filter=[], single_key_mode=True, attach=True):
"""
The constructor for KeyboardCB class.
Args:
callback (dict or function): A dictionary of callback functions or a single callback function.
Default is an empty dictionary (no callbacks).
filter (list): A list of keys to filter. Default is an empty list.
single_key_mode (bool): A flag to indicate if only single key press events should be handled. Default is True.
attach (bool): A flag to indicate if the keyboard should be attached during initialization. Default is True.
"""
super().__init__(attach=attach)
if isinstance(filter,list):
self.filter=filter
else:
self.filter=[ filter ]
self._cb=callback
self.single_key_mode=single_key_mode
self.active=True
def set_callback(self,func_or_dict):
"""
Function to set a non-default callback.
Args:
func_or_dict (function or dict): The function or dictionary to set as the callback.
"""
self._cb=func_or_dict
async def _do_callback(self,cb,k):
try:
await cb(k) # async if possible
except TypeError: # well, it was really a sync call, np...
pass
async def key(self, b):
"""
Function to handle key press events.
This function checks if the keyboard is active and if the pressed key is in the filter.
If it is, it calls the appropriate callback function.
Args:
b (int): The keycodes of the pressed keys as a list.
"""
# if you specify a filter, use it
# if not but you use a dictionary callback, use its keys as a filter
# otherwise, no filter
if self.active==False:
return
if self.filter!=[]:
flt=self.filter
else:
if isinstance(self._cb,dict):
flt=self._cb.keys()
else:
flt=[]
# remove all not in filter here first
if flt!=[]:
b1=[ item for item in b if item in flt ]
else:
b1=b
if (b1!=[] and self.single_key_mode==True): # single keypress mode
b1=[ item for item in b1 if item not in self._prev ]
if b1:
for k in b1:
if flt!=[]:
if k not in flt:
continue
if isinstance(self._cb,dict):
asyncio.create_task(self._do_callback(self._cb[k],k))
else:
asyncio.create_task(self._do_callback(self._cb,k))
def replace_chord(b,chord,value):
"""
Helper function to find chords (two or more keys) and replace them with one key.
Args:
b (list): The list of pressed keys.
chord (list): The list of keys that form a chord.
value (int): The keycode of the key that will replace the chord.
Returns:
list: The list of pressed keys with the chord replaced by the value.
"""
b1=b
if all(item in b1 for item in chord):
for item in chord:
b1.remove(item)
b1.append(value)
return b1

223
keyboardio.py Normal file
View File

@ -0,0 +1,223 @@
import asyncio
import keyleds
from machine import SoftSPI, Pin
import vos_debug
class KeyboardIO:
"""
A class that reads keys from the board and manages LEDs.
Attributes:
leds (int): The LEDs to write out.
task (Task): The task for the keyboard polling loop.
current_keys (list): The list of currently pressed keys.
_timeout (int): The poll rate in ms.
_spi (SoftSPI): The SPI interface.
_latch_load (Pin): The shift-register load pin.
_button_sense (list): The two columns of button matrix.
_user_sense (Pin): The user sense pin.
_subscribers (list): The list of subscribers who want to know about keys.
_prev (list): The list of keys that were down in the last poll.
_capture (KeyboardIO): The subscriber that hogs all the keyboard.
"""
leds=0
task=None
current_keys=[]
_timeout=100
_spi = SoftSPI(baudrate=500_000, sck=Pin(0), mosi=Pin(1), miso=Pin(19))
_spi.init()
_latch_load = Pin(16, mode=Pin.OUT, value=0)
_button_sense = [Pin(17, mode=Pin.IN), Pin(18, mode=Pin.IN)]
_user_sense = Pin(19, mode=Pin.IN)
_subscribers=[]
_prev=[]
_capture=None
_cancel=False
def __init__(self, attach=True):
"""
The constructor for KeyboardIO class.
Args:
attach (bool): A flag to indicate if the keyboard should be attached during initialization. Default is True.
"""
self.subref=self.key
if (attach):
self._subscribers.append(self.subref);
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._capture==self.subref:
self._capture=None
self.detach()
def capture(tf=True):
"""
Function to grab all keyboard events for this instance.
Args:
tf (bool): A flag to indicate if the keyboard events should be captured. Default is True.
Returns:
bool: True if the capture was successful, False otherwise.
"""
if tf==True and self._capture!=None and self._capture!=self.subref:
return false
if tf==False and self._capture!=self.subref:
return false
if tf==True:
self._capture=self.subref
else:
self._capture=None
return True
@classmethod
async def _run(cls, timeout_ms=100):
"""
Class method to run the class level "server".
Args:
timeout_ms (int): The timeout in ms. Default is 100.
Returns:
Task: The created task.
"""
if timeout_ms!=0:
cls._timeout=timeout_ms
cls.task=asyncio.create_task(cls._job())
@classmethod
def run(cls,timeout_ms=100):
"""
Class method to launch the event loop without having to create a task yourself.
Args:
timeout_ms (int): The timeout in ms. Default is 100.
Returns:
Task: The created task.
"""
return asyncio.create_task(cls._run(timeout_ms))
@classmethod
async def _job(cls):
"""
Class method for the actual polling task.
This method runs a loop that sleeps for a specified timeout and then scans the keyboard.
Returns:
Nothing
"""
vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"Starting keyboard loop")
while cls._cancel==False:
await asyncio.sleep_ms(cls._timeout)
await cls._do_scan()
vos_debug.debug_print(vos_debug.DEBUG_LEVEL_INFO,"KEYIO exiting")
@classmethod
def cancel(cls):
"""
Class method to cancel the polling task.
"""
cls._cancle=True
cls.task.cancel()
def attach(self):
"""
Method to attach a subscriber to the keyboard.
This method adds a subscriber to the list of subscribers if it is not already there.
"""
if self.subref not in self._subscribers:
self._subscribers.append(self.subref)
def detach(self):
"""
Method to detach a subscriber from the keyboard.
This method removes a subscriber from the list of subscribers if it is there.
"""
if self.subref in self._subscribers:
self._subscribers.remove(self.subref)
@classmethod
async def _do_scan(cls):
"""
Class method to scan the keyboard and process all keys.
This method scans the keyboard and calls the appropriate callback function for each pressed key.
"""
b=cls.scan()
if b!=[]:
cls.current_keys=b
if cls._capture==None:
for sub in cls._subscribers:
try:
await sub(b)
except TypeError:
pass
else:
try:
await cls._capture(b)
except TypeError:
pass
cls._prev=b
else:
cls._prev=[]
@classmethod
def scan(cls):
"""
Class method to scan the buttons.
This method scans the buttons and returns a list of pressed keys.
Returns:
list: The list of pressed keys.
"""
buttons = []
button_scan = 1
if cls._user_sense()==0:
buttons = [ 24 ]
# update leds and scan buttons (if scan provided)
for i in range(8):
register = cls.leds + (button_scan << 8)
cls._spi.write(register.to_bytes(2,'big'))
cls._latch_load(1)
cls._latch_load(0)
if (cls._button_sense[0]() == 0 ): # buttons are inverted
buttons.append(i*3+1)
if (cls._button_sense[1]() == 0):
buttons.append(i*3+2)
button_scan = button_scan<<1 ## next scan
return buttons
async def key(self,b):
"""
Method to handle key press events. Always overridden
This method prints the list of pressed keys.
Args:
b (list): The list of pressed keys.
"""
print(f"Keyboardio: {b}")

61
keyleds.py Normal file
View File

@ -0,0 +1,61 @@
## MENU = 19
## User = 24
## SAVE = 22
## A=8 B=7 C=1 D=4
## Level=5 RANGE=2
## XY=10 WAVE=16 SCOPE=13
## J=17 JN=20 JS=11 JE=23 JW=14
KEY_MENU=19
KEY_USER=24
KEY_SAVE=22
KEY_A=8
KEY_B=7
KEY_C=1
KEY_D=4
KEY_LEVEL=5
KEY_RANGE=2
KEY_XY=10
KEY_WAVE=16
KEY_SCOPE=13
JOY_PRESS=17
JOY_UP=20
JOY_DN=11
JOY_RT=23
JOY_LF=14
# synonyms and synthetic joystick
JOY_LT=JOY_LF
JOY_N=JOY_UP
JOY_S=JOY_DN
JOY_E=JOY_RT
JOY_W=JOY_LF
JOY_NE=100
JOY_NW=101
JOY_SE=102
JOY_SW=103
JOY_ALL=[JOY_PRESS, JOY_UP, JOY_DN, JOY_RT, JOY_LF, JOY_NE, JOY_NW, JOY_SE, JOY_SW]
KEY_ALL=[KEY_MENU, KEY_USER, KEY_SAVE, KEY_A, KEY_B, KEY_C, KEY_D, KEY_LEVEL, KEY_RANGE, KEY_XY, KEY_WAVE, KEY_SCOPE]
KEY_EVERYTHING=JOY_ALL + KEY_ALL
KEY_ABCD=[KEY_A, KEY_B, KEY_C, KEY_D]
## Numerically
## C, RANGE, [3], D, Level, [6], B, A, [9], XY, JS, [12], SCOPE, JW, [15], WAVE, J, [18], Menu, JN, [21], Save, JE, User
## LEDs
## 1- SIG generator
## 2 - Scope
## 4 - Saw tooth
## 8 - Triangle
## 16 - Square
## 32 - Sine
## 64 - Y
## 128 - X
LED_SIG=1
LED_SCOPE=2
LED_SAW=4
LED_TRI=8
LED_SQ=16
LED_SINE=32
LED_Y=64
LED_X=128

147
lissajous.py Normal file
View File

@ -0,0 +1,147 @@
from vectorscope import Vectorscope
from dds import DDS
import gc
from vos_debug import debug_print as debug
import vectoros
import vos_state
import vos_debug
import keyboardcb
import keyleds
import keyboardio
import asyncio
_abort=False
## map waveform types to LEDs
_waves_lookup = {0:"sine", 3:"sawtooth", 1:"square", 2:"triangle"}
_waves_reverse_lookup = {"sine":0, "sawtooth":3, "square":1, "triangle":2}
lissajous_state = {
"selected_axis":0,
"selected_waveform":0,
"waves_leds":[0,0]
}
async def do_dds_loop(d):
while not _abort:
for i in range(50):
d.do_dds()
d.populate_buffer()
await asyncio.sleep(0)
def do_abort(key):
global _abort
_abort=True
async def vos_main():
vectoros.get_screen().idle()
gc.collect()
vos_state.gc_suspend=True
# await asyncio.sleep(1)
keyboardio.KeyboardIO.leds = (1<<7)
keyboardio.KeyboardIO.leds |= (1<<5) ## sine wave
keyboardio.KeyboardIO.scan()
v = Vectorscope()
d = DDS(v)
d.increment = [1500, 1200]
d.amplitude=[0.5, 0.5]
d.recalculate_waveforms()
def toggle_xy(key):
## not right yet
global lissajous_state
global d
if lissajous_state["selected_axis"] == 0:
lissajous_state["selected_axis"] = 1
keyboardio.KeyboardIO.leds |= (1<<6)
keyboardio.KeyboardIO.leds &= ~(1<<7)
else:
lissajous_state["selected_axis"] = 0
keyboardio.KeyboardIO.leds |= (1<<7)
keyboardio.KeyboardIO.leds &= ~(1<<6)
## Update leds to reflect switch
keyboardio.KeyboardIO.leds &= (0b11000011)
which_led = lissajous_state["waves_leds"][lissajous_state["selected_axis"]]
keyboardio.KeyboardIO.leds |= (1<<(5-which_led))
def toggle_waveform(key):
global lissajous_state
which_led = lissajous_state["waves_leds"][lissajous_state["selected_axis"]]
## clear leds
keyboardio.KeyboardIO.leds &= (0b11000011)
## update led
which_led = ( which_led + 1) % 4
keyboardio.KeyboardIO.leds |= (1<<(5-which_led ))
## update storage
lissajous_state["waves_leds"][lissajous_state["selected_axis"]] = which_led
## update waveform
d.waveform[lissajous_state["selected_axis"]] = _waves_lookup[which_led]
d.recalculate_waveforms()
def handle_joystick_up(key):
current_keys = keyboardcb.KeyboardCB.current_keys
if len(current_keys) == 1: ## just joystick
d.increment[1] = int(d.increment[1] * 1.1)
if keyleds.KEY_RANGE in current_keys:
d.amplitude[1] = d.amplitude[1] * 1.1
d.recalculate_waveforms()
## increase amplitude Y
if keyleds.KEY_LEVEL in current_keys:
d.phase_increment[1] = d.phase_increment[1] + 1
## increase phase Y
def handle_joystick_down(key):
current_keys = keyboardcb.KeyboardCB.current_keys
if len(current_keys) == 1: ## just joystick
d.increment[1] = int(d.increment[1] * 0.91)
if keyleds.KEY_RANGE in current_keys:
d.amplitude[1] = d.amplitude[1] * 0.91
d.recalculate_waveforms()
## increase amplitude Y
if keyleds.KEY_LEVEL in current_keys:
d.phase_increment[1] = d.phase_increment[1] - 1
## increase phase Y
def handle_joystick_right(key):
current_keys = keyboardcb.KeyboardCB.current_keys
if len(current_keys) == 1: ## just joystick
d.increment[0] = int(d.increment[0] * 1.1)
if keyleds.KEY_RANGE in current_keys:
d.amplitude[0] = d.amplitude[0] * 1.1
d.recalculate_waveforms()
## increase amplitude Y
if keyleds.KEY_LEVEL in current_keys:
d.phase_increment[0] = d.phase_increment[0] + 1
## increase phase Y
def handle_joystick_left(key):
current_keys = keyboardcb.KeyboardCB.current_keys
if len(current_keys) == 1: ## just joystick
d.increment[0] = int(d.increment[0] * 0.91)
if keyleds.KEY_RANGE in current_keys:
d.amplitude[0] = d.amplitude[0] * 0.91
d.recalculate_waveforms()
## increase amplitude X
if keyleds.KEY_LEVEL in current_keys:
d.phase_increment[0] = d.phase_increment[0] - 1
## increase phase X
mykeys=keyboardcb.KeyboardCB({keyleds.KEY_MENU: do_abort,
keyleds.KEY_XY:toggle_xy,
keyleds.KEY_WAVE: toggle_waveform,
keyleds.JOY_UP: handle_joystick_up,
keyleds.JOY_DN: handle_joystick_down,
keyleds.JOY_RT: handle_joystick_right,
keyleds.JOY_LT: handle_joystick_left
})
await do_dds_loop(d)
vectoros.reset()

16
main.py Normal file
View File

@ -0,0 +1,16 @@
import _thread
import vectoros
from time import sleep
import machine
user_button = machine.Pin(19, machine.Pin.IN)
if user_button.value():
vectoros.run()
else:
import vectorscope
v = vectorscope.Vectorscope()

282
menu.py Normal file
View File

@ -0,0 +1,282 @@
import screennorm
import keyboardcb
import keyleds
import joystick
import asyncio
import gc9a01
import vectoros
import vos_debug
screen=vectoros.get_screen()
BACK=-1
EXIT=1
CONT=0
SUBMENU=[]
def m_back(arg):
return BACK
def m_exit(arg):
return EXIT
class Menu:
"""
A class that manages a menu.
Attributes:
fg (int): The foreground color.
bg (int): The background color.
clear_after (bool): A flag to indicate if the menu should be cleared after exit.
joycontroller (Joystick): The joystick controller.
scanrate (int): The scan rate.
cursor (int): The position of the screen cursor.
dispmenu (int): The top line of the display.
current (list): The current menu string.
level (int): The current level in the submenus.
stack (list): The stack of how we got here.
clear_after (bool): A flag to indicate if the menu should be cleared after exit.
fg (int): The foreground color.
bg (int): The background color.
update_callback (function): A function to call to update the menu in real time.
"""
def __init__(self, fg_color=gc9a01.color565(45, 217, 80), bg_color=gc9a01.color565(0, 0, 0),cursor_fg=gc9a01.color565(120,247,180),
cursor_bg=None,clear_after=False, joy_controller=None, scan_rate=0):
"""
The constructor for Menu class.
Args:
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).
clear_after (bool): A flag to indicate if the menu should be cleared after exit. Default is False.
joycontroller (Joystick): The joystick controller. Default is None.
scanrate (int): The scan rate. Default is 0.
"""
if joy_controller != None:
self.joy=joy_controller
self._extjoy=True
else:
self.joy=None
self._extjoy=False
if scan_rate!=0:
joystick.Joystick.run(scan_rate)
self.cursor=0
self.dispmenu=0
self.current=[]
self.level=0
self.stack=[]
self.clear_after=clear_after
self.fg=fg_color
self.bg=bg_color
if cursor_fg==None:
self.cfg=self.bg
else:
self.cfg=cursor_fg
if cursor_bg==None:
self.cbg=self.fg
else:
self.cbg=cursor_bg
self.update_callback=None
self.font=None
self.scale=1
def __enter__(self):
return self
def __exit__(self,exc_type,exc_val,exc_tb):
if self.joy!=None:
self.joy.detach()
def detach(self):
"""
Method to detach the joystick from the menu.
Returns:
Task: The created task.
"""
self.joy.detach()
def set_font(self,font,scale=1.0):
"""
Method to set a font and scale factor.
Default: use default font (set to None)
"*": use default vector font (romans)
Or you can load a vector font and pass it
Args:
font (font or string or None): See above
scale (float): Text scale (defaults to 1.0)
"""
if font=="*":
self.font=screen.get_vfont()
else:
self.font=font
self.scale=scale
async def menu_custom(self):
"""
Method you can override to customize the menu in real time
Or, if you don't want to subclass, you can use set_callback
and the default will call that
"""
if self.update_callback!=None:
try:
await self.update_callback(self)
except TypeError:
pass
def set_callback(self,func):
"""
Method to set a callback function.
Args:
func (function): The function to set as the callback.
"""
self.update_callback=func
async def menu_update(self):
"""
Method to update the menu with cursor, scrolling, etc.
This method updates the menu based on the current state of the cursor and calls the custom update function if it is set.
"""
screen.clear(self.bg)
await self.menu_custom()
for i in range(0,min(4,len(self.current))):
if i==self.cursor:
xfg=self.cfg
xbg=self.cbg
else:
xfg=self.fg
xbg=self.bg
# screen.tft.fill_rect(24,40*(i+1),195,40,xbg)
if self.font==None:
screen.text(24,40*(i+1),self.current[self.dispmenu+i][0],xfg,xbg)
else:
screen.text_font(self.font,24,40*(i+1)+20,self.current[self.dispmenu+i][0],xfg,self.scale)
# the controller callback for the keyboard
# all the real work happens here
async def _menu_control(self,key):
"""
Method to handle joystick events.
This method updates the menu based on the joystick event. It can move the cursor, select a menu item,
go to a submenu, or exit the menu.
Args:
key (int): The keycode of the joystick event.
"""
rv=0
if self.level<=0:
return # no menu
if key==keyleds.JOY_UP:
if self.cursor>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<min(3,len(self.current)-1):
self.cursor=self.cursor+1
else:
if self.dispmenu<len(self.current)-4:
self.dispmenu=self.dispmenu+1
await self.menu_update()
if key==keyleds.JOY_PRESS or key==keyleds.JOY_RT:
cmd=self.current[self.cursor+self.dispmenu][1]
arg=self.current[self.cursor+self.dispmenu][2]
if cmd==None: # replace with ret val from built-in callback
rv=-1
elif cmd==[]:
self.stack.append(self.current)
self.current=arg
self.cursor=0
self.dispmenu=0
self.level=self.level+1
else:
if len(self.current[self.cursor+self.dispmenu])<4:
rv=self.current[self.cursor+self.dispmenu][1](self.current[self.cursor+self.dispmenu][2])
else:
rv=await self.current[self.cursor+self.dispmenu][1](self.current[self.cursor+self.dispmenu][2])
if rv==1:
self.stack=[]
rv=-1 # make sure we exit
await self.menu_update()
if key==keyleds.JOY_LT or rv==-1:
self.level=self.level-1
if self.stack==[]:
self.level=-1
self.current=None
else:
self.current=self.stack.pop()
self.cursor=0
self.dispmenu=0
await self.menu_update()
# This is how you kick off the menu. The menu list is a list
# with a sublist for each entry. The sublists have three items:
# A text string, a function, and an argument
# Pro tip: pass a single list, tuple, etc as an argument and you can pass as much as you want
async def do_menu(self,menulist):
"""
Method to start the menu.
This method starts the menu and runs a loop until the level is less than 0. It also updates the menu after each event.
Args:
menulist (list): The list of menu items. Each item is a sublist with three items: a text string, a function, and an argument.
"""
if self._extjoy==False:
self.joy=joystick.Joystick(self._menu_control,True)
self.current=menulist
self.stack=[]
self.dispmenu=0
self.cursor=0
self.level=1
screen.clear()
await self.menu_update()
while self.level>=0:
await asyncio.sleep(0)
if self.clear_after:
screen.clear()
if (self._extjoy):
self.joy.detach()

79
menudemo.py Normal file
View File

@ -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()

20
minimal_example.py Normal file
View File

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

10
phosphor_gradient_14.py Normal file
View File

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

40
pin_defs.py Normal file
View File

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

124
pio_code.py Normal file
View File

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

39
pio_defs.py Normal file
View File

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

249
pixel_pusher.py Normal file
View File

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

BIN
pl_earth.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
pl_jupiter.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
pl_mars.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
pl_mercury.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
pl_moon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
pl_neptune.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
pl_saturn.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
pl_uranus.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
pl_venus.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

24
random_walk.py Normal file
View File

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

208
romans.py Normal file
View File

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

74
screen.py Normal file
View File

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

125
screennorm.py Normal file
View File

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

60
screentest.py Normal file
View File

@ -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()

169
sketch.py Normal file
View File

@ -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<SIZE-1:
cursor_x=newx
newy=cursor_y+y
if newy>=0 and newy<SIZE-1:
cursor_y=newy
if pendown:
model[cursor_x][cursor_y]=color
cursor(False)
# command keys (A=Black, B=Red, C=Green, D=Blue, User=Clear)
def red(key):
global color
color=gc9a01.RED
def green(key):
global color
color=gc9a01.GREEN
def blue(key):
global color
color=gc9a01.BLUE
def black(key):
global color
color=gc9a01.BLACK
def white(key): # erase
global color
color=gc9a01.WHITE
def cls(key):
fill_model(gc9a01.WHITE)
cursor()
def menu(key): # exit and return to menu
global stopflag
print("menu")
if vos_state.active:
stopflag=True
joy.detach()
btn.detach()
csel.detach()
# create our screen and keyboard/joystick
screen=screennorm.ScreenNorm()
joy=joystick.Joystick(joycmd,attach=False)
btn=keyboardcb.KeyboardCB(joybtn,keyleds.JOY_PRESS,attach=False) # no repeat
csel=keyboardcb.KeyboardCB({ keyleds.KEY_LEVEL: white, keyleds.KEY_A: black,
keyleds.KEY_B: red, keyleds.KEY_C: green, keyleds.KEY_D: blue, keyleds.KEY_USER: cls,
keyleds.KEY_MENU: menu},
attach=False)
import asyncio
import gc
async def vos_main():
global stopflag
if keyboardcb.KeyboardCB.task==None:
keyboardcb.KeyboardCB.run(100) # start keyboard service
cls(None) # zero out model and draw
joy.attach()
btn.attach()
csel.attach()
while stopflag==False:
await asyncio.sleep(5)
if vos_state.active==False:
gc.collect()
stopflag=False # ready for next time
vos_state.show_menu=True
vectoros.remove_task('sketch')
print("Exiting")
def main():
asyncio.run(vos_main())
if __name__ == "__main__":
main()

60
slideshow.py Normal file
View File

@ -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()

BIN
splash_7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
splash_wrencher.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
splash_x.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

25
static_buffer_example.py Normal file
View File

@ -0,0 +1,25 @@
import math
import time
from vectorscope import Vectorscope
from random_walk import RW
def static_buffer_example(v):
## Example of more complicated, repetitive waveform
## v.wave has two buffers of 256 samples for putting sample-wise data into:
## v.wave.outBufferX and outBufferY. These are packed 16 bits each, LSB first
## To make your life easier, v.wave.packX() will put a list of 16-bit ints there for you
ramp = range(-2**15, 2**15, 2**8)
v.wave.packX(ramp)
sine = [int(math.sin(2*x*math.pi/256)*16_000) for x in range(256)]
v.wave.packY(sine)
time.sleep(5)
## That discontinuity and wobble is real --
## that's what happens when you try to push around a real DAC that's bandwidth-limited.
async def slot_main(v):
static_buffer_example(v)

102
supercon_menu.py Normal file
View File

@ -0,0 +1,102 @@
import machine
import joystick
from menu import * # bad habit but makes our menu definition nice
from vos_state import vos_state
import vectoros
import colors
import gc
import random
# 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 planets(arg):
vos_state.show_menu=False # get the menu of the way
vectoros.launch_task('planets') # launch
return EXIT
def menu_custom(the_menu):
if the_menu.level==1:
if machine.Pin(22).value():
the_menu.current[2]=[" Sound Off",toggle_sound,0]
else:
the_menu.current[2]=[" Sound On",toggle_sound,1]
def toggle_sound(arg):
## toggle sound here
machine.Pin(22, machine.Pin.OUT).toggle()
return CONT
# the main vector scope demo
def run_lissajous(arg):
vos_state.show_menu=False
vectoros.launch_task('lissajous')
# we never come back, vectorscope
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
## Start with sound off
machine.Pin(22, machine.Pin.OUT).toggle()
screen=vectoros.get_screen()
splashes = ["splash_x.jpg", "splash_7.jpg", "splash_wrencher.jpg"]
screen.jpg(random.choice(splashes))
await asyncio.sleep_ms(1000)
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:
## name in menu, command to run, return value?
submenu=[[" Planets", planets, 0],[" Sketch",runsketch,0],[" Back",m_exit,None]]
mainmenu=[[" Lissajous", run_lissajous,None],
[" Demos", SUBMENU, submenu] ,
[" Sound", toggle_sound, None],
[" Reboot",reboot,False],
]
# comment next line for default font
amenu.set_font("*") # set default vector font
amenu.set_callback(menu_custom)
await amenu.do_menu(mainmenu)
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()

277
timer.py Normal file
View File

@ -0,0 +1,277 @@
import vos_launch
import asyncio
import vos_debug
# if you don't care about the asyncio aspect of this
# you can try using machine.Timer
class Timer:
"""
A class that manages a timer for VectorOS.
Attributes:
clients (dict): Each item is [reset value, value, callback, sync/async, oneshot].
baserate (int): The base rate in milliseconds.
_next_id (int): The next id to use.
current_id (int): The current id.
task (Task): The task for the timer.
gc_delay (int): The delay for garbage collection.
"""
clients={}
baserate=vos_launch.timer_base_rate
_next_id=1
current_id=None
task=None
gc_delay=0
_cancel=False
@classmethod
async def _run(cls):
"""
Class method to run the class level "server".
Returns:
Task: The created task.
"""
cls.task=asyncio.create_task(cls._job())
@classmethod
def run(cls):
"""
Class method to launch the event loop without having to create a task yourself.
Returns:
Task: The created task.
"""
return asyncio.create_task(cls._run())
@classmethod
async def _job(cls):
"""
Class method for the actual polling task.
This method runs a loop that sleeps for a specified timeout and then ticks the timer.
Returns:
Task: The created task.
"""
ctr=0
while cls._cancel==False:
await asyncio.sleep_ms(cls.baserate)
await cls._tick()
ctr+=1
if cls.gc_delay!=0 and ctr>cls.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())

364
vectoros.py Normal file
View File

@ -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()

112
vectorscope.py Normal file
View File

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

104
vga1_16x32.py Normal file
View File

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

17
vos_debug.py Normal file
View File

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

40
vos_launch.py Normal file
View File

@ -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()

18
vos_state.py Normal file
View File

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

132
waveform.py Normal file
View File

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