This commit is contained in:
Peter D. Gray 2023-08-02 16:14:52 -04:00 committed by scgbckbone
parent 4b6247105a
commit 9c8827cdab
4 changed files with 120 additions and 63 deletions

View File

@ -2098,9 +2098,11 @@ Secure Elements:
msg = msg.format(rel=rel, built=built, bl=bl, chk=chk,
se=se, ser=serial, hw=hw)
if version.has_qr:
from glob import SCAN
from glob import SCAN, dis
msg += '\nQR Scanner:\n %s\n' % (SCAN.version or 'missing')
msg += '\nGPU:\n %s\n' % dis.gpu.get_version()
await ux_show_story(msg)
async def ship_wo_bag(*a):

View File

@ -4,12 +4,13 @@
#
# - see notes in misc/gpu/README.md
# - bl = Bootloader, provided by ST Micro in ROM of chip
# - useful: import gpu; g=gpu.GPUAccess(); g.enter_bl()
# - errors are suppressed so we can boot w/o GPU loaded (factory)
#
import utime, struct
import uasyncio as asyncio
from utils import B2A
from machine import Pin
from ustruct import pack
# boot loader ROM response to this I2C address
BL_ADDR = const(0x64)
@ -42,7 +43,8 @@ class GPUAccess:
from machine import I2C
self.i2c = I2C(1, freq=400000) # same bus & speed as nfc.py
# let the GPU run
# let the GPU run, but we have SPI for now
self.g_ctrl(1)
self.g_reset(1)
def bl_cmd_read(self, cmd, expect_len, addr=None, arg2=None, no_final=False):
@ -192,8 +194,9 @@ class GPUAccess:
# "Go command" - starts code, but wants a reset vector really (stack+PC values)
self.bl_cmd_read(0x21, 0, addr=addr)
def cmd_resp(self, cmd_args, expect_len):
def cmd_resp(self, cmd_args, expect_len=0):
# send a command and read response back from our code running on GPU
# - will fail w/ OSError: ENODEV if i2c device (GPU) doesn't respond
self.i2c.writeto(GPU_ADDR, cmd_args)
return self.i2c.readfrom(GPU_ADDR, expect_len)
@ -217,41 +220,76 @@ class GPUAccess:
return 'BL' # ready to load via BL
return resp[0:resp.index(b'\0')].decode()
def spi(self, ours=False):
def take_spi(self):
# change the MOSI/SCLK lines to be input so we don't interfere
# with the GPU.. other lines are OD
# - signal by G_CTRL that GPU can take over
if ours:
self.g_ctrl(1)
self.mosi_pin.init(mode=Pin.ALT, pull=Pin.PULL_DOWN, af=Pin.AF5_SPI1)
self.sclk_pin.init(mode=Pin.ALT, pull=Pin.PULL_DOWN, af=Pin.AF5_SPI1)
if self.g_ctrl() == 1: return
self.g_ctrl(1)
self.mosi_pin.init(mode=Pin.ALT, pull=Pin.PULL_DOWN, af=Pin.AF5_SPI1)
self.sclk_pin.init(mode=Pin.ALT, pull=Pin.PULL_DOWN, af=Pin.AF5_SPI1)
def give_spi(self):
self.mosi_pin.init(mode=Pin.IN)
self.sclk_pin.init(mode=Pin.IN)
self.g_ctrl(0)
def have_spi(self):
# do we control the display?
return self.g_ctrl() == 1
def busy_bar(self, enable):
if enable:
# start the bar
try:
self.cmd_resp(b'a')
except: pass
self.give_spi()
else:
self.mosi_pin.init(mode=Pin.IN)
self.sclk_pin.init(mode=Pin.IN)
self.g_ctrl(0)
# stop showing it
self.take_spi()
def cursor_off(self):
# stop showing the cursor
self.take_spi()
try:
self.cmd_resp(b'a')
except: pass
def cursor_at(self, x, y, dbl_wide=False, outline=False):
# use outline to leave most of the cell unaffects (just 1px inside border)
cmd = b'c' + bytes([x, y, int(not outline), int(dbl_wide)])
try:
self.cmd_resp(cmd)
except: pass
self.give_spi()
def upgrade(self):
# do in-circuit programming of GPU chip
import gpu_binary
# get into bootloader
if self.get_version() != 'BL':
self.goto_bootloader()
assert self.get_version() == 'BL'
# wipe old program
ok = self.bulk_erase()
assert ok, 'bulk erase fail'
# write block by block, but skip first part, so we can handle powerfail w/o brick
for pos in range(256, gpu_binary.LENGTH, 256):
self.write_at(FLASH_START+pos, gpu_binary.BINARY[pos:pos+256])
ok = self.write_at(FLASH_START+pos, gpu_binary.BINARY[pos:pos+256])
assert ok
# last! the first part
# finally, the first part, which commits us to running this code on reset
self.write_at(FLASH_START, gpu_binary.BINARY[0:256])
self.run_at(FLASH_START)
utime.sleep_ms(50)
v = self.get_version()
assert v== gpu_binary.VERSION
assert v == gpu_binary.VERSION
return v

View File

@ -11,6 +11,7 @@ from graphics import Graphics as obsoleteGraphics
import sram2
from st7788 import ST7788
from utils import xfp2str
from ucollections import namedtuple
# the one font: fixed-width (except for a few double-width chars)
from font_iosevka import CELL_W, CELL_H, TEXT_PALETTE, TEXT_PALETTE_INV, COL_TEXT
@ -46,6 +47,9 @@ AT_GREY50 = 0x1
AT_RED = 0x1
AT_GREEN = 0x1
# use this to describe cursor you need.
CursorSpec = namedtuple('CursorSpec', 'x y dbl_wide outline')
def grey_level(amt):
# give percent 0..1.0
r = int(amt * 0x1f)
@ -93,6 +97,7 @@ def get_sys_status():
return rv
class Display:
# XXX move to global, but rest of system looks at these member vars
@ -112,6 +117,9 @@ class Display:
def __init__(self):
self.dis = ST7788()
from gpu import GPUAccess
self.gpu = GPUAccess()
self.last_buf = self.make_buf(0)
self.next_buf = self.make_buf(32)
@ -134,6 +142,8 @@ class Display:
self.draw_status(**kws)
def draw_status(self, full=False, **kws):
self.gpu.take_spi()
if full:
y = TOP_MARGIN
self.dis.fill_rect(0, 0, WIDTH, y-1, 0x0)
@ -178,6 +188,7 @@ class Display:
w,h, data = getattr(Graphics, name)
if x == None:
x = max(0, (WIDTH - w) // 2)
self.gpu.take_spi()
self.dis.show_zpixels(x, y, w, h, data)
self.mark_correct(x, y, w, h)
@ -224,18 +235,22 @@ class Display:
def text(self, x,y, msg, font=None, invert=0, attr=None):
# Draw at x,y (in cell positions, not pixels)
# Use invert=1 to get reverse video
# - use invert=1 to get reverse video
# - returns ending X position, if we centered it
end_x = None
if x is None or x < 0:
w = self.width(msg)
if x == None:
# center: also blanks rest of line
x = max(0, (CHARS_W - w) // 2)
end_x = x + w
msg = ((' '*x) + msg + (' ' * CHARS_W))[0:CHARS_W]
x = 0
else:
# measure from right edge (right justify)
x = max(0, CHARS_W - w + 1 + x)
end_x = x + w
if y < 0:
# measure up from bottom edge
@ -253,9 +268,12 @@ class Display:
self.next_buf[y][x] = 0
x += 1
return end_x
def real_clear(self, _internal=False):
# fill to black, but only text area, not status bar
if not _internal:
self.gpu.take_spi()
self.dis.fill_rect(0, TOP_MARGIN, WIDTH, HEIGHT-TOP_MARGIN, 0x0)
self.last_buf = self.make_buf(32)
self.next_buf = self.make_buf(32)
@ -267,8 +285,10 @@ class Display:
# clear progress bar
self.next_prog_x = 0
def show(self, just_lines=None):
def show(self, just_lines=None, cursor=None):
# Push internal screen representation to device, effeciently
self.gpu.take_spi()
lines = just_lines or range(CHARS_H)
for y in lines:
x = 0
@ -311,13 +331,20 @@ class Display:
# maybe update progress bar
if self.next_prog_x != self.last_prog_x:
# NOTE: misc/gpu/lcd.c must be updated to match any changes here
x = self.next_prog_x
if x:
self.dis.fill_rect(0, HEIGHT-3, x, 3, COL_PROGRESS)
if x != WIDTH:
self.dis.fill_rect(x, HEIGHT-3, WIDTH-x, 3, COL_BLACK)
self.last_prog_x = x
if cursor:
# implement CursorSpec values
self.gpu.cursor_at(*cursor)
self.last_buf[cursor.y][cursor.x] = 0xfffd
if cursor.dbl_wide:
self.last_buf[cursor.y][cursor.x+1] = 0xfffd
# rather than clearing and redrawing, use this buffer w/ fixed parts of screen
# - obsolete concept
@ -329,14 +356,20 @@ class Display:
raise NotImplementedError
def hline(self, y):
self.dis.fill_rect(0,y, WIDTH, 1, 0xffff)
# used only in hsm_ux.py
#self.dis.fill_rect(0,y, WIDTH, 1, 0xffff)
pass
def vline(self, x):
self.dis.fill_rect(x,TOP_MARGIN, 1, ACTIVE_H, 0xffff)
# used only in hsm_ux.py
#self.dis.fill_rect(x,TOP_MARGIN, 1, ACTIVE_H, 0xffff)
pass
def scroll_bar(self, fraction):
# along right edge
# MAYBE TODO: make this internal, part of show and make fraction a var?
self.gpu.take_spi()
self.dis.fill_rect(WIDTH-5, 0, 5, HEIGHT, 0)
#self.icon(WIDTH-3, 1, 'scroll'); // dots + arrow
mm = HEIGHT-6
pos = min(int(mm*fraction), mm)
self.dis.fill_rect(WIDTH-2, pos, 1, 16, 1)
@ -348,23 +381,6 @@ class Display:
if percent is not None:
self.progress_bar(percent)
def DELME_splash(self):
# test code
from qrs import QRDisplaySingle
import glob, time
glob.dis = self
#q = QRDisplaySingle(['mtHSVByP9EYZmB26jASDdPVm19gvpecb5R'], is_alnum=True)
#q2 = QRDisplaySingle(['R5bcepvg91mVPdDSAj62BmZYE9PyBVSHtm'], is_alnum=True)
q = QRDisplaySingle(['a'*2953], is_alnum=False)
q2 = QRDisplaySingle(['b'*2953], is_alnum=False)
q.redraw()
while 1:
#time.sleep_ms(250)
q2.redraw()
#time.sleep_ms(250)
q.redraw()
assert False
def splash(self):
# display a splash screen with some version numbers
self.real_clear()
@ -411,12 +427,18 @@ class Display:
return
def busy_bar(self, enable, speed_code=5):
# TODO: activate the GPU to render/animate this.
#print("busy_bar: %s" % enable)
# impt, this show() is relied-upon by callers
self.next_prog_x = 0
self.show()
# activate the GPU to render/animate this.
# - show() in this funct is relied-upon by callers
if enable:
self.last_prog_x = self.next_prog_x = -1
self.show()
self.gpu.busy_bar(True)
else:
# - self.show will stop animation
# - and redraw w/ no bar visible
self.last_prog_x = -1
self.next_prog_x = 0
self.show()
def set_brightness(self, val):
# normal = 0x7f, brightness=0xff, dim=0x00 (but they are all very similar)
@ -549,7 +571,8 @@ class Display:
# - 8-bit aligned rows of data
scan_w, _, data = qr_data.packed()
self.real_clear(_internal=True)
self.gpu.take_spi()
self.real_clear()
self.dis.show_qr_data(x, TOP_MARGIN + y, w, expand, scan_w, data)
self.mark_correct(x, TOP_MARGIN + y, qw, qw)
@ -564,7 +587,7 @@ class Display:
# show path index number: just 1 or 2 digits
self.text(-1, 0, idx_hint)
self.busy_bar(False)
self.show()
# here for mpy reasons

View File

@ -5,11 +5,9 @@
from uasyncio import sleep_ms
import utime, gc
from charcodes import *
from lcd_display import CHARS_W
from lcd_display import CHARS_W, CursorSpec
from exceptions import AbortInteraction
CURSOR = ''
class PressRelease:
def __init__(self, need_release=KEY_SELECT+KEY_CANCEL):
# Manage key-repeat: track last key, measure time it's held down, etc.
@ -82,8 +80,8 @@ async def ux_enter_number(prompt, max_value, can_cancel=False):
while 1:
# TODO: check width, go to two lines if needed? depends on prompt text
bx = dis.text(2, 4, prompt + ' ' + value + CURSOR)
dis.show()
bx = dis.text(2, 4, prompt + ' ' + value)
dis.show(cursor=CursorSpec(bx, 4))
ch = await press.wait()
if ch == KEY_SELECT:
@ -141,8 +139,9 @@ async def ux_input_text(value, confirm_exit=True, hex_only=False, max_len=100):
press = PressRelease()
while 1:
dis.text(1, 1, value + CURSOR)
dis.show()
dis.text(1, 1, value+' ')
bx = dis.width(value) + 1
dis.show(cursor=CursorSpec(bx, 1, 0, 0))
ch = await press.wait()
if ch == KEY_SELECT:
@ -171,10 +170,7 @@ def ux_show_pin(dis, pin, subtitle, is_first_part, is_confirmation, force_draw,
# extra (empty) box after
ln = len(pin)
FILLED = ''
EMPTY = '' #, '◌'
#msg = ''.join(FILLED if n < ln else EMPTY for n in range(6))
msg = ('' * ln) + '██'
msg = ('' * ln)
y = 1 if randomize else 2
if force_draw:
@ -218,16 +214,14 @@ def ux_show_pin(dis, pin, subtitle, is_first_part, is_confirmation, force_draw,
dis.text(None, -1, cta)
# auto-center broken w/ double-wides
x = None # 10
dis.text(None, y+2, ' '*20)
dis.text(x, y+2, msg)
dis.show()
y += 2
x = dis.text(None, y, msg)
dis.show(cursor=CursorSpec(x, y, True, False))
async def ux_login_countdown(sec):
# Show a countdown, which may need to
# run for multiple **days**
# XXX untested
# XXX untested TODO
from glob import dis
from utime import ticks_ms, ticks_diff