Skip to content

Kart Medulla (ESP32-S3)

The Kart Medulla is the MCU-based control hub between the Orin computer and the kart's sensors and actuators. The next revision is an interface PCB built around the ESP32-S3, with external level shifting, analog conditioning, an SPI DAC, an on-PCB manual/autonomous signal mux, and Wago-style push-in connectors replacing the hand-wired Dupont setup.

Currently hand-wired in the kart: classic ESP32

The kart is currently running a hand-wired classic-ESP32 (no PCB). That setup is documented on the Legacy wiring page and will be removed once the ESP32-S3 board is deployed.

Firmware repository: UM-Driverless/kart-medulla

Why ESP32-S3

The classic ESP32 ran out of usable GPIOs once CAN, SPI, status RGB, buzzer, and the Orin link were added on top of the existing I/O (3× halls, 3× pressure, accelerator, brake, SDC, steering, relay). The S3 solves this and adds several quality-of-life wins:

  • ~45 GPIOs (vs ~34 on the classic), with fewer of them reserved or strap-pin traps.
  • Native USB-OTG — the Orin link becomes a direct USB cable (CDC-ACM), dropping the USB-UART bridge IC and moving from ~1 Mbit/s UART to ~12 Mbit/s full-speed USB.
  • Built-in USB-Serial-JTAG — flashing, serial monitor, and step-debugging all over the same USB cable. No external ESP-Prog / FT2232H needed.
  • External DAC on the PCB (MCP4922, dual 12-bit, SPI) — replaces the classic ESP32's built-in 8-bit DAC. 12-bit resolution × 2 channels covers CMD_ACC (accelerator, 0–5 V direct) and CMD_BRAKE (brake, 0–5 V → ×2 op-amp → 0–10 V for the Festo proportional valve) with no extra pin cost beyond the existing SPI bus.

Variants considered and rejected: S2 (has DAC but single-core, no BT), C3 (too few GPIOs), C6 (no DAC, Wi-Fi 6 overkill for a kart), H2 (no Wi-Fi).

ESP32-S3 Overview

ESP32-S3-DevKitC-1 pinout (high resolution — click to open reference page)

Click the image for the full high-resolution pinout and specs page at mischianti.org.

  • CPU: Xtensa dual-core 32-bit LX7, up to 240 MHz
  • GPIOs: ~45 usable
  • ADCs: 2× 12-bit, multi-channel
  • DACs: none (external MCP4922 dual 12-bit SPI DAC on the interface PCB)
  • USB: native USB-OTG + USB-Serial-JTAG
  • Wireless: Wi-Fi 4 + BLE 5
  • Communication Interfaces: SPI, I²C, UART, CAN (TWAI), I²S

Dev-board mechanical reference (ESP32-S3-DevKitC-1)

The medulla PCB hosts the ESP32-S3 module via a stock ESP32-S3-DevKitC-1 dev board (or a pin-compatible clone such as the YD-ESP32-S3 / "44 pines tipo C"). The medulla footprint must match this:

Quantity Value
Pin pitch (within a row) 2.54 mm (0.1 ″)
Pins per row 22 (44 total — 2 rows of female sockets)
Row centerline ↔ row centerline 22.86 mm (0.9 ″)
PCB outer width 25.40 mm (= 22.86 + 2 × 1.27 mm edge offset)
USB-C protrusion past board edge ~8.00 mm

The row spacing is 22.86 mm (0.9 ″), NOT 25.40 mm. Pin centerlines are inset 1.27 mm from each PCB edge. This was confirmed by physical caliper measurement on an official Espressif board on 2026-05-02 after two earlier wrong PDF-derived answers — the Espressif DXF_…_V1.1_20220429.pdf mechanical drawing has ambiguous 1.27 mm callouts that can be read as either antenna keepout or pin-row offset. Trust the physical measurement, not any single drawing. Local mirrors of the Espressif drawing, schematic, and the ground-truth measurement photo are kept in the dv vault at dv/kart/kart-medulla/resources/esp32-s3-devkitc-1/.

ESP32-S3 Pin Assignment

Pin map for the ESP32-S3 module on the interface PCB. Source of truth: projects/kart-medulla/docs/pinout-esp32-s3.md in the dv-hardware repo (which itself defers to the schematic). This table mirrors that file as of 2026-05-08; if the two disagree, dv-hardware wins. For the per-pin capability reference (which GPIOs can do ADC, which are strap pins, etc.) see lib/esp32-s3-pin-capabilities.md in the same repo.

Pin numbers 1–44 follow a chip-style counter-clockwise convention: pin 1 is the bottom-right contact (USB-C at the top, component side facing you), pins 1–22 climb the right edge, pins 23–44 descend the left edge. The medulla's left header is dual-row (44 pads, 22 unique nets — each row is shorted between its two pads for daughterboard pass-through).

Status legend: HOLD = unassigned, kept free for future use or module-variant compatibility. BLOCKED = physically off-limits (DevKit-side hardware constraint). NC = not wired on this PCB rev (no-connect symbol in schematic).

Pin Silkscreen GPIO Signal Type Notes
1 GND GND Power Ground (bottom of right edge)
2 GND GND Power Ground
3 19 19 NC No USB-C connector on the medulla PCB; GPIO 19 unwired this rev
4 20 20 NC Same as Pin 3
5 21 21 MOTOR_HALL_3 Digital In Motor hall sensor 3
6 47 47 MOTOR_HALL_2 Digital In Motor hall sensor 2
7 48 48 BLOCKED DevKit on-board RGB LED; no external LED on the medulla
8 45 45 HOLD Strap pin (VDD_SPI voltage select). Idle-LOW at boot required.
9 0 0 HOLD Strap pin (BOOT mode, must be HIGH at boot). Was previously CMD_STEER_DIR (moved to GPIO 17 on 2026-05-08 to remove strap risk).
10 35 35 HOLD Octal-PSRAM pin on R8 modules; kept free for N8R2/N8R8 compatibility
11 36 36 HOLD Octal-PSRAM pin on R8 modules. Was briefly CMD_REVERSE; moved to PCF8574 P0 on 2026-05-03.
12 37 37 HOLD Octal-PSRAM pin on R8 modules
13 38 38 HOLD Was SDC_NOT_EMERGENCY until 2026-05-08; signal moved to GPIO 18 (Pin 33) so the gate driver sits next to Q3 on the PCB.
14 39 39 HOLD Free GPIO
15 40 40 CMD_STEER_PWM LEDC PWM Steering motor PWM (Cytron H-bridge)
16 41 41 HOLD Held for future CAN_RX (no CAN transceiver this rev — CAN moved to Orin carrier)
17 42 42 HOLD Held for future CAN_TX (same as Pin 16)
18 2 2 HYDRAULIC_2 ADC1_CH1 Hydraulic pressure sensor 2
19 1 1 PRESSURE_3 ADC1_CH0 Pressure sensor 3
20 RX 44 BLOCKED DevKit USB-UART bridge owns U0RXD; not reclaimable on DevKitC-1
21 TX 43 BLOCKED DevKit USB-UART bridge owns U0TXD
22 GND GND Power Ground (top of right edge)
23 3V3 3V3 Power 3.3 V supply (S3 module LDO from 5 V)
24 3V3 3V3 Power 3.3 V supply
25 EN RST Reset Reset (silkscreened EN)
26 4 4 PEDAL_ACC ADC1_CH3 Accelerator pedal
27 5 5 PEDAL_BRAKE ADC1_CH4 Brake pedal
28 6 6 PRESSURE_1 ADC1_CH5 Pressure sensor 1
29 7 7 PRESSURE_2 ADC1_CH6 Pressure sensor 2
30 15 15 SELECT_THROTTLE Digital Out Drives the U14 MAX4660 SELECT pin (manual/autonomous throttle mux). 10 kΩ pulldown to GND on this net → hardware default = manual passthrough.
31 16 16 MOTOR_HALL_1 Digital In Motor hall sensor 1 (moved from GPIO 37 for N8R8 compatibility)
32 17 17 CMD_STEER_DIR__3V3 Digital Out Steering motor direction (Cytron H-bridge). Moved from GPIO 0 on 2026-05-08 to drop the BOOT-strap risk.
33 18 18 SDC_NOT_EMERGENCY__3V3 Digital Out Drives Q3 (IRLZ44N) gate via R22 (100 Ω). HIGH = Q3 ON = SDC chain return path closed = no emergency. R23 (100 kΩ) gate-pulldown ensures emergency-state at boot until firmware drives HIGH. Moved from GPIO 38 on 2026-05-08.
34 8 8 SDA I²C I²C data — AS5600 steering angle sensor + PCF8574 GPIO expander share the bus
35 3 3 BUZZER Digital Out Debug/status buzzer. Strap pin (JTAG src select), default-high; idle-high at boot is acceptable. Moved from GPIO 36 for N8R8 compatibility.
36 46 46 HOLD Strap pin (ROM-print enable). Default LOW = silent boot — required idle state.
37 9 9 SCL I²C I²C clock (same bus as SDA)
38 10 10 HYDRAULIC_1 ADC1_CH9 Hydraulic pressure sensor 1
39 11 11 MOSI SPI SPI data out → MCP4922 SDI
40 12 12 CLK SPI SPI clock → MCP4922 SCK
41 13 13 MISO SPI SPI data in (unused by MCP4922; available for future SPI peripheral)
42 14 14 CMD_DAC_CS SPI MCP4922 chip select (active low)
43 5V +5V_USB Power 5 V from medulla USB-C VBUS — powers the ESP32 dev board only (split-rail design)
44 GND GND Power Ground (bottom of left edge)

CMD_ACC and CMD_BRAKE go through the external SPI DAC

The classic ESP32 exposed CMD_ACC on a dedicated DAC pin. On the S3 there is no native DAC — both CMD_ACC and CMD_BRAKE are generated by the MCP4922 (dual 12-bit SPI DAC, see hardware decisions below) and ride the existing SPI bus (CS = GPIO 14). Channel A → CMD_ACC (0–5 V direct), Channel B → CMD_BRAKE → ×2 op-amp → 0–10 V for the Festo proportional brake valve.

GPIO restrictions (ESP32-S3)

Strap/boot pins on the S3 — notably GPIO 0, 3, 45, 46 — must be left at safe levels at reset; the table notes mark which assignments are constrained by this. On WROOM-1 modules some of GPIO 26–32 are tied to the SPI flash internally (always reserved). GPIO 33–37 are reserved by octal PSRAM on R8 variants (see the danger block below).

Module suffix: N8R2 preferred; N8R8 still works

The current pinout is N8R8-compatible: GPIO 33–37 are all HOLD (no signal routed), and CMD_REVERSE lives on the PCF8574 instead of GPIO 36. Either module variant fits, with the caveat below kept for context.

Module suffix: N8R2 is the ordered part — octal-PSRAM variants (R8) are tolerated, never preferred

The ordered part is ESP32-S3-WROOM-1-N8R2 (8 MB flash, 2 MB quad PSRAM). Do not substitute an R8 variant (e.g. N16R8), even if the plan is to "ignore" the extra PSRAM in firmware.

On R8 modules, the 8 MB octal PSRAM is hard-wired inside the module package to GPIO 33–37 (SPI0/1 extension pins). Espressif's ESP32-S3-WROOM-1 datasheet marks those pins as not available on R8 variants. This is a physical constraint: disabling PSRAM in sdkconfig does NOT reclaim the pins — the PSRAM die is still electrically attached to those traces, and driving them externally risks bus contention during boot.

Our pinout treats GPIO 33, 35, 37 as SPARE so that an R8 module would physically fit (courtesy compatibility), and keeps the assigned-but-droppable signal (CMD_REVERSE on GPIO 36) flagged as SPARE/CMD_REVERSE to make the loss obvious. That is not an endorsement of R8 — the module standard remains N8R2, and any move to R8 requires re-auditing the pinout.

Valid upgrade path if 8 MB flash isn't enough: N16R2 (16 MB flash, 2 MB quad PSRAM) — zero pinout change, zero GPIO cost. Not N16R8.

See the dv vault kart/kart-medulla/history.md (2026-04-23, 2026-04-29) for full reasoning.

Kart Medulla Interface PCB

Interface PCB hosting the ESP32-S3 module, signal conditioning, the SPI DAC, the manual/autonomous analog mux, and outside-world connectors. Design lineage (EasyEDA .epro project files) lives in the Drive folder formula_24-25-26/dv/kart/kart-medulla/project-backups/.

Hardware Decisions

  • Shutdown circuit (SDC): a single ESP32 GPIO — SDC_NOT_EMERGENCY on GPIO 18 (Pin 33) — drives the gate of Q3 (IRLZ44N) through R22 (100 Ω). When HIGH, Q3 conducts and pulls the kart's SDC_IN_LOW_SIDE to GND, completing the SDC return path → no emergency. When LOW, Q3 is off and the chain breaks → emergency. R23 (100 kΩ) gate-pulldown forces Q3 OFF (= emergency) at boot until firmware actively drives HIGH. The signal name reflects the intent the ESP32 asserts, not the chain's electrical state. There is no separate SDC_STAT readback in this rev — the ESP32 trusts its own command. (The previous design used a relay + a status pin on GPIO 38/39; replaced 2026-05-08 with the MOSFET-only scheme so the gate driver sits next to Q3 on the PCB layout.)
  • Analog command outputs (CMD_ACC, CMD_BRAKE): external MCP4922-E/SL — dual 12-bit SPI DAC. On the existing SPI bus (CS = GPIO 14). VREF tied to the 5 V rail through a 100 Ω + 10 µF RC filter to attenuate ~150 kHz switching ripple from the upstream XW-1224 buck. Channel A → CMD_ACC 0–5 V direct; Channel B → CMD_BRAKE → ×2 op-amp → 0–10 V for the Festo proportional brake valve. Decision history: 2026-04-13 (initial choice was MCP4728 I²C) → 2026-04-17 (switched to MCP4922 SPI because we already had MCP4922 chips on hand and SPI is cleaner for an analog-command bus shared with no other slow devices).
  • Manual/autonomous signal mux (decision 2026-05-01, refined 2026-05-02 / 03 / 08): one MAX4660EUA+T SPDT analog switch (U14) on the PCB muxes the throttle signal between the manual source and the ESP32 DAC output. Brake is NOT muxed — manual mode does not need brake control routed through the ESP32, so the brake DAC output goes directly to the brake valve driver with no switch (decision 2026-05-08). Reverse is NOT muxed via MAX4660 nor via a direct ESP32 GPIO — it is driven by U25 PCF8574T port P0 (I²C GPIO expander) in parallel with the manual reverse button (wired-OR via the motor controller's existing pull-up; the PCF8574's quasi-bidirectional outputs are natively open-drain). This frees GPIO 36 and gains N8R8 compatibility (decision 2026-05-03). The MAX4660's SELECT pin is driven by SELECT_THROTTLE on GPIO 15 with a 10 kΩ pulldown to GND, so the hardware default is manual passthrough whenever the ESP32 is crashed, hung, resetting, or unbooted. Steering is NOT muxed — the ESP32 always drives the Cytron H-bridge directly; in manual mode firmware sets PWM = 0.
  • Cytron H-bridge (steering driver) power (decision 2026-05-01): powered permanently from kart 12 V — NOT switched through the manual/autonomous mode switch. The Cytron's inrush capacitors were browning out the Orin every time the kart was switched into autonomous. The PCB only routes signals (CMD_STEER_PWM, CMD_STEER_DIR) to the Cytron, not power.
  • REVERSE-signal driver to the kart electronics box (U12, decision 2026-04-26): swapping PC357N1J000F optocoupler → BSS123 N-channel logic-level MOSFET (SOT-23) in the next schematic edit. Reason: medulla GND and box GND are bonded through several paths (USB ↔ Orin, signals ↔ Cytron, motor return ↔ battery), so the opto's isolation is moot — MOSFET is cheaper, smaller, faster, and doesn't age. Board not yet manufactured, swap is free. Drives the box's REVERSE wire (5 V via ~60 kΩ internal pull-up; pull to 0 V to engage reverse).
  • Pressure sensor inputs (3× Festo, 24 V): voltage divider + input clamp / TVS protection on each channel to bring the signal into the S3's ADC range (≤ 3.3 V).
  • Hydraulic pressure sensor inputs (2×): routed to ADC1_CH9 (GPIO 10) and ADC1_CH1 (GPIO 2).
  • Hall sensor inputs (3× 5 V): dedicated level translator (NOT the optocoupler) to 3.3 V before the GPIO pins.
  • Orin link: native USB-OTG on GPIO 19/20 (D∓). No USB-UART bridge chip.
  • Power architecture: kart 12 V → external XW-1224 buck → 5 V kart-wide rail → medulla 5 V (H1.21) → ESP32-S3 module LDO → 3.3 V. The medulla can alternatively be powered from an on-board LM2596SX-ADJ buck (qty 8 in stock) if the kart-wide 5 V rail is unavailable. MCP4922 VDD and MAX4660 Vcc both run from the same 5 V rail.

Connector Pinout (Outside World)

The main connector is a set of green push-in headers labeled CN1..CN4 in the schematic. Signal names follow the Net Name Nomenclature convention.

Kart Medulla main connector (green push-in)

Connector Pin Signal Notes
CN1 1 MOTOR_HALL_3__5V Motor hall sensor 3 (5 V, level-shifted on PCB)
CN1 2 MOTOR_HALL_2__5V Motor hall sensor 2 (5 V, level-shifted on PCB)
CN1 3 MOTOR_HALL_1__5V Motor hall sensor 1 (5 V, level-shifted on PCB)
CN2 1 PRESSURE_1__24V Festo pressure sensor 1 (24 V, divided + clamped on PCB)
CN2 2 PRESSURE_2__24V Festo pressure sensor 2 (24 V, divided + clamped on PCB)
CN2 3 PRESSURE_3__24V Festo pressure sensor 3 (24 V, divided + clamped on PCB)
CN3 1 GND
CN3 2 CMD_STEER_DIR__3V3 Steering direction command
CN3 3 CMD_STEER__PWM_3V3 Steering PWM command (to Cytron H-bridge)
CN4 1 3V3
CN4 2 I2C_STEER_SDA__I2C AS5600 steering angle sensor data
CN4 3 I2C_STEER_SCL__I2C AS5600 steering angle sensor clock