工业PLC梯形图转C语言编程:在ARM工业网关中实现软PLC逻辑运行
梯形图不是给C直接“照着翻”的图形,PLC CPU本身也不认LD,开发系统一般先转成指令表IL,再由runtime解释执行。 ARM工业网关跑软PLC,正确做法是:上位机把梯形图编译成字节码/指令表,网关固件里写一个轻量虚拟机,按“输入采样→逻辑执行→输出刷新”循环扫描。
一、IO映像区先隔离
别在逻辑里直接读GPIO、直接写继电器,扫描周期中会读到半更新状态。建输入/输出映像:
#define BIT(x,n) (((x)>>(n))&1)
uint16_t in_img; // I0.0~I0.15
uint16_t out_img; // Q0.0~Q0.15
uint16_t mem_img; // M中间继电器
void sample_input(void){ in_img = gpio_in_port(); }
void refresh_output(void){ gpio_out_port(out_img); }
每个扫描周期开头拷物理输入,结尾刷物理输出,逻辑只碰映像。
二、梯形图转指令表
一行启保停:
LD I0.0
OR Q0.0
AN I0.1
= Q0.0
转成定长指令结构:
typedef struct {
uint8_t op; // LD/AND/OR/ANI/OUT
uint8_t area; // 0=I 1=Q 2=M
uint8_t bit;
} ins_t;
const ins_t prog[] = {
{OP_LD, AREA_I,0},
{OP_OR, AREA_Q,0},
{OP_ANI,AREA_I,1},
{OP_OUT,AREA_Q,0},
{OP_END,0,0}
};
复杂梯形图靠AOV图/二叉树后序遍历生成IL,分支压栈用AND_STK/OR_STK即可,不用在C里写一堆if嵌套。
三、C虚拟机执行
用值栈模拟母线电流:
uint8_t stack[8];
uint8_t sp;
uint8_t rd(uint8_t a,uint8_t b){
if(a==0)return BIT(in_img,b);
if(a==1)return BIT(out_img,b);
return BIT(mem_img,b);
}
void run_scan(void){
sp=0;
for(int pc=0; prog[pc].op!=OP_END; pc++){
ins_t* p=&prog[pc];
switch(p->op){
case OP_LD: stack[sp++]=rd(p->area,p->bit); break;
case OP_OR: stack[sp-1]|=rd(p->area,p->bit); break;
case OP_AND:stack[sp-1]&=rd(p->area,p->bit); break;
case OP_ANI:stack[sp-1]&=!rd(p->area,p->bit); break;
case OP_OUT:
if(stack[--sp]) out_img|=(1<>p->bit);
break;
}
}
}
母线结果出栈写映像,不直接动硬件。
四、定时器做成功能块
梯形图TON不能直接用delay(),要在扫描周期里累计ET:
typedef struct {
uint8_t en;
uint16_t et;
uint16_t pt;
uint8_t q;
} ton_t;
void ton_update(ton_t*t, uint8_t en, uint16_t pt, uint32_t cyc_ms){
t->en=en; t->pt=pt;
if(!en){ t->et=0; t->q=0; }
else {
t->et += cyc_ms;
if(t->et>=pt){ t->et=pt; t->q=1; }
}
}
扫描周期1ms就按1ms累加,别在定时器中断里另起一套状态,否则和逻辑扫描不同步。
五、ARM网关主循环
Linux用户态跑软PLC,周期稳定性靠PREEMPT_RT+CPU隔离兜底;纯离散逻辑10~50ms也够用,运动控制才要1ms以内。
void plc_task(void){
while(1){
sample_input();
run_scan();
ton_update(&t1, BIT(out_img,0), 1000, cycle_ms);
refresh_output();
usleep(cycle_ms*1000);
}
}
更稳的做法:
实时线程SCHED_FIFO,绑独立核
输入映像用共享内存,南向Modbus/EtherCAT驱动写映像
变量名绑定网关tag(site0_val)
在线修改只换prog[]不重启runtime
六、落坑清单
逻辑里直接读gpio→同周期输入输出混扰
梯形图逐行翻成if-else→多输出/栈逻辑崩
双线圈Q在两处OUT→后者覆盖前者
TON用系统定时中断自加→和扫描周期脱钩
输出立即写硬件→外部抖动
Linux默认调度跑PLC→周期跳到几十ms
没输入映像消抖→按钮毛刺误触发
定时器ET用float秒→累积误差
指令表不校验END→pc越界
把软PLC当安全PLC用→SIL场景不合格
七、落地顺序
上位机LD→IL编译器(或接OpenPLC/CODESYS)
2 定指令集:LD/AND/OR/ANI/OUT/END
C虚拟机值栈执行,只操作映像
输入采样+输出刷新包三层
TON/TOF/计数器做功能块
ARM Linux起实时线程,周期1~10ms
IO走Modbus/EtherCAT映像层
在线下载字节码,不重启runtime
梯形图转C的本质不是“翻译图形”,是把LD编译成IL字节码,再用C写一个确定性扫描虚拟机。逻辑层只碰IO映像、定时器跟扫描周期走、输出统一刷新——这三件事定了,ARM网关就能当软PLC跑。至于实时性,普通Linux够离散控制,PREEMPT_RT+核隔离才碰1ms以下;安全回路别省硬件继电器,软PLC只管逻辑不解安全认证。





