工业设备远程IO编程:通过以太网实现异地IO信号的实时远程控制
远程IO不是“网线拉长一点的本地IO”。异地站通过以太网把DI/DO/AI/AO映射成寄存器或过程映像,主站周期轮询、统一刷新。 选协议先看实时等级:Modbus TCP最简单开放,但属请求-响应轮询,典型扫描30~80ms,不适合硬实时运动环;PROFINET RT/EtherNet/IP、EtherCAT才做周期同步过程数据。 本文以Modbus TCP主站为例,讲清“映像解耦+断线安全态”这套工业做法。
一、点位映射:IO进寄存器
远程IO耦合器一般把:
DI → 输入线圈/输入寄存器(主站读)
DO → 线圈/保持寄存器(主站写)
AI → 输入寄存器
状态字 → 保持寄存器某偏移
主站侧建两份映像:
typedef struct {
uint16_t di[16]; // 输入映像
uint16_t do_img[16]; // 输出映像(只改这里)
uint16_t do_hw[16]; // 上次实际下发
uint32_t last_ok_ms;
uint8_t online;
} rio_t;
rio_t g_rio[4]; // 4个远程站
业务代码只碰do_img,绝不直写网络;轮询任务负责把映像同步到模块。
二、主站轮询:读入+写回
Modbus TCP是同步请求-响应,同一连接要串行,别多线程并发发帧。
int mb_read_di(int sock, uint8_t unit, uint16_t base, uint16_t*out){
uint8_t req[12] = {
0x00,0x01,0x00,0x00,0x00,0x06, unit,
0x01, // FC01读线圈
base>>8,base&0xFF, 0x00,0x10
};
if(sock_send(sock,req,12)<0) return -1;
uint8_t rsp[32];
if(sock_recv_full(sock,rsp,offset_by_mbap())<0) return -1;
for(int i=0;i<16;i++)
out[i/8]|=((rsp[9+i/8]>>(i&7))&1)<<i;
return 0;
}
int mb_write_do(int sock, uint8_t unit, uint16_t base, uint16_t val){
uint8_t req[13] = {
0x00,0x01,0x00,0x00,0x00,0x07, unit,
0x0F, // FC15写多线圈
base>>8,base&0xFF, 0x00,0x10, 0x02, val&0xFF,(val>>8)&0xFF
};
return sock_send(sock,req,13);
}
主循环按站号错开轮询,别4个站同毫秒发:
void rio_poll_task(void){
static int cur=0;
rio_t*r=&g_rio[cur];
if(mb_read_di(sock[cur], r->unit, 0, r->di)==0){
if(memcmp(&r->do_hw, &r->do_img, sizeof(r->do_img))){
mb_write_do(sock[cur], r->unit, 0, r->do_img[0]);
memcpy(r->do_hw, r->do_img, sizeof(r->do_img));
}
r->last_ok_ms = sys_ms();
r->online = 1;
}else{
r->online = 0; // 不写模块,交给看门狗
}
cur = (cur+1)%4;
}
轮询间隔按负载定,过程IO 50~200ms够用,别10ms狂刷把从站打崩。
三、断线安全态:别“保持最后输出”
通信断了远程模块还保持旧DO,看着稳其实是开环——主站已经不知道现场状态。
正确做法:
模块侧配“通信看门狗超时→输出安全值”
主站侧检测到离线→输出映像切安全态,重连后先抑制旧命令
void rio_watchdog_check(void){
for(int i=0;i<4;i++){
rio_t*r=&g_rio[i];
if(sys_ms()-r->last_ok_ms > 500){ // 500ms无响应
r->online=0;
// 主站本地也回安全态,避免业务继续按旧状态算
r->do_img[0] = RIO_SAFE_DO; // 例如全0
}
}
}
模块端配置(以通用以太网IO为例):
Host Watchdog使能,超时250~500ms
Safe Value按通道设:阀岛全关、加热器关、风机可保持
重连后主站发“新会话序号”再刷新DO,防旧帧覆盖安全态
四、重连防旧命令覆盖
重连最危险的一步:主站映像里还留着断线前的旧输出,一连通立马写下去,刚停下的执行器又动。
加会话版本号:
uint32_t sess_id; // 每次重连++
void rio_on_reconnect(int i){
g_rio[i].online=1;
g_rio[i].sess = ++sess_id;
// 先读模块状态字,确认处于Safe态
mb_read_status(...);
// 输出映像清零/安全值,再发首帧
g_rio[i].do_img[0] = RIO_SAFE_DO;
g_rio[i].do_hw[0] = 0xFF; // 强制首帧刷新
}
脉冲命令(启动电磁阀、点动)必须带seq,模块去重,避免重传重复触发。
五、实时性边界
Modbus TCP走TCP 502,拥塞会重传,抖动不可控;
数字量联锁、急停:别只靠远程IO,本地硬接线或安全继电器兜底
阀岛顺序动作:100ms级OK
伺服力矩/电流环:别走Modbus TCP,用EtherCAT/PN IRT
高干扰工位(焊机旁):IO更新周期放宽到4~8ms,看门狗容错窗口放大
六、落坑清单
业务线程直写DO寄存器→映像乱覆盖
断线保持最后输出→开环失控
重连立即刷旧命令→执行器误动作
多站同毫秒轮询→交换机广播风暴
10ms狂刷从站→从站看门狗超时
不配Safe Value→通信断输出乱跳
急停用远程DO→网络抖一下就失效
TCP没按MBAP长度收满→帧解析错位
多连接不加锁→事务ID串了
模拟量不滤波→现场干扰跳变
七、落地顺序
远程站定IP、Unit ID,拿厂商寄存器映射表
主站建DI输入映像+DO输出映像
单连接串行轮询,读DI/状态、写DO差异
模块配通信看门狗+Safe Value
主站500ms无响应标离线、切安全态
重连走会话号,先读状态再刷输出
急停/安全链走本地硬接线兜底
高干扰工位放宽周期,别硬压2ms
远程IO编程的成熟形态不是“网络通了就写IO”,是映像解耦+安全态兜底:主站业务只改输出映像,轮询任务负责同步;通信正常时周期刷新,通信断了模块自己落安全值,主站重连后不让旧命令覆盖。 Modbus TCP够过程IO用,但别拿它扛运动环;真正“实时”该交给PROFINET/EtherCAT的地方别省。断线保持最后输出是最常见误区——工业现场要的是故障导向安全,不是看起来没变。





