5.9 KiB
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.