A flexible Ethernet-based Modbus TCP IO module built on the Wiznet W5500-EVB-Pico board (RP2040). Features a modern web interface for configuration and real-time monitoring with support for multiple sensor types, mathematical calibration, and built-in diagnostic tools.
- Digital IO: 8 inputs + 8 outputs with configurable pullup, inversion, and latching
- Analog Inputs: 3 channels (12-bit ADC, 0-3300 mV with calibration)
- I2C Sensors: Configurable multi-sensor support with formula-based calibration
- Modbus TCP: Stable register interface with per-client synchronization
- Web UI: Modern, responsive interface for configuration and monitoring
- Terminal: Built-in diagnostics for network and sensor troubleshooting
- Network: DHCP + static IP fallback with persistent configuration
- Power & Network: Connect via USB or PoE; connect Ethernet cable
- Upload Filesystem: In PlatformIO, run
PlatformIO: Upload File System Imageto upload web assets - Access Web Interface: Find device IP from router or Ethernet status, open in browser
- Configure: Set network, IO, and sensors via web UI
- Monitor: Watch real-time IO status; control outputs; read sensors
For detailed setup, see the Getting Started Guide.
- Board: Wiznet W5500-EVB-Pico (RP2040 + W5500)
- IO: 8 DI (GP0-7), 8 DO (GP8-15), 3 AI (GP26-28)
- Sensors: I2C bus (GP24/25) for EZO, BME280, DS18B20, and custom sensors
- Modbus: TCP port 502 (configurable)
For complete pinout and electrical specs, see Pinout & Interfaces.
- Contributing Guide – Development standards and workflow
- Project Roadmap – Strategic phases and feature planning
- Architecture Overview – Design and data flows
- Hardware Pinout – GPIO and interfaces
- Calibration Guide – Sensor calibration formulas
- Terminal Guide – Diagnostic commands
- Full Documentation Index – Complete guide directory
- Build: PlatformIO with Arduino framework
- Board: Raspberry Pi Pico (RP2040)
- Libraries: ArduinoModbus, ArduinoJson, W5500lwIP, LittleFS
# Build firmware
pio run
# Upload to device
pio run -t upload
# Upload filesystem (web assets)
pio run -t uploadfs- Follow the Sensor Integration Workflow in CONTRIBUTING.md
- Review System Architecture for register allocation
- Reference EZO Sensor Polling for I2C patterns
| Type | Address | Function | Source |
|---|---|---|---|
| FC2 | 0-7 | Digital Input states | GPIO 0-7 |
| FC1/FC5 | 0-7 | Digital Output control | GPIO 8-15 |
| FC1/FC5 | 100-107 | DI Latch reset | Latch array |
| FC4 | 0-2 | Analog input (mV) | GPIO 26-28 (ADC) |
| FC4 | 3+ | Sensor values | I2C / configured sensors |
See Pinout & Interfaces for complete map.
- EZO Sensors (Atlas Scientific): pH, DO, EC, RTD with calibration
- Environmental: BME280, SHT3x, SHT4x (via I2C)
- Temperature: DS18B20 (OneWire), RTD (I2C)
- Distance: VL53L1X (time-of-flight)
- Expandable: Generic I2C, UART, OneWire, Digital, Analog
- Verify
PlatformIO: Upload File System Imagewas run - Check device IP via router status page
- Ensure Ethernet cable is connected
- Run terminal command
i2cscanto enumerate I2C devices - Verify sensor I2C address in configuration
- Check SDA (GP24) and SCL (GP25) connections
- Confirm 4.7kΩ pull-ups on I2C bus
The firmware uses LittleFS (flash filesystem) to store persistent configuration. Understanding these JSON files is essential for troubleshooting and development.
Location: Flash filesystem (LittleFS)
Purpose: Stores network configuration (IP, gateway, subnet, Modbus port) and IO behavior (pullups, inversion, latching).
Structure:
{
"version": 1,
"dhcpEnabled": false,
"ip": [192, 168, 1, 10],
"gateway": [192, 168, 1, 1],
"subnet": [255, 255, 255, 0],
"modbusPort": 502,
"hostname": "modbus-io-module",
"diPullup": [true, true, ...], // Digital input pullup flags
"diInvert": [false, false, ...], // Digital input inversion
"diLatch": [false, false, ...], // Digital input latching
"doInvert": [false, false, ...], // Digital output inversion
"doInitialState": [false, ...] // Initial DO states on boot
}Firmware Interaction:
- Loaded during boot via
loadConfig() - Applied to Ethernet driver immediately via
setupEthernet() - When modified via web UI POST
/config, saved and applied immediately viareapplyNetworkConfig() - Changes persist across reboots
Location: Flash filesystem (LittleFS)
Purpose: Stores all I2C, UART, OneWire, and analog sensor configurations including calibration parameters.
Structure:
{
"sensors": [
{
"enabled": true,
"name": "IMU",
"type": "LIS3DH",
"protocol": "I2C",
"i2cAddress": 24,
"modbusRegister": 3,
"command": "0x00 0x00",
"updateInterval": 1000,
"delayBeforeRead": 0,
"calibration": {
"offset": 0,
"scale": 1,
"expression": "", // Optional: math expression for calibration
"polynomialStr": "" // Optional: polynomial coefficients
}
}
]
}Firmware Interaction:
- Loaded during boot via
loadSensorConfig() - Populates
configuredSensors[]array used for polling - Polling queue (
i2cQueue,uartQueue,oneWireQueue) reads sensor type and parameters to determine how to read each sensor - Sensor type string ("LIS3DH", "EZO_PH", "DS18B20", etc.) determines which protocol handler processes the read
- Calibration parameters applied to raw sensor values in
applyCalibration(),applyCalibrationB(),applyCalibrationC()functions - When modified via web UI POST
/sensors/config, saved and device reboots to apply
- Boot:
setup()→loadSensorConfig()→ populatesconfiguredSensors[] - Main Loop:
loop()→updateIOpins()→ checks sensorupdateIntervaltimers - Enqueue: If timer elapsed, calls
enqueueBusOperation(sensorIndex, protocol) - Queue Processing:
processI2CQueue()reads sensor type, executes I2C state machine- For LIS3DH: writes register address 0xA8 (with auto-increment), reads 6 bytes, parses X/Y/Z
- For EZO: sends command string, waits for response, parses millivolt reading
- For DS18B20: sends read command, waits conversion, parses temperature
- Data Storage: Raw value → Calibration function →
configuredSensors[i].calibratedValue→ Modbus register - Web Export:
/iostatusand/sensors/dataendpoints serializeconfiguredSensors[]to JSON
| Endpoint | Purpose | Data Source |
|---|---|---|
GET /iostatus |
Real-time IO status + sensor values | ioStatus struct + configuredSensors[] |
GET /sensors/data |
Extended sensor telemetry | configuredSensors[] array |
GET /config |
Current network config | config struct |
POST /config |
Update network settings | Parses JSON, calls saveConfig(), reapplyNetworkConfig() |
GET /sensors/config |
Sensor configuration list | sensors.json contents |
POST /sensors/config |
Update sensor definitions | Saves sensors.json, triggers reboot |
Design Philosophy: Debugging is not implemented via embedded Serial.print() statements scattered throughout the code. Instead, all diagnostics flow through the web UI Terminal which provides real-time filtering and protocol analysis.
- I2C Traffic Monitor: Watch register writes/reads for any sensor in real-time
- Protocol Filtering: Select specific I2C address, sensor name, or "all" to narrow focus
- Timestamp Correlation: See exact millisecond timing of each operation
- Transaction Logging: Every I2C operation logged as: TX (write) → ACK → REQ (read) → RX → VAL (parsed)
- Open Web UI → Terminal tab
- Start Watching I2C: Select sensor or I2C address
- Observe Logs:
- TX: Register address or command being sent
- ACK: Acknowledgment received
- REQ: How many bytes requested
- RX: Raw hex bytes received
- VAL: Parsed/calibrated values
- Interpret Pattern:
- All zeros → sensor not outputting data (initialization or register issue)
- 0x80 repeated → sensor in default state (not polled or bad config)
- Garbage values → scaling factor or byte parsing wrong
- Timeouts → I2C bus issue or wrong address
The firmware does not add temporary Serial.printf() statements for debugging. Instead:
- Use the web terminal to watch live I2C traffic
- Check
/sensors/dataendpoint for current sensor values - Review
config.jsonandsensors.jsonto verify configuration - Use Modbus client to read registers and verify export
This keeps the codebase clean and allows debugging without recompilation.
- Verify port 502 in network configuration
- Check firewall allows connections
- Use terminal
ipconfigcommand to verify network status
For more, see Terminal Guide and Pinout & Interfaces.
- Current: v2.0.0 (September 2025)
- See Changelog for version history
See CONTRIBUTING.md for development standards, coding guidelines, and contribution workflow.