gpu
This commit is contained in:
parent
4b6247105a
commit
9c8827cdab
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user