GPIO & boards¶
This page tells you which ESP32 pins AWTRIX NG can use, how to enter your own pin map, and what happens when a pin is not allowed.
There is one firmware per chip, not per board. A Ulanzi TC001, an AWTRIX 2 mainboard and a panel you wired yourself all run the same firmware file. Only the pin map is different, and you set it in the web UI or through the API.
Changes apply after a reboot
When you save a new pin map, AWTRIX stores it and answers 200. It keeps using the old map
until you restart it.
Set the pin map¶
- Open the web UI and go to System → GPIO.
- Pick a pin for each part you have wired. Choose not connected for parts you do not have.
- Save.
- Restart AWTRIX.
The dropdowns only offer pins that work for that part on your chip. See The web UI's pin dropdowns.
To do the same through the API, see Reading and writing the map.
Rules come from the chip¶
The ESP32 has these pin rules:
| ESP32 | |
|---|---|
| GPIO numbers | 0–39 |
| Input-only | 34–39 |
| ADC1 (analog input) | 32–39 |
| Reserved | 6–11 (SPI flash) |
| LED matrix data | 2, 4, 5, 13, 14, 15, 16, 18, 21, 25, 26, 27, 32, 33 |
| Strapping | 0, 2, 5, 12, 15 |
| Wake from deep sleep | 0, 2, 4, 12–15, 25–27, 32–39 |
For example, GPIO 34 is a good battery input, but it cannot drive a button or the buzzer.
Strapping pins are read by the chip at power-on to decide how it starts. If you connect something that pulls such a pin high or low, the board may not start. AWTRIX accepts these pins, so the choice is yours.
Wake pins matter only for the select button (pinBtnSelect). If the select button is on a
wake pin, pressing it ends a POST /api/v1/device/sleep early.
On any other pin the button works normally while AWTRIX is awake, but it cannot wake it up. The
sleep then runs for its full durationMs. The left and right buttons never wake AWTRIX.
AWTRIX reports the rules for its own chip under gpio in
GET /api/v1/capabilities, and the chip type as soc in
device state. Use those values in your own tools instead of copying this table.
The pin map¶
Each field holds a GPIO number – not a label printed on the board and not a Dx pin name.
-1 means "not connected".
The defaults are the Ulanzi TC001 wiring.
| Key | Type | Default | -1 allowed |
Meaning |
|---|---|---|---|---|
pinMatrix |
int | 32 |
no | LED matrix data line. Must be on the LED matrix list. |
pinBtnLeft |
int | 26 |
yes | Left button. Wire it to ground; pressed = LOW. AWTRIX turns on the internal pull-up. |
pinBtnSelect |
int | 27 |
yes | Select (middle) button, wired like the left one. Also wakes AWTRIX from deep sleep – see the wake row above. |
pinBtnRight |
int | 14 |
yes | Right button, wired like the left one. |
pinBattery |
int | 34 |
yes | Battery voltage divider tap. Must be ADC1. |
pinLdr |
int | 35 |
yes | Light sensor (LDR) tap. Must be ADC1. |
pinBuzzer |
int | 15 |
yes | Passive buzzer. |
pinI2cSda |
int | 21 |
yes | I²C data line for the temperature/humidity sensor. |
pinI2cScl |
int | 22 |
yes | I²C clock line. |
pinDfRx |
int | 23 |
yes | DFPlayer Mini serial RX. Used only while dfplayer is true, but always checked when set. |
pinDfTx |
int | 18 |
yes | DFPlayer Mini serial TX. Used only while dfplayer is true, but always checked when set. |
The ESP32 has no I²S audio output. Leave pinI2sBclk, pinI2sLrclk, pinI2sDout, pinI2sMclk
and pinAmpEnable at -1. The web UI does not show them.
One more field belongs to the pin map:
| Key | Type | Default | Meaning |
|---|---|---|---|
dfplayer |
bool | false |
Switches the DFPlayer Mini on. With true and both pinDfRx and pinDfTx set, AWTRIX plays numbered tracks on the DFPlayer Mini. The passive buzzer on pinBuzzer keeps working either way. The DF pins are checked the same way whether this is on or off. |
What -1 does¶
| Field | What happens with -1 |
|---|---|
pinMatrix |
Rejected. You cannot switch off the matrix. |
pinBtnLeft / pinBtnSelect / pinBtnRight |
The button always reads as not pressed. |
pinBattery |
No battery support: batteryPercent, batteryVoltage, batteryPinMillivolts and lowBattery are left out of device state, the battery entities disappear from Home Assistant, and the Battery app leaves the rotation. |
pinLdr |
No light sensor: lightLevel and ldrRaw are left out of device state, and the Light level and Brightness mode entities disappear from Home Assistant. autoBrightness has no effect – the panel uses brightness – and the web UI hides the auto-brightness switch. |
pinBuzzer |
No buzzer. Melodies and RTTTL tunes make no sound. |
pinI2cSda / pinI2cScl |
No sensor. Temperature and humidity stay empty. |
pinDfRx / pinDfTx |
No DFPlayer. The buzzer is not affected. |
Board presets¶
System → GPIO in the web UI has two preset buttons. A preset fills in the form but does not save it, so you can check the values first.
The stock Ulanzi hardware. These are also the defaults: a new AWTRIX, or one whose stored map is not valid, starts with exactly this map.
| Field | Value |
|---|---|
pinMatrix |
32 |
pinBtnLeft |
26 |
pinBtnSelect |
27 |
pinBtnRight |
14 |
pinBattery |
34 |
pinLdr |
35 |
pinBuzzer |
15 |
pinI2cSda |
21 |
pinI2cScl |
22 |
pinDfRx |
23 |
pinDfTx |
18 |
dfplayer |
false |
An AWTRIX 2 mainboard with a WeMos D1 mini32. The board is labeled with D-names, but AWTRIX needs GPIO numbers, so the table shows both. There is no battery and no buzzer; a DFPlayer Mini plays the sound.
| Field | Value | Board label |
|---|---|---|
pinMatrix |
21 |
D2 |
pinBtnLeft |
26 |
D0 |
pinBtnSelect |
16 |
D4 |
pinBtnRight |
5 |
D8 |
pinBattery |
-1 |
– (no battery) |
pinLdr |
36 |
A0 |
pinBuzzer |
-1 |
– (DFPlayer instead) |
pinI2cSda |
17 |
D3 |
pinI2cScl |
22 |
D1 |
pinDfRx |
23 |
|
pinDfTx |
18 |
|
dfplayer |
true |
pinMatrix: 21 is the same pin as the default pinI2cSda: 21. If you change only
pinMatrix, the request is rejected. Change both in the same request – send the whole map
as shown in Write a complete map.
Validation rules¶
AWTRIX checks every pin map before it stores anything. A rejected request changes nothing – not even the other fields in the same request.
The exact values for your chip are under gpio in
GET /api/v1/capabilities.
AWTRIX combines the fields you send with the stored map and checks the result in this order:
-
Each value on its own. Every
pin*value must be an integer:-1or a GPIO from0to39. If not, you get422 validationFailedwith the field name: -
The rules 1 to 6 below. The first failure is returned as
400 invalidPinConfig:
Rule 1 is checked first. Rules 2 to 4 are then checked pin by pin in the order of the table
above, starting with pinMatrix. Rules 5 and 6 come last. So you always see only one problem at
a time; fix it and send again.
1. Matrix pin whitelist¶
pinMatrix must be on the LED matrix data list in the chip table.
Any other value, including -1, is rejected, and the message lists the allowed pins:
2. Valid GPIO range¶
Each pin that is not -1 must exist on the chip.
3. Reserved pins¶
The chip uses these pins itself. The message says what for:
| Range | Reserved for |
|---|---|
6–11 |
the SPI flash |
4. Input-only pins¶
GPIO 34–39 cannot drive an output and have no internal pull-up. Fields that must drive a line
or need a pull-up are rejected on these pins.
| Field | Needs output? | Why |
|---|---|---|
pinMatrix |
yes | Drives the LED data line. |
pinBtnLeft, pinBtnSelect, pinBtnRight |
yes | Need the internal pull-up. |
pinBuzzer |
yes | Drives the buzzer. |
pinI2cSda, pinI2cScl |
yes | I²C drives both lines. |
pinDfTx |
yes | AWTRIX sends on it. |
pinBattery |
no | Analog input only. |
pinLdr |
no | Analog input only. |
pinDfRx |
no | AWTRIX receives on it. |
5. ADC1 requirement¶
pinBattery and pinLdr must be ADC1 pins: GPIO 32–39. ADC2 pins
cannot be read while Wi-Fi is on.
6. No duplicates¶
Each pin that is not -1 may be used only once.
If one of the two fields is pinMatrix, the message also tells you how to fix it:
Both changes have to be in the same request, because either one on its own still collides.
Coming from AWTRIX 2, set pinI2cSda to 17 together with pinMatrix 21.
Any number of fields can be -1 at the same time. A pin that is set is always checked, even when
the part behind it is switched off. For example, pinDfTx: 34 is rejected as input-only even
with dfplayer: false, and pinBuzzer: 23 collides with the default pinDfRx. If you have no DFPlayer, set both DF pins to -1.
Reading and writing the map¶
The pin fields are normal keys of the system configuration.
Read the current map¶
Write a complete map¶
Send the whole map in one request. Then every rule is checked against the values you want, not a mix of new and stored values.
curl -X PUT http://<awtrix-ip>/api/v1/system \
-H "Content-Type: application/json" \
-d '{
"pinMatrix": 21,
"pinBtnLeft": 26,
"pinBtnSelect": 16,
"pinBtnRight": 5,
"pinBattery": -1,
"pinLdr": 36,
"pinBuzzer": -1,
"pinI2cSda": 17,
"pinI2cScl": 22,
"pinDfRx": 23,
"pinDfTx": 18,
"dfplayer": true
}'
On success AWTRIX answers 200 with the new configuration. Then restart it:
Always send Content-Type: application/json. Without it, curl -d marks the body as a form, and
AWTRIX refuses the PUT with 415 (Content-Type).
A rejected write¶
curl -X PUT http://<awtrix-ip>/api/v1/system \
-H "Content-Type: application/json" \
-d '{"pinBattery": 25}'
Nothing was stored. AWTRIX stays exactly as it was, including any other fields in the same request.
All messages a rejected pin map can return, and the other answers of this route, are listed in Errors – GPIO validation.
Recovery from a bad map¶
A pin map cannot make AWTRIX unusable.
AWTRIX checks the stored map again at every start. If it is not valid, for example a map saved on a board with a different chip, AWTRIX starts with the default pins. The web UI is reachable and you can fix the map. The stored map stays as it is, so AWTRIX keeps using the defaults until you save a valid map.
A map can be valid but wrong for your hardware. Then AWTRIX starts, but the panel or the buttons do nothing. If you cannot reach the web UI, a factory reset restores the default pins together with all other settings.
Panel layout¶
A panel you built yourself also needs a description: the width of one panel, how many panels the cable runs through, the corner where the data enters, whether the LED strip runs along rows or columns, and whether every second row runs back the other way.
Every panel is 8 pixels high and panelWidth × panels is 32–128. The default is 32×8.
All panel keys, their ranges and the values for common builds are in
Panel and orientation. They are set with PUT /api/v1/system.
A new total width needs a restart; wiring and orientation changes apply at once.
Wiring your own board¶
Work through this list in order:
- Read your chip's rules: run
curl http://<awtrix-ip>/api/v1/capabilitiesand look atgpio. - Pick the matrix pin first, from the LED matrix list. It limits your layout more than any other pin.
- Battery and LDR need ADC1 (
32–39). If you have only one ADC1 pin free, use it for the LDR: without it, auto-brightness is not available. Without a battery pin, only the battery display is missing. - Buttons need a pull-up, so they cannot use
34–39. Wire each button between the pin and ground. AWTRIX turns on the internal pull-up; LOW means pressed. If the select button should wake AWTRIX from deep sleep, pick a pin from the wake row of the chip table. - Avoid reserved pins:
6–11(flash). - Set parts you do not have to
-1instead of leaving a pin that looks right. - Send the whole map at once and restart.
- Calibrate the analog inputs. The pin map only says where to read. What the readings
mean is set separately:
batteryDividerRatiofor the battery divider, andldrFactor,ldrGammaandldrOnGroundfor the light sensor. The defaults match the Ulanzi wiring and will be wrong for your divider. See Brightness & sensors and Power & battery.
If your panel is mounted differently, you may also need rotate or mirror under
Panel and orientation, and swapButtons under
Buttons.
Sensor bus¶
AWTRIX finds I²C sensors by itself at startup. You only set pinI2cSda and pinI2cScl.
AWTRIX looks for these sensors in this order: BME280 (0x76, then 0x77), BMP280 (same
two addresses), HTU21DF (its fixed address), SHT31 (0x44). It uses the first one that
answers. If a BME280 and an SHT31 share the bus, the SHT31 is ignored.
| Sensor | Temperature | Humidity | Air pressure |
|---|---|---|---|
| BME280 | yes | yes | yes |
| BMP280 | yes | – | yes |
| HTU21DF | yes | yes | – |
| SHT31 | yes | yes | – |
Values the sensor does not measure are left out of device state. With no sensor, all of them
are left out, and tempOffset / humOffset have no effect.
The web UI's pin dropdowns¶
Each pin field in the web UI is a dropdown. It lists only the pins your chip can use for that part:
- analog pins for the battery and light sensor,
- output pins for the buttons, buzzer, I²C and the DFPlayer TX line,
- the LED matrix list for the matrix,
- not connected wherever
-1is allowed.
Pins the chip needs for flash are never offered. If the stored map contains a pin the list cannot offer – for example a map saved on a different board – that pin is shown as its own entry marked as the stored value, so it is not overwritten by accident.
The API checks the same rules, so a direct API call cannot get around them.
Related¶
- System configuration – Wi-Fi, MQTT, time, calibration and every other key of
PUT /api/v1/system. - DIY build – parts, power and wiring for your own clock.
- Errors – GPIO validation