183 lines
8.9 KiB
Markdown
183 lines
8.9 KiB
Markdown
# 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`](../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`](../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`](../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`](../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`](teensy41_signer_port.md) Phase 2+.
|
||
|
||
Phase 0 of [`plans/teensy41_signer_port.md`](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:
|
||
```bash
|
||
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:
|
||
```bash
|
||
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](https://www.pjrc.com/teensy/td_download.html).
|
||
> 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:
|
||
```bash
|
||
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()`.)
|
||
|
||
## 3. Blink sketch (first upload)
|
||
|
||
Create [`firmware/teensy41/teensy41_blink.ino`](../firmware/teensy41/teensy41_blink.ino):
|
||
|
||
```cpp
|
||
// 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:
|
||
```bash
|
||
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`](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`](../firmware/teensy41/teensy41_hello_serial.ino):
|
||
|
||
```cpp
|
||
// 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:
|
||
```bash
|
||
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`](teensy41_signer_port.md) proceeds:
|
||
|
||
1. **ST7796S 480×320 display** — port [`firmware/cyd_esp32_2432s028/main/ili9341.c`](../firmware/cyd_esp32_2432s028/main/ili9341.c)
|
||
with the ST7796S init sequence from [`firmware/teensy41/ST7796S_Datasheet.pdf`](../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`](../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`](../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.
|