8.9 KiB
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:avr1.62.0 + udev rules), blink sketch confirmed (red LED @ 1 Hz), HelloSerial confirmed (teensy41 alive #Nover/dev/ttyACM0once per second). CDC-ACM transport path verified.- SD card test sketch (
firmware/teensy41/sd_test/sd_test.ino) mounted a 32 GB FAT32 card viaSD.begin(BUILTIN_SDCARD)(4-bit SDMMC), listed the root directory, read the first 512 bytes ofbcm2710-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. UsesST7796_t3library (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):
- 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 useinit(480, 320)— that hits the library's else-branch which computes a negative_colstartand offsets the image off the panel.tft.width()/tft.height()are broken. TheST7796_t3accessors always return the unrotated 480×320 regardless ofsetRotation(). Use the constantsSCREEN_W=480/SCREEN_H=320for all drawing coordinates, nevertft.width()/tft.height().- Pin 10 is SPI0 CS0 — do not
pinModeit. CallingpinMode(10, INPUT)(e.g. fort_irq) corrupts the SPI0 engine and reverts the display to the wrong orientation.t_irqis on pin 2 (seefirmware/teensy41/WIRING.md).- 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_maxas the range of raw Y,y_min/y_maxas the range of raw X, andraw_to_screen()swaps accordingly.drawPixel()mispositions near edges. UsefillRect(x, y, 1, n, c)/fillRect(x, y, n, 1, c)for crosshairs and thin lines instead ofdrawPixel()—fillRectis 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.mdPhase 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
lsusbshows16c0: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/ttyACMxin this state because Halfkay enumerates as HID, not CDC —arduino-cli board listmay 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
- Install
arduino-cli(e.g.pip install arduino-clior download the binary). - 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 - Verify the FQBN is available:
Exit criterion:
arduino-cli board listall teensy: | grep teensy41teensy:avr:teensy41appears in the list.
Note: the
teensy:avrpackage 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 uploadwill fail with a permission error on/dev/ttyACMx.
2. Detect the board
- Plug the Teensy 4.1 into USB (use the onboard USB port, not the USB host header).
- List Arduino ports:
Exit criterion: a port shows up as
arduino-cli board list/dev/ttyACMx(Linux),COMx(Windows), or/dev/cu.usbmodem*(macOS) with protocolserialand boardteensy:avr:teensy41. - 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().)
3. Blink sketch (first upload)
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:
- ST7796S 480×320 display — port
firmware/cyd_esp32_2432s028/main/ili9341.cwith the ST7796S init sequence fromfirmware/teensy41/ST7796S_Datasheet.pdfand 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. - XPT2046 touch — port
firmware/cyd_esp32_2432s028/main/touch.cwith 480×320 constants. T_CS=7, T_IRQ=6. Exit: read touch coords, map to pixels. - 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.