Files
n_signer/plans/teensy41_bringup.md

8.9 KiB
Raw Permalink Blame History

Plan: Teensy 4.1 Bring-up (Blink → Serial → Display → Touch → SD)

Status: Phase 0 + SD + TFT + touch all verified on hardware (2026-07-26).

  • Toolchain installed (teensy:avr 1.62.0 + udev rules), blink sketch confirmed (red LED @ 1 Hz), HelloSerial confirmed (teensy41 alive #N over /dev/ttyACM0 once per second). CDC-ACM transport path verified.
  • SD card test sketch (firmware/teensy41/sd_test/sd_test.ino) mounted a 32 GB FAT32 card via SD.begin(BUILTIN_SDCARD) (4-bit SDMMC), listed the root directory, read the first 512 bytes of bcm2710-rpi-2-b.dtb. exFAT re-test pending the 1 TB SDXC card.
  • TFT display test sketch (firmware/teensy41/tft_test/tft_test.ino) confirmed on hardware: landscape 480×320, fill edge-to-edge, text readable, colors correct. Uses ST7796_t3 library (hardware SPI0).
  • Touch calibration sketch (firmware/teensy41/touch_cal/touch_cal.ino) confirmed on hardware: 4-corner tap calibration → live dot-draw tracks the stylus across the full 480×320 screen. Calibration constants: x_min=207, x_max=1909, y_min=168, y_max=1798, invert_x=1, invert_y=1 (with axis swap: raw Y → screen X, raw X → screen Y).

Hard-won lessons (read these before porting the display/touch code):

  1. Landscape, not portrait. The target orientation is landscape 480×320 (wider than tall). Use tft.init(320, 480) + setRotation(1) to get landscape with zero offsets. Do NOT use init(480, 320) — that hits the library's else-branch which computes a negative _colstart and offsets the image off the panel.
  2. tft.width() / tft.height() are broken. The ST7796_t3 accessors always return the unrotated 480×320 regardless of setRotation(). Use the constants SCREEN_W=480 / SCREEN_H=320 for all drawing coordinates, never tft.width()/tft.height().
  3. Pin 10 is SPI0 CS0 — do not pinMode it. Calling pinMode(10, INPUT) (e.g. for t_irq) corrupts the SPI0 engine and reverts the display to the wrong orientation. t_irq is on pin 2 (see firmware/teensy41/WIRING.md).
  4. XPT2046 axes are swapped in landscape rotation 1. Raw Y maps to screen X, raw X maps to screen Y. The calibration struct stores x_min/x_max as the range of raw Y, y_min/y_max as the range of raw X, and raw_to_screen() swaps accordingly.
  5. drawPixel() mispositions near edges. Use fillRect(x, y, 1, n, c) / fillRect(x, y, n, 1, c) for crosshairs and thin lines instead of drawPixel()fillRect is reliable across the full screen.

Next: exFAT re-test on the 1 TB SDXC card (when it arrives), then start porting the CYD display/touch/UI code per plans/teensy41_signer_port.md Phase 2+.

Phase 0 of plans/teensy41_signer_port.md. Goal: prove the toolchain, the board, and the USB CDC transport path before porting any signer code.

Toolchain: Arduino CLI + Teensyduino (matches the primary path in the port plan).

0. On the LED you see at power-on

The Teensy 4.1 ships from PJRC with a factory blink sketch on the onboard red LED (pin 13). Expected behavior at first plug-in:

  • Red LED near USB connector blinks ~1 Hz → factory blink program is running → board is healthy.
  • Steady red LED → a previous sketch left it on, or the board is halted.
  • Steady orange/green LED → that is the power LED, always on when the board has power. Not the user LED.
  • No LED activity at all, and lsusb shows 16c0:0478 ... Teensy Halfkay Bootloader → the application flash was erased and the board is sitting in the Halfkay bootloader. This is healthy and is the easiest state to upload from: arduino-cli upload (via Teensy Loader) pushes a sketch to Halfkay directly, no Program-button press needed. There is no /dev/ttyACMx in this state because Halfkay enumerates as HID, not CDC — arduino-cli board list may show nothing, and that is expected.

A steady power LED plus a blinking red user LED = everything is fine. A Halfkay enumeration with no LED = erased flash, ready to receive a sketch.

1. Host toolchain

  1. Install arduino-cli (e.g. pip install arduino-cli or download the binary).
  2. Add the Teensyduino core:
    arduino-cli config init
    arduino-cli config set board_manager.additional_urls https://www.pjrc.com/teensy/package_teensy_index.json
    arduino-cli core update-index
    arduino-cli core install teensy:avr
    
  3. Verify the FQBN is available:
    arduino-cli board listall teensy: | grep teensy41
    
    Exit criterion: teensy:avr:teensy41 appears in the list.

Note: the teensy:avr package from PJRC's index includes the Teensy Loader and compiler toolchain (arm-none-eabi-gcc). On Linux, udev rules are required for the bootloader device — see PJRC's Linux udev rules. Without them, arduino-cli upload will fail with a permission error on /dev/ttyACMx.

2. Detect the board

  1. Plug the Teensy 4.1 into USB (use the onboard USB port, not the USB host header).
  2. List Arduino ports:
    arduino-cli board list
    
    Exit criterion: a port shows up as /dev/ttyACMx (Linux), COMx (Windows), or /dev/cu.usbmodem* (macOS) with protocol serial and board teensy:avr:teensy41.
  3. If no port appears, press the Program button on the Teensy once — that forces the bootloader to enumerate. (The factory blink sketch does not expose a CDC port by default, so the port may only show up after the first upload of a sketch that calls Serial.begin().)

Create firmware/teensy41/teensy41_blink.ino:

// Teensy 4.1 bring-up: blink the onboard red LED on pin 13 at 1 Hz.
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);   // LED_BUILTIN == 13 on Teensy 4.1
}
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);
}

Compile + upload:

arduino-cli compile --fqbn teensy:avr:teensy41 firmware/teensy41
arduino-cli upload -p /dev/ttyACM0 --fqbn teensy:avr:teensy41 firmware/teensy41

If upload fails with "no Teensy found", press the Program button on the board to force the bootloader, then retry.

Exit criterion: the red LED toggles at exactly 1 Hz (slower than the factory blink, which is ~5 Hz, so the change is obvious).

4. HelloSerial (validate the CDC-ACM transport path)

The signer's USB transport (plans/teensy41_signer_port.md §USB transport) is TinyUSB CDC-ACM + 4-byte length-prefix framing. Before writing the framer, confirm that a plain Serial.print over USB CDC works end-to-end.

Create firmware/teensy41/teensy41_hello_serial.ino:

// Teensy 4.1 bring-up: print a counter over USB CDC every second.
void setup() {
  Serial.begin(115200);            // USB CDC on Teensy 4.1
  pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
  static uint32_t n = 0;
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.print("teensy41 alive #");
  Serial.println(n++);
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);
}

Compile + upload, then open a monitor:

arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200
# or: stty -F /dev/ttyACM0 115200 raw -echo && cat /dev/ttyACM0

Exit criterion: teensy41 alive #0, #1, #2… prints once per second, and the red LED blinks in lockstep. This proves the CDC-ACM path we will reuse for the signer's length-prefix framing.

5. Next milestones (covered by separate plans)

Once blink + serial are confirmed, the rest of Phase 1 from plans/teensy41_signer_port.md proceeds:

  1. ST7796S 480×320 display — port firmware/cyd_esp32_2432s028/main/ili9341.c with the ST7796S init sequence from firmware/teensy41/ST7796S_Datasheet.pdf and the Elecrow wiki page. Wiring per the port plan: TFT SPI on pins 11/12/13, CS=10, DC=9, RESET=8, BL=22. Exit: fill screen + draw text.
  2. XPT2046 touch — port firmware/cyd_esp32_2432s028/main/touch.c with 480×320 constants. T_CS=7, T_IRQ=6. Exit: read touch coords, map to pixels.
  3. SD card + exFAT — install SdFat via Arduino Library Manager, mount a 1 TB SDXC card via sd.begin(SdioConfig(FIFO_SDIO)). Exit: write + read a test file.

6. Status tracking

Update firmware/teensy41/README.md from "Planned — not yet implemented" to "In progress — Phase 0 bring-up" once the blink sketch is confirmed running on the board.