Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AXON-C v1.0

An ESP32-S3 development board with two USB-C ports — one wired straight to the chip's native USB peripheral, one behind an FT231XQ USB-to-UART bridge — on a 68.6 × 25.4 mm 4-layer impedance-controlled PCB with 90 Ω differential USB routing.

Top

AXON-C v1.0 — top side, fully assembled

Bottom — the silkscreen carries AXON-C v1.0, (C) 2026 HAI NGUYEN, github.com/HaiNguyenEE and eerfLab.com, plus the full pin legend for both 24-pin headers, so the board tells you what every pin does without a datasheet in hand.

AXON-C v1.0 — bottom side, with the full J1 / J2 pin legend

Both are Altium Designer renders of ESP32wUSB.PcbDoc — the schematic, the 4-layer impedance-controlled stackup, the placement, the routing and the fabrication output are all original work.

esp32-s3 · usb-c · pcb-design · high-speed-pcb · differential-pair · signal-integrity · controlled-impedance · altium-designer · hardware-design · ft231x · platformio · jlcpcb · 4-layer-pcb · reverse-engineering

The point of the two ports is that you never have to choose. The ESP port gives you native USB (CDC, MSC, HID, TinyUSB) straight off GPIO19/20. The UART port gives you a classic FTDI console with hardware DTR/RTS auto-reset, so esptool can put the chip into download mode without you touching a button. Plug in either one, or both at once, and a 3-pin jumper decides which port feeds the 5 V rail.

Everything in this repository — the pinout tables, the diagram, the firmware — was generated from the actual PCB source file rather than typed out by hand. See How this documentation was produced.


Board at a glance

MCU ESP32-S3FN8 inside an ESP32-S3-MINI-1-N8 module — dual-core Xtensa LX7 @ 240 MHz
Flash 8 MB, in package
PSRAM none
Radio 2.4 GHz Wi-Fi b/g/n + Bluetooth LE 5, PCB antenna
USB 2 × USB-C receptacle (Molex 2171790001), both sink-only (5.1 kΩ CC pulldowns)
USB-UART FTDI FT231XQ with DTR/RTS auto-program circuit
Regulator TI TL1963A-33 — 1.5 A LDO, 5 V → 3.3 V
I/O 40 GPIO on two 24-pin 2.54 mm headers (Molex KK254)
Protection 6 × AQ3045-01ETG ESD suppressors on both VBUS rails and both USB data pairs
Board 68.6 × 25.4 mm (2.700 × 1.000 in), 4 layers, 1.56 mm
Stackup Impedance-controlled — 0.0994 mm prepreg / 1.265 mm core, 1 oz outer, ½ oz inner, solid internal GND plane
High speed 3 differential pairs in a DIFF90 class — 6.03 mil / 8 mil, ≈90 Ω differential

Board map

Top view with silkscreen

Silkscreen Part What it is
U1 ESP32-S3-MINI-1-N8 The module. PCB antenna hangs off the right edge.
U2 FT231XQ USB-to-UART bridge for the UART port
U3 TL1963A-33 3.3 V LDO
UART J3 USB-C into the FT231XQ
ESP J4 USB-C into the chip's native USB (GPIO19/20)
J1 24-pin header Top row — GPIO0…GPIO18, +3V3, +5V, GND
J2 24-pin header Bottom row — GPIO19…GPIO48, UART0, EN, +3V3, GND
RESET SW2 Pulls CHIP_PU low
USER SW1 Pulls GPIO0 low — hold at reset for download mode
POW D1, green Hard-wired to +3V3. On whenever the rail is up. Not software-controllable.
USER D2, red On GPIO2, active LOW
REG/3V3 J5 2-pin jumper: LDO output → 3.3 V rail
USB1/+5V/USB2 J6 3-pin jumper: which USB-C port feeds the 5 V rail

Read this before you power it up

Two jumpers have to be right or nothing happens.

J5 (REG3V3) connects the LDO output to the board's 3.3 V rail. With the shunt off, the ESP32-S3 has no power at all. Leaving it open is deliberate: it turns J5 into a current-measurement point for the entire 3.3 V rail, which is how you measure deep-sleep current without cutting a trace. Put a multimeter in series across J5 and you are reading everything downstream.

J6 (USB1+5VUSB2) picks which USB-C port supplies 5 V. USB1 is the UART port (J3), USB2 is the ESP port (J4). The centre pin is the board's +5V node, which feeds the LDO and header pin J1-22. There is no ORing diode and no power multiplexer here — the shunt is the only thing joining the two VBUS rails, so never bridge both sides at once with two chargers plugged in.

Only the UART port's VBUS is monitored: a 4.7 kΩ / 10 kΩ divider from +5V_VBUS1 drives the FT231XQ's VBUS-sense input. The ESP port's VBUS has no sense divider.


Pinout

AXON-C pinout

Full tables, including every net name as it appears in the PCB file, are in docs/PINOUT.md. The short version of what you need to watch out for:

Pin Why it is not just a GPIO
GPIO0 Strapping (boot mode). Wired to the USER button through a 27 Ω series resistor, and to the auto-program transistor.
GPIO2 Drives the red USER LED. Usable as I/O, but it will flicker the LED.
GPIO3, GPIO45, GPIO46 Strapping pins — JTAG source, VDD_SPI level, ROM boot log.
GPIO19, GPIO20 Native USB D− / D+. Broken out on J2-21 / J2-20, but they are the ESP port's data pair.
GPIO43, GPIO44 UART0 TX / RX, driven by the FT231XQ. Free only if you leave the UART port unplugged.
GPIO26 SPICS1 by default. Free on the -N8 module used here; not free on -N4R2.
GPIO33 – GPIO37 Free on -N8. Reserved for octal flash/PSRAM on -N8R8 and similar parts.

ADC1 is GPIO1–GPIO10, ADC2 is GPIO11–GPIO20. ADC2 stops working while Wi-Fi is active, so if you need analog inputs alongside Wi-Fi, stay on J1 pins 4 through 13.


Getting code onto it

Native USB (ESP port, J4). Set J6 to USB2, plug into ESP, and the S3's USB-serial-JTAG peripheral enumerates on its own. This is the simpler path and needs no driver.

FTDI (UART port, J3). Set J6 to USB1, plug into UART. The FT231XQ's DTR and RTS lines drive two SS8050 transistors into CHIP_PU and GPIO0, which is the standard auto-reset arrangement — esptool toggles the modem lines and the chip drops into download mode by itself. No button pressing.

By hand, if you ever need it: hold USER, tap RESET, release USER.


Firmware

firmware/ holds a bring-up and self-test application written for this board specifically. It is not a blink sketch — it is the thing you run on a freshly assembled board to find out whether the assembly house did their job.

On boot it prints the chip revision, flash size, MAC and reset reason, then heartbeats the USER LED. Over the serial console it gives you an interactive shell:

axon> test          run the full self-test
axon> info          chip / flash / heap / MAC / reset reason
axon> led blink 100 drive the USER LED (remembering it is active LOW)
axon> btn           live state of the USER button
axon> scan          drive and read back every free header GPIO
axon> adc 4         sample an ADC pin
axon> i2c 8 9       scan an I2C bus on any two header pins
axon> wifi          scan for networks — proves the PCB antenna works
axon> bootloader    reboot into ROM download mode

Two PlatformIO environments are provided, one per USB port:

cd firmware
pio run -e axon-c-usb  -t upload -t monitor   # ESP port, native USB CDC
pio run -e axon-c-uart -t upload -t monitor   # UART port, FT231XQ

Build and wiring details are in firmware/README.md; the bring-up procedure for a brand-new board — what to measure, in what order, before you ever apply 5 V — is in docs/BRINGUP.md.


High-speed design

Three USB 2.0 differential pairs cross a 4-layer controlled-impedance stackup. The full signal-integrity record — rules, measured geometry, impedance, and return-path analysis — is in docs/HIGHSPEED.md. The headline numbers, all extracted from the PCB file rather than claimed:

Stackup. 0.0994 mm PP-006 prepreg (Dk 4.1) / 1.265 mm FR-4 core / 1 oz outer copper / ½ oz inner, 1.5642 mm total. That is JLCPCB's JLC04161H-3313 impedance-controlled stackup exactly. L3 is a solid, unbroken GND plane; the thin outer dielectric is what lets a 90 Ω pair be routed at a manufacturable 6 mil width.

The DIFF90 class. ESP_USB, CON1_USB and CON2_USB are constrained by a DiffPairsRouting rule at 8 mil gap, 6.03 mil outer-layer width, 5.1 mil on mid-layer 1, and a 500 mil max uncoupled length — plus a dedicated 6 mil clearance override that gives the pairs more room from other nets than the board's generic 5 mil. Routing corners are 45°, and vias connect to planes directly rather than through thermal spokes.

What was actually achieved, measured from Tracks6/Arcs6/Vias6:

Pair Edge-to-edge gap Est. Z_diff Intra-pair skew
ESP_USB 8.00 mil — exactly on rule 91.8 Ω 1.4 ps
CON2_USB 8.00 mil — exactly on rule 90.0 Ω 9.2 ps*
FTDI_USB 13.68 mil — outside the class 97.1 Ω 1.2 ps

* connector-side nets include the USB-C A6↔B6 / A7↔B7 flip-support tie stubs, so raw net length over-reports the electrical path. The stub-free pairs are the honest figures.

Return paths. Every trace segment's midpoint was tested against the 19 copper pours, in POURINDEX order so the result is the copper that is actually there. Eight of ten pair legs run over 100% GND. CON2_USB — the longest pair, 56–57 mm — does not: about 40% of each leg crosses onto the +3V3 and +3V3_REG pours on L2, three boundary crossings per leg, with the nearest GND stitching via 1.9–3.9 mm away. At 12 Mbps that is harmless, and the document says so plainly rather than dressing it up. At High Speed it would not be, and the fix is free on a respin.


Gallery

Top, angled Front, showing both USB-C receptacles
Layer 2 — the power pour. Mid Layer 1 carries four polygons: two +3V3, one +3V3_REG and one GND. The split lines visible in the copper are rail boundaries, not clearance gaps — and they are why CON2_USB changes reference plane three times per leg. Layer 3 — the ground plane. Internal Plane 1, a single GND split plane covering the whole board. Both of these are internal layers — the bottom side is at the top of this page.
Soldermask off — bare copper and ENIG Top, from the USB-C end
All layers, 2D Assembly drawing with net names on pads
Bottom-layer routing Top paste / stencil apertures

Repository layout

axon-c/
├── docs/
│   ├── PINOUT.md      every pin of J1 and J2, net names verbatim from the PCB
│   ├── HARDWARE.md    power tree, auto-program circuit, USB paths, errata, fitted/DNP
│   ├── HIGHSPEED.md   differential pairs, stackup, impedance, return-path analysis
│   └── BRINGUP.md     first-power-up procedure for a freshly assembled board
├── firmware/
│   ├── platformio.ini
│   ├── README.md
│   └── src/
│       ├── axon_c_board.h   pin map, generated from the PCB netlist
│       └── main.cpp         bring-up self-test and serial shell
├── hardware/
│   ├── netlist.json           component → pin → net, extracted from the PcbDoc
│   ├── NETLIST.md             the same thing, readable
│   ├── extract_netlist.py     the netlist extractor, so you can re-run it
│   ├── highspeed.json         stackup, rules, pair geometry, pours, impedance
│   └── extract_highspeed.py   the high-speed extractor
├── tools/
│   └── mkpinout.py            draws the pinout diagram from real PCB coordinates
└── images/

How this documentation was produced

The pin tables in this repository were not copied off a schematic by eye. ESP32wUSB.PcbDoc is an OLE2 compound document, and its Nets6, Components6 and Pads6 streams were parsed directly to recover which net lands on which pad of which component. hardware/extract_netlist.py is that parser; run it against the PCB file and you get hardware/netlist.json back.

The decode was validated three ways before any of it was trusted: the 303 pad records consume exactly 82,330 of 82,330 bytes with nothing left over, the highest net index seen is 74 against 75 declared nets, and the highest component index is 65 against 66 declared components. An off-by-one anywhere in the record layout would have broken all three.

That mattered more than it sounds. Nothing in the board renders tells you which way round the USER LED sits, and there is no schematic image in the output folder. Reading it out of the PCB file gave the answer: current runs from +3V3 through R19 into D2 and out to GPIO2, so the LED lights when GPIO2 is driven LOW. A blink sketch written the obvious way, with digitalWrite(LED, HIGH), would have run inverted and nobody would have noticed for a while.

The diagram in images/pinout.svg is generated from the same extracted data by tools/mkpinout.py, including the board silhouette — the module, buttons, LEDs, jumpers and USB receptacles are all drawn at their real coordinates from the PCB file.

The same approach produced the high-speed analysis. hardware/extract_highspeed.py goes further into the format — Rules6 records carry a two-byte prefix the other streams do not, Arcs6 uses a different record-type byte from Tracks6, dimensions are stored as strings with unit suffixes like '1.378mil', and the layer stack has to be walked through the LAYER{n}NEXT chain because reading LAYER1..LAYER32 in order returns 28 unused FR-4 defaults. Track, arc and via geometry then gives real routed lengths, real pair gaps and real via counts; Polygons6 gives the copper pours, and resolving them in POURINDEX order gives each trace's actual reference plane.

That is what turned "the pairs look fine" into two findings that were not visible in any render: the measured 8.00 mil gaps landing exactly on the 8 mil rule, and CON2_USB crossing three reference-plane boundaries per leg. It is also what caught E4 — the swapped USB data pair at the FT231XQ, which is a fault you cannot see on a board photo and would otherwise have found after paying for assembly.


Contributors

See CONTRIBUTORS.md.

Board design, layout, firmware, documentation, netlist extraction and pinout diagram by Hai Nguyen (eerfLab).

Licence

Firmware and documentation are MIT licensed — see LICENSE.

The PCB source files are not redistributed here. The renders and derived pin data in this repository describe one physical build of the board.

About

ESP32-S3 dual USB-C dev board - 4-layer impedance-controlled PCB with 90 ohm differential USB routing, bring-up firmware, and PCB data extracted straight from the Altium file

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages