Q1 display and ux

This commit is contained in:
Peter D. Gray 2023-06-07 12:34:14 -04:00 committed by scgbckbone
parent 394d1c0590
commit 3781f7a08d
28 changed files with 449 additions and 278 deletions

View File

@ -46,20 +46,20 @@ def crunch(n):
return a[0]
# LCD Display wants RGB565 values, but wrong endian from us, so green gets split weird.
# LCD Display wants RGB565 values, but big endian, so green gets split weird.
def swizzle(r,g,b):
# from 0-255 per component => two bytes
b = (b >> 3)
g = (g >> 3) # should be >> 2 for 6 bits; but looks trash?
r = (r >> 3)
return pack('<H', ((r<<11) | (g<<6) | b))
return pack('>H', ((r<<11) | (g<<6) | b))
# these values tested on real hardware
assert swizzle(255, 0, 0) == b'\x00\xf8' # red
#assert swizzle(0, 255, 0) == b'\xc0\x0f' # green (6 bits)
assert swizzle(0, 255, 0) == b'\xc0\x07' # green (5 bits)
assert swizzle(0, 0, 255) == b'\x1f\x00' # blue
assert swizzle(255, 0, 0) == b'\xf8\x00' # red
##assert swizzle(0, 255, 0) == b'\xc0\x0f' # green (6 bits)
assert swizzle(0, 255, 0) == b'\x07\xc0' # green (5 bits)
assert swizzle(0, 0, 255) == b'\x00\x1f' # blue
def into_bgr565(img):

File diff suppressed because one or more lines are too long

View File

@ -9,8 +9,8 @@ GlyphInfo = namedtuple('GlyphInfo', 'w h bits')
#FONT_SHADES = [0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 223, 239, 255]
TEXT_PALETTE = b'\x00\x00a\x08\xe3\x18e)\xe79iJ\xebZmk\xef{q\x8c\xf3\x9cu\xad\xf7\xbd\xfb\xde}\xef\xff\xff'
TEXT_PALETTE_INV = b'\xff\xff}\xef\xfb\xdey\xce\xf7\xbdu\xad\xf3\x9cq\x8c\xef{mk\xebZiJ\xe79\xe3\x18a\x08\x00\x00'
TEXT_PALETTE = b'\x00\x00\x08a\x18\xe3)e9\xe7JiZ\xebkm{\xef\x8cq\x9c\xf3\xadu\xbd\xf7\xde\xfb\xef}\xff\xff'
TEXT_PALETTE_INV = b'\xff\xff\xef}\xde\xfb\xcey\xbd\xf7\xadu\x9c\xf3\x8cq{\xefkmZ\xebJi9\xe7\x18\xe3\x08a\x00\x00'
# same, but w/o byte swapping, packing (useful for simulator)
#TEXT_PALETTE = [0x0000, 0x0861, 0x18e3, 0x2965, 0x39e7, 0x4a69, 0x5aeb, 0x6b6d, 0x7bef, 0x8c71, 0x9cf3, 0xad75, 0xbdf7, 0xdefb, 0xef7d, 0xffff]

View File

@ -83,7 +83,7 @@ def make_palette(shades, col):
assert len(shades) == NUM_GREYS
vals = [remap(col, s) for s in shades]
txt = ', '.join('0x%04x'% i for i in vals)
return txt, pack('<%dH' % NUM_GREYS, *vals)
return txt, pack('>%dH' % NUM_GREYS, *vals)
def doit(out_fname='font_iosevka.py', cls_name='FontIosevka'):
font = ImageFont.truetype(FONT, FONT_SIZE)

View File

@ -363,39 +363,6 @@ Press 6 to prove you read to the end of this message.''', title='WARNING', escap
from flow import EmptyWallet
return MenuSystem(EmptyWallet)
async def login_countdown(sec):
# Show a countdown, which may need to
# run for multiple **days**
from glob import dis
from display import FontSmall, FontLarge
from utime import ticks_ms, ticks_diff
# pre-render fixed parts
dis.clear()
y = 0
dis.text(None, y, 'Login countdown in', font=FontSmall); y += 14
dis.text(None, y, 'effect. Must wait:', font=FontSmall); y += 14
y += 5
dis.save()
st = ticks_ms()
while sec > 0:
dis.restore()
dis.text(None, y, pretty_short_delay(sec), font=FontLarge)
dis.show()
dis.busy_bar(1)
# this should be more accurate, errors were accumulating
now = ticks_ms()
dt = 1000 - ticks_diff(now, st)
await sleep_ms(dt)
st = ticks_ms()
sec -= 1
dis.busy_bar(0)
async def block_until_login():
#
# Force user to enter a valid PIN.
@ -428,17 +395,22 @@ async def show_nickname(nick):
# Show a nickname for this coldcard (as a personalization)
# - no keys here, just show it until they press anything
from glob import dis
from display import FontLarge, FontTiny, FontSmall
from ux import ux_wait_keyup
dis.clear()
if dis.width(nick, FontLarge) <= dis.WIDTH:
dis.text(None, 21, nick, font=FontLarge)
if dis.has_lcd:
from lcd_display import CHARS_H
dis.text(None, CHARS_H//3, nick)
else:
dis.text(None, 27, nick, font=FontSmall)
from display import FontLarge, FontSmall
dis.show()
if dis.width(nick, FontLarge) <= dis.WIDTH:
dis.text(None, 21, nick, font=FontLarge)
else:
dis.text(None, 27, nick, font=FontSmall)
dis.show()
await ux_wait_keyup()
@ -788,7 +760,7 @@ async def start_login_sequence():
#
# - easy to brick units here, so catch and ignore errors where possible/appropriate
#
from ux import idle_logout
from ux import idle_logout, ux_login_countdown
from glob import dis
import callgate
@ -841,7 +813,7 @@ async def start_login_sequence():
if delay:
# kill some time, with countdown, and get "the" PIN again for real login
pa.reset()
await login_countdown(delay * (60 if not version.is_devmode else 1))
await ux_login_countdown(delay * (60 if not version.is_devmode else 1))
# keep it simple for Mk4+: just challenge again for any PIN
# - if it's the same countdown pin, it will be accepted and they

View File

@ -12,7 +12,6 @@ from mk4 import dev_enable_repl
from multisig import make_multisig_menu, import_multisig_nfc
from seed import make_ephemeral_seed_menu, make_seed_vault_menu
from address_explorer import address_explore
from users import make_users_menu
from drv_entro import drv_entro_start, password_entry
from backups import clone_start, clone_write_data
from xor_seed import xor_split_start, xor_restore_start
@ -23,13 +22,15 @@ from trick_pins import TrickPinMenu
# Optional feature: HSM, depends on hardware
# - code for HSM support wont exist on other version, so dont call it
# - code for HSM support wont exist on some platforms, so dont call it
if version.supports_hsm:
from hsm import hsm_policy_available
from users import make_users_menu
hsm_feature = lambda: True
else:
hsm_policy_available = lambda: False
hsm_feature = lambda: False
make_users_menu = lambda: []
trick_pin_menu = TrickPinMenu.make_menu

View File

@ -11,19 +11,16 @@ from graphics import Graphics as obsoleteGraphics
import sram2
from st7788 import ST7788
# we support 4 fonts
from zevvpeep import FontSmall, FontLarge, FontTiny
FontFixed = object() # ugly 8x8 PET font
# 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
from font_iosevka import FontIosevka
# free unused screen buffers, we will make bigger ones
# free unused screen buffers, we don't work that way
del sram2.display_buf
del sram2.display2_buf
# one byte per pixel; fixed palette maps to BGR565 in C code
display2_buf = bytearray(320 * 240)
#display2_buf = bytearray(320 * 240)
#WIDTH = const(320)
#HEIGHT = const(240)
@ -106,7 +103,7 @@ class Display:
def draw_status(self, full=False, **kws):
if full:
y = TOP_MARGIN
self.dis.fill_rect(0, 0, WIDTH, y-2, 0x0)
self.dis.fill_rect(0, 0, WIDTH, y-1, 0x0)
self.dis.fill_rect(0, y-1, WIDTH, 1, grey_level(0.25))
kws = get_sys_status()
@ -132,10 +129,7 @@ class Display:
self.image(x, 0, '%s_%d' % (meta, kws[meta]))
def width(self, msg, font):
if font == FontFixed:
return len(msg) * 8
else:
return sum(font.lookup(ord(ch)).w for ch in msg)
return sum(font.lookup(ord(ch)).w for ch in msg)
def image(self, x, y, name):
# display a graphics image, immediately
@ -148,45 +142,6 @@ class Display:
# XXX plan is these are chars or images
return 10, 10
def XXX_text(self, x,y, msg, font=FontSmall, invert=0):
# Draw at x,y (top left corner of first letter)
# using font. Use invert=1 to get reverse video
if x is None or x < 0:
# center/rjust
w = self.width(msg, font)
if x == None:
x = max(0, (WIDTH - w) // 2)
else:
# measure from right edge (right justify)
x = max(0, WIDTH - w + 1 + x)
if y < 0:
# measure up from bottom edge
y = HEIGHT - font.height + 1 + y
if font == FontFixed:
# use font provided by Micropython: 8x8
self.dis.text(msg, x, y)
return x + (len(msg) * 8)
for ch in msg:
fn = font.lookup(ord(ch))
if fn is None:
# use last char in font as error char for junk we don't
# know how to render
fn = font.lookup(font.code_range.stop)
bits = bytearray(fn.w * fn.h)
bits[0:len(fn.bits)] = fn.bits
if invert:
bits = bytearray(i^0xff for i in bits)
gly = framebuf.FrameBuffer(bits, fn.w, fn.h, framebuf.MONO_HLSB)
self.dis.blit(gly, x, y, invert)
x += fn.w
return x
def text(self, x,y, msg, font=None, invert=0):
# Draw at x,y (in cell positions, not pixels)
# Use invert=1 to get reverse video
@ -228,8 +183,7 @@ class Display:
if x >= WIDTH: break
def clear(self):
# fill to black, but only text area
# - not status bar
# fill to black, but only text area, not status bar
self.dis.fill_rect(0, TOP_MARGIN, WIDTH, HEIGHT-TOP_MARGIN, 0x0)
def clear_rect(self, x,y, w,h):
@ -242,10 +196,11 @@ class Display:
pass
# rather than clearing and redrawing, use this buffer w/ fixed parts of screen
# - obsolete concept
def save(self):
display2_buf[:] = self.dis.buffer
pass
def restore(self):
self.dis.buffer[:] = display2_buf
pass
def hline(self, y):
self.dis.fill_rect(0,y, WIDTH, 1, 0xffff)
@ -435,7 +390,7 @@ class Display:
# centered text under that
y = CHARS_H - num_lines
for line in parts:
self.text(None, y, line, FontTiny)
self.text(None, y, line)
y += 1
if idx_hint:

View File

@ -4,7 +4,6 @@
#
import pincodes, version, random
from glob import dis
from display import FontLarge, FontTiny
from ux import PressRelease, ux_wait_keyup, ux_show_story, ux_show_pin
from callgate import show_logout
from pincodes import pa
@ -70,6 +69,7 @@ class LoginUX:
dis.text(None, -1, "CANCEL or SELECT to continue")
else:
# Old style
from display import FontLarge, FontTiny
y = 15
x = 18
dis.text(x, y, words[0], FontLarge)

View File

@ -16,7 +16,7 @@ assert not glob.dis, "main reimport"
# this makes the GC run when larger objects are free in an attempt to reduce fragmentation.
gc.threshold(4096)
if 0:
if 1: #XXX
# useful for debug: keep this stub!
import ckcc
ckcc.vcp_enabled(True)

View File

@ -3,7 +3,6 @@
# menu.py - Implement an interactive menu system.
#
import gc
from display import FontLarge, FontTiny, Display
from ux import PressRelease, the_ux
from uasyncio import sleep_ms
from charcodes import (KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_HOME,

View File

@ -253,9 +253,9 @@ individual words if you wish.''')
def late_draw(self, dis):
# add an overlay with "word N" in small text, top right.
from display import FontTiny
if dis.has_lcd: return # unreachable anyway?
if dis.has_lcd: return # unreachable?
from display import FontTiny
count = len(self.words)
if count >= self.target_words:
@ -303,6 +303,7 @@ async def show_words(words, prompt=None, escape=None, extra='', ephemeral=False)
async def add_dice_rolls(count, seed, judge_them, nwords=None, enforce=False):
from glob import dis
# XXX q1 support
from display import FontTiny, FontLarge
low_entropy_msg = "You only provided %d dice rolls, and each roll adds only 2.585 bits of entropy."

View File

@ -5,7 +5,6 @@
import ckcc
from uasyncio import sleep_ms
from glob import dis
from display import FontLarge
from ux import ux_wait_keyup, ux_clear_keys, ux_poll_key
from ux import ux_show_story
from callgate import get_is_bricked, get_genuine, clear_genuine
@ -14,6 +13,11 @@ import version
from glob import settings
from charcodes import KEY_SELECT, KEY_CANCEL
try:
from display import FontLarge
except ImportError:
FontLarge = None
async def wait_ok():
k = await ux_wait_keyup('xy' + KEY_SELECT + KEY_CANCEL)
if k not in 'y' + KEY_SELECT:
@ -51,6 +55,12 @@ async def test_keyboard():
# XXX
pass
async def test_qr_scanner():
# QR Scanner module: assume pretested, just testing connection
from glob import SCAN
assert SCAN
assert glob.SCAN.version.startswith('V2.3.')
def set_genuine():
# PIN must be blank for this to work
# - or logged in already as main
@ -309,6 +319,8 @@ async def start_selftest():
await test_keyboard()
else:
await test_numpad()
if version.has_qr:
await test_qr_scanner()
await test_secure_element()
await test_sd_active()
await test_usb_light()

View File

@ -22,7 +22,8 @@ RAMWR = const(0x2c)
# - maybe: with QR module expansion?
# - clear to pixel value
# - palette + xy/wh + nible-packed palette lookup (for font)
from ckcc import lcd_blast
# - see stm32/COLDCARD_Q1/modlcd.c for code
import lcd
class ST7788():
def __init__(self):
@ -32,9 +33,9 @@ class ST7788():
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)
#reset_pin = Pin('LCD_RESET', Pin.OUT) # not using
self.dc = Pin('LCD_DATA_CMD', Pin.OUT, value=0)
self.cs = Pin('LCD_CS', Pin.OUT, value=1)
if 0:
# BUST - just fades away
@ -47,9 +48,6 @@ class ST7788():
# 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
@ -72,18 +70,6 @@ class ST7788():
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)
@ -97,33 +83,52 @@ class ST7788():
# .. 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 fill_screen(self, pixel=0x0000):
# clear ENTIRE screen to indicated pixel value
self.fill_rect(0,0, 320, 240, pixel)
def show_zpixels(self, x, y, w, h, zpixels):
# display compressed pixel data
print('st7788.show_zpixels ... write me')
# display compressed pixel data, used for images/icons
# - keeping in mpy since C version would be same speed
data = uzlib.decompress(zpixels, -10)
self._set_window(x, y, w, h)
self.write_data(data)
def show_pal_pixels(self, x, y, w, h, palette, pixels):
# show 4-bit packed paletted lookup pixels; used for fonts, icons
# show 4-bit packed paletted lookup pixels; used for fonts
assert len(palette) == 2 * 16
if 0:
buf = bytearray()
for here in pixels:
px1 = (here >> 4) * 2
px2 = (here & 0xf) * 2
buf.extend(palette[px1:px1+2])
buf.extend(palette[px2:px2+2])
def show(self):
# send entire frame buffer
self._set_window(0, 0)
self.write_pixel_data(self.buffer)
if (w*h) % 2 == 1:
buf = memoryview(buf[0:-2])
self._set_window(x, y, w, h)
self.write_data(buf)
else:
lcd.send_packed(self.spi, x, y, w, h, palette, pixels)
def show_qr_data(self, x, y, w, expand, scan_w, packed_data):
# 8-bit packed QR data, and where to draw it, expanded by 'expand'
assert len(packed_data) == (scan_w*w) // 8
# XXX write me
def fill_rect(self, x,y, w,h, pixel=0x0000):
# need C code
pass
def fill_screen(self, pixel=0x0000):
# clear screen to indicated pixel value
# XXX need C code
pass
# set a rectangle to a single colour
if not w or not h: return
if 0:
assert h >= 1 and w >= 1
pixel = struct.pack('>H', pixel)
ln = pixel * w
self._set_window(x, y, w, h)
for y in range(h):
self.write_data(ln)
else:
lcd.fill_rect(self.spi, x, y, w, h, pixel)
# EOF

View File

@ -18,12 +18,14 @@ if version.has_qwerty:
CH_PER_W = CHARS_W
STORY_H = CHARS_H
from ux_q1 import PressRelease, ux_enter_number, ux_input_numbers, ux_input_text, ux_show_pin
from ux_q1 import ux_login_countdown
else:
# How many characters can we fit on each line? How many lines?
# (using FontSmall)
CH_PER_W = 17
STORY_H = 5
from ux_mk4 import PressRelease, ux_enter_number, ux_input_numbers, ux_input_text, ux_show_pin
from ux_mk4 import ux_login_countdown
class UserInteraction:
def __init__(self):

View File

@ -425,4 +425,37 @@ def ux_show_pin(dis, pin, subtitle, is_first_part, is_confirmation, force_draw,
dis.show()
async def ux_login_countdown(sec):
# Show a countdown, which may need to
# run for multiple **days**
from glob import dis
from display import FontSmall, FontLarge
from utime import ticks_ms, ticks_diff
# pre-render fixed parts
dis.clear()
y = 0
dis.text(None, y, 'Login countdown in', font=FontSmall); y += 14
dis.text(None, y, 'effect. Must wait:', font=FontSmall); y += 14
y += 5
dis.save()
st = ticks_ms()
while sec > 0:
dis.restore()
dis.text(None, y, pretty_short_delay(sec), font=FontLarge)
dis.show()
dis.busy_bar(1)
# this should be more accurate, errors were accumulating
now = ticks_ms()
dt = 1000 - ticks_diff(now, st)
await sleep_ms(dt)
st = ticks_ms()
sec -= 1
dis.busy_bar(0)
# EOF

View File

@ -219,5 +219,31 @@ def ux_show_pin(dis, pin, subtitle, is_first_part, is_confirmation, force_draw,
dis.text(10, y+2, msg)
dis.show()
async def ux_login_countdown(sec):
# Show a countdown, which may need to
# run for multiple **days**
# XXX untested
from glob import dis
from utime import ticks_ms, ticks_diff
y = 4
dis.clear()
dis.text(None, y-2, "Login countdown in effect.", invert=1)
dis.text(None, y-1, "Must wait:")
st = ticks_ms()
while sec > 0:
dis.text(None, y, pretty_short_delay(sec))
dis.busy_bar(1)
# this should be more accurate, errors were accumulating
now = ticks_ms()
dt = 1000 - ticks_diff(now, st)
await sleep_ms(dt)
st = ticks_ms()
sec -= 1
dis.busy_bar(0)
# EOF

View File

@ -2,12 +2,12 @@
//
// AUTO-generated.
//
// built: 2023-09-08
// version: 5.1.4
// built: 2023-06-05
// version: 6.0.1
//
#include <stdint.h>
// this overrides ports/stm32/fatfs_port.c
uint32_t get_fattime(void) {
return 0x57282820UL;
return 0x56c53000UL;
}

View File

@ -249,68 +249,6 @@ STATIC mp_obj_t watchpoint(volatile mp_obj_t arg1)
}
MP_DEFINE_CONST_FUN_OBJ_1(watchpoint_obj, watchpoint);
#define SWAB16(n) (( ((n)>>8) | ((n) << 8) )&0xffff)
#define GREY(n) SWAB16( (n<<11) | (n<<5) | n)
// BGR565 values, but wrong endian, so green split weird
static uint16_t palette[16] = {
0x0000, // 0 => black
0xffff, // 1 => white
SWAB16(0xf800), // 2 => red
SWAB16(0x07e0), // 3 => green
SWAB16(0x001f), // 4 => blue
// some greys: 5 .. 12
GREY(5), GREY(9), GREY(13), GREY(17), GREY(21), GREY(25), GREY(29),
0x2000, // tbd
0x4000, // tbd
0x8000, // tbd
// Coinkite brand colour? ie. orange rgb(241, 100, 34) => rgb(.945, .392, .133)
// XXX needs work -- too red
SWAB16((29 << 11) | (25<<5) | 4),
};
STATIC mp_obj_t lcd_blast(mp_obj_t spi_arg, mp_obj_t buf_arg)
{
// Just send a bunch of bytes, expanded via fixed palette to the LCD
// over SPI. CS/CMD_vs_DATA must already be set correctly, and cleared
// at end... we are just reformatting and sending the pixel data.
mp_buffer_info_t buf;
mp_get_buffer_raise(buf_arg, &buf, MP_BUFFER_READ);
const spi_t *spi = spi_from_mp_obj(spi_arg);
int len = buf.len;
if((len < 1) || (len > 320*240)) {
mp_raise_ValueError(NULL);
}
const uint8_t *pixels = buf.buf;
// working buffer
const int max_rows = 30;
uint16_t fb[max_rows * 320]; // largish: 19.2k
while(len) {
uint32_t here = 0;
for(; len && here < (sizeof(fb)/2); len--, here++, pixels++) {
fb[here] = palette[(*pixels) & 0xf];
}
if(!here) break;
// send what we have
spi_transfer(spi, here*2, (const uint8_t *)fb, NULL, SPI_TRANSFER_TIMEOUT(here*2));
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(lcd_blast_obj, lcd_blast);
// See psram.c
extern const mp_obj_type_t psram_type;
@ -330,7 +268,6 @@ STATIC const mp_rom_map_elem_t ckcc_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_stack_limit), MP_ROM_PTR(&stack_limit_obj) },
{ MP_ROM_QSTR(MP_QSTR_usb_active), MP_ROM_PTR(&usb_active_obj) },
{ MP_ROM_QSTR(MP_QSTR_PSRAM), MP_ROM_PTR(&psram_type) },
{ MP_ROM_QSTR(MP_QSTR_lcd_blast), MP_ROM_PTR(&lcd_blast_obj) },
};
STATIC MP_DEFINE_CONST_DICT(ckcc_module_globals, ckcc_module_globals_table);

173
stm32/COLDCARD_Q1/modlcd.c Normal file
View File

@ -0,0 +1,173 @@
//
// (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
//
// modlcd.c - module for driving the Q1 LCD fastly.
//
#include <stdio.h>
#include <string.h>
#include "py/obj.h"
#include "bufhelper.h"
#include "py/gc.h"
#include "py/runtime.h"
#include "py/mphal.h"
#include "py/mpstate.h"
#include "py/stackctrl.h"
#include "boardctrl.h"
#include "spi.h"
#include "extint.h"
#define PIN_LCD_TEAR pin_B11
#define PIN_LCD_CS pin_A4
#define PIN_LCD_SCLK pin_A5
#define PIN_LCD_RESET pin_A6
#define PIN_LCD_MOSI pin_A7
#define PIN_LCD_DATA_CMD pin_A8
// few key commands for this display
#define CASET 0x2a
#define RASET 0x2b
#define RAMWR 0x2c
#define SWAB16(n) (( ((n)>>8) | ((n) << 8) )&0xffff)
static inline void write_cmd(const spi_t *spi, uint8_t cmd)
{
// write a command byte
mp_hal_pin_write(PIN_LCD_CS, 1);
mp_hal_pin_write(PIN_LCD_DATA_CMD, 0);
mp_hal_pin_write(PIN_LCD_CS, 0);
spi_transfer(spi, 1, (const uint8_t *)&cmd, NULL, SPI_TRANSFER_TIMEOUT(1));
mp_hal_pin_write(PIN_LCD_CS, 1);
}
static inline void write_cmd2(const spi_t *spi, uint8_t cmd, uint16_t arg1, uint16_t arg2)
{
// Write a command byte, followed by 2 big-endian 16 bit arguments.
uint16_t args[2] = { SWAB16(arg1), SWAB16(arg2)};
mp_hal_pin_write(PIN_LCD_CS, 1);
mp_hal_pin_write(PIN_LCD_DATA_CMD, 0);
mp_hal_pin_write(PIN_LCD_CS, 0);
//spi_transfer(spi, 1, (const uint8_t *)&cmd, NULL, SPI_TRANSFER_TIMEOUT(1));
HAL_SPI_Transmit(spi->spi, (uint8_t *)&cmd, 1, SPI_TRANSFER_TIMEOUT(1));
mp_hal_pin_write(PIN_LCD_DATA_CMD, 1);
// faster to avoid DMA for little transfers, so do that
//spi_transfer(spi, 4, (const uint8_t *)&args, NULL, SPI_TRANSFER_TIMEOUT(4));
HAL_SPI_Transmit(spi->spi, (uint8_t *)&args, 4, SPI_TRANSFER_TIMEOUT(4));
mp_hal_pin_write(PIN_LCD_CS, 1);
}
static void write_data(const spi_t *spi, int len, const uint8_t *data)
{
// Send a bunch of data, like pixel data.
mp_hal_pin_write(PIN_LCD_CS, 1);
mp_hal_pin_write(PIN_LCD_DATA_CMD, 1);
mp_hal_pin_write(PIN_LCD_CS, 0);
spi_transfer(spi, len, data, NULL, SPI_TRANSFER_TIMEOUT(len));
mp_hal_pin_write(PIN_LCD_CS, 1);
}
static void set_window(const spi_t *spi, int x, int y, int w, int h)
{
// set active window; controls where pixel data will show up on screen
write_cmd2(spi, CASET, x, x+w-1);
write_cmd2(spi, RASET, y, y+h-1);
write_cmd(spi, RAMWR); // RAMWR - memory write, implies data to follow
}
STATIC mp_obj_t send_packed(size_t n_args, const mp_obj_t *args)
{
// take 4-bit packed palette-ized data, unpack and send
// signature: spi, x, y, w, h, pal, pixels
const spi_t *spi = spi_from_mp_obj(args[0]);
mp_int_t x = mp_obj_get_int(args[1]);
mp_int_t y = mp_obj_get_int(args[2]);
mp_int_t w = mp_obj_get_int(args[3]);
mp_int_t h = mp_obj_get_int(args[4]);
mp_buffer_info_t palette;
mp_get_buffer_raise(args[5], &palette, MP_BUFFER_READ);
mp_buffer_info_t pixels;
mp_get_buffer_raise(args[6], &pixels, MP_BUFFER_READ);
if(palette.len != 16*2) mp_raise_ValueError(NULL);
const uint8_t *pal = palette.buf;
// working buffer
uint8_t fb[(w * h * 2) + 4]; // may write one extra bogus pixel for odd w*h cases
const uint8_t *p = pixels.buf;
uint8_t *o = fb;
for(int i=0; i<pixels.len; i++, p++, o+=4) {
uint8_t px1 = (*p >> 4) * 2;
uint8_t px2 = (*p & 0xf) * 2;
o[0] = pal[px1];
o[1] = pal[px1+1];
o[2] = pal[px2];
o[3] = pal[px2+1];
}
set_window(spi, x, y, w, h);
write_data(spi, w*h*2, (const uint8_t *)fb);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(send_packed_obj, 7, 7, send_packed);
STATIC mp_obj_t fill_rect(size_t n_args, const mp_obj_t *args)
{
// write the same pixel value to a region
// signature: spi, x, y, w, h, pixel_value
const spi_t *spi = spi_from_mp_obj(args[0]);
mp_int_t x = mp_obj_get_int(args[1]);
mp_int_t y = mp_obj_get_int(args[2]);
mp_int_t w = mp_obj_get_int(args[3]);
mp_int_t h = mp_obj_get_int(args[4]);
mp_int_t pixel = mp_obj_get_int(args[5]);
uint16_t line[w];
for(int i=0; i<w; i++) {
line[i] = SWAB16(pixel);
}
set_window(spi, x, y, w, h);
for(int y=0; y<h; y++) {
write_data(spi, w*2, (const uint8_t *)line);
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fill_rect_obj, 6, 6, fill_rect);
STATIC const mp_rom_map_elem_t lcd_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_lcd) },
{ MP_ROM_QSTR(MP_QSTR_send_packed), MP_ROM_PTR(&send_packed_obj) },
{ MP_ROM_QSTR(MP_QSTR_fill_rect), MP_ROM_PTR(&fill_rect_obj) },
};
STATIC MP_DEFINE_CONST_DICT(lcd_module_globals, lcd_module_globals_table);
const mp_obj_module_t lcd_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&lcd_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_lcd, lcd_module, 1);
// EOF

View File

@ -26,7 +26,7 @@ USER_C_MODULES = boards/$(BOARD)/c-modules
# - do not want contents of stm32/boards/manifest.py
FROZEN_MANIFEST = \
boards/$(BOARD)/shared/manifest.py \
boards/$(BOARD)/shared/manifest_mk4.py
boards/$(BOARD)/shared/manifest_q1.py
# This will relocate things up by 128k=0x2_0000
# see also ./layout.ld
@ -73,18 +73,18 @@ checksum:
COPT += -g
# bugfix IIRC
build-COLDCARD_MK4/boards/COLDCARD_MK4/modckcc.o: COPT = -O0 -DNDEBUG
build-COLDCARD_Q1/boards/COLDCARD_Q1/modckcc.o: COPT = -O0 -DNDEBUG
# pickiness
build-COLDCARD_MK4/dma.o: COPT=-Werror=unused-const-variable=0
build-COLDCARD_MK4/boards/COLDCARD_MK4/psramdisk.o: COPT=-Werror=unused-const-variable=0
build-COLDCARD_Q1/dma.o: COPT=-Werror=unused-const-variable=0
build-COLDCARD_Q1/boards/COLDCARD_Q1/psramdisk.o: COPT=-Werror=unused-const-variable=0
# bugfix: remove unwanted setup code called from ports/stm32/resethandler.s
build-COLDCARD_MK4/lib/stm32lib/CMSIS/STM32L4xx/Source/Templates/system_stm32l4xx.o: \
build-COLDCARD_Q1/lib/stm32lib/CMSIS/STM32L4xx/Source/Templates/system_stm32l4xx.o: \
CFLAGS += -DSystemInit=SystemInit_OMIT
# bugfix: replace keyboard interrupt handling
build-COLDCARD_MK4/lib/utils/interrupt_char.o: \
build-COLDCARD_Q1/lib/utils/interrupt_char.o: \
CFLAGS += -Dmp_hal_set_interrupt_char=mp_hal_set_interrupt_char_OMIT

1
stm32/COLDCARD_Q1/shared Symbolic link
View File

@ -0,0 +1 @@
../../shared

View File

@ -2,10 +2,13 @@
#
# Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images.
#
# XXX obsolete, not supported but kept as reference?
#
include version.mk
BOARD = COLDCARD
MK_NUM = 3
HW_MODEL = mk3
PARENT_MKFILE = MK3-Makefile
# These values used to make .DFU files. Flash memory locations.
FIRMWARE_BASE = 0x08008000

View File

@ -9,7 +9,8 @@ include version.mk
BOARD = COLDCARD_MK4
FIRMWARE_BASE = 0x08020000
BOOTLOADER_BASE = 0x08000000
MK_NUM = 4
HW_MODEL = mk4
PARENT_MKFILE = MK4-Makefile
# This is release of the bootloader that will be built into the release firmware.
BOOTLOADER_VERSION = 3.1.4

View File

@ -1,25 +1,21 @@
# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# Placeholder for lazy devs. Builds debug version of Mk4 by default. For a few specific
# goals, will do it for both Mk3 and Mk4.
# Placeholder for lazy devs. Builds debug version of Q1 by default. For a few specific
# goals, will do it for all supported platforms.
#
# Normally you should use:
#
# make -f MK4-Makefile
# or
# make -f MK3-Makefile
# make -f Q1-Makefile
#
.DEFAULT:
$(MAKE) DEBUG_BUILD=1 -f MK4-Makefile $(MAKECMDGOALS)
$(MAKE) DEBUG_BUILD=1 -f Q1-Makefile $(MAKECMDGOALS)
clean clobber rc1:
clean clobber rc1 release repro:
$(MAKE) -f Q1-Makefile $(MAKECMDGOALS)
$(MAKE) -f MK4-Makefile $(MAKECMDGOALS)
$(MAKE) -f MK3-Makefile $(MAKECMDGOALS)
release repro:
$(MAKE) -f MK4-Makefile $(MAKECMDGOALS)
#NOTYET#$(MAKE) -f MK3-Makefile $(MAKECMDGOALS)
# EOF

55
stm32/Q1-Makefile Normal file
View File

@ -0,0 +1,55 @@
# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images.
#
# Q1 .. mostly same as Mk4
#
include version.mk
BOARD = COLDCARD_Q1
FIRMWARE_BASE = 0x08020000
BOOTLOADER_BASE = 0x08000000
HW_MODEL = q1
PARENT_MKFILE = Q1-Makefile
# This is release of the bootloader that will be built into the release firmware.
BOOTLOADER_VERSION = 1.0.0
BOOTLOADER_DIR = q1-bootloader
LATEST_RELEASE = $(shell ls -t1 ../releases/*-q1-*.dfu | head -1)
# keep near top, because defined default target (all)
include shared.mk
# This is fast for Coinkite devs, but no DFU support in the wild.
dfu-up: dev.dfu
echo 'dfu' | nc localhost 4444
$(PYTHON_DO_DFU) -u dev.dfu
dfu-up2:
$(PYTHON_DO_DFU) -u dev.dfu
# Super fast, assumes Coldcard already attached and unlock on this Mac.
up: dev.dfu
cp dev.dfu /Volumes/COLDCARD/.
diskutil eject /Volumes/COLDCARD
# Fairly fast, assumes openocd already running, and its current directory is here.
ocp-up: dev.dfu
echo "load_image dev.dfu $(FIRMWARE_BASE) bin; reset run" | nc localhost 4444
# In another window:
#
# openocd -f openocd-q1.cfg
#
# Can do:
# - "load" which writes the flash (medium speed, lots of output on st-util)
# - "cont" starts/continues system
# - "br main" sets breakpoints
# - "mon reset" to reset micro
# - and so on
#
debug:
arm-none-eabi-gdb $(BUILD_DIR)/firmware.elf -x gogo-q1.gdb
# EOF

1
stm32/openocd-q1.cfg Symbolic link
View File

@ -0,0 +1 @@
openocd-mk4.cfg

View File

@ -10,7 +10,8 @@ include version.mk
#FIRMWARE_BASE = 0x08020000
#BOOTLOADER_BASE = 0x08000000
#BOOTLOADER_DIR = mk4-bootloader vs bootloader
#MK_NUM = 4
#HW_MODEL = mk4
#PARENT_MKFILE = xx-Makefile
MPY_TOP = ../external/micropython
PORT_TOP = $(MPY_TOP)/ports/stm32
@ -26,7 +27,7 @@ PROD_KEYNUM = -k 1
BUILD_DIR = l-port/build-$(BOARD)
MAKE_ARGS = BOARD=$(BOARD) -j 4 EXCLUDE_NGU_TESTS=1 DEBUG_BUILD=$(DEBUG_BUILD)
all: $(BOARD)/file_time.c sigheader.py
all: $(BOARD)/file_time.c
cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS)
clean:
@ -48,7 +49,7 @@ firmware.elf: $(BUILD_DIR)/firmware.elf
# Sign and merge various parts
#
firmware-signed.bin: $(BUILD_DIR)/firmware0.bin $(BUILD_DIR)/firmware1.bin
$(SIGNIT) sign -b $(BUILD_DIR) -m $(MK_NUM) $(VERSION_STRING) -o $@
$(SIGNIT) sign -b $(BUILD_DIR) -m $(HW_MODEL) $(VERSION_STRING) -o $@
firmware-signed.dfu: firmware-signed.bin
$(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):$< $@
@ -60,7 +61,7 @@ dfu: firmware-signed.dfu
.PHONY: dev.dfu
dev.dfu: $(BUILD_DIR)/firmware0.bin
cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS)
$(SIGNIT) sign -b $(BUILD_DIR) -m $(MK_NUM) $(VERSION_STRING) $(PROD_KEYNUM) -o dev.bin
$(SIGNIT) sign -b $(BUILD_DIR) -m $(HW_MODEL) $(VERSION_STRING) $(PROD_KEYNUM) -o dev.bin
$(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):dev.bin dev.dfu
.PHONY: relink
@ -81,18 +82,13 @@ $(BOARD)/file_time.c: make_filetime.py version.mk
./make_filetime.py $(BOARD)/file_time.c $(VERSION_STRING)
cp $(BOARD)/file_time.c .
# Makes the .py from a shared header file
# - used by q1/mk4/earlier bootroms, and also signit
sigheader.py: make-sigheader.py sigheader.h
python3 make-sigheader.py
# Make a factory release: using key #1
# - when executed in a repro w/o the required key, it defaults to key zero
# - and that's what happens inside the Docker build
production.bin: firmware-signed.bin Makefile
$(SIGNIT) sign -m $(MK_NUM) $(VERSION_STRING) -r firmware-signed.bin $(PROD_KEYNUM) -o $@
$(SIGNIT) sign -m $(HW_MODEL) $(VERSION_STRING) -r firmware-signed.bin $(PROD_KEYNUM) -o $@
SUBMAKE = $(MAKE) -f MK$(MK_NUM)-Makefile
SUBMAKE = $(MAKE) -f $(PARENT_MKFILE)
.PHONY: release
release: code-committed
@ -107,27 +103,25 @@ release: code-committed
rc1:
$(SUBMAKE) clean # critical, or else you get a mix of debug/not
$(SUBMAKE) DEBUG_BUILD=0 all
$(SIGNIT) sign -b $(BUILD_DIR) -m $(MK_NUM) $(VERSION_STRING) $(PROD_KEYNUM) -o rc1.bin
$(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):rc1.bin \
`signit version rc1.bin`-mk$(MK_NUM)-RC1-coldcard.dfu
$(SIGNIT) sign -b $(BUILD_DIR) -m $(HW_MODEL) $(VERSION_STRING) $(PROD_KEYNUM) -o rc1.bin
$(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):rc1.bin \
-b $(BOOTLOADER_BASE):$(BOOTLOADER_DIR)/releases/$(BOOTLOADER_VERSION)/bootloader.bin \
`signit version rc1.bin`-mk$(MK_NUM)-RC1-coldcard-factory.dfu
`signit version rc1.bin`-$(HW_MODEL)-RC1-coldcard.dfu
ls -1 *-RC1-*.dfu
# This target just combines latest version of production firmware with bootrom into a DFU
# file, stored in ../releases with appropriately dated file name.
.PHONY: release-products
release-products: NEW_VERSION = $(shell $(SIGNIT) version built/production.bin)
release-products: RELEASE_FNAME = ../releases/$(NEW_VERSION)-mk$(MK_NUM)-coldcard.dfu
release-products: RELEASE_FNAME = ../releases/$(NEW_VERSION)-$(HW_MODEL)-coldcard.dfu
release-products: built/production.bin
test ! -f $(RELEASE_FNAME)
cp built/file_time.c $(BOARD)/file_time.c
$(SIGNIT) sign -m $(MK_NUM) $(VERSION_STRING) -r built/production.bin $(PROD_KEYNUM) -o built/production.bin
$(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):built/production.bin $(RELEASE_FNAME)
-git commit $(BOARD)/file_time.c -m "For $(NEW_VERSION)"
$(SIGNIT) sign -m $(HW_MODEL) $(VERSION_STRING) -r built/production.bin $(PROD_KEYNUM) -o built/production.bin
$(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):built/production.bin \
-b $(BOOTLOADER_BASE):$(BOOTLOADER_DIR)/releases/$(BOOTLOADER_VERSION)/bootloader.bin \
$(RELEASE_FNAME:%.dfu=%-factory.dfu)
$(RELEASE_FNAME)
@echo
@echo 'Made release: ' $(RELEASE_FNAME)
@echo
@ -148,7 +142,7 @@ latest:
code-committed:
@echo ""
@echo "Are all changes commited already?"
git diff --stat --ignore-submodules=dirty --exit-code
git diff --stat --exit-code .
@echo '... yes'
# Sign a message with the contents of ../releases on the developer's machine
@ -156,6 +150,7 @@ code-committed:
sign-release:
(cd ../releases; shasum -a 256 *.dfu *.md | sort -rk 2 | \
gpg --clearsign -u A3A31BAD5A2A5B10 --digest-algo SHA256 --output signatures.txt --yes - )
git commit -m "Signed for release." ../releases/signatures.txt
# Tag source code associate with built release version.
# - do "make release" before this step!
@ -163,8 +158,8 @@ sign-release:
# - update & sign signatures file
# - and tag everything
tag-source: PUBLIC_VERSION = $(shell $(SIGNIT) version built/production.bin)
tag-source: sign-release
git commit -m "New release: "$(PUBLIC_VERSION) ../releases/signatures.txt $(BOARD)/file_time.c
tag-source: sign-release code-committed
git commit --allow-empty -am "New release: "$(PUBLIC_VERSION)
echo "Tagging version: " $(PUBLIC_VERSION)
git tag -a $(PUBLIC_VERSION) -m "Release "$(PUBLIC_VERSION)
git push
@ -232,6 +227,8 @@ setup:
ln -s ../../../../../stm32/COLDCARD COLDCARD; fi
cd $(PORT_TOP)/boards; if [ ! -L COLDCARD_MK4 ]; then \
ln -s ../../../../../stm32/COLDCARD_MK4 COLDCARD_MK4; fi
cd $(PORT_TOP)/boards; if [ ! -L COLDCARD_Q1 ]; then \
ln -s ../../../../../stm32/COLDCARD_Q1 COLDCARD_Q1; fi
# Caution: docker container has read access to your source tree
@ -240,11 +237,11 @@ setup:
# - works from this repo, but starts with copy of HEAD
DOCK_RUN_ARGS = -v $(realpath ..):/work/src:ro \
-v $(realpath built):/work/built:rw \
-u $$(id -u):$$(id -g) coldcard-build
--privileged coldcard-build
repro: code-committed
repro:
docker build -t coldcard-build - < dockerfile.build
(cd ..; docker run $(DOCK_RUN_ARGS) sh src/stm32/repro-build.sh $(VERSION_STRING) $(MK_NUM))
(cd ..; docker run $(DOCK_RUN_ARGS) sh src/stm32/repro-build.sh $(VERSION_STRING) $(HW_MODEL))
# debug: shell into docker container
shell:
@ -253,7 +250,7 @@ shell:
# debug: allow docker to write into source tree
#DOCK_RUN_ARGS := -v $(realpath ..):/work/src:rw --privileged coldcard-build
PUBLISHED_BIN ?= $(wildcard ../releases/*-v$(VERSION_STRING)-mk$(MK_NUM)-coldcard.dfu)
PUBLISHED_BIN ?= $(wildcard ../releases/*-v$(VERSION_STRING)-$(HW_MODEL)-coldcard.dfu)
# final step in repro-building: check you got the right bytes
# - but you don't have the production signing key, so that section is removed

View File

@ -79,6 +79,7 @@ class SimulatedScreen:
class LCDSimulator(SimulatedScreen):
# Simulate the LCD found on the Q1: 320x240xRGB565
# - written with little-endian (16 bit) data
background_img = 'q1-images/background.png'
@ -189,7 +190,7 @@ class LCDSimulator(SimulatedScreen):
for x in range(X, X+w):
#val = (raw[pos] << 8) + raw[pos+1]
#val = raw[pos+1] + (raw[pos] << 8)
val, = struct.unpack('<H', raw[pos:pos+2])
val, = struct.unpack('>H', raw[pos:pos+2])
self.mv[x][y] = val
pos += 2