← Gateway Modbus TCP a RTU en un PLC M-Duino
Control de biorreactoresM-DuinoModbus TCPModbus RTURS485Comunicación
Gateway Modbus TCP a RTU en un PLC M-Duino — ejemplo completo
Convierte un M-Duino en esclavo Modbus TCP y maestro RTU. Expon una planta de biorreactores en el puerto 502 mientras interrogas seis esclavos RS485. Codigo completo.
Programa completo y ejecutable para el M-Duino (gateway-modbus-tcp-rtu.ino): incluye cabecera de conexionado, requisitos y notas de integración.
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales
Vista de solo lectura.
/*
* COMPLETE EXAMPLE — Gateway Modbus TCP slave <-> Modbus RTU master
*
* Device: M-Duino (Industrial Shields, Arduino PLC with Ethernet + RS485)
* Based on: bioreactor control project, ModbusTCPSlaveRTUMaster.ino
*
* Topology:
* SCADA / client --Modbus TCP (port 502)--> M-Duino --Modbus RTU (RS485, 9600 8N1)--> 6 slaves
*
* RTU bus (RS485, 9600 8N1) with 6 slaves:
* addr 1,2 Thermoelectric chillers
* addr 3,4 Variable frequency drives
* addr 5,6 Flow meters
*
* Modbus TCP map exposed by this gateway (summary):
* Coils 0-17 : 8 relays + 8 pilot lights + 2 counter resets
* Holding 0-9 : setpoints (temperature x10, RPM x100) and run commands
* Input regs 0-11 : actual temperature, pressure, pH, liters and RPM
*
* Key idea: the TCP side is just memory (modbus.update() keeps it alive)
* and a round-robin scheduler moves those values to the RTU bus without blocking.
*
* Integration: the catalog modules "Chiller control", "Drive control"
* and "Flow meter reading" implement the detail of each slave; here the
* whole set is orchestrated.
*/
#include
#include
#include
// --- RTU side: master on the M-Duino RS485
ModbusRTUMaster master(RS485);
// --- TCP side: slave on port 502
ModbusTCPSlave modbus;
// --- RTU addresses of the 6 slaves
const uint8_t CHILLER[2] = {1, 2};
const uint8_t DRIVE[2] = {3, 4};
const uint8_t FLOW[2] = {5, 6};
// --- Memory blocks exposed over TCP
bool coils[18]; // 0-7 relays, 8-15 pilot lights, 16-17 resets
uint16_t holdingRegs[10]; // setpoints and run commands
uint16_t inputRegs[12]; // real-time measurements
// RTU registers used (see the datasheet of each slave)
const uint16_t REG_CHILLER_RUN = 0x06; // chiller start/stop
const uint16_t REG_VAR_RPM_SP = 0x0144; // RPM setpoint x100
const uint16_t REG_VAR_RPM_RD = 0x2149; // actual RPM reading (2 regs)
const uint16_t REG_FLOW_ACC = 0x100; // accumulated liters (2 regs)
uint32_t tPoll = 0;
uint8_t step = 0; // round-robin over the slaves
void setup() {
Serial.begin(115200);
// RTU master at 9600 8N1 (short timeout: we do not want to block the TCP)
master.begin(9600);
master.setTimeout(200);
// TCP slave: publish the three memory blocks
modbus.addCoils(0, coils, 18);
modbus.addHoldingRegisters(0, holdingRegs, 10);
modbus.addInputRegisters(0, inputRegs, 12);
modbus.begin(); // listen on port 502
}
void loop() {
// 1. Serve incoming TCP requests (non-blocking)
modbus.update();
// 2. Every 200 ms take one round-robin step over the RTU bus
if (millis() - tPoll > 200) {
tPoll = millis();
serveSlave(step);
step = (step + 1) % 6; // 0,1 chillers · 2,3 drives · 4,5 flow meters
}
}
// Translates a round-robin slot into a concrete RTU transaction
void serveSlave(uint8_t slot) {
if (slot < 2) {
// Chiller: push the run command the SCADA left in holding 8/9
uint8_t run = holdingRegs[8 + slot] ? 1 : 0;
master.writeSingleRegister(CHILLER[slot], REG_CHILLER_RUN, run);
drainResponse();
} else if (slot < 4) {
// Drive: write setpoint and read actual RPM (2 x 16-bit regs)
uint8_t i = slot - 2;
master.writeSingleRegister(DRIVE[i], REG_VAR_RPM_SP, holdingRegs[6 + i]);
drainResponse();
if (master.readHoldingRegisters(DRIVE[i], REG_VAR_RPM_RD, 2)) {
if (master.isWaitingResponse()) {
ModbusResponse r = master.available();
if (r && !r.hasError())
inputRegs[10 + i] = (uint16_t)((r.getRegister(0) << 16) | r.getRegister(1));
}
}
} else {
// Flow meter: read 32-bit accumulated value into input regs
uint8_t i = slot - 4;
if (master.readHoldingRegisters(FLOW[i], REG_FLOW_ACC, 2)) {
if (master.isWaitingResponse()) {
ModbusResponse r = master.available();
if (r && !r.hasError())
inputRegs[6 + i] = (uint16_t)((r.getRegister(0) << 16) | r.getRegister(1));
}
}
}
}
// Consumes the response of a write so the master is not left busy
void drainResponse() {
if (master.isWaitingResponse()) {
ModbusResponse r = master.available();
(void)r;
}
}
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales