Teensy 9-bit UART for Wheelwriter Bus
Notes from PJRC forum thread and reference projects.
Basic Configuration
Serial2.begin(187500, SERIAL_9N1); // 9 data bits, no parity, 1 stop bit
- Paul Stoffregen confirmed: all Teensy serial ports support arbitrary baud rates
187500works directly, no special setup needed- Different ports can run at different baud rates simultaneously
9-bit Support
Commented out by default in HardwareSerial.h on both Teensy 3.x and 4.0. Don’t edit the core file — define via build flags:
; platformio.ini
build_flags = -DSERIAL_9BIT_SUPPORT
With 9-bit mode enabled:
Serial2.write(0x121)sends 9-bit word (bit8=1, byte=0x21) – address byteSerial2.write(0x003)sends 9-bit word (bit8=0, byte=0x03) – data byte- Read returns
uint16_twith 9th bit in bit 8
Half-Duplex (Shared Bus)
The Wheelwriter bus is half-duplex single-wire. Two approaches:
Approach 1: Loopback Mode (Teensy 3.x)
// Enable loopback + single-wire mode
UART0_C1 |= UART_C1_LOOPS | UART_C1_RSRC;
// TX pin: open-collector with pull-up
pinMode(txPin, OUTPUT_OPENDRAIN);
// Switch to transmit
UART0_C3 |= UART_C3_TXDIR;
// Switch to receive
UART0_C3 &= ~UART_C3_TXDIR;
Approach 2: Separate TX/RX with MOSFET (wheelwriter-interface)
- TX pin through N-channel MOSFET to bus (inverts signal)
- RX pin reads bus directly (no inversion needed)
- TX must be software-inverted (MCU HIGH -> MOSFET ON -> bus LOW)
- Sender sees own transmission on RX (must ignore/expect echo)
Approach 3: Separate TX/RX via Level Shifter (current TKB approach)
Both TX and RX connect to the same BUS line through separate bidirectional level shifter channels. Standard UART mode — no single-wire register tricks.
Serial2.begin(187500, SERIAL_9N1);
pinMode(9, INPUT); // GPIO bus monitor
Send protocol (per word):
- Wait bus idle (pin 9 HIGH)
- Flush stale RX data
write9bit(word)+flush()(wait TX complete)read()— echo (own transmission, discard)read()— ACK/response from Motor Control Board
The RX flush before each send prevents desync cascade: if a previous ACK read timed out, the stale byte is cleared before the next transaction.
A third level shifter channel (pin 9) monitors the bus as GPIO for bus arbitration.
Approach 4: Teensy 4.0 LPUART Single-Wire
Teensy 4.0 LPUART supports single-wire half-duplex natively via LPUART CTRL register:
CTRL[LOOPS]= 1,CTRL[RSRC]= 1 for single-wire modeCTRL[TXDIR]to switch direction- Registers:
LPUART4_CTRL(for Serial2), etc.
Not used in the current firmware — separate TX/RX via level shifter (Approach 3) works well.
TX Signal Inversion
The bus is idle-HIGH, open-collector. If driving through MOSFET:
- Hardware inverts: MCU HIGH -> bus LOW
- Need inverted TX output
Teensy 4.0 LPUART has built-in inversion:
Serial2.begin(187500, SERIAL_9N1_TXINV); // invert TX only
// or
Serial2.begin(187500, SERIAL_9N1_RXINV_TXINV); // invert both
Whether to use TXINV, RXINV, or neither depends on the hardware interface circuit.
Relevant Teensy 4.0 LPUART Registers
| Register | Purpose |
|---|---|
LPUART4_CTRL | Control (LOOPS, RSRC, TXDIR, M=9-bit, etc.) |
LPUART4_STAT | Status |
LPUART4_DATA | Data (bits 0-8 for 9-bit mode, bit 8 = 9th bit) |
LPUART4_BAUD | Baud rate |
Serial1 = LPUART6, Serial2 = LPUART4 (used for Wheelwriter), etc.