16: ESP32 replacement board: notes behind the wiring rules
For someone who wants to know why the build guide says what it says, or who is about to change a
pin. The wiring, the parts list and the pins to avoid are in the guide:
docs/guide/ESP32-Replacement-Build.md and
docs/guide/Hardware-and-Wiring.md. This page keeps
the reasons and the measurements behind those rules.
Most of them were found on the ESP32 Matter firmware, which is retired (tag matter-final,
docs/guide/Matter-Firmware-Retired.md). They are
facts about the board and the bus, so they hold for the ESPHome firmware on the same hardware.
Pins
| Signal | ESP32-C3 SuperMini | classic ESP32-D0WDQ6 | transceiver |
|---|---|---|---|
| TX | GPIO5 | GPIO19 | DI |
| RX | GPIO6 | GPIO18 | RO |
| DE | GPIO10 | GPIO4 | DE+RE tied |
- C3 DE moved from GPIO7 to GPIO10 on 2026-08-10 after a wiring fault shorted the DE/EN line to 3V3 on the first board. The C3 pin set avoids the strapping pins (2/8/9), USB (18/19) and UART0 (20/21). GPIO5/6 double as the JTAG pins MTDI/MTCK, which is harmless: debugging goes through the C3’s built-in USB-Serial/JTAG, so external JTAG is never muxed in.
- GPIO16/17 on a WROVER or D0WDQ6 module are bonded to the PSRAM die and dead as I/O even with SPIRAM disabled. An internal loopback test passes on them, because it bypasses the pads.
- Firmware and wiring must agree on DE. If they disagree the transceiver never gets transmit-enable: the node sends, nothing reaches the bus, and nothing comes back.
DE timing
The transceiver’s DE line has to be high before the first bit and stay high until the last stop
bit has left the wire. The values that work on this bus, validated on hardware: 5 ms of settle
before the first byte, and after the last byte the wire time of the frame plus 25 ms before DE
drops. The ESPHome component does this itself when de_pin is set
(firmware/esphome/components/hisense_ac/).
The trap behind the second number: on ESP-IDF, writing to the UART only queues bytes into the
FIFO. Dropping DE when the write call returns cuts the frame short. The retired firmware waited
for uart_wait_tx_done(). The ESPHome component waits out the frame’s wire time, counted from
before the write, so the release is right on a platform whose write blocks too
(01-component-architecture.md).
The alternative, letting the UART peripheral drive DE (flow_control_pin on ESPHome’s uart:),
has not been validated against this A/C.
The stock module’s own DE behaviour (PA_17, the toggle sequence, why it must be set up from a
task) is in
reverse-engineering/docs/10-stock-fw-init-and-comms.md.
Why a brand-new board must be erased first
A normal flash writes the bootloader, the partition table and the application. It leaves the NVS partition untouched, including whatever the vendor’s factory test firmware left there. A Wi-Fi station config in that NVS is picked up by the new firmware as if it were its own.
On the Matter firmware this made every commissioning attempt fail within about 150 ms, with no
radio activity, as CHIP Error 0x000000AC: the Wi-Fi driver adopted the stale network and then
refused any other SSID. It read like a credentials problem when the radio was never involved.
Observed on a new ESP32-C3 SuperMini, 2026-08-07.
The rule for any firmware: erase a new board once before the first flash
(dev.py erase --port P, or esptool.py erase_flash). Never erase a board that is already
running, since that throws away its settings. dev.py erase asks for a typed confirmation and
tells esptool which chip to expect
(firmware/scripts/README.md).
Wi-Fi power saving
packages/node.yaml sets power_save_mode: none. ESPHome’s default on ESP32 is light, where
the radio sleeps between beacons. On an ESP32-C3 node with a weak signal (about -77 dBm) that
default gave 65 % ping loss, an API connection that dropped about once a minute, and commands
that did not reach the unit. With none: 100 of 100 pings and a command taken in 0.5 s
(2026-10-10). The retired ESP32 Matter build forced the radio on for the same reason. ESP8266
and LibreTiny already default to none.
The cost is current: the radio no longer sleeps between beacons. The setting is a substitution,
wifi_power_save, for a board whose supply cannot carry that.
A supply that dips when Wi-Fi starts
On a SuperMini whose 3.3 V rail dips when Wi-Fi starts at full power, a new image resets on its
first boot and the bootloader rolls the update back. The symptom, and the cap on the transmit
power for a board that has to run as it is, are in
firmware/esphome/README.md.
Ground loop
While the board is powered from a laptop’s USB port, connect only A and B to the A/C. The A/C’s ground is mains-earthed, and so is the laptop’s. Joining them through the board browns it out: RTCWDT resets and flash-read errors. Ground and 5 V from the A/C connector go on when the laptop is unplugged.
Envelope bytes [7]/[8] are the A/C’s device type
They are the unit’s device type and sub type, static per model, and not a session token (measured
2026-07-16). The node reads them from the DevType (0x0A) reply’s inner [3]/[4] and stamps them
on later frames. The default before they are learned is 01 01. Do not stamp that reply’s
envelope [9]/[10]: it is 00 00 on the 0x0A frame, and a firmware that did so made the A/C
reject every frame until it was replaced. See
reverse-engineering/docs/10-stock-fw-init-and-comms.md,
section 4.5. The ESPHome build shows the learned bytes as the AC device type text sensor.
The whole reply was captured on 2026-10-12 from a deployed ESP32-C3 node, which settles its length and checksum:
F4 F5 01 40 0D 01 00 FE 01 00 00 01 00 0A 04 01 01 01 01 60 F4 FB
22 bytes: the LEN byte (0D, 13) plus 9, as for every frame. The device type 01 01 is at
bytes 16 and 17, which are inner [3]/[4] (the inner payload starts at byte 13). Bytes 9 and 10
are 00 00. Bytes 18 and 19 (01 60) are the 16-bit sum of bytes 2 to 17, the same checksum
rule as the other frames.
Task stack units differ by platform
Recorded for anyone who builds the reference driver (firmware/test/reference-driver/) on a new
platform, as firmware/esp32-recon/ does: the bus task’s stack depth of 1024 is words on
AmebaZ2 (4 KB) and bytes on ESP-IDF (1 KB). On ESP-IDF the status callback’s printf overflowed
it into a crash loop. The recon tool’s hisense_rs485 component sets
HISENSE_BUS_TASK_STACK=4096 for that reason.