当前位置:首页 > 工业控制 > 工业控制
[导读]远程IO不是“网线拉长一点的本地IO”。异地站通过以太网把DI/DO/AI/AO映射成寄存器或过程映像,主站周期轮询、统一刷新。 选协议先看实时等级:Modbus TCP最简单开放,但属请求-响应轮询,典型扫描30~80ms,不适合硬实时运动环;PROFINET RT/EtherNet/IP、EtherCAT才做周期同步过程数据。 本文以Modbus TCP主站为例,讲清“映像解耦+断线安全态”这套工业做法。


远程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的地方别省。断线保持最后输出是最常见误区——工业现场要的是故障导向安全,不是看起来没变。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭