Files
n_signer/firmware/teensy41/README.md
T

5.9 KiB
Raw Blame History

n_signer Teensy 4.1 Firmware

Status: In progress — Phase 0 + SD + TFT + touch all verified on hardware (toolchain, blink, USB CDC serial, 4-bit SDMMC card read, ST7796S 480×320 TFT landscape fill + text, XPT2046 touch calibrated + live dot-draw verified). See plans/teensy41_bringup.md for the bring-up log, firmware/teensy41/WIRING.md for the display wiring, and plans/teensy41_signer_port.md for the full port plan. Next: exFAT re-test on the 1 TB SDXC card, then port the CYD display/touch/UI code.

The Teensy 4.1 (NXP i.MX RT1062, Cortex-M7 @ 600 MHz) is the high-capacity OTP pad target. Its built-in SD slot supports 1 TB SDXC cards (exFAT) via PJRC's SdFat library, making it the ideal hardware signer for large one-time-pad storage.

Why the Teensy 4.1

Concern Teensy 4.1 CYD (ESP32) Feather S3 TFT (ESP32-S3)
MCU 600 MHz Cortex-M7 240 MHz Xtensa LX6 240 MHz Xtensa LX7
SRAM 1 MB + 16 MB PSRAM 512 KB (no PSRAM) 512 KB + quad PSRAM
SD slot 4-bit SDMMC, exFAT, up to 2 TB 1-bit SDSPI, FAT32, up to 32 GB
USB Hi-Speed (480 Mbps) device + host CH340 UART only Full-Speed USB
WiFi None Yes (unused) Yes (unused)
Ethernet 10/100 PHY (optional)
PQ crypto speed ~1-2 s SLH-DSA-128s 5-30 s SLH-DSA-128s 5-30 s SLH-DSA-128s

SD card size support

Card type Size Works?
SDSC ≤ 2 GB Yes (FAT16/32)
SDHC 2 32 GB Yes (FAT32)
SDXC 32 GB 2 TB Yes (exFAT via SdFat)
SDUC 2 128 TB No

1 TB SDXC cards work — SdFat has native exFAT support, and the Teensy's 4-bit SDMMC bus runs at ~20-40 MB/s. No reformatting needed.

Display + touch

4.0" ST7796S 480×320 SPI TFT with XPT2046 resistive touch (Hosyond or equivalent, $12-15). Specs: 4-wire SPI, RGB 65K, 3.3V5V (works at the Teensy's 3.3V logic), XPT2046 resistive touch, includes touch pen + on-module SD slot.

Resistive touch is the right choice for a hardware signer (deliberate physical activation, stylus-compatible, simple driver). The XPT2046 driver ports from the CYD's touch.c with 480×320 resolution constants. The ST7796S display driver is new code (different init sequence than the CYD's ILI9341).

Wiring: TFT SPI on pins 11/12/13 (hardware SPI0), CS=5, DC=7, RESET=6, BL=8; touch shares the SPI bus with T_CS=9, T_IRQ=2. See WIRING.md for the full pin table and wire-by-wire chart.

Calibrated values & settings (verified on hardware)

These are the values to use when porting the CYD display/touch/UI code to the Teensy 4.1. They were measured on the actual Elecrow 4.0" ST7796S + XPT2046 module during bring-up (see plans/teensy41_bringup.md).

Display

Setting Value Notes
Orientation Landscape 480×320 Wider than tall.
tft.init() tft.init(320, 480) Pass the native (portrait) dims so the library takes its zero-offset branch. Do NOT use init(480, 320) — that computes a negative _colstart and offsets the image.
tft.setRotation() setRotation(1) Produces landscape 480×320 from the init(320, 480) base.
SCREEN_W / SCREEN_H 480 / 320 Use these constants for all drawing coordinates. Do not use tft.width() / tft.height() — the ST7796_t3 accessors are broken and always return 480×320 regardless of rotation.
Library ST7796_t3 (in Teensy's ST7735_t3 library) Hardware SPI0.
drawPixel() Avoid near edges Use fillRect(x, y, 1, n, c) / fillRect(x, y, n, 1, c) for crosshairs and thin lines — drawPixel mispositions near the right/bottom edges.

Touch (XPT2046)

Setting Value Notes
T_CS pin 9 Touch chip select (active low).
T_IRQ pin 2 Touch interrupt. Must not be pin 10 — pin 10 is SPI0 CS0, and pinMode(10, INPUT) corrupts the SPI engine and reverts the display to the wrong orientation.
SPI clock 2 MHz XPT2046 max is ~2.5 MHz; drop the clock before every touch read (SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0))).
Axis swap Yes In landscape rotation 1, raw Y → screen X, raw X → screen Y. The calibration struct + raw_to_screen() handle this (see below).
Control bytes 0xD0 = X, 0x90 = Y, 0xB0/0xC0 = Z1/Z2 Same as the CYD's touch.c.
Pressure threshold 80 Reject readings with pressure < 80 (stylus not pressing hard enough). Same as CYD.

Touch calibration constants (measured 2026-07-26)

static TouchCal s_cal = {
  /* x_min = */ 207,
  /* x_max = */ 1909,
  /* y_min = */ 168,
  /* y_max = */ 1798,
  /* invert_x = */ 1,
  /* invert_y = */ 1,
};

With the axis swap, x_min/x_max is the range of raw Y that maps to screen X, and y_min/y_max is the range of raw X that maps to screen Y. The raw_to_screen() function swaps accordingly:

*sx = map_clamped((int)raw_y, s_cal.x_min, s_cal.x_max,
                  s_cal.invert_x ? (SCREEN_W - 1) : 0,
                  s_cal.invert_x ? 0 : (SCREEN_W - 1));
*sy = map_clamped((int)raw_x, s_cal.y_min, s_cal.y_max,
                  s_cal.invert_y ? (SCREEN_H - 1) : 0,
                  s_cal.invert_y ? 0 : (SCREEN_H - 1));

These constants are for the specific panel we calibrated. If you swap a display module, re-run firmware/teensy41/touch_cal/touch_cal.ino and paste the new constants.

Build (planned)

# Arduino CLI / Teensyduino
arduino-cli compile --fqbn teensy:avr:teensy41 firmware/teensy41
arduino-cli upload -p /dev/ttyACM0 --fqbn teensy:avr:teensy41 firmware/teensy41

See the port plan: plans/teensy41_signer_port.md.