Firmware for the Waveshare ESP32-S3-ETH-8DI-8RO controller that exposes the board as a feature-rich Modbus RTU slave while keeping Ethernet, Wi-Fi, MQTT, Bluetooth, RTC, SD logging, and local buzzer support available for higher level applications (SCADA, HMI, alarm systems, etc.).
- Modbus RTU slave running on UART1 with configurable pulse indications for RX/TX activity.
- Eight relay outputs and eight digital inputs managed through dedicated WS_* modules.
- MQTT, Bluetooth, Ethernet, and SD card subsystems initialised during boot for integration flexibility.
- Up to ten DS18B20 1-Wire temperature sensors with Modbus-accessible telemetry and editable sensor names.
- Event logging with automatic mirroring to SD (
/logs/events.csv) when a card is mounted. - Buzzer controller with Modbus-accessible configuration stored persistently in NVS.
- RGB status LED feedback for Modbus events and error states.
- Waveshare ESP32-S3-ETH-8DI-8RO (ESP32-S3 with PoE Ethernet, 8 DI, 8 relay outputs).
- MicroSD card (optional) for event log persistence.
- RS485 transceiver connected to UART1 for Modbus RTU transport.
- Framework: Arduino (via PlatformIO).
- Target board:
esp32-s3-devkitc-1with PSRAM enabled. - Tooling: PlatformIO CLI or the PlatformIO IDE extension.
- Key libraries: ArduinoJson, PubSubClient, NimBLE-Arduino, ESPAsyncWebServer, AsyncTCP, Adafruit Sensor suite.
Configuration lives in
platformio.ini; upload speed is set to 921600 baud and serial monitoring defaults to 115200 baud.
- Install PlatformIO (CLI or VS Studio Code extension).
- Clone this repository and open the project folder in your IDE or terminal.
- Adjust
include/WS_Config.hif you need to customise defaults (Wi-Fi SSID, MQTT topics, Modbus device name, RGB colours). - Connect the ESP32-S3 board over USB. For PoE deployments keep the RS485 lines connected but isolated during flashing.
- Build and upload from the command line:
pio run -e esp32s3-poe-eth-8di8ro pio run -e esp32s3-poe-eth-8di8ro -t upload pio device monitor -b 115200
- After boot the serial monitor prints module initialisation messages, including SD card status and Modbus startup confirmation.
WS_ModbusRTU- Modbus RTU slave implementation with coil and holding register support, RX/TX pulses, CRC validation, and SCADA logging.WS_DS18B20- One-Wire bridge that polls up to ten DS18B20 sensors, exposes readings over Modbus, and stores user-defined labels in NVS.WS_Relay,WS_DIN,WS_GPIO- Hardware access layers for relay outputs, digital inputs, and general GPIO.WS_MQTT,WS_Bluetooth,WS_ETH,WS_SD- Networking stacks providing MQTT cloud connectivity, BLE, Ethernet, and SD card services.WS_Buzzer- PWM-based buzzer manager with queued events, manual control, and Modbus-mapped configuration registers backed by NVS.WS_EventLog- Thread-safe event log with circular buffer, UART mirroring, and optional SD persistence.
- UART:
Serial1(TXD1 = GPIO17,RXD1 = GPIO18) - Slave ID:
0x01 - Baud rate:
9600 - Frame format:
8 data bits, no parity, 1 stop bit(SERIAL_8N1)
| Code | Name | Description |
|---|---|---|
0x01 |
Read Coils | Returns relay states plus auxiliary coils (RX/TX pulses). |
0x05 |
Write Single Coil | Switches one relay output and triggers optional buzzer feedback. |
0x0F |
Write Multiple Coils | Updates all relays in one transaction with RGB and buzzer feedback. |
0x03 |
Read Holding Registers | Exposes buzzer configuration and status registers. |
0x06 |
Write Single Register | Allows SCADA to configure buzzer behaviour and manual pulses. |
Unsupported functions yield a warning entry in the event log and flash the RGB LED orange.
| Coil | Address | Description |
|---|---|---|
| CH1 | 0x0001 |
Relay channel 1 |
| CH2 | 0x0002 |
Relay channel 2 |
| CH3 | 0x0003 |
Relay channel 3 |
| CH4 | 0x0004 |
Relay channel 4 |
| CH5 | 0x0005 |
Relay channel 5 |
| CH6 | 0x0006 |
Relay channel 6 |
| CH7 | 0x0007 |
Relay channel 7 |
| CH8 | 0x0008 |
Relay channel 8 |
| CH9 | 0x0009 |
Modbus RX pulse (true for ~100 ms after each valid request) |
| CH10 | 0x000A |
Modbus TX pulse (true for ~100 ms whenever a response is queued) |
| Register | Address | Access | Description |
|---|---|---|---|
REG_BUZZER_ENABLE |
0x0063 |
R/W | Global buzzer enable (0 = disabled, 1 = enabled). |
REG_BUZZER_ON_START |
0x0064 |
R/W | Play buzzer on system startup. |
REG_BUZZER_ON_RELAY |
0x0065 |
R/W | Play buzzer when relays change via Modbus. |
REG_BUZZER_MANUAL |
0x0066 |
R/W | Manual control (0 stop, 1 hold on, 2 release, >2 = duration ms pulse). |
REG_BUZZER_STATE |
0x0067 |
R | Instantaneous buzzer PWM output state (0/1). |
REG_DS18B20_COUNT |
0x0100 |
R | Number of DS18B20 sensors that reported valid data during the last poll. |
REG_DS18B20_REFRESH |
0x0101 |
R/W | Write any non-zero value to rescan the 1-Wire bus; reads as 1 while a rescan is pending. |
REG_DS18B20_STATUS |
0x0102 |
R | Bitfield: bit0 module initialised, bit1 refresh pending, bits2+ sensor-online mask. |
REG_DS18B20_TEMP_BASE + n |
0x0110 + n |
R | Temperature for sensor slot n (signed value, degC * 100). |
REG_DS18B20_PRESENCE_BASE + n |
0x0120 + n |
R | Slot presence word for sensor n (bit0 address valid, bit1 online, bit2 raw valid, bit3 corrected valid, bit4 offset applied, bit5 stale/held value). |
REG_DS18B20_OFFSET_BASE + n |
0x0130 + n |
R/W | Offset applied to sensor slot n (signed value, degC * 100, persisted). |
REG_DS18B20_TEMP_TENTH_BASE + n |
0x0140 + n |
R | Temperature for slot n scaled to 0.1 degC (signed value, degC * 10). |
REG_DS18B20_SLOT_BASE + (n * 16) + w |
0x0150 + (n * 16) + w |
R | Slot metadata (w=0 status, 1 slot id, 2-5 ROM code, 6 temp register, 7 offset register, 8 name base). |
REG_DS18B20_NAME_BASE + (n * 8) + w |
0x0200 + (n * 8) + w |
R/W | Sensor n name storage (two ASCII chars per register, offset w in [0,7]). |
Additional registers can be added in WS_Buzzer.cpp via Buzzer_ModbusWrite and Buzzer_ModbusRead.
- DS18B20 names persist in NVS; write ASCII pairs through holding registers
0x0200-0x024F. The high byte is stored first (0x0200high byte = name character 0, low byte = character 1). - Temperature offsets persist per sensor via registers
0x0130-0x0139. Values are signed and scaled by 100 (set5to apply +0.05 degC). Corrections survive resets and Modbus writes validate ranges automatically. - Sensors are rescanned automatically every 60 s (
DS18B20_RESCAN_INTERVAL_MSinWS_Config.h). Manual rescans via register0x0101still work and force an immediate update/log entry. - Metadata per slot lives under
0x0150with a stride of 16 registers per sensor. Within each block, register+0returns the same status bits exposed inREG_DS18B20_PRESENCE_BASE + n(bit0 installed, bit1 online, bit2 raw valid, bit3 corrected valid, bit4 offset applied, bit5 stale/held value),+1exposes the slot index,+2..+5encode the 64-bit ROM code (little endian pairs),+6mirrors the temperature register number,+7the offset register, and+8the starting register for the sensor name. - Temperatures are exposed twice:
0x0110 + nholds degC * 100 (25.37 C ->2537), while0x0140 + nmirrors the same reading scaled by 10 (25.3 C ->253). Offline or missing sensors return0x8000(-32768) only after three consecutive failed polls; until then the last valid reading is held and bit5 in the presence word signals that SCADA is seeing cached data. - Firmware keeps the last valid reading through up to three consecutive 1-Wire faults before flagging the sensor offline, so SCADA trends stay flat.
After the threshold the temperature register switches back to
0x8000, meaning alarms still trip when a probe truly disconnects.
- Holding registers are zero-based inside the firmware. Masters that label the first holding as
40001/400001must add that bias to the offsets below (example:0x0110decimal 272 ›400273when the SCADA’s base is400001 = offset 0). - Slot 1 examples:
- Temperature (degC * 100)
0x0110› decimal 272. - Temperature (degC * 10)
0x0140› decimal 320. - Presence/status word
0x0120› decimal 288 (bit0 address valid, bit1 online, bit2 raw valid, bit3 corrected valid, bit4 offset applied, bit5 stale/held value). - Offset register
0x0130› decimal 304 (signed, degC * 100).
- Temperature (degC * 100)
- In FUXA: set the tag type to signed 16-bit, use divisor 100 for
0x0110or divisor 10 for0x0140; the presence word can be broken into booleans via bit masks without extra firmware changes.
- Coils
00009and00010expose RX/TX activity to SCADA. They latch totruewhen a frame is received or a reply is staged and automatically revert tofalseonceMODBUS_PULSE_MS(default 100 ms) has elapsed. - Pulses update even during FC1 requests so polling masters can detect transmissions without timing gaps.
- Manual clearing is possible through
FC05/FC0Fif your SCADA requires explicit resets.
EventLog_Add()pushes human-readable messages into an in-memory ring buffer and prints them to the USB serial console.- When
SD_Init()mounts a card successfully, log entries also append to/logs/events.csvon the SD card (CSV header auto-created on first run). - Logging covers system startup, Modbus requests, relay changes, SD status, buzzer actions, and warnings (CRC errors, unsupported function codes, etc.).
- The buzzer module relies on the ESP32
PreferencesAPI to persist the configuration namespacebuzzer. Buzzer_LoadConfig()restores the last saved state duringBuzzer_Init(), ensuring settings survive power loss.- Any write via Modbus registers
0x0063-0x0066is immediately stored in NVS, and the event log records the change for traceability.
- Poll coils at 5-10 Hz to reliably observe RX/TX pulses and relay updates without saturating the UART link.
- Use holding registers to keep buzzer behaviour aligned with plant safety rules (disable audible feedback on night shifts, enable manual alarm pulses, etc.).
- Monitor
/logs/events.csvfor diagnostics; the CSV structure is timestamped for direct import into analysis tools.
- No response on Modbus: verify UART1 wiring (GPIO17/18) and ensure the master is using slave ID
0x01. The RGB LED flashes red on CRC errors. - SD log missing: confirm the card mounts during boot; the serial console reports failures and the event log keeps a copy in RAM even without SD.
- Buzzer does not sound: check
REG_BUZZER_ENABLE(0x0063) and confirm manual hold is not active (REG_BUZZER_MANUALset to 2).
src/- Core firmware modules (Modbus, relay control, MQTT, SD, buzzer, etc.).include/- Public headers and configuration defines (WS_Config.h,WS_ModbusRTU.h, ...).lib/- Optional space for project-specific libraries (empty scaffolding provided by PlatformIO).platformio.ini- Build configuration for theesp32s3-poe-eth-8di8roenvironment.