Files
n_signer/firmware/teensy41/WIRING.md
T

19 KiB
Raw Blame History

Wiring: 4.0" ST7796S 480×320 SPI TFT + XPT2046 Touch → Teensy 4.1

This document describes how to wire the Elecrow/Hosyond 4.0" 480×320 SPI TFT module with ST7796S driver and XPT2046 resistive touch to the Teensy 4.1.

It is the reference for Phase 1 of plans/teensy41_signer_port.md (display + touch bring-up). Source documents for the pinout live in firmware/teensy41/documents/:

Module pinout

The module breaks out 14 pins along one long edge. The labels below are the silk-screen names on the actual Elecrow/Hosyond board (read in order from one end of the header to the other). Alternate names in parentheses are the ones used in some other vendors' docs.

# Silkscreen Alt name Function
1 t_irq Touch interrupt (active low)
2 t_do T_MISO Touch SPI MISO (shared bus)
3 t_din T_MOSI Touch SPI MOSI (shared bus)
4 t_cs Touch chip select (active low)
5 t_clk T_SCK Touch SPI clock (shared bus)
6 sdo (miso) SDO / MISO TFT SPI MISO (shared with touch)
7 led BL / BKL Backlight (high = on; PWM-able)
8 sck CLK / SCL TFT SPI clock (shared with touch)
9 sdi (mosi) SDI / SDA TFT SPI MOSI (shared with touch)
10 dc/rs DC / A0 TFT data/command select
11 reset RST TFT reset (active low)
12 cs LCD_CS TFT chip select (active low)
13 gnd Ground
14 vcc Power (3.3V5V)

The module has a second SD card slot on the PCB (separate from the Teensy's built-in slot). Its pins are not broken out on the 14-pin header — they go to the SD contacts on the back of the module. We do not use the module's SD slot; the 1 TB OTP pad lives in the Teensy's built-in SDMMC slot (4-bit bus, ~2040 MB/s, exFAT). See README.md §SD card size support.

Connection chart (wire-by-wire)

This is the simple "connect this Teensy pin to that module pin" list. Power first, then signals.

By display header position (use this when the silkscreen is hidden)

When the display is plugged into a breadboard, you can't read the silkscreen. This table is indexed by physical position on the display header, counting from the end with t_irq (pin 1) to the end with vcc (pin 14). Hold the display with the header at the bottom and the screen facing you; pin 1 is the leftmost pin (the t_irq end), pin 14 is the rightmost (the vcc end).

Display header pos Silkscreen Teensy 4.1 pin Function
1 t_irq 2 Touch interrupt (active low) — see note below on why not pin 10
2 t_do 12 SPI0 MISO (shared)
3 t_din 11 SPI0 MOSI (shared)
4 t_cs 9 Touch chip select (active low)
5 t_clk 13 SPI0 SCK (shared) — opposite edge
6 sdo (miso) 12 SPI0 MISO (same wire as pos 2)
7 led 8 Backlight (PWM-able)
8 sck 13 SPI0 SCK (same wire as pos 5)
9 sdi (mosi) 11 SPI0 MOSI (same wire as pos 3)
10 dc/rs 7 TFT data/command
11 reset 6 TFT reset (active low)
12 cs 5 TFT chip select (software CS)
13 gnd GND Ground (opposite edge, next to pin 13)
14 vcc 3V3 3.3V power (long edge, past pin 12) — NOT 5V

Shared pins (pos 2↔6, 3↔9, 5↔8): these pairs are tied together on the display PCB, so you only need to wire one of each pair to the Teensy. The table shows both for completeness, but in practice you can wire pos 2, 3, 5 and leave pos 6, 8, 9 floating (or wire them to the same Teensy pin — harmless). That brings it to 11 wires total (8 unique signals + 3V3 + GND

  • the 3 shared-pair duplicates you choose to wire).

By Teensy pin number (for lookup at the Teensy)

Sorted by Teensy pin number for easy lookup at the Teensy end of the wires.

Teensy 4.1 pin Module silkscreen What it does
GND gnd Ground (GND is on the opposite edge, next to pin 13)
3V3 vcc 3.3V power (not 5V) — the 3V3 pin is on the long edge, just past pin 12
2 t_irq Touch interrupt (active low) — not pin 10 (see note below)
5 cs TFT chip select (software CS — see note below)
6 reset TFT reset (active low)
7 dc/rs TFT data/command
8 led Backlight (HIGH = on; PWM-able)
9 t_cs Touch chip select (active low)
11 sdi (mosi) SPI0 MOSI (also wires to t_din)
12 sdo (miso) SPI0 MISO (also wires to t_do)
13 sck SPI0 SCK (also wires to t_clk) — pin 13 is on the opposite edge, route via a short jumper

That's 11 wires total, all in the pins 5-13 range plus 3V3 and GND. The three shared SPI lines (sdi (mosi), sdo (miso), sck) each fan out to two module pins — see the next section for why and how.

Why cs is on pin 5 (software CS) and t_irq is on pin 2 (not pin 10): Pin 10 is the hardware CS0 for SPI0. Do not use pin 10 for anything else — calling pinMode(10, ...) (e.g. for t_irq as an input) corrupts the SPI0 engine state and reverts the ST7796S display to landscape mode after setRotation(). This is a hard-won lesson documented in plans/teensy41_bringup.md. So:

  • cs is on pin 5 (software CS via SPI.beginTransaction()) — the speed difference vs hardware CS0 is negligible for a signer UI.
  • t_irq is on pin 2 (long edge, before pin 5) — keeps it off pin 10 and out of the SPI0 CS0 conflict. If you want hardware CS0 for the TFT, you can put cs on pin 10, but then t_irq must stay on pin 2 (or be dropped entirely in favor of polling).

Shared-bus shortcut: sdi (mosi) and t_din are the same net on the module PCB, sdo (miso) and t_do are the same net, and sck and t_clk are the same net. So for each shared pair you can either (a) wire the Teensy pin to both module pins (harmless, just a Y-jumper), or (b) wire the Teensy pin to one module pin and leave the other floating (it's tied internally on the PCB). The connection chart above wires each Teensy SPI pin to the TFT-side silkscreen; the touch-side silkscreen of each pair is tied to the same net on the board.

Teensy 4.1 pin assignments

The Teensy 4.1 has 3 hardware SPI ports. We use SPI0 (the primary port with the FIFO) on pins 11/12/13. The TFT and the XPT2046 share this bus; each has its own CS. All pins are 3.3V — the module is 3.3V-logic compatible, so no level shifting is needed.

Silkscreen Teensy 4.1 pin Teensy function Notes
gnd GND Ground GND is on the opposite edge, next to pin 13.
vcc 3V3 3.3V output Use 3.3V, not 5V/VIN. The 3V3 pin is on the long edge, just past pin 12. The Teensy's 3.3V regulator can supply up to ~250 mA; the display + backlight draw ~80120 mA, well within budget.
cs 5 GPIO (software CS) TFT chip select (active low). Software CS via SPI.beginTransaction() — see the note in the connection chart about why we didn't use hardware CS0 on pin 10.
reset 6 GPIO TFT reset. Drive low for >10 µs to reset; pull high (or set as OUTPUT HIGH) for normal operation. Can also be tied to a 10 kΩ pull-up and left floating if you don't need software reset.
dc/rs 7 GPIO TFT data/command. High = data (pixel/command param), Low = command.
led 8 GPIO / PWM Backlight. Drive HIGH for full brightness, or use analogWrite(8, 0..255) for dimming. Can also be tied to 3.3V through a ~100 Ω resistor for always-on.
t_cs 9 GPIO Touch chip select. Active low. Must be HIGH when talking to the TFT, LOW when reading the touch.
t_irq 2 GPIO (input + interrupt) Touch interrupt. Pulled high by the XPT2046 internally; goes low when touch pressure is detected. Optional — can be left unconnected and polled, but wiring it lets us sleep until a touch happens. Must not be pin 10 (SPI0 CS0 — pinMode(10, INPUT) corrupts the SPI engine and reverts the display to landscape).
sdi (mosi) 11 SPI0 MOSI Shared with touch t_din.
sdo (miso) 12 SPI0 MISO Shared with touch t_do. The TFT rarely drives MISO (most ST7796S commands are write-only), but the XPT2046 reads return on this line.
sck 13 SPI0 SCK Shared with touch t_clk. Pin 13 is on the opposite edge of the Teensy (next to GND), so route it via a short jumper across the board. Pin 13 also drives the onboard orange LED — that's fine, the LED just blinks during SPI activity.
t_clk 13 SPI0 SCK (shared) Same wire as sck.
t_do 12 SPI0 MISO (shared) Same wire as sdo (miso).
t_din 11 SPI0 MOSI (shared) Same wire as sdi (mosi).

Pin map summary (Teensy side)

Wires, listed in the same order as the module's silkscreen header (pin 1 → 14):

Teensy 4.1   Silkscreen   Function
-----------  -----------  ------------------------------------------
pin 2        t_irq        Touch interrupt (active low — NOT pin 10, see note)
pin 12       t_do         Touch MISO  (shared SPI0 MISO)
pin 11       t_din        Touch MOSI  (shared SPI0 MOSI)
pin 9        t_cs         Touch chip select (active low)
pin 13       t_clk        Touch SCK   (shared SPI0 SCK — opposite edge)
pin 12       sdo (miso)   TFT MISO    (shared SPI0 MISO — same wire as t_do)
pin 8        led          Backlight (HIGH = on; analogWrite for dimming)
pin 13       sck          TFT SCK     (shared SPI0 SCK — same wire as t_clk)
pin 11       sdi (mosi)   TFT MOSI    (shared SPI0 MOSI — same wire as t_din)
pin 7        dc/rs        TFT data/command
pin 6        reset        TFT reset (active low)
pin 5        cs           TFT chip select (software CS)
GND          gnd          Ground (opposite edge, next to pin 13)
3V3          vcc          Power (3.3V — NOT 5V; long edge, past pin 12)

Shared-bus note: t_do and sdo (miso) are the same electrical net on the module (both connect to the SPI MISO line), and t_din/sdi (mosi) and t_clk/sck are likewise the same nets. You only need to wire one of each shared pair to the Teensy — but on this module the header brings both out, so you can either wire both to the same Teensy pin (harmless) or wire one and leave the other floating (also fine, since they're tied together on the PCB). The table above wires both to the same Teensy pin for clarity.

SPI bus sharing

The TFT and the XPT2046 share MOSI / SCK / MISO. The rule for sharing:

  1. Only one CS is low at a time. Before talking to the TFT, set t_cs HIGH and cs LOW. Before reading the touch, set cs HIGH and t_cs LOW.
  2. Use SPI.beginTransaction(SPISettings(spi_speed, MSBFIRST, SPI_MODE0)) / SPI.endTransaction() around each device's access. The TFT and the XPT2046 both use SPI mode 0, but they may want different clock speeds:
    • TFT: up to 40 MHz (the ST7796S supports 1-line SPI up to ~62 MHz; 40 MHz is a safe conservative choice on the Teensy's SPI0).
    • XPT2046: 22.5 MHz max (the XPT2046 datasheet specifies a max serial clock of ~2.5 MHz). Always drop the clock before reading the touch.
  3. The XPT2046 control byte is 0x90 for X read (channel 5) and 0xD0 for Y read (channel 1), 8-bit, MSB first, with the start bit set. See firmware/cyd_esp32_2432s028/main/touch.c xpt2046_transfer_12b() for the bit-banged reference; on the Teensy we can use the hardware SPI engine instead of bit-banging.

Routing notes (trace-crossing analysis)

This assignment uses hardware SPI0 (MOSI=11, MISO=12, SCK=13) for maximum display throughput (FIFO + DMA, up to 40 MHz), with all display pins in a contiguous block on the Teensy's long edge (pins 5-12) plus SCK=13 on the opposite edge. The display module's silkscreen pin order doesn't match the Teensy's SPI0 pin order, so a small number of trace crossings are unavoidable. This is fine for jumper wires and trivial on a 2-layer PCB.

Teensy 4.1 physical layout (relevant part):

  • Long edge: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3V3, 24, 25, ...
  • Opposite edge: 0, 13, 14, ... with GND in the middle

So pins 5-12 are contiguous on the long edge, 3V3 is right past pin 12, and pin 13 (SCK) + GND are on the opposite edge. All display signals fit in the pins 5-13 range plus 3V3 and GND — no need to reach up to pins 24+.

Crossings (2 on the long edge + 1 SCK via):

  1. MOSI/MISO swap. Display order is MISO(t_do/sdo, pos 2/6) then MOSI(t_din/sdi, pos 3/9); Teensy SPI0 is MOSI(11) then MISO(12). One crossing — unavoidable given the fixed SPI0 pin assignments.
  2. t_cs jumps over the SPI pins. Display pos 3-4-5 is MOSI-t_cs-SCK; Teensy pins 11-12-13 are MOSI-MISO-SCK with no free pin between MOSI(11) and SCK(13). t_cs (Teensy pin 9) routes below the SPI block, crossing the MOSI/MISO pair. One crossing.
  3. SCK via to the opposite edge. SCK is on pin 13, which is on the opposite edge of the Teensy from pins 5-12. This is a via (a short jumper across the board), not a trace crossing on the long edge.

The remaining GPIOs (led=8, dc/rs=7, reset=6, cs=5) run monotonically down the long edge below t_cs=9 — zero crossings among them.

Why not zero crossings? A zero-crossing assignment is possible by bit-banging SPI on Teensy pins in exact display order, but it loses the hardware SPI engine — display refresh drops to ~0.2-0.5 s per full 480×320 frame. We chose hardware SPI0 for a snappy UI and accepted 2 crossings + 1 via, which is trivial for jumper wires or a 2-layer PCB.

Why cs=5 (software CS) and t_irq=2? Pin 10 is the hardware CS0 for SPI0, but pin 10 cannot be used for t_irq — calling pinMode(10, INPUT) corrupts the SPI0 engine and reverts the display to landscape after setRotation() (verified during bring-up, see plans/teensy41_bringup.md). So t_irq is on pin 2 (long edge, before pin 5), and cs is on pin 5 (software CS via SPI.beginTransaction()). The speed difference vs hardware CS0 is negligible for a signer UI. If you want hardware CS0 for the TFT, put cs on pin 10 and leave t_irq on pin 2 (or drop t_irq and poll).

Power notes

  • 3.3V only. The Teensy 4.1's pins are not 5V tolerant. The display module is rated 3.3V5V and works at 3.3V logic — power it from the Teensy's 3V3 pin, not VIN/VUSB (5V). Driving VCC with 5V while the signal pins are at 3.3V can back-power the ST7796S level shifters and is unnecessary.
  • Backlight current. The LED pin on these modules is typically driven through a transistor on the module; a direct 3.3V GPIO high is enough to turn it on. If the backlight flickers or the GPIO can't hold high, drive LED from 3.3V through a ~100 Ω resistor instead and skip PWM dimming.
  • Total draw. Display + backlight + touch ≈ 80120 mA at 3.3V. The Teensy's onboard regulator is rated for ~250 mA external use, so this is within budget. If you add other peripherals (Ethernet PHY, etc.), re-check the budget.

Wiring checklist (do this before powering on)

  • VCC → 3V3 (not 5V/VIN).
  • GND → GND.
  • All SPI signal pins (cs, dc/rs, reset, sdi (mosi), sck, sdo (miso), t_cs, t_irq) go to the Teensy pins listed above — confirm none are swapped. The easy one to get backwards is sdi (mosi)sdo (miso): on this module sdi (mosi) = Teensy pin 11 (MOSI) and sdo (miso) = Teensy pin 12 (MISO).
  • The shared-bus pairs (t_do/sdo (miso), t_din/sdi (mosi), t_clk/sck) are tied together on the module PCB — wire one of each pair to the Teensy pin and the other can go to the same pin or float. Don't wire them to different Teensy pins.
  • No 5V signal reaches any Teensy pin.
  • The Teensy's built-in SD slot is not wired to the display — the module's SD slot is unused. The 1 TB pad goes in the Teensy's built-in slot only.

Bring-up order (Phase 1)

  1. TFT only first. Wire vcc, gnd, cs, reset, dc/rs, sdi (mosi), sck, led, sdo (miso). Leave t_cs, t_irq floating for now. Run a fill-screen + draw-text sketch. Exit criterion: solid color fill + readable text.
  2. Touch second. Add t_cs, t_clk (shared with sck), t_din (shared with sdi (mosi)), t_do (shared with sdo (miso)), t_irq. Run a touch-read sketch that prints raw X/Y + mapped pixel coords. Exit criterion: stylus press prints coordinates that track the stylus position across the full 480×320 area.
  3. Calibration. The XPT2046 raw readings need a 2-point calibration per axis (min/max raw → 0/480 and 0/320). Port the calibration struct from firmware/cyd_esp32_2432s028/main/touch.c s_cal and re-calibrate on the Teensy (the CYD's numbers won't match — different panel, different controller).

References