Bioreactor controlM-DuinoModbus TCPCommunication
A Modbus TCP register map for the whole plant
A shared Modbus TCP register map is the contract between a PLC and the SCADA that reads it, and getting it right early saves endless integration pain. This example, from a real bioreactor control deployment, lays out the map an M-Duino exposes on port 502: coils for relays, pilots and resets, holding registers for setpoints and run commands, and input registers for live temperature, pressure, pH, litres and RPM across the plant.
Coils, holding and input registers
Coils 0 to 17 cover eight output relays, eight pilot indicators and two flow-meter resets. Holding registers 0 to 9 hold analog outputs, chiller temperature setpoints, drive RPM setpoints and chiller run commands. Input registers 0 to 11 carry temperatures, pressures, pH, accumulated litres and real RPM. Read versus write intent is encoded in the block, not in convention.
Named indices instead of magic numbers
The sketch uses enums so the code reads like the map itself: HR_TEMP_SP1 instead of holding register 4, IR_RPM1 instead of input register 10. When the map grows, named indices keep every read and write self-documenting and make off-by-one addressing mistakes obvious in review.
A live, testable contract
Because the map is published with update() running, any Modbus client can connect and verify addressing before the field devices are wired. The plant modules for chillers, drives and flow meters all share these same addresses behind the gateway, so the map doubles as documentation and as an integration test fixture.
A snippet from the implementation
Straight from the example as deployed on the M-Duino — copy it freely:
void setup() {
Serial.begin(115200);
// Publish the three blocks starting at address 0
modbus.addCoils(0, coils, 18);
modbus.addHoldingRegisters(0, holdingRegs, 10);
modbus.addInputRegisters(0, inputRegs, 12);
modbus.begin(); // listen on port 502
// Consistent initial values (what a SCADA would read on connecting)
holdingRegs[HR_TEMP_SP1] = 185; // 18.5 C x10
holdingRegs[HR_TEMP_SP2] = 185;
holdingRegs[HR_RPM_SP1] = 250; // 250 rpm
holdingRegs[HR_RPM_SP2] = 250;
}The full example is a complete program — wiring header, setup and main loop — ready to adapt to your application.
Frequently asked questions
What is the difference between coils and registers here?
Coils are single-bit on/off values for relays, pilots and resets. Holding registers are 16-bit read/write values for setpoints and commands, and input registers are read-only 16-bit measurements.
Why use named enums for register indices?
They make the code read like the register map and prevent off-by-one addressing errors. HR_TEMP_SP1 is far clearer than holding register 4 when someone revisits the code later.
Can I test the map before the field devices exist?
Yes. The map is published on port 502 with update() running, so any Modbus client can connect and confirm addressing while the chillers, drives and meters are still being wired.