working keyboard sim

This commit is contained in:
Peter D. Gray 2023-01-19 08:43:28 -05:00 committed by scgbckbone
parent 9b6b676dd1
commit a7c1cdf8ff
15 changed files with 458 additions and 170 deletions

101
shared/charcodes.py Normal file
View File

@ -0,0 +1,101 @@
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# Constants for Q1's keyboard.
#
# - using ascii internally
# - but 'key numbers' useful sometimes for some as well
# - power key is special, not a key (altho implemented in keyboard.py)
#
try:
from micropython import const
except ImportError:
# this file also used by simulator.py
const = int
NUM_ROWS = const(6)
NUM_COLS = const(10)
# ascii key codes;
KEY_NFC = '\x0e' # ctrl-N
KEY_QR = '\x11' # ctrl-Q
KEY_TAB = '\t' # tab = ctrl-I
KEY_SELECT = '\r' # = CR
KEY_CANCEL = '\x1b' # ESC = Cancel
KEY_LEFT = '\x15' # ^U = left (incompatible)
KEY_UP = '\x0b' # ^K = up on ADM-3A
KEY_RIGHT = '\x0c' # ^L = right on ADM-3A
KEY_DOWN = '\x0a' # ^J = LF = down on ADM-3A
KEY_PAGE_DOWN = '\x18' # ^x
KEY_PAGE_UP = '\x19' # ^y
KEY_END = '\x1a' # ^z
KEY_HOME = '\x1c' # ^\
# these meta keys might not be visible to higher layers:
KEY_LAMP = '\x07' # BELL = ^G
KEY_SHIFT = '\x01'
KEY_SPACE = ' '
KEY_SYMBOL = '\x02'
KEY_BS = '\x08' # ^H = backspace
# function keys, filling gaps, running out of space!
KEY_F1 = '\x0f'
KEY_F2 = '\x12'
KEY_F3 = '\x13'
KEY_F4 = '\x14'
KEY_F5 = '\x16'
KEY_F6 = '\x17'
# (row, col) => keycode
# - unused spots are \0
# - these are unshifted values
# - ten per row, gaps with zero
DECODER = (KEY_NFC + KEY_QR + KEY_TAB
+ KEY_LEFT + KEY_UP + KEY_DOWN + KEY_RIGHT + KEY_SELECT + KEY_CANCEL + '\0'
+ '1234567890'
+ 'qwertyuiop'
+ 'asdfghjkl`'
+ 'zxcvbnm,./'
+ KEY_LAMP + KEY_SHIFT + KEY_SPACE + KEY_SYMBOL + KEY_BS + '\0\0\0\0\0')
# - same when shift is down
# - make some unmarked combos dead (like shift+UP)
# - but shift+' can be ", which really should be symb+'
DECODER_SHIFT = (
'\0\0\0\0\0\0\0\0\0\0'
+ '!@#$%^&*()'
+ 'QWERTYUIOP'
+ 'ASDFGHJKL"'
+ 'ZXCVBNM<>?'
'\0\0\0\0\0\0\0\0\0\0' )
# - in caps mode: numbers unaffected, and also allow meta keys normally
DECODER_CAPS = (KEY_NFC + KEY_QR + KEY_TAB
+ KEY_LEFT + KEY_UP + KEY_DOWN + KEY_RIGHT + KEY_SELECT + KEY_CANCEL + '\0'
+ '1234567890'
+ 'QWERTYUIOP'
+ "ASDFGHJKL'"
+ 'ZXCVBNM,./'
+ KEY_LAMP + KEY_SHIFT + KEY_SPACE + KEY_SYMBOL + KEY_BS + '\0\0\0\0\0')
# - same w/ SYMBOL pressed
# - be nice and allow number+symbol == number + shift
DECODER_SYMBOL = (
'\0\0\0\0\0\0\0\0\0\0'
+ '!@#$%^&*()'
+ '-_`\0\0\0[]{}'
+ '+=\0\0:;~|\\"'
+ KEY_F1 + KEY_F2 + KEY_F3 + KEY_F4 + KEY_F5 + KEY_F6 + '\0<>?'
'\0\0\0\0\0\0\0\0\0\0' )
KEYNUM_LAMP = const(50)
KEYNUM_SHIFT = const(51)
KEYNUM_SYMBOL = const(53)
assert len(DECODER) == NUM_ROWS * NUM_COLS
assert len(DECODER_SYMBOL) == NUM_ROWS * NUM_COLS
assert len(DECODER_SHIFT) == NUM_ROWS * NUM_COLS
assert DECODER[KEYNUM_SHIFT] == KEY_SHIFT
assert DECODER[KEYNUM_SYMBOL] == KEY_SYMBOL
assert DECODER[KEYNUM_LAMP] == KEY_LAMP

View File

@ -8,41 +8,11 @@ from machine import Pin
from random import shuffle
from numpad import NumpadBase
from utils import call_later_ms
from charcodes import *
NUM_ROWS = const(6)
NUM_COLS = const(10)
SAMPLE_FREQ = const(60) # (Hz) how fast to do each scan
NUM_SAMPLES = const(3) # this many matching samples required for debounce
Q_CHECK_RATE = const(5) # (ms) how fast to check event Q
# ascii key codes;
KEY_NFC = '\x0e' # ctrl-N
KEY_QR = '\x11' # ctrl-Q
KEY_TAB = '\t' # tab = ctrl-I
KEY_SELECT = '\r' # = CR
KEY_CANCEL = '\x1b' # ESC = Cancel
KEY_LEFT = '\x15' # ^U = left (incompatible)
KEY_UP = '\x0b' # ^K = up on ADM-3A
KEY_RIGHT = '\x0c' # ^L = right on ADM-3A
KEY_DOWN = '\x0a' # ^J = LF = down on ADM-3A
# these meta keys might not be visible to higher layers:
KEY_LIGHT = '\x07' # BELL = ^G
KEY_SHIFT = '\x01'
KEY_SPACE = ' '
KEY_SYMBOL = '\x01'
KEY_BS = '\x08' # ^H = backspace
# (row, col) => keycode
# - unused spots are \0
# - these are unshifted values
DECODER = (KEY_NFC + KEY_QR + KEY_TAB
+ KEY_LEFT + KEY_UP + KEY_DOWN + KEY_RIGHT + KEY_SELECT + KEY_CANCEL + '\0'
+ '1234567890'
+ 'qwertyuiop'
+ 'asdfghjkl`'
+ 'zxcvbnm,./'
+ KEY_LIGHT + KEY_SHIFT + KEY_SPACE + KEY_SYMBOL + KEY_BS + '\0\0\0\0\0')
class FullKeyboard(NumpadBase):

View File

@ -9,7 +9,7 @@ import uasyncio
from uasyncio import sleep_ms
from graphics import Graphics
import sram2
from ckcc import lcd_blast
from st7788 import ST7788
# we support 4 fonts
from zevvpeep import FontSmall, FontLarge, FontTiny
@ -20,99 +20,8 @@ del sram2.display_buf
del sram2.display2_buf
# one byte per pixel; fixed palette maps to BGR565 in C code
display_buf = bytearray(320 * 240)
display2_buf = bytearray(320 * 240)
# few key commands for this display
CASET = const(0x2a)
RASET = const(0x2b)
RAMWR = const(0x2c)
class ST7788(framebuf.FrameBuffer):
def __init__(self):
# assume the Bootrom setup the interface and LCD correctly already
# - its fairly slow, complex and no need to change
from machine import Pin
from pyb import Timer # not from machine
self.spi = machine.SPI(1, baudrate=60_000_000, polarity=0, phase=0)
#reset_pin = Pin('PA6', Pin.OUT) # not using
self.dc = Pin('PA8', Pin.OUT, value=0)
self.cs = Pin('PA4', Pin.OUT, value=1)
if 0:
# BUST - just fades away
# backlight control - will not see display with it off!
self.bl_enable = Pin('BL_ENABLE', Pin.OUT, value=1)
t = Timer(3, freq=100_000)
# must be channel 3 because BL_ENABLE=>PE3?
self.dimmer = t.channel(3, Timer.PWM, pin=self.bl_enable)
# for framebuf.FrameBuffer
self.width = 320
self.height = 240
self.buffer = bytearray(320*240)
super().__init__(self.buffer, self.width, self.height, framebuf.GS8)
def write_cmd(self, cmd, args=None):
# send a command byte and a number of arguments
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytes([cmd]))
if args:
self.dc(1)
self.spi.write(args)
self.cs(1)
def write_data(self, buf):
# just send data bytes; lcd needs to be right mode already
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
def write_pixel_data(self, buf):
# lcd_blast expands 1-byte per pixel to BGR565
self.cs(1)
self.dc(1)
self.cs(0)
try:
lcd_blast(self.spi, buf)
except:
print('lcd_blast fail')
self.cs(1)
def _set_window(self, x, y, w=320, h=240):
#self.write_cmd(0x2a, 0, LCD_WIDTH-1) # CASET - Column address set range (x)
#self.write_cmd(0x2b, y, LCD_HEIGHT-1) # RASET - Row address set range (y)
a = struct.pack('>HH', x, x+w-1)
self.write_cmd(CASET, a)
a = struct.pack('>HH', y, y+h-1)
self.write_cmd(RASET, a)
self.write_cmd(RAMWR) # RAMWR - memory write
# .. follow with w*h*2 bytes of pixel data
def show_partial(self, y, h):
# update just a few rows of the display
assert h >= 1
self._set_window(0, y, h=h)
rows = memoryview(self.buffer)[320*y:320*(y+h)]
self.write_pixel_data(rows)
def show(self):
# send entire frame buffer
self._set_window(0, 0)
self.write_pixel_data(self.buffer)
class Display:
WIDTH = 320

View File

@ -6,6 +6,7 @@ freeze_as_mpy('', [
'keyboard.py',
'scanner.py',
'lcd_display.py',
'st7788.py',
'vdisk.py',
'nfc.py',
'ndef.py',

View File

@ -3,7 +3,7 @@
# DEBUG ONLY -- only installed for debug builds
# Hack to monitor screen contents, as text.
# Import this file to install the hacks.
import ux
import ux, version
global contents, full_contents, story
@ -16,8 +16,10 @@ full_contents = ''
# copy of the story being shown
story = None
from display import Display
if version.hw_label == 'q1':
from lcd_display import Display
else:
from display import Display
orig_text = Display.text
orig_clear = Display.clear

104
shared/st7788.py Normal file
View File

@ -0,0 +1,104 @@
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# st7788.py - LCD communications for Q1's 320x240 pixel *colour* display!
#
import machine, uzlib, ckcc, utime, struct, array, sys
from version import is_devmode
import framebuf
import uasyncio
from uasyncio import sleep_ms
from graphics import Graphics
import sram2
from ckcc import lcd_blast
# few key commands for this display
CASET = const(0x2a)
RASET = const(0x2b)
RAMWR = const(0x2c)
class ST7788(framebuf.FrameBuffer):
def __init__(self):
# assume the Bootrom setup the interface and LCD correctly already
# - its fairly slow, complex and no need to change
from machine import Pin
from pyb import Timer # not from machine
self.spi = machine.SPI(1, baudrate=60_000_000, polarity=0, phase=0)
#reset_pin = Pin('PA6', Pin.OUT) # not using
self.dc = Pin('PA8', Pin.OUT, value=0)
self.cs = Pin('PA4', Pin.OUT, value=1)
if 0:
# BUST - just fades away
# backlight control - will not see display with it off!
self.bl_enable = Pin('BL_ENABLE', Pin.OUT, value=1)
t = Timer(3, freq=100_000)
# must be channel 3 because BL_ENABLE=>PE3?
self.dimmer = t.channel(3, Timer.PWM, pin=self.bl_enable)
# for framebuf.FrameBuffer
self.width = 320
self.height = 240
self.buffer = bytearray(320*240)
super().__init__(self.buffer, self.width, self.height, framebuf.GS8)
def write_cmd(self, cmd, args=None):
# send a command byte and a number of arguments
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytes([cmd]))
if args:
self.dc(1)
self.spi.write(args)
self.cs(1)
def write_data(self, buf):
# just send data bytes; lcd needs to be right mode already
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
def write_pixel_data(self, buf):
# lcd_blast expands 1-byte per pixel to BGR565
self.cs(1)
self.dc(1)
self.cs(0)
try:
lcd_blast(self.spi, buf)
except:
print('lcd_blast fail')
self.cs(1)
def _set_window(self, x, y, w=320, h=240):
#self.write_cmd(0x2a, 0, LCD_WIDTH-1) # CASET - Column address set range (x)
#self.write_cmd(0x2b, y, LCD_HEIGHT-1) # RASET - Row address set range (y)
a = struct.pack('>HH', x, x+w-1)
self.write_cmd(CASET, a)
a = struct.pack('>HH', y, y+h-1)
self.write_cmd(RASET, a)
self.write_cmd(RAMWR) # RAMWR - memory write
# .. follow with w*h*2 bytes of pixel data
def show_partial(self, y, h):
# update just a few rows of the display
assert h >= 1
self._set_window(0, y, h=h)
rows = memoryview(self.buffer)[320*y:320*(y+h)]
self.write_pixel_data(rows)
def show(self):
# send entire frame buffer
self._set_window(0, 0)
self.write_pixel_data(self.buffer)
# EOF

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 254 KiB

View File

@ -12,14 +12,15 @@
# Limitations:
# - USB light not fully implemented, because happens at irq level on real product
#
import os, sys, tty, pty, termios, time, pdb, tempfile
import os, sys, tty, pty, termios, time, pdb, tempfile, struct
import subprocess
import sdl2.ext
from PIL import Image
from PIL import Image, ImageSequence
from select import select
import fcntl
from binascii import b2a_hex, a2b_hex
from bare import BareMetal
from sdl2.scancode import * # SDL_SCANCODE_F1.. etc
MPY_UNIX = 'l-port/micropython'
@ -47,7 +48,6 @@ class SimulatedScreen:
def movie_end(self):
fn = time.strftime('../movie-%j-%H%M%S.gif')
from PIL import Image, ImageSequence
if not self.movie: return
@ -61,8 +61,6 @@ class SimulatedScreen:
self.movie = None
def new_frame(self):
from PIL import Image
dt = int((time.time() - self.last_frame) * 1000)
self.last_frame = time.time()
@ -74,7 +72,82 @@ class SimulatedScreen:
self.movie.append((dt, img))
class LCDSimulator(SimulatedScreen):
pass
# where the simulated screen is, relative to fixed background
TOPLEFT = (65, 60)
background_img = 'q1-images/background.png'
# see stm32/COLDCARD_Q1/modckcc.c where this pallet is defined.
palette_colours = [
'#000', '#fff', # black/white, must be 0/1
'#f00', '#0f0', '#00f', # RGB demos
# some greys: 5 .. 12
'#555', '#999', '#ddd', '#111111', '#151515', '#191919', '#1d1d1d',
# tbd/unused
'#200', '#400', '#800',
# #15: Coinkite brand
'#f16422'
]
def __init__(self, factory):
self.movie = None
self.sprite = s = factory.create_software_sprite( (320,240), bpp=32)
s.x, s.y = self.TOPLEFT
s.depth = 100
self.palette = [sdl2.ext.prepare_color(code, s) for code in self.palette_colours]
assert len(self.palette) == 16
sdl2.ext.fill(s, self.palette[0])
self.mv = sdl2.ext.PixelView(self.sprite)
# for any LED's .. no position implied
self.led_red = factory.from_image("q1-images/led-red.png")
self.led_green = factory.from_image("q1-images/led-green.png")
def new_contents(self, readable):
# got bytes for new update. expect a header and packed pixels
while 1:
prefix = readable.read(8)
if not prefix: return
X,Y, w, h = struct.unpack('<4H', prefix)
assert X>=0 and Y>=0
assert X+w <= 320
assert Y+h <= 240
sz = w*h
here = readable.read(sz)
assert len(here) == sz
pos = 0
for y in range(Y, Y+h):
for x in range(X, X+w):
val = here[pos]
pos += 1
self.mv[y][x] = self.palette[val & 0xf]
if self.movie is not None:
self.new_frame()
def click_to_key(self, x, y):
# take a click on image => keypad key if valid
# - not planning to support, tedious
return None
def draw_leds(self, spriterenderer, active_set=0):
# always draw SE led, since one is always on
GEN_LED = 0x1
SD_LED = 0x2
USB_LED = 0x4
spriterenderer.render(self.led_green if (active_set & GEN_LED) else self.led_red)
if active_set & SD_LED:
spriterenderer.render(self.led_sdcard)
if active_set & USB_LED:
spriterenderer.render(self.led_usb)
class OLEDSimulator(SimulatedScreen):
# top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels
@ -107,8 +180,14 @@ class OLEDSimulator(SimulatedScreen):
self.led_sdcard = factory.from_image("mk4-images/led-sd.png")
self.led_usb = factory.from_image("mk4-images/led-usb.png")
def new_contents(self, buf):
def new_contents(self, readable):
# got bytes for new update.
# Must be bigger than a full screen update.
buf = readable.read(1024*1000)
if not buf:
return
buf = buf[-1024:] # ignore backlogs, get final state
assert len(buf) == 1024, len(buf)
@ -148,6 +227,68 @@ class OLEDSimulator(SimulatedScreen):
if active_set & USB_LED:
spriterenderer.render(self.led_usb)
def shift_up(ch):
# what ascii code for ascii key, ch, when shift also pressed?
# IMPORTANT: this has nothing to do with Q1's keyboard layout
if 'a' <= ch <= 'z':
return ch.upper()
f,t = '1234567890-=\`[];\',./', \
'!@#$%^&*()_+|~{}:"<>?'
idx = f.find(ch)
return t[idx] if idx != -1 else ch
def load_shared_mod(name, path):
# load indicated file.py as a module
# from <https://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path>
import importlib.util
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
q1_charmap = load_shared_mod('charcodes', '../shared/charcodes.py')
def scancode_remap(sc):
# return an ACSII (non standard) char to represent arrows and other similar
# special keys on Q1 only.
# - see ENV/lib/python3.10/site-packages/sdl2/scancode.py
# - select/cancel/tab/bs all handled already
# - NFC, lamp, QR buttons in alt_up()
m = {
SDL_SCANCODE_RIGHT: q1_charmap.KEY_RIGHT,
SDL_SCANCODE_LEFT: q1_charmap.KEY_LEFT,
SDL_SCANCODE_DOWN: q1_charmap.KEY_DOWN,
SDL_SCANCODE_UP: q1_charmap.KEY_UP,
SDL_SCANCODE_HOME: q1_charmap.KEY_HOME,
SDL_SCANCODE_END: q1_charmap.KEY_END,
SDL_SCANCODE_PAGEDOWN: q1_charmap.KEY_PAGE_DOWN,
SDL_SCANCODE_PAGEUP: q1_charmap.KEY_PAGE_UP,
SDL_SCANCODE_F1: q1_charmap.KEY_F1,
SDL_SCANCODE_F2: q1_charmap.KEY_F2,
SDL_SCANCODE_F3: q1_charmap.KEY_F3,
SDL_SCANCODE_F4: q1_charmap.KEY_F4,
SDL_SCANCODE_F5: q1_charmap.KEY_F5,
SDL_SCANCODE_F6: q1_charmap.KEY_F6,
}
return m[sc] if sc in m else None
def alt_up(ch):
# ALT+(ch) => special needs of Q1
print(f"Alt: {ch}")
if ch == 'n':
return q1_charmap.KEY_NFC
if ch == 'q':
return q1_charmap.KEY_QR
if ch == 'l':
return q1_charmap.KEY_LAMP
return None
def start():
print('''\nColdcard Simulator: Commands (over simulated window):
@ -162,7 +303,7 @@ def start():
is_q1 = ('--q1' in sys.argv)
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
simdis = OLEDSimulator(factory)
simdis = (OLEDSimulator if not is_q1 else LCDSimulator)(factory)
bg = factory.from_image(simdis.background_img)
window = sdl2.ext.Window("Coldcard Simulator", size=bg.size, position=(100, 100))
@ -224,7 +365,7 @@ def start():
str(display_w), str(numpad_r), str(led_w)] \
+ metal_args + sys.argv[1:]
xterm = subprocess.Popen(['xterm', '-title', 'Coldcard Simulator REPL',
'-geom', '132x40+450+40', '-e'] + cc_cmd,
'-geom', '132x40+650+40', '-e'] + cc_cmd,
env=env,
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
pass_fds=pass_fds, shell=False)
@ -248,15 +389,15 @@ def start():
pressed = set()
def send_event(ch, is_down):
before = len(pressed)
#print(f'{ch} down={is_down}')
if is_down:
pressed.add(ch)
if ch not in pressed:
numpad_tx.write(ch.encode())
pressed.add(ch)
else:
pressed.discard(ch)
if len(pressed) != before:
numpad_tx.write(b''.join(pressed) + b'\n')
if not pressed:
numpad_tx.write(b'\0') # all up signal
while running:
@ -269,17 +410,27 @@ def start():
if event.type == sdl2.SDL_KEYUP or event.type == sdl2.SDL_KEYDOWN:
try:
ch = chr(event.key.keysym.sym)
#print('0x%0x => %s mod=0x%x'%(event.key.keysym.sym, ch, event.key.keysym.mod))
#print('0x%0x => chr %s mod=0x%x'%(event.key.keysym.sym, ch, event.key.keysym.mod))
if event.key.keysym.mod & 0x3: # left or right shift
ch = shift_up(ch)
if event.key.keysym.mod & 0x300: # left or right ALT
ch = alt_up(ch)
except:
# things like 'shift' by itself
#print('0x%0x' % event.key.keysym.sym)
if 0x4000004f <= event.key.keysym.sym <= 0x40000052:
# arrow keys
ch = '9785'[event.key.keysym.sym - 0x4000004f]
else:
ch = '\0'
# things like 'shift' by itself and anything not really ascii
# control+KEY
scancode = event.key.keysym.sym & 0xffff
#print(f'keysym=0x%0x => {scancode}' % event.key.keysym.sym)
if is_q1:
ch = scancode_remap(scancode)
if not ch: continue
elif SDL_SCANCODE_RIGHT <= scancode <= SDL_SCANCODE_UP:
# arrow keys remap for Mk4
ch = '9785'[scancode - SDL_SCANCODE_RIGHT]
else:
print('Ignore: 0x%0x' % event.key.keysym.sym)
continue
# control+KEY => for our use
if event.key.keysym.mod == 0x40 and event.type == sdl2.SDL_KEYDOWN:
if ch == 'q':
# control-Q
@ -329,14 +480,13 @@ def start():
continue
# need this to kill key-repeat
ch = ch.encode('ascii')
send_event(ch, event.type == sdl2.SDL_KEYDOWN)
if event.type == sdl2.SDL_MOUSEBUTTONDOWN:
#print('xy = %d, %d' % (event.button.x, event.button.y))
ch = simdis.click_to_key(event.button.x, event.button.y)
if ch is not None:
send_event(ch.encode('ascii'), True)
send_event(ch, True)
if event.type == sdl2.SDL_MOUSEBUTTONUP:
for ch in list(pressed):
@ -348,20 +498,19 @@ def start():
if bare_metal and r == bare_metal.request:
bare_metal.readable()
continue
# Must be bigger than a full screen update.
buf = r.read(1024*1000)
if not buf:
break
if r is display_rx:
simdis.new_contents(buf)
simdis.new_contents(r)
spriterenderer.render(simdis.sprite)
window.refresh()
elif r is led_rx:
# XXX 8+8 bits
for c in buf:
c = r.read(1)
if not c:
break
c = c[0]
if 1:
#print("LED change: 0x%02x" % c[0])
mask = (c >> 4) & 0xf

View File

@ -234,5 +234,8 @@ def get_cpi_id():
#default mk4
return 0x470 # STM32L4S5
def lcd_blast(buf):
# sends to LCD
return
# EOF

View File

@ -24,6 +24,17 @@ class Pin:
from ckcc import led_pipe
led_pipe.write(bytes([(bm << 4) | (bm if n else 0)]))
def pin(self):
# ? pin number
return 99
def irq(self, *a, **k):
# hack in the keyboard system
if self.name == 'LCD_TEAR':
from touch import Touch
Touch()
pass
ALT = None
PULL_NONE = None
PULL_UP = None
@ -33,7 +44,6 @@ class Pin:
IN = None
IRQ_FALLING = 1
IRQ_RISING = 2
irq = lambda a,b,c: None
SPI = Mock
UART = Mock

View File

@ -16,6 +16,7 @@ freeze_as_mpy('', [
'sim_settings.py',
'sim_vdisk.py',
'ssd1306.py',
'st7788.py',
'stm.py',
'struct.py',
'touch.py',

View File

@ -145,7 +145,7 @@ class ExtInt:
class Timer:
def __init__(self, n):
# hack in the fake "touch" for mark 2 boards.
# hack in the fake "touch" for mark 2-4 boards.
assert n == 7
from touch import Touch
Touch()

36
unix/variant/st7788.py Normal file
View File

@ -0,0 +1,36 @@
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# st7788.py - LCD communications for Q1's 320x240 pixel *colour* display!
#
import struct, sys
import framebuf
import uasyncio
class ST7788(framebuf.FrameBuffer):
def __init__(self):
# for framebuf.FrameBuffer
self.width = 320
self.height = 240
self.buffer = bytearray(320*240)
self.pipe = open(int(sys.argv[1]), 'wb')
super().__init__(self.buffer, self.width, self.height, framebuf.GS8)
def show_partial(self, y, h):
# update just a few rows of the display
assert h >= 1
assert h < 120 # sim limitation
hdr = struct.pack('<4H', 0, y, 320, h)
rows = memoryview(self.buffer)[320*y:320*(y+h)]
self.pipe.write(hdr + rows)
def show(self):
# send entire frame buffer, but two packets
hdr = struct.pack('<4H', 0, 0, 320, 120)
self.pipe.write(hdr + self.buffer[0:320*120])
hdr = struct.pack('<4H', 0, 120, 320, 120)
self.pipe.write(hdr + self.buffer[320*120:])
# EOF

View File

@ -23,14 +23,16 @@ class Touch:
s = StreamReader(self.pipe)
# hack!
from main import numpad
from glob import numpad
while 1:
ln = await s.readline()
key = ln[:-1].decode()
#numpad.key_pressed = key if key else ''
await numpad._changes.put(key)
# rx's any key that is pressed and now released
key = await s.read(1)
print("Sim: %s" % key)
if key == b'\0':
await numpad._changes.put('') # all up
else:
await numpad._changes.put(key.decode())
def discharge(self):
pass

View File

@ -69,13 +69,13 @@ if '--mk3' in sys.argv:
has_psram = False
has_nfc = False
mk_num = int(hw_label[2:])
if '--q1' in sys.argv:
hw_label = 'q1'
has_qr = True
num_sd_slots = 2
mk_num = int(hw_label[2:])
from public_constants import MAX_TXN_LEN, MAX_UPLOAD_LEN
from public_constants import MAX_TXN_LEN_MK4, MAX_UPLOAD_LEN_MK4