Skip to content

Board and pins

The target is the Hackaday Supercon 2025 Communicator badge, also issued at Hackaday Europe 2026. Three optional peripherals share its two expansion headers, and between them they use every signal pin on both:

PeripheralPinsHeaderAddon board socket
Compass (ICM-20948)SDA 4, SCL 5J8, SAO I2CJ4
GPS (ATGM336H)ESP RX 7, ESP TX 6J8, SAO_GPIO1 and SAO_GPIO2J3
Vibration motor12J6, IO12 padJ1 pin 1

All three peripherals are enabled by default, and every pin in the table is a setting, so none of the assignment is fixed. Wire a peripheral to any free pair and set the pins to match.

Two labels in that table are easy to get wrong. The SAO numbering runs against the ESP numbering: SAO_GPIO1 is ESP GPIO7 and SAO_GPIO2 is ESP GPIO6. The J6 silkscreen “IO12” reads as “IO10” at a glance; GPIO10 is the LoRa antenna switch and is not on J6 at all.

Neither header is fitted from the factory, so both are bare pads and you solder the header or the wires yourself. Both carry the 3.3 V rail only: there is no 5 V on either, and the ESP32-S3 pins are not 5 V tolerant, so every peripheral has to be a 3.3 V part. A back-side PCB that breaks both headers out to JST connectors is documented in Internal addon board.

Every pin number is defined in one place, components/bsp/include/board_pins.h. If a page here disagrees with that file, the file is right.

The GPS default moved from J6 to the SAO spare GPIO pair

Section titled “The GPS default moved from J6 to the SAO spare GPIO pair”

The GPS UART used to default to J6, ESP RX on IO12 and ESP TX on IO11. It now defaults to the SAO header’s two spare signal pins, ESP RX on GPIO7 and ESP TX on GPIO6, because the compass took the SAO I2C pair and the motor took J6’s IO12 pad.

J6 still works, since both ends are settings. Set gps_rx_pin to 12 and gps_tx_pin to 11, and move or disable the motor first, because it defaults to that same IO12 pad. J6 is a 4-pin connector whose pad order is 3V3, IO11 (ESP TX), IO12 (ESP RX), GND.

PartDetail
MCUESP32-S3-WROOM, 8 MB octal PSRAM, 16 MB flash
DisplayNV3007 TFT, 428x142 used rotated, SPI at 40 MHz, RGB565 byte swapped
RadioSemtech SX1262 LoRa, TCXO on DIO3 at 1.7 V, DIO2 plus GPIO10 RF switch
KeyboardTCA8418 I2C matrix controller, interrupt driven
GPS (optional)ATGM336H NMEA over UART on the SAO spare GPIO pair
Vibration motor (optional)Driven high on a spare GPIO, J6’s IO12 by default
IMU (optional)TDK ICM-20948 accelerometer, gyroscope and AK09916 magnetometer on I2C, header J8
ExpansionSAO v2 header (Simple Add-On): 3V3, GND, a second I2C bus and two GPIO
PowerLiPo with MCP73831 charger, backlight PWM on GPIO2
FunctionPins
Display SPIMOSI 21, SCLK 38, DC 39, RST 40, CS 41, TE 42, backlight 2
Radio SPINSS 17, MOSI 3, SCLK 8, MISO 9, RST 18, BUSY 15, DIO1 16, RF switch 10
Keyboard I2CSCL 14, SDA 47, INT 13, RST 48
GPS UART (SAO GPIO)ESP RX 7 (from GPS TX), ESP TX 6 (to GPS RX)
SAO header (I2C + GPIO)SDA 4, SCL 5, GPIO1 7, GPIO2 6
Vibration motor12 (J6 IO12 pad), high vibrates
Debug LED1, active low

The display and radio sit on separate SPI hosts, the display on SPI2 and the radio on SPI3, so they do not share a bus.

This badge has no battery sense circuit. The VBAT net has four pads: the charger’s battery terminal, a 10 uF cap, the drain of the load switch, and the JST battery connector. It never reaches an MCU pin, and there is no resistor divider anywhere on the board. There is no charge-status line and no USB-present line either, because the charger’s STAT pin drives only the optional charge LED and VBUS stops at the USB-C connector, the charger and the load switch. Nothing about the pack is readable as the badge ships, so battery reporting is off by default and the sidebar hides the icon rather than showing an empty one.

If you fit a divider by hand, turn on bat_enabled and set bat_pin and bat_div_x100 in Settings; no firmware edit is needed. Every ADC1 pin (GPIO1 to GPIO10) is committed, so the free candidates are GPIO11 and GPIO12 on the J6 pads, and both sit on ADC2, which the ESP32-S3 cannot read while WiFi is running. GPIO12 is also this firmware’s default motor pin. Nobody has fitted one, so treat that path as untested.

The stock badge has no magnetometer and no real-time clock. Without an IMU the heading-up views fall back to GPS course over ground, so they only orient while you move, and the clock is set from the GPS time when a fix is available. Fitting an ICM-20948 on the SAO header gives a tilt-compensated compass that holds while you stand still, and the heading-up views and bearing needles then take their heading from it.

The three optional peripherals have their own pages

Section titled “The three optional peripherals have their own pages”

Each one is a page rather than a section here, because fitting one is a soldering job with its own pinout, its own settings and its own failure modes:

The SAO header itself, J8, is a standard SAO v2 connector (the Hackaday “Simple Add-On”, a 2x3 0.1” header) carrying 3V3, GND, a second I2C bus and two spare GPIO:

SAO signalJ8 pinESP32-S3
3V31-
GND2-
SDA3GPIO4
SCL4GPIO5
GPIO15GPIO7
GPIO26GPIO6

Note that GPIO4 and GPIO5 are pins 3 and 4, not 4 and 5, and that the SAO numbering runs against the ESP numbering. In the back view J8 pin 1 is the top of the rightmost column. This I2C bus is ESP32-S3 unit 1, independent of the keyboard bus on unit 0, so an add-on never contends with the keyboard.

The hardware/case/ folder has a 3D-printable case that adds a bay for the GPS module so you can carry the badge with the receiver attached. See Case and enclosure for the file and printing notes.