From 38553d1ac51cbf1c2b1686cb4bef5c5f3445df8a Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Mon, 23 Feb 2026 12:19:06 -0500 Subject: [PATCH] Mk hardware --- cli/signit.py | 10 +- graphics/graphics_mk4.py | 8 ++ graphics/mono/mk5_nfc_1.txt | 49 +++++++ graphics/mono/mk5_nfc_2.txt | 49 +++++++ graphics/mono/mk5_nfc_3.txt | 49 +++++++ graphics/mono/mk5_nfc_4.txt | 49 +++++++ shared/actions.py | 20 ++- shared/display.py | 71 +++------- shared/flow.py | 2 + shared/mk4.py | 2 +- shared/nfc.py | 3 +- shared/selftest.py | 2 +- shared/ssd1306.py | 209 ++++++++++++++++++++-------- shared/utils.py | 4 +- shared/version.py | 12 +- stm32/COLDCARD_MK4/pins.csv | 5 + stm32/{MK4-Makefile => MK-Makefile} | 8 +- stm32/Makefile | 8 +- stm32/mk4-bootloader/gpio.c | 32 ++++- stm32/mk4-bootloader/gpio.h | 8 +- stm32/mk4-bootloader/oled.c | 63 ++++++++- stm32/shared.mk | 2 +- stm32/sigheader.h | 3 +- stm32/sigheader.py | 3 +- testing/clone_tests.py | 23 ++- testing/conftest.py | 29 ++-- testing/run_sim_tests.py | 24 +++- testing/test_bip322.py | 5 +- testing/test_bip39pw.py | 10 +- testing/test_ccc.py | 4 +- testing/test_hobble.py | 3 +- testing/test_hsm.py | 5 +- testing/test_sign.py | 35 ++--- testing/test_sssp.py | 2 +- testing/test_upgrades.py | 12 +- testing/test_vdisk.py | 2 +- unix/README.md | 1 + unix/mk5-images/background.png | Bin 0 -> 15819 bytes unix/mk5-images/led-green.png | Bin 0 -> 4314 bytes unix/mk5-images/led-red.png | Bin 0 -> 3749 bytes unix/simulator.py | 76 +++++++--- unix/variant/ssd1306.py | 16 ++- unix/variant/version.py | 10 +- 43 files changed, 690 insertions(+), 238 deletions(-) create mode 100644 graphics/mono/mk5_nfc_1.txt create mode 100644 graphics/mono/mk5_nfc_2.txt create mode 100644 graphics/mono/mk5_nfc_3.txt create mode 100644 graphics/mono/mk5_nfc_4.txt rename stm32/{MK4-Makefile => MK-Makefile} (83%) create mode 100755 unix/mk5-images/background.png create mode 100644 unix/mk5-images/led-green.png create mode 100644 unix/mk5-images/led-red.png diff --git a/cli/signit.py b/cli/signit.py index 62a2bb43..31ddff22 100755 --- a/cli/signit.py +++ b/cli/signit.py @@ -208,8 +208,9 @@ def readback(fname): if v & MK_2_OK: d.append('Mk2') if v & MK_3_OK: d.append('Mk3') if v & MK_4_OK: d.append('Mk4') + if v & MK_5_OK: d.append('Mk5') if v & MK_Q1_OK: d.append('Q1') - if v & ~(MK_1_OK | MK_2_OK | MK_3_OK | MK_4_OK | MK_Q1_OK): + if v & ~(MK_1_OK | MK_2_OK | MK_3_OK | MK_4_OK | MK_5_OK | MK_Q1_OK): d.append('?other?') v = nv + '+'.join(d) elif fld == 'timestamp': @@ -245,7 +246,7 @@ def readback(fname): @click.option('--pubkey-num', '-k', type=int, help='Which key # to use for signing', default=0) @click.option('--high_water', '-h', is_flag=True, help='Mark version as new highwater mark (no downgrades below this version)') @click.option('--verbose', '-v', default=False, is_flag=True, help='Show numbers related to signature') -@click.option('--hw-compat', '-m', type=str, metavar='Mk4', help="Set HW compat field (hw_label value)") +@click.option('--hw-compat', '-m', type=str, metavar='mk', help="Set HW compat field (hw_label value)") @click.option('--backdate', type=int, metavar='DAYS', help='Make downgrade attack test version', default=0) @click.option('--build_dir', '-b', default='l-port/build-COLDCARD') @@ -278,8 +279,9 @@ def doit(keydir, outfn=None, build_dir=None, high_water=False, vectors = open(build_dir + '/firmware0.bin', 'rb').read() body = open(build_dir + '/firmware1.bin', 'rb').read() - if hw_compat in { 'mk4', '4'}: - hw_compat = MK_4_OK + if hw_compat in { 'mk4', '4', 'mk5', '5', 'mk' }: + # Mk4 and 5 can run the same firmware, once Mk5 support was added + hw_compat = MK_4_OK | MK_5_OK elif hw_compat == 'q1': hw_compat = MK_Q1_OK elif hw_compat in { 'mk3', '3'}: diff --git a/graphics/graphics_mk4.py b/graphics/graphics_mk4.py index 4db6a7dd..7b2e7522 100644 --- a/graphics/graphics_mk4.py +++ b/graphics/graphics_mk4.py @@ -17,6 +17,14 @@ class Graphics: mk4_nfc_4 = (102, 49, 13, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\x9f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\x8f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00') + mk5_nfc_1 = (126, 49, 16, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\xfe\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\xfe\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + mk5_nfc_2 = (118, 49, 15, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + mk5_nfc_3 = (110, 49, 14, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00') + + mk5_nfc_4 = (102, 49, 13, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\x9f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\x8f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00') + scroll = (3, 61, 1, 0, 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@@\xe0@') selected = (9, 12, 2, 0, b'\x00\x00\x00\x00\x00\x80\x01\x80\x01\x00\x03\x00\x82\x00\xc6\x00d\x00<\x00\x18\x00\x00\x00') diff --git a/graphics/mono/mk5_nfc_1.txt b/graphics/mono/mk5_nfc_1.txt new file mode 100644 index 00000000..ba3527e5 --- /dev/null +++ b/graphics/mono/mk5_nfc_1.txt @@ -0,0 +1,49 @@ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx xxx xxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy + xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxx xxx xxx xxx yy n n n f c yyyy yyy + xxx xxx xxx xxx yy n n n f c yyyy yyy + xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy + xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxx xxx xxx xxx yy yyyyyyyyyyy + xxx xxx xxx xxx yy yyyyyyyyyyy + xxx xxx xxx xxx yy yyyyyyyyyyy + xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/graphics/mono/mk5_nfc_2.txt b/graphics/mono/mk5_nfc_2.txt new file mode 100644 index 00000000..71317e94 --- /dev/null +++ b/graphics/mono/mk5_nfc_2.txt @@ -0,0 +1,49 @@ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx xxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx xxx yy nn n f c c yyyyyyyyyyy + xxx xxx xxx yy nn n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxx xxx xxx yy n n n f c yyyy yyy + xxx xxx xxx yy n n n f c yyyy yyy + xxx xxx xxx yy n nn f c yyyyyyyyyyy + xxx xxx xxx yy n nn f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxx xxx xxx yy yyyyyyyyyyy + xxx xxx xxx yy yyyyyyyyyyy + xxx xxx xxx yy yyyyyyyyyyy + xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/graphics/mono/mk5_nfc_3.txt b/graphics/mono/mk5_nfc_3.txt new file mode 100644 index 00000000..86386d96 --- /dev/null +++ b/graphics/mono/mk5_nfc_3.txt @@ -0,0 +1,49 @@ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx yy nn n f c c yyyyyyyyyyy + xxx xxx yy nn n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxx xxx yy n n n f c yyyy yyy + xxx xxx yy n n n f c yyyy yyy + xxx xxx yy n nn f c yyyyyyyyyyy + xxx xxx yy n nn f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/graphics/mono/mk5_nfc_4.txt b/graphics/mono/mk5_nfc_4.txt new file mode 100644 index 00000000..7bdcab91 --- /dev/null +++ b/graphics/mono/mk5_nfc_4.txt @@ -0,0 +1,49 @@ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxx xxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy + xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx yy n n ffffff ccccc yyyyyyyyyyy + xxx xxx yy nn n f c c yyyyyyyyyyy + xxx xxx yy nn n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy + xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy + xxx xxx yy n n n f c yyyy yyy + xxx xxx yy n n n f c yyyy yyy + xxx xxx yy n nn f c yyyyyyyyyyy + xxx xxx yy n nn f c yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxx xxx yy yyyyyyyyyyy + xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + xxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/shared/actions.py b/shared/actions.py index bd59671c..996ae369 100644 --- a/shared/actions.py +++ b/shared/actions.py @@ -2397,9 +2397,23 @@ async def microsd_2fa(*a): async def keyboard_test(*a): # to aid keyboard testing/dev - from ux import ux_input_text - await ux_input_text('', max_len=128, scan_ok=True, confirm_exit=False, - prompt='Keyboard Test', placeholder='(type whatever)') + if version.has_qwerty: + await ux_input_text('', max_len=128, scan_ok=True, confirm_exit=False, + prompt='Keyboard Test', placeholder='(type whatever)') + else: + from ux_mk4 import ux_input_digits + await ux_input_digits('') + +async def quick_nfc_test(*a): + from selftest import test_nfc + await test_nfc() + +async def clear_tested_flag(*a): + # so can re-create first time power up in + # factory case (direct to selftest) + settings.remove_key('tested') + settings.save() + await reset_self() # # Q wrappers; these will be present, but are very short on mk4 diff --git a/shared/display.py b/shared/display.py index 6bfd83f9..805f1e36 100644 --- a/shared/display.py +++ b/shared/display.py @@ -2,9 +2,8 @@ # # display.py - OLED rendering # -import machine, uzlib, ckcc, utime +import machine, uzlib, ckcc, utime, version from ssd1306 import SSD1306_SPI -from version import is_devmode import framebuf from graphics_mk4 import Graphics from charcodes import OUT_CTRL_TITLE, OUT_CTRL_ADDRESS @@ -35,11 +34,14 @@ class Display: dc_pin = Pin('PA8', Pin.OUT) cs_pin = Pin('PA4', Pin.OUT) - try: - self.dis = SSD1306_SPI(128, 64, spi, dc_pin, reset_pin, cs_pin) - except OSError: - print("OLED unplugged?") - raise + if version.mk_num == 5: + # Early revs (A-D) needed this pin asserted to enable +12v to OLED + # - removed in rev E and later boards, but keep here for dev boards + # - remove this in 2027 + vcc_en = Pin('V12EN', Pin.OUT) # aka PC1 + vcc_en(1) + + self.dis = SSD1306_SPI(128, 64, spi, dc_pin, reset_pin, cs_pin, is_mk5=(version.mk_num==5)) self.last_bar_update = 0 self.clear() @@ -142,7 +144,7 @@ class Display: self.icon(128-3, 1, 'scroll') self.dis.fill_rect(128-2, pos, 1, bh, 1) - if is_devmode and not ckcc.is_simulator(): + if version.is_devmode and not ckcc.is_simulator(): self.dis.fill_rect(128-6, 20, 5, 21, 1) self.text(-2, 21, 'D', font=FontTiny, invert=1) self.text(-2, 28, 'E', font=FontTiny, invert=1) @@ -204,61 +206,20 @@ class Display: def busy_bar(self, enable): # Render a continuous activity (not progress) bar in lower 8 lines of display - # - using OLED itself to do the animation, so smooth and CPU free - # - cannot preserve bottom 8 lines, since we have to destructively write there - # - assumes normal horz addr mode: 0x20, 0x00 - # - speed_code=>framedelay: 0=5fr, 1=64fr, 2=128, 3=256, 4=3, 5=4, 6=25, 7=2frames - # unused: assert 0 <= speed_code <= 7 - - setup = bytes([ - 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127 - 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines - ]) - animate = bytes([ - 0x2e, # stop animations in progress - 0x26, # scroll leftwards (stock ticker mode) - 0, # placeholder - 7, # start 'page' (vertical) - 5, # "speed_code" # scroll speed: 7=fastest, but no order to it - 7, # end 'page' - 0, 0xff, # placeholders - 0x2f # start - ]) - - cleanup = bytes([ - 0x2e, # stop animation - 0x20, 0x00, # horz addr-ing mode - 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127 - 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines - ]) - + # if not enable: - # stop animation, and redraw old (new) screen - self.write_cmds(cleanup) + self.dis.busy_bar(False, None) self.show() else: - - # a pattern that repeats nicely mod 128 + # Need a pattern that repeats nicely mod 128 # - each byte here is a vertical column, 8 pixels tall, MSB at bottom - data = bytes(0x80 if (x%4)<2 else 0x0 for x in range(128)) + pat = bytes(0x80 if (x%4)<2 else 0x0 for x in range(128)) - if ckcc.is_simulator(): - # just show as static pattern - t = self.dis.buffer[:-128] + data - self.dis.write_data(t) - else: - self.write_cmds(setup) - self.dis.write_data(data) - self.write_cmds(animate) - - def write_cmds(self, cmds): - for c in cmds: - self.dis.write_cmd(c) + self.dis.busy_bar(True, pat) def set_brightness(self, val): # normal = 0x7f, brightness=0xff, dim=0x00 (but they are all very similar) - self.dis.write_cmd(0x81) # Set Contrast Control - self.dis.write_cmd(val) + return self.dis.contrast(val) def menu_draw(self, ry, msg, is_sel, is_checked, space_indicators): # draw a menu item, perhaps selected, checked. diff --git a/shared/flow.py b/shared/flow.py index 1aef9597..09b42655 100644 --- a/shared/flow.py +++ b/shared/flow.py @@ -307,6 +307,8 @@ DebugFunctionsMenu = [ # xxxxxxxxxxxxxxxx MenuItem("Keyboard Test", f=keyboard_test), MenuItem('BBQr Demo', f=debug_bbqr_test, predicate=version.has_qwerty), + MenuItem("NFC Test", f=quick_nfc_test), + MenuItem('Clear Tested', f=clear_tested_flag), MenuItem('Debug: assert', f=debug_assert), MenuItem('Debug: except', f=debug_except), MenuItem('Check: BL FW', f=check_firewall_read), diff --git a/shared/mk4.py b/shared/mk4.py index 69b024ce..3a3e229d 100644 --- a/shared/mk4.py +++ b/shared/mk4.py @@ -1,6 +1,6 @@ # (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC. # -# mk4.py - Mk4 specific code, not needed on earlier devices. +# mk4.py - Mk4 and Mk5 specific code, not needed on earlier devices. # # import os, sys, pyb, ckcc, version, glob diff --git a/shared/nfc.py b/shared/nfc.py index c66c6548..115a3865 100644 --- a/shared/nfc.py +++ b/shared/nfc.py @@ -419,7 +419,8 @@ class NFCHandler: dis.text(None, -3, line2) else: from graphics_mk4 import Graphics - frames = [getattr(Graphics, 'mk4_nfc_%d'%i) for i in range(1, 5)] + from version import mk_num + frames = [getattr(Graphics, 'mk%d_nfc_%d'%(mk_num, i)) for i in range(1, 5)] aborted = True phase = -1 diff --git a/shared/selftest.py b/shared/selftest.py index 4550b171..5f105ce0 100644 --- a/shared/selftest.py +++ b/shared/selftest.py @@ -171,7 +171,7 @@ async def test_secure_element(): dis.clear() - if version.has_qwerty: + if version.has_qwerty or version.mk_num == 5: dis.text(0, 0, "^^-- Green? " if gg else " ^^-- Red?") else: if gg: diff --git a/shared/ssd1306.py b/shared/ssd1306.py index 11a14058..c08a0642 100644 --- a/shared/ssd1306.py +++ b/shared/ssd1306.py @@ -1,6 +1,6 @@ # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC. # -# ssd1306.py - MicroPython SSD1306 OLED driver, I2C and SPI interfaces +# ssd1306.py - MicroPython SSD1306 OLED driver, with SPI interface # # Copied from ../external/micropython/drivers/display/ssd1306.py # @@ -28,49 +28,81 @@ SET_VCOM_DESEL = const(0xdb) SET_CHARGE_PUMP = const(0x8d) # Subclassing FrameBuffer provides support for graphics primitives -# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html +# see +# class SSD1306(framebuf.FrameBuffer): - def __init__(self, width, height, external_vcc): + def __init__(self, width, height, is_mk5): self.width = width self.height = height - self.external_vcc = external_vcc + self.is_mk5 = is_mk5 self.pages = self.height // 8 - #self.buffer = bytearray(self.pages * self.width) - self.buffer = bytearray(1024) - assert len(self.buffer) == self.pages * self.width + #assert len(self.buffer) == self.pages * self.width super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) self.init_display() def init_display(self): - for cmd in ( - SET_DISP | 0x00, # off - # address setting - SET_MEM_ADDR, 0x00, # horizontal - # resolution and layout - SET_DISP_START_LINE | 0x00, - SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 - SET_MUX_RATIO, self.height - 1, - SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 - SET_DISP_OFFSET, 0x00, - SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12, - # timing and driving scheme - SET_DISP_CLK_DIV, 0xF0, - SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1, - SET_VCOM_DESEL, 0x30, # 0.83*Vcc - # display - SET_CONTRAST, 0xff, # maximum - SET_ENTIRE_ON, # output follows RAM contents - SET_NORM_INV, # not inverted - # charge pump - SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, - SET_DISP | 0x01): # on - self.write_cmd(cmd) + if not self.is_mk5: + # Mk4 and earlier + cmds = ( + SET_DISP | 0x00, # display off + # address setting + SET_MEM_ADDR, 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE | 0x00, + SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 + SET_MUX_RATIO, self.height - 1, + SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 + SET_DISP_OFFSET, 0x00, + SET_COM_PIN_CFG, 0x12, + # timing and driving scheme + SET_DISP_CLK_DIV, 0xF0, + SET_PRECHARGE, 0xf1, + SET_VCOM_DESEL, 0x30, # 0.83*Vcc + # display + SET_CONTRAST, 0xff, # maximum + SET_ENTIRE_ON, # output follows RAM contents + SET_NORM_INV, # not inverted + # charge pump + SET_CHARGE_PUMP, 0x14) + else: + # Mk5 has external +12v power supply, and different setup protocol + + cmds = ( + SET_DISP | 0x00, # display off + # address setting + SET_MEM_ADDR, 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE | 0x00, + SET_SEG_REMAP | 0x00, # column addr 0 mapped to SEG127 + SET_MUX_RATIO, self.height - 1, + SET_COM_OUT_DIR | 0x00, # scan from COM[8] to COM[N] + SET_DISP_OFFSET, 0x00, + SET_COM_PIN_CFG, 0x12, + # timing and driving scheme + SET_DISP_CLK_DIV, 0xF0, + SET_PRECHARGE, 0x22, + SET_VCOM_DESEL, 0x40, # per spec sheet + # display + SET_CONTRAST, 0x85, # NOT maximum, because spec sheet + SET_ENTIRE_ON, # output follows RAM contents + SET_NORM_INV, # not inverted + SET_CHARGE_PUMP, 0x10, # charge pump: DISABLE + ) + + self.write_cmds(cmds) + self.fill(0) self.show() + self.write_cmd(SET_DISP | 0x01) + + def write_cmds(self, cmds): + for c in cmds: + self.write_cmd(c) + def poweroff(self): self.write_cmd(SET_DISP | 0x00) @@ -78,6 +110,10 @@ class SSD1306(framebuf.FrameBuffer): self.write_cmd(SET_DISP | 0x01) def contrast(self, contrast): + # brightness: normal = 0x7f, brightness=0xff, dim=0x00 (but they are all very similar) + if self.is_mk5: + # - limit to a specific max value from OLED specs used on Mk5 + contrast = max(contrast, 0x85) self.write_cmd(SET_CONTRAST) self.write_cmd(contrast) @@ -85,56 +121,113 @@ class SSD1306(framebuf.FrameBuffer): self.write_cmd(SET_NORM_INV | (invert & 1)) def show(self): - x0 = 0 - x1 = self.width - 1 - if self.width == 64: - # displays with width of 64 pixels are shifted by 32 - x0 += 32 - x1 += 32 self.write_cmd(SET_COL_ADDR) - self.write_cmd(x0) - self.write_cmd(x1) + self.write_cmd(0) + self.write_cmd(self.width - 1) + self.write_cmd(SET_PAGE_ADDR) self.write_cmd(0) self.write_cmd(self.pages - 1) + self.write_data(self.buffer) -SPI_RATE = const(40000000) # max chip can do, still slower than display limit tho + def busy_bar(self, enable, pattern): + # Render a continuous activity (not progress) bar in lower 8 lines of display + # - using OLED itself to do the animation, so smooth and CPU free + # - cannot preserve bottom 8 lines, since we have to destructively write there + # - assumes normal horz addr mode: 0x20, 0x00 + # - speed_code=>framedelay: 0=5fr, 1=64fr, 2=128, 3=256, 4=3, 5=4, 6=25, 7=2frames + # unused: assert 0 <= speed_code <= 7 + + setup = bytes([ + 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127 + 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines + ]) + if not self.is_mk5: + animate = bytes([ + 0x2e, # stop animations in progress + 0x26, # scroll leftwards (stock ticker mode) + 0, # placeholder + 7, # start 'page' (vertical) + 5, # "speed_code" # scroll speed: 7=fastest, but no order to it + 7, # end 'page' + 0, 0x7f, # start/end columns + 0x2f # start + ]) + else: + # SSD1309? doesn't implement 0x26 but has other commands + animate = bytes([ + 0x2e, # stop animations in progress + 0x29, # Vert+Right horz animation setup + 1, # A: enable horz scroll + 7, # B: start 'page' (vertical) + 5, # C: "speed_code" # scroll speed: 7=fastest, but no order to it + 7, # D: end 'page' + 1, # E: vert scrolling offset (unused) + 0, 0x7f, # F,G: start/end columns + 0xa3, # Set Vertical scroll Area + 0, 0, # A, B: # of rows in fixed vs. scroll area + 0x2f # start animating + ]) + + cleanup = bytes([ + 0x2e, # stop animation + 0x20, 0x00, # horz addr-ing mode + 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127 + 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines + ]) + + if not enable: + # stop animation, and redraw old (new) screen + self.write_cmds(cleanup) + else: + # needs a pattern that repeats nicely mod 128 + self.write_cmds(setup) + self.write_data(pattern) + self.write_cmds(animate) class SSD1306_SPI(SSD1306): - def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): - dc.init(dc.OUT, value=0) - res.init(res.OUT, value=0) - cs.init(cs.OUT, value=1) + def __init__(self, width, height, spi, dc, res, cs, is_mk5=False): self.spi = spi self.dc = dc - self.res = res self.cs = cs - self.res(1) + self.res = res + + # initial states + dc(0) + cs(1) + + # reset sequence + res(1) time.sleep_ms(1) - self.res(0) + res(0) time.sleep_ms(10) - self.res(1) - super().__init__(width, height, external_vcc) + res(1) + + super().__init__(width, height, is_mk5) + + def _setup_spi(self): + # need to re-do this constantly + # max chip can do, still slower than display limit tho + # - 40Mhz (target) is fine for short-cabled Mk4 (actual is lower?) + # - max spec is 10Mhz on Mk5 + rate = 40_000_000 if not self.is_mk5 else 10_000_000 + self.spi.init(baudrate=rate, polarity=0, phase=0) def write_cmd(self, cmd): - self.spi.init(baudrate=SPI_RATE, polarity=0, phase=0) + self._setup_spi() self.cs(1) self.dc(0) self.cs(0) - try: - self.spi.write(bytearray([cmd])) - except: - print("SPI[cmd]: %r" % self.spi) + self.spi.write(bytearray([cmd])) self.cs(1) def write_data(self, buf): - self.spi.init(baudrate=SPI_RATE, polarity=0, phase=0) + self._setup_spi() self.cs(1) self.dc(1) self.cs(0) - try: - self.spi.write(buf) - except: - print("SPI[data]: %r" % self.spi) + self.spi.write(buf) self.cs(1) + +# EOF diff --git a/shared/utils.py b/shared/utils.py index 93d60294..3bfa4f20 100644 --- a/shared/utils.py +++ b/shared/utils.py @@ -383,7 +383,7 @@ def check_firmware_hdr(hdr, binary_size): # - hdr must be a bytearray(FW_HEADER_SIZE+more) from sigheader import FW_HEADER_SIZE, FW_HEADER_MAGIC, FWH_PY_FORMAT - from sigheader import MK_1_OK, MK_2_OK, MK_3_OK, MK_4_OK, MK_Q1_OK + from sigheader import MK_1_OK, MK_2_OK, MK_3_OK, MK_4_OK, MK_5_OK, MK_Q1_OK from ustruct import unpack_from from version import hw_label import callgate @@ -412,6 +412,8 @@ def check_firmware_hdr(hdr, binary_size): ok = (hw_compat & MK_3_OK) elif hw_label == 'mk4': ok = (hw_compat & MK_4_OK) + elif hw_label == 'mk5': + ok = (hw_compat & MK_5_OK) elif hw_label == 'q1': ok = (hw_compat & MK_Q1_OK) diff --git a/shared/version.py b/shared/version.py index 3fab78a9..ee90a290 100644 --- a/shared/version.py +++ b/shared/version.py @@ -80,6 +80,7 @@ def probe_system(): from sigheader import RAM_BOOT_FLAGS, RBF_FACTORY_MODE import ckcc, callgate, machine + from machine import Pin hw_label = 'mk4' has_608 = True @@ -97,7 +98,7 @@ def probe_system(): # detect Q1 based on pins.csv try: - machine.Pin('LCD_TEAR') # only defined on Q1 build, will error otherwise + Pin('LCD_TEAR') # only defined on Q1 build, will error otherwise has_qr = True num_sd_slots = 2 hw_label = 'q1' @@ -108,6 +109,15 @@ def probe_system(): except ValueError: pass + try: + # only defined on Mk4/5 build, will error otherwise; was open on Mk1-4, low on Mk5 + s0 = Pin('STRAP_MK5', mode=Pin.IN, pull=Pin.PULL_UP) + if s0() == 0: + hw_label = 'mk5' + mk_num = 5 + except ValueError: + pass + # Boot loader needs to tell us stuff about how we were booted, sometimes: # - did we just install a new version, for example (obsolete in mk4) # - are we running in "factory mode" with flash un-secured? diff --git a/stm32/COLDCARD_MK4/pins.csv b/stm32/COLDCARD_MK4/pins.csv index 5a285429..70a861f6 100644 --- a/stm32/COLDCARD_MK4/pins.csv +++ b/stm32/COLDCARD_MK4/pins.csv @@ -89,3 +89,8 @@ SE2_SDA,PB14 NFC_SCL,PB6 NFC_SDA,PB7 NFC_ED,PC4 +STRAP_MK5,PE0 +STRAP_S1,PE1 +STRAP_S2,PE2 +STRAP_S3,PE3 +V12EN,PC1 diff --git a/stm32/MK4-Makefile b/stm32/MK-Makefile similarity index 83% rename from stm32/MK4-Makefile rename to stm32/MK-Makefile index 43ccdcc5..1ae8915b 100644 --- a/stm32/MK4-Makefile +++ b/stm32/MK-Makefile @@ -2,20 +2,20 @@ # # Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images. # -# MARK 4 with different chip and layout +# COLDCARD Mk4 and Mk5 -- OLED display, calculator style. # BOARD = COLDCARD_MK4 FIRMWARE_BASE = 0x08020000 BOOTLOADER_BASE = 0x08000000 -HW_MODEL = mk4 -PARENT_MKFILE = MK4-Makefile +HW_MODEL = mk +PARENT_MKFILE = MK-Makefile # This is release of the bootloader that will be built into the factory.dfu BOOTLOADER_VERSION = 3.2.1 BOOTLOADER_DIR = mk4-bootloader -LATEST_RELEASE = $(shell ls -t1 ../releases/*-mk4-*.dfu | head -1) +LATEST_RELEASE = $(shell ls -t1 ../releases/*-mk-*.dfu ../releases/*-mk4-*.dfu | head -1) # Our version for this release. # - caution, the bootrom will not accept version < 3.0.0 diff --git a/stm32/Makefile b/stm32/Makefile index 6e5cb457..818524f9 100644 --- a/stm32/Makefile +++ b/stm32/Makefile @@ -5,23 +5,23 @@ # # Normally you must use: # -# make -f MK4-Makefile +# make -f MK-Makefile # or # make -f Q1-Makefile # .DEFAULT_GOAL := all .DEFAULT all: - $(MAKE) DEBUG_BUILD=1 -f Q1-Makefile $(MAKECMDGOALS) + $(MAKE) DEBUG_BUILD=1 -f MK-Makefile $(MAKECMDGOALS) clean clobber repro: @echo You should do either: @echo @echo " make" -f Q1-Makefile $(MAKECMDGOALS) @echo "-OR-" - @echo " make" -f MK4-Makefile $(MAKECMDGOALS) + @echo " make" -f MK-Makefile $(MAKECMDGOALS) rc1 rc2 release: - make -f Q1-Makefile $(MAKECMDGOALS) && make -f MK4-Makefile $(MAKECMDGOALS) + make -f Q1-Makefile $(MAKECMDGOALS) && make -f MK-Makefile $(MAKECMDGOALS) # EOF diff --git a/stm32/mk4-bootloader/gpio.c b/stm32/mk4-bootloader/gpio.c index 71146421..c858f22a 100644 --- a/stm32/mk4-bootloader/gpio.c +++ b/stm32/mk4-bootloader/gpio.c @@ -8,7 +8,7 @@ #include "gpio.h" #include "stm32l4xx_hal.h" -// PA0 - onewire bus for 608a +// PA0 - onewire bus for 608 #define ONEWIRE_PIN GPIO_PIN_0 #define ONEWIRE_PORT GPIOA @@ -62,15 +62,20 @@ gpio_setup(void) // SD active LED: PC7 // USB active LED: PC6 + // Mk5 (disconnected & unused on Mk4): + // PC0: early experiments, unused + // PC1: +12v en for OLED + // - but by rev E, neither being used, but keep support for older revs { GPIO_InitTypeDef setup = { - .Pin = GPIO_PIN_7 | GPIO_PIN_6, + .Pin = GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_0 | GPIO_PIN_1, .Mode = GPIO_MODE_OUTPUT_PP, .Pull = GPIO_NOPULL, .Speed = GPIO_SPEED_FREQ_LOW, }; + HAL_GPIO_Init(GPIOC, &setup); - HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7|GPIO_PIN_6, 0); // turn LEDs off + HAL_GPIO_WritePin(GPIOC, setup.Pin, 0); // turn all off } // SD card detect switch: PC13 @@ -83,6 +88,18 @@ gpio_setup(void) HAL_GPIO_Init(GPIOC, &setup); } + // Strapping pins (Mk5, but all disconnected on Mk4): PE0-3 + // - important to have the internal pull-ups enabled on these + // - PE0: low if Mk5 + { GPIO_InitTypeDef setup = { + .Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, + .Mode = GPIO_MODE_INPUT, + .Pull = GPIO_PULLUP, + .Speed = GPIO_SPEED_FREQ_LOW, + }; + HAL_GPIO_Init(GPIOE, &setup); + } + #if 0 // TEST CODE -- keep @@ -125,5 +142,14 @@ gpio_setup(void) #endif } +// is_mk5() +// + bool +is_mk5(void) +{ + // sample the PE0 strapping pin to know if mk4 or 5 + return !HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_0); +} + // EOF diff --git a/stm32/mk4-bootloader/gpio.h b/stm32/mk4-bootloader/gpio.h index 6d78cf23..dd93072f 100644 --- a/stm32/mk4-bootloader/gpio.h +++ b/stm32/mk4-bootloader/gpio.h @@ -6,10 +6,12 @@ // set directions, lock critical ones, etc. void gpio_setup(void); -// sample the DFU button -inline bool dfu_button_pressed(void) { return false; } - #ifdef FOR_Q1_ONLY // kill system power; instant extern void turn_power_off(void); #endif + +// sample the strapping pin to know if mk4 or 5 +extern bool is_mk5(void); + +// EOF diff --git a/stm32/mk4-bootloader/oled.c b/stm32/mk4-bootloader/oled.c index 18823c07..6eded6b2 100644 --- a/stm32/mk4-bootloader/oled.c +++ b/stm32/mk4-bootloader/oled.c @@ -5,6 +5,7 @@ #include "delay.h" #include "rng.h" #include "console.h" +#include "gpio.h" // for is_mk5() #include "stm32l4xx_hal.h" #include @@ -14,7 +15,7 @@ // Reset and config sequence. // // As measured! No attempt to understand them here. -static const uint8_t reset_commands[] = { +static const uint8_t reset_commands_mk4[] = { 0xae, // display off 0x20, 0x00, // horz addr-ing mode 0x40, // ram display start line: 0 @@ -33,6 +34,28 @@ static const uint8_t reset_commands[] = { 0xaf // display on }; +// .. similar but a little different on Mk5 +static const uint8_t reset_commands_mk5[] = { + 0xae, // display off + 0xd5, 0x80, // set display clock divide ratio + 0xa8, 0x3f, // set multiplex ratio: 64 + 0xd3, 0x00, // display offset (vertical shift): 0 + 0x40, // ram display start line: 0 + 0xad, 0x8a, // set charge pump (NEW) + 0xa0, // column addr 127 mapped to seg0 (NEW was 0xa1) + 0xc0, // remapped mode: scan from COMn to COM0 (NEW was 0xc8) + 0xda, 0x12, // seq com pin conf: alt com pin + 0x81, 0x65, // Contrast: was max, now 0x65 (NEW) + 0xd9, 0x22, // set pre-change period (NEW, was 0xf1) + 0xdb, 0x40, // Cvomh deselect level (NEW, was 0x30) + 0xa6, // normal not inverted + + 0x20, 0x00, // horz addr-ing mode + 0xa4, // display ram contents (not all on) + //0x8d, 0x14, // enable charge pump (not in use, omitted from their sequence) + 0xaf // display on (done after VCC enabled) +}; + // Bytes to send before sending the 1024 bytes of pixel data. // static const uint8_t before_show[] = { @@ -50,6 +73,9 @@ static const uint8_t before_show[] = { #define SPI_SCK GPIO_PIN_5 #define SPI_MOSI GPIO_PIN_7 +// port C, Mk5 only +#define VCC_EN_PIN GPIO_PIN_1 + #ifndef DISABLE_OLED static SPI_HandleTypeDef spi_port; @@ -177,7 +203,7 @@ oled_setup(void) HAL_GPIO_Init(GPIOA, &setup); // lock the RESET pin so that St's DFU code doesn't clear screen - // it might be trying to use it a MISO signal for SPI loading + // it might be trying to use it as a MISO signal for SPI loading HAL_GPIO_LockPin(GPIOA, RESET_PIN | CS_PIN | DC_PIN); // 10ms low-going pulse on reset pin @@ -196,7 +222,14 @@ oled_setup(void) //SPI1->CR1 = 0x354; // write a sequence to reset things - oled_write_cmd_sequence(sizeof(reset_commands), reset_commands); + if(is_mk5()) { + // note: +12v is always on now, this line supports older revs + HAL_GPIO_WritePin(GPIOC, VCC_EN_PIN, 1); + + oled_write_cmd_sequence(sizeof(reset_commands_mk5), reset_commands_mk5); + } else { + oled_write_cmd_sequence(sizeof(reset_commands_mk4), reset_commands_mk4); + } rng_delay(); } @@ -429,13 +462,12 @@ oled_factory_busy(void) // Render a continuous activity (not progress) bar in lower 8 lines of display // - using OLED itself to do the animation, so smooth and CPU free // - cannot preserve bottom 8 lines, since we have to destructively write there - //oled_spi_setup(); static const uint8_t setup[] = { 0x21, 0x00, 0x7f, // setup column address range (start, end): 0-127 0x22, 7, 7, // setup page start/end address: page 7=last 8 lines }; - static const uint8_t animate[] = { + static const uint8_t animate_mk4[] = { 0x2e, // stop animations in progress 0x26, // scroll leftwards (stock ticker mode) 0, // placeholder @@ -445,6 +477,21 @@ oled_factory_busy(void) 0, 0xff, // placeholders 0x2f // start }; + // slightly different on Mk5 display + static const uint8_t animate_mk5[] = { + 0x2e, // stop animations in progress + 0x29, // Vert+Right horz animation setup + 1, // A: enable horz scroll + 7, // B: start 'page' (vertical) + 5, // C: "speed_code" # scroll speed: 7=fastest, but no order to it + 7, // D: end 'page' + 1, // E: vert scrolling offset (unused) + 0, 0x7f, // F,G: start/end columns + 0xa3, // Set Vertical scroll Area + 0, 0, // A, B: # of rows in fixed vs. scroll area + 0x2f // start animating + }; + uint8_t data[128]; for(int x=0; x<128; x++) { @@ -454,7 +501,11 @@ oled_factory_busy(void) oled_write_cmd_sequence(sizeof(setup), setup); oled_write_data(sizeof(data), data); - oled_write_cmd_sequence(sizeof(animate), animate); + if(is_mk5()) { + oled_write_cmd_sequence(sizeof(animate_mk5), animate_mk5); + } else { + oled_write_cmd_sequence(sizeof(animate_mk4), animate_mk4); + } } // EOF diff --git a/stm32/shared.mk b/stm32/shared.mk index a568e056..6cf47be6 100644 --- a/stm32/shared.mk +++ b/stm32/shared.mk @@ -1,6 +1,6 @@ # (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC. # -# Shared values and target rules for Mk4 and Q. +# Shared values and target rules for Mk and Q platforms. # # Define these vars to suit board diff --git a/stm32/sigheader.h b/stm32/sigheader.h index e0868ea3..dec3c99d 100644 --- a/stm32/sigheader.h +++ b/stm32/sigheader.h @@ -70,8 +70,7 @@ typedef struct { #define MK_3_OK 0x04 #define MK_4_OK 0x08 #define MK_Q1_OK 0x10 -// RFU: -#define MK_6_OK 0x20 +#define MK_5_OK 0x20 // (Mk1-3) There is a copy of the header at this location in RAM, copied by bootloader // **after** it has been verified. If you write to this memory area, you will be reset! diff --git a/stm32/sigheader.py b/stm32/sigheader.py index 609bf6fe..ef11649b 100644 --- a/stm32/sigheader.py +++ b/stm32/sigheader.py @@ -54,8 +54,7 @@ MK_2_OK = 0x02 MK_3_OK = 0x04 MK_4_OK = 0x08 MK_Q1_OK = 0x10 -# RFU: -MK_6_OK = 0x20 +MK_5_OK = 0x20 # (Mk1-3) There is a copy of the header at this location in RAM, copied by bootloader # **after** it has been verified. If you write to this memory area, you will be reset! diff --git a/testing/clone_tests.py b/testing/clone_tests.py index 31ab03a1..d4b1e4e5 100644 --- a/testing/clone_tests.py +++ b/testing/clone_tests.py @@ -9,10 +9,23 @@ from ckcc_protocol.client import ColdcardDevice def _clone(source, target): - assert source in ["Q", "Mk4"] - assert target in ["Q", "Mk4"] - source_sim_arg, source_is_Q = ("--q1", True) if source == "Q" else ("", False) - target_sim_arg, target_is_Q = ("--q1", True) if target == "Q" else ("", False) + allowed_devices = ["Q", "Mk4", "Mk5"] + assert source in allowed_devices + assert target in allowed_devices + + source_is_Q = False + source_sim_arg = "" + if source == "Q": + source_sim_arg, source_is_Q = "--q1", True + elif source == "Mk4": + source_sim_arg, source_is_Q = "--mk4", False + + target_is_Q = False + target_sim_arg = "" + if target == "Q": + target_sim_arg, target_is_Q = "--q1", True + elif target == "Mk4": + target_sim_arg, target_is_Q = "--mk4", False # first the TARGET clean_sim_data() # remove all from previous @@ -105,7 +118,7 @@ def _clone(source, target): sim_target.stop() -@pytest.mark.parametrize("source,target", list(itertools.product(["Q", "Mk4"], repeat=2))) +@pytest.mark.parametrize("source,target", list(itertools.product(["Q", "Mk4", "Mk5"], repeat=2))) def test_clone(source, target): _clone(source, target) time.sleep(1) diff --git a/testing/conftest.py b/testing/conftest.py index b7f6ba50..bf246de2 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1518,19 +1518,22 @@ def is_mark3(dev_hw_label): def is_mark4(dev_hw_label): return (dev_hw_label == 'mk4') +@pytest.fixture(scope='session') +def is_mark5(dev_hw_label): + return (dev_hw_label == 'mk5') + @pytest.fixture(scope='session') def is_q1(dev_hw_label): return (dev_hw_label == 'q1') - @pytest.fixture(scope="session") def is_headless(request): return request.config.getoption('--headless') @pytest.fixture(scope='session') -def is_mark4plus(is_mark4, is_q1): - # mark4 PLUS ... so Q1 and Mk4 - return is_mark4 or is_q1 +def is_mark4plus(is_mark4, is_q1, is_mark5): + # mark4 PLUS ... so Q1, Mk4 and Mk5 + return is_mark4 or is_q1 or is_mark5 @pytest.fixture(scope='session') def mk_num(dev_hw_label): @@ -1544,35 +1547,29 @@ def mk_num(dev_hw_label): else: raise ValueError(v) -@pytest.fixture(scope='session') -def only_mk4(is_mark4): - # NOTE: avoid this, and try to be more specific! ie. NFC vs. QR etc - if not is_mark4: - raise pytest.skip("Mk4 only") - @pytest.fixture(scope='session') def only_q1(is_q1): if not is_q1: raise pytest.skip("Q only") @pytest.fixture(scope='session') -def needs_nfc(is_mark4, is_q1): - if is_mark4 or is_q1: +def needs_nfc(is_mark4plus): + if is_mark4plus: return raise pytest.skip("Needs NFC support") @pytest.fixture(scope='session') -def needs_virtdisk(is_mark4, is_q1): +def needs_virtdisk(is_mark4plus): # TODO/MAYBE: test if feature enabled in settings? - if is_mark4 or is_q1: + if is_mark4plus: return raise pytest.skip("Needs VirtDisk support") @pytest.fixture(scope='session') def only_mk4plus(mk_num): - # Mk4 and Q1 + # Mk4, Q1 and Mk5 if mk_num < 4: - raise pytest.skip("Mk4/Q1 only") + raise pytest.skip("Mk4/Mk5/Q1 only") @pytest.fixture(scope='session') def only_mk3(mk_num): diff --git a/testing/run_sim_tests.py b/testing/run_sim_tests.py index 29eb89e9..4782cfe0 100644 --- a/testing/run_sim_tests.py +++ b/testing/run_sim_tests.py @@ -298,7 +298,8 @@ def main(): help="Choose how much to sleep after simulator is started") parser.add_argument("-m", "--module", action="append", help="Choose only n modules to run") parser.add_argument("--pdb", action="store_true", help="Go to debugger on failure") - parser.add_argument("--q1", action="store_true", help="Simulate a Q instead of Mk COLDCARD") + parser.add_argument("--q1", action="store_true", help="Simulate a Q instead of Mk5 COLDCARD") + parser.add_argument("--mk4", action="store_true", help="Simulate a Mk4 instead of Mk5 COLDCARD") parser.add_argument("--psbt2", action="store_true", help="`fake_txn` produces PSBTv2") parser.add_argument("--ff", action="store_true", help="Run the last failures first") parser.add_argument("--onetime", action="store_true", default=False, @@ -381,8 +382,11 @@ def main(): # proper `settings.load` _ virtual disk sim_args = ["--set", "nfc=1", "--set", "vidsk=1"] + # by default Mk5 is run if args.q1 and '--q1' not in sim_args: sim_args.append('--q1') + elif args.mk4 and '--mk4' not in sim_args: + sim_args.append("--mk4") module_args.append((test_module, sim_args, args.pytest_k, args.pdb, args.ff, args.psbt2, args.q1, args.headless)) @@ -416,8 +420,10 @@ def main(): tmp_dir = "/tmp/cc-simulators" clean_directory(tmp_dir) # clean it mk4_log_dir = f"{tmp_dir}/mk4_logs" + mk5_log_dir = f"{tmp_dir}/mk5_logs" q1_log_dir = f"{tmp_dir}/q1_logs" os.makedirs(mk4_log_dir, exist_ok=True) + os.makedirs(mk5_log_dir, exist_ok=True) os.makedirs(q1_log_dir, exist_ok=True) q = [] # build priority queue @@ -455,7 +461,14 @@ def main(): break sim = ColdcardSimulator(sim_args, segregate=True) sim.start(start_wait=0) - ld = q1_log_dir if "--q1" in sim_args else mk4_log_dir + + if "--q1" in sim_args: + ld = q1_log_dir + elif "--mk4" in sim_args: + ld = mk4_log_dir + else: + ld = mk5_log_dir + q_chunks.append((sim, mn, mod_add, k, ld)) time.sleep(5) @@ -468,7 +481,12 @@ def main(): if k: cmd_list.extend(["-k", k]) p = subprocess.Popen(cmd_list, preexec_fn=os.setsid, stdout=out_fd, stderr=out_fd) - mark = "Q" if "q1" in log_dir else "Mk4" + if "q1" in log_dir: + mark = "Q" + elif "mk5" in log_dir: + mark = "Mk5" + else: + mark = "Mk4" procs.append((mn+mod_add, p, out_fd, sim, mark, time.time())) print(f'started: {mark:<6}{mn+mod_add:<30}{sim.socket.split("-")[-1].split(".")[0]:<10}') diff --git a/testing/test_bip322.py b/testing/test_bip322.py index b3120ff4..f561267b 100644 --- a/testing/test_bip322.py +++ b/testing/test_bip322.py @@ -91,10 +91,13 @@ def verify_msg_bip322_por(cap_story, need_keypress, press_select, press_cancel, [["p2pkh", None, None]] + ([["p2wpkh", None, 1000000]] * 5) + ([["p2sh-p2wpkh", None, 10000000]] * 5), ]) def test_bip322_por(msg, ins, bip322_txn, start_sign, end_sign, cap_story, need_keypress, - press_select, verify_msg_bip322_por): + press_select, verify_msg_bip322_por, sim_root_dir): num_ins = len(ins) amt = sum([i[2] or 0 for i in ins]) psbt, msg_challenge = bip322_txn(ins, msg=msg) + with open(f'{sim_root_dir}/debug/last-b322-por.psbt', 'wb') as f: + f.write(psbt) + start_sign(psbt, finalize=True) verify_msg_bip322_por(msg.decode(), way="sd") diff --git a/testing/test_bip39pw.py b/testing/test_bip39pw.py index 95b33293..c03b8ad6 100644 --- a/testing/test_bip39pw.py +++ b/testing/test_bip39pw.py @@ -165,7 +165,10 @@ def test_b39p_refused(dev, press_cancel, pw='testing 123'): @pytest.mark.parametrize('version', range(8)) def test_bip39_pick_words(target, version, cap_menu, pick_menu_item, cap_story, word_menu_entry, get_pp_sofar, reset_seed_words, - press_select, only_mk4, go_to_passphrase): + press_select, is_q1, go_to_passphrase): + if is_q1: + raise pytest.skip("not on Q") + # Check we can pick words reset_seed_words() @@ -192,13 +195,16 @@ def test_bip39_pick_words(target, version, cap_menu, pick_menu_item, cap_story, @pytest.mark.parametrize('target', ['123', '1', '4'*32, '12'*8]) @pytest.mark.parametrize('backspaces', [1, 0, 12]) -def test_bip39_add_nums(target, backspaces, pick_menu_item, cap_story, only_mk4, +def test_bip39_add_nums(target, backspaces, pick_menu_item, cap_story, is_q1, cap_menu, word_menu_entry, get_pp_sofar, need_keypress, press_select, press_cancel, go_to_passphrase): # Check we can pick numbers (appended) # - also the "clear all" menu item + if is_q1: + raise pytest.skip("not on Q") + go_to_passphrase() pick_menu_item('Add Numbers') diff --git a/testing/test_ccc.py b/testing/test_ccc.py index 4af2aead..cd5b3ac0 100644 --- a/testing/test_ccc.py +++ b/testing/test_ccc.py @@ -22,12 +22,12 @@ from psbt import BasicPSBT SERVER_PUBKEY = '0231301ec4acec08c1c7d0181f4ffb8be70d693acccc86cccb8f00bf2e00fcabfd' @pytest.fixture -def goto_ccc_menu(goto_home, pick_menu_item, is_mark4): +def goto_ccc_menu(goto_home, pick_menu_item, is_q1): def doit(): goto_home() pick_menu_item("Advanced/Tools") pick_menu_item("Spending Policy") - pick_menu_item("Co-Sign Multi." if is_mark4 else "Co-Sign Multisig (CCC)") + pick_menu_item("Co-Sign Multisig (CCC)" if is_q1 else "Co-Sign Multi.") return doit diff --git a/testing/test_hobble.py b/testing/test_hobble.py index cdf2a223..96b129c5 100644 --- a/testing/test_hobble.py +++ b/testing/test_hobble.py @@ -55,8 +55,7 @@ goto_top_menu() @pytest.mark.parametrize('en_nfc', [ True, False] ) @pytest.mark.parametrize('en_multisig', [ True, False] ) def test_menu_contents(set_hobble, pick_menu_item, cap_menu, en_okeys, en_notes, settings_set, - need_some_notes, is_q1, is_mark4, en_nfc, sim_exec, en_multisig, - vdisk_disabled): + need_some_notes, is_q1, en_nfc, sim_exec, en_multisig, vdisk_disabled): # just enough to pass/fail the menu predicates! settings_set('seedvault', True) diff --git a/testing/test_hsm.py b/testing/test_hsm.py index fb9af23c..f5e99cae 100644 --- a/testing/test_hsm.py +++ b/testing/test_hsm.py @@ -114,7 +114,10 @@ def compute_policy_hash(policy): return b2a_hex(sha256(json_.encode()).digest()).decode() @pytest.fixture(autouse=True) -def enable_hsm_commands(dev, sim_exec, only_mk4): +def enable_hsm_commands(dev, sim_exec, is_q1): + if is_q1: + raise pytest.skip("Q does not have HSM support") + cmd = 'from glob import settings; settings.set("hsmcmd", 1)' sim_exec(cmd) yield diff --git a/testing/test_sign.py b/testing/test_sign.py index 9a4620ea..a8797d6b 100644 --- a/testing/test_sign.py +++ b/testing/test_sign.py @@ -134,21 +134,13 @@ def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec, src_root_dir, sim_root_d assert oo == rb @pytest.mark.unfinalized -def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign, - press_select, press_cancel, sim_root_dir, is_q1): +def test_speed_test(dev, fake_txn, start_sign, end_sign, press_select, press_cancel, sim_root_dir): # measure time to sign a larger txn - if is_mark4 or is_q1: - # Mk4: expect - # 20/250 => 15.5s (or 10.0 if seed is cached) - # 200/500 => 96.3s - num_in = 20 - num_out = 250 - elif is_mark3: - num_in = 20 - num_out = 250 - else: - num_in = 9 - num_out = 100 + # Mk4: expect + # 20/250 => 15.5s (or 10.0 if seed is cached) + # 200/500 => 96.3s + num_in = 20 + num_out = 250 psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=True) @@ -179,9 +171,7 @@ if 0: # see # - how big woudl PSBT be? # - not a great test case because so slow. - def test_mega_txn(fake_txn, is_mark4, start_sign, end_sign, dev): - if not is_mark4: - raise pytest.xfail('no way') + def test_mega_txn(fake_txn, start_sign, end_sign, dev): psbt = fake_txn(5569, 1, dev.master_xpub) @@ -3634,10 +3624,13 @@ def test_txn_nVersion_zero(segwit, fake_txn, start_sign, cap_story, goto_home): goto_home() def hack(psbt): - t = CTransaction() - t.deserialize(BytesIO(psbt.txn)) - t.nVersion = 0 - psbt.txn = t.serialize() + if psbt.is_v2(): + psbt.txn_version = 0 + else: + t = CTransaction() + t.deserialize(BytesIO(psbt.txn)) + t.nVersion = 0 + psbt.txn = t.serialize() psbt = fake_txn(1, 2, segwit_in=segwit, change_outputs=[0], psbt_hacker=hack) start_sign(psbt) diff --git a/testing/test_sssp.py b/testing/test_sssp.py index 85f4e06b..e043f30a 100644 --- a/testing/test_sssp.py +++ b/testing/test_sssp.py @@ -11,7 +11,7 @@ from ckcc.protocol import CCProtocolPacker @pytest.fixture -def goto_sssp_menu(goto_home, pick_menu_item, is_mark4): +def goto_sssp_menu(goto_home, pick_menu_item): def doit(): goto_home() pick_menu_item("Advanced/Tools") diff --git a/testing/test_upgrades.py b/testing/test_upgrades.py index b89eac03..984a4799 100644 --- a/testing/test_upgrades.py +++ b/testing/test_upgrades.py @@ -92,18 +92,12 @@ def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_sele @pytest.mark.parametrize('mode', ['compat', 'incompat']) @pytest.mark.parametrize('transport', ['sd', 'usb']) def test_hacky_upgrade(mode, cap_story, transport, dev, sim_exec, make_firmware, upload_file, - upgrade_by_sd, press_cancel, is_q1): + upgrade_by_sd, press_cancel, is_q1, is_mark5): if mode == 'compat': - data = make_firmware("q1" if is_q1 else 4) + data = make_firmware("q1" if is_q1 else (5 if is_mark5 else 4)) elif mode == 'incompat': - if is_q1: - data = make_firmware(4) - else: - with pytest.raises(RuntimeError) as err: - make_firmware(3) - assert "too big for our USB upgrades" in str(err) - return + data = make_firmware(4 if is_q1 else "q1") hdr = data[FW_HEADER_OFFSET:FW_HEADER_OFFSET+FW_HEADER_SIZE] diff --git a/testing/test_vdisk.py b/testing/test_vdisk.py index b6db2d3d..c7a5e013 100644 --- a/testing/test_vdisk.py +++ b/testing/test_vdisk.py @@ -214,7 +214,7 @@ def test_virtdisk_signing(encoding, num_outs, partial, try_sign_virtdisk, fake_t if 0: @pytest.mark.parametrize('num_outs', [ 1, 20, 250]) - def test_virtdisk_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress, cap_story, only_mk4): + def test_virtdisk_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress, cap_story): # Read signing result (transaction) over NFC, decode it. psbt = fake_txn(1, num_outs) orig, result = try_sign(psbt, accept=True, finalize=True) diff --git a/unix/README.md b/unix/README.md index 03e79d71..c3a122d1 100644 --- a/unix/README.md +++ b/unix/README.md @@ -33,6 +33,7 @@ wallet (on testnet, always with the same seed). But there are other options: - `--mk2` => emulate mark2 hardware (older micro, etc), default is current-gen (mark4) - `--mk3` => emulate mark3 hardware - `--mk4` => emulate mark4 hardware +- `--mk5` => emulate mark5 hardware (default) - `--q1` => emulate Q1 hardware - `--addr` => go to the address explorer at startup - `--xw` => go to the wallet export submenu diff --git a/unix/mk5-images/background.png b/unix/mk5-images/background.png new file mode 100755 index 0000000000000000000000000000000000000000..8702e20ed1fcd8a6cb04e7ba5b677a0d72af9404 GIT binary patch literal 15819 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 literal 0 HcmV?d00001 diff --git a/unix/simulator.py b/unix/simulator.py index c6363773..2852b837 100755 --- a/unix/simulator.py +++ b/unix/simulator.py @@ -439,15 +439,6 @@ class LCDSimulator(SimulatedScreen): self.draw_single_led(spriterenderer, 465, 315) class OLEDSimulator(SimulatedScreen): - # top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels - OLED_ACTIVE = (46, 85) - - # keypad touch buttons - KEYPAD_LEFT = 52 - KEYPAD_TOP = 216 - KEYPAD_PITCH = 73 - - background_img = 'mk4-images/background.png' def __init__(self, factory): self.movie = None @@ -462,12 +453,8 @@ class OLEDSimulator(SimulatedScreen): sdl2.ext.fill(s, self.bg) self.mv = sdl2.ext.pixels2d(self.sprite, transpose=False) - - # for genuine/caution lights and other LED's - self.led_red = factory.from_image("mk4-images/led-red.png") - self.led_green = factory.from_image("mk4-images/led-green.png") - self.led_sdcard = factory.from_image("mk4-images/led-sd.png") - self.led_usb = factory.from_image("mk4-images/led-usb.png") + + self.load_leds(factory) def new_contents(self, readable): # got bytes for new update. @@ -509,13 +496,59 @@ class OLEDSimulator(SimulatedScreen): SD_LED = 0x2 USB_LED = 0x4 - spriterenderer.render(self.led_green if (active_set & GEN_LED) else self.led_red) + spriterenderer.render(self.led_genuine if (active_set & GEN_LED) else self.led_unsafe) if active_set & SD_LED: spriterenderer.render(self.led_sdcard) if active_set & USB_LED: spriterenderer.render(self.led_usb) +class Mk4OLEDSimulator(OLEDSimulator): + # top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels + OLED_ACTIVE = (46, 85) + + # keypad touch buttons + KEYPAD_LEFT = 52 + KEYPAD_TOP = 216 + KEYPAD_PITCH = 73 + + background_img = 'mk4-images/background.png' + + def load_leds(self, factory): + # for genuine/caution lights and other LED's + # - these are pre-positioned where they need to end up + self.led_unsafe = factory.from_image("mk4-images/led-red.png") + self.led_genuine = factory.from_image("mk4-images/led-green.png") + self.led_sdcard = factory.from_image("mk4-images/led-sd.png") + self.led_usb = factory.from_image("mk4-images/led-usb.png") + +class Mk5OLEDSimulator(OLEDSimulator): + OLED_ACTIVE = (28, 41) + + # keypad touch buttons + KEYPAD_LEFT = 28 + KEYPAD_TOP = 125 + KEYPAD_PITCH = 42 + + background_img = 'mk5-images/background.png' + + def load_leds(self, factory): + # position each carefully + r = factory.from_image("mk5-images/led-red.png") + g = factory.from_image("mk5-images/led-green.png") + + self.led_unsafe = r.subsprite(r.area) + self.led_genuine = g.subsprite(g.area) + self.led_sdcard = g.subsprite(g.area) + self.led_usb = g.subsprite(g.area) + + self.led_unsafe.position = (14, -9) + self.led_genuine.position = (-1, -9) + + self.led_sdcard.position = (-14, 23) + self.led_usb.position = (65, 283) + + def load_shared_mod(name, path): # load indicated file.py as a module # from @@ -783,7 +816,15 @@ Q1 specials: factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE) - simdis = (OLEDSimulator if not is_q1 else LCDSimulator)(factory) + if is_q1: + simdis = LCDSimulator(factory) + elif ('--mk4' in sys.argv): + # retro look + simdis = Mk4OLEDSimulator(factory) + else: + # default: Mk5 + simdis = Mk5OLEDSimulator(factory) + bg = factory.from_image(simdis.background_img) window = sdl2.ext.Window("Coldcard Simulator", size=bg.size, position=(100, 100)) @@ -952,6 +993,7 @@ Q1 specials: if not pressed: numpad_tx.write(b'\0') # all up signal + while running: events = sdl2.ext.get_events() for event in events: diff --git a/unix/variant/ssd1306.py b/unix/variant/ssd1306.py index bd3d5f7a..385ac554 100644 --- a/unix/variant/ssd1306.py +++ b/unix/variant/ssd1306.py @@ -26,10 +26,10 @@ SET_CHARGE_PUMP = const(0x8d) # Subclassing FrameBuffer provides support for graphics primitives # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html class SSD1306(framebuf.FrameBuffer): - def __init__(self, width, height, external_vcc): + def __init__(self, width, height, is_mk5): self.width = width self.height = height - self.external_vcc = external_vcc + self.is_mk5 = is_mk5 self.pages = self.height // 8 self.buffer = bytearray(self.pages * self.width) super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) @@ -72,12 +72,20 @@ class SSD1306(framebuf.FrameBuffer): self.write_cmd(self.pages - 1) self.write_data(self.buffer) + + def busy_bar(self, enable, pattern): + # Render a continuous activity (not progress) bar in lower 8 lines of display + if enable: + # just show as static pattern + t = self.buffer[:-128] + pattern + self.write_data(t) + class SSD1306_SPI(SSD1306): - def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): + def __init__(self, width, height, spi, dc, res, cs, is_mk5=False): import sys self.pipe = open(int(sys.argv[1]), 'wb') - super().__init__(width, height, external_vcc) + super().__init__(width, height, is_mk5) def write_cmd(self, cmd): pass diff --git a/unix/variant/version.py b/unix/variant/version.py index 62875beb..0eee86d4 100644 --- a/unix/variant/version.py +++ b/unix/variant/version.py @@ -32,8 +32,8 @@ def get_header_value(fld_name): return b'\x18\x07\x11\x19S\x08\x00\x00' return 0 -# default is Mk4 hardware -hw_label = 'mk4' +# default is Mk5 hardware +hw_label = 'mk5' has_608 = True has_membrane = True supports_hsm = True @@ -47,7 +47,7 @@ has_qwerty = False is_edge = False if '--mk1' in sys.argv: - # doubt this works still + # doubt this works anymore hw_label = 'mk1' has_608 = False has_membrane = False @@ -72,6 +72,9 @@ if '--mk3' in sys.argv: has_nfc = False supports_hsm = False +if '--mk4' in sys.argv: + hw_label = 'mk4' + mk_num = int(hw_label[2:]) if '--q1' in sys.argv: @@ -81,6 +84,7 @@ if '--q1' in sys.argv: has_battery = True has_qwerty = True supports_hsm = False + mk_num = 4 from public_constants import MAX_TXN_LEN, MAX_UPLOAD_LEN from public_constants import MAX_TXN_LEN_MK4, MAX_UPLOAD_LEN_MK4