PLC 作为主站的轮询程序编写
第一阶段:网络规划与参数设定
建立 稳定的物理连接是通信成功的前提。确认 控制器硬件支持的协议类型,常见为 Modbus RTU 或 Modbus TCP。如果是串口通信,核对 RS485 接线极性,确保 A 接 A,B 接 B。如果是以太网通信,配置 设备 IP 地址,确保主站与从站处于同一网段且无冲突。
制定 详细的通信参数表,这直接决定了后续程序的变量定义。主站必须统一规定波特率、数据位、停止位和校验位,所有从站必须严格匹配。以下是标准参数的配置参考:
| 参数项 | 推荐值 | 备注说明 |
|---|---|---|
| 波特率 | 9600 bps |
长距离建议 4800 以减少误码 |
| 数据位 | 8 bit |
固定标准 |
| 停止位 | 1 bit |
固定标准 |
| 校验位 | Even 偶校验 |
部分设备支持 None 无校验 |
记录 每个从站的站号(Slave ID)及其对应的功能代码需求。主站需要循环访问多个设备,必须在程序中预先分配内存区域用于存储读取的数据和待发送的指令。将输入寄存器映射到主站的 %IW 区,将输出寄存器映射到 %QW 区,避免 在同一个扫描周期内对同一地址进行重复读写操作,防止逻辑冲突。
第二阶段:超时时间计算
计算 合理的通信超时时间是防止程序死锁的关键。如果等待响应的时间过短,通信会频繁报错;如果时间过长,单个从站故障会拖慢整个系统。
根据波特率和报文长度,推导 单个字节传输耗时公式。对于 Modbus RTU 协议,帧间间隔至少需 3.5 个字符时间。单个字节的传输时间 $T_{byte}$ 计算公式如下:
$$T_{byte} = \frac{11}{Baud}$$
其中 $11$ 代表起始位、数据位、停止位及校验位的总位数,$Baud$ 为波特率。假设波特率为 9600,则单字节耗时约为 $1.145ms$。
设定 主站等待响应的最大超时时间 $T_{timeout}$。该值应大于最坏情况下的全帧传输时间加上传输延迟。经验公式如下:
$$T_{timeout} = N_{bytes} \times T_{byte} + T_{margin}$$
其中 $N_{bytes}$ 为预期回包的最大字节数,$T_{margin}$ 为安全余量,建议预留 $50ms$ 至 $100ms$。注意 在西门子 S7-1200/1500 系列中,系统函数块已有内置超时参数,填入 计算好的毫秒数值即可。
第三阶段:轮询逻辑架构设计
构建 基于状态机的轮询流程。不要试图在一个扫描周期内完成所有设备的通信,应将任务分散到不同的 PLC 扫描周期中。采用步进控制思想,将轮询过程划分为空闲、发送、等待、接收处理四个核心状态。
上述流程图中,关键路径在于 状态 2 的处理。监控 通信就绪标志位,只有当总线空闲且主站发送缓冲区可用时,才能进入发送状态。若连续多次通信失败,执行 自动重试机制,达到最大重试次数后跳过 当前从站,继续下一个,防止整条链路瘫痪。
第四阶段:程序代码实现
编写 结构化文本(SCL)或梯形图(LAD)。以下提供基于状态逻辑的伪代码示例,适用于大多数主流 PLC 品牌。
// 定义状态枚举
TYPE ePollState : (
stIdle := 0, // 空闲
stSendCmd := 1, // 发送命令
stWaitResp := 2, // 等待响应
stHandleData := 3 // 处理数据
);
VAR
currentState AT %MW100 : ePollState;
nextSlaveAddr : INT := 1; // 下一个从站地址
retryCount : INT := 0; // 重试计数器
maxRetry : INT := 3; // 最大重试次数
commTimeout : TIME := T#200MS; // 通信超时
END_VAR
CASE currentState OF
stIdle:
// 准备发送帧
SetRequestFrame(nextSlaveAddr);
currentState := stSendCmd;
stSendCmd:
// 调用通信功能块发送
IF WriteBlock THEN
currentState := stWaitResp;
StartTimer(commTimeout);
retryCount := 0;
END_IF
stWaitResp:
// 检测是否收到有效帧
IF ReadStatus = ACK THEN
KillTimer();
currentState := stHandleData;
ELSIF TimerElapsed THEN
// 处理超时
IF retryCount < maxRetry THEN
retryCount := increment(retryCount);
currentState := stIdle; // 重新尝试
ELSE
LogError('Timeout');
nextSlaveAddr := increment(nextSlaveAddr);
currentState := stIdle;
END_IF
END_IF
stHandleData:
// 提取 CRC 校验并写入数据
CheckCRC();
SaveToMemory();
// 准备切换到下一个站
nextSlaveAddr := increment(nextSlaveAddr);
IF nextSlaveAddr > MaxSlaveID THEN
nextSlaveAddr := 1; // 循环回到第一个站
END_IF
currentState := stIdle;
END_CASE
封装 通用功能块。将发送逻辑、CRC 校验算法和超时判断放入子程序,主程序仅调用接口。减少 主循环中的逻辑复杂度,提高 CPU 效率。务必处理 校验和错误,当接收到数据但校验不通过时,应丢弃 数据包并判定为重试,而非直接存入内存,防止脏数据污染控制逻辑。
第五阶段:调试与优化策略
启动 仿真环境或连接真实设备进行测试。首先使用串口助手工具抓取 原始报文,对比 PLC 内部生成的报文是否一致。重点观察 第一帧通信的时序,确保从站有足够的时间切换主从模式。
设置 在线监视变量,实时查看状态机的跳转情况。若程序卡在某一步骤,检查 对应状态的触发条件是否满足。例如,卡在 stWaitResp 通常意味着没有收到应答,此时排查 线路干扰或从站电源供电是否稳定。
优化 轮询周期。不要为了追求速度而盲目减小延时。根据现场负载情况,调整 每个从站的等待时间。对于响应慢的设备,单独设置 较长的超时阈值;对于高速运动控制模块,优先 安排在轮询队列的最前端。
实施 看门狗保护。在主程序外增加独立的系统时钟监测,若轮询程序因异常陷入死循环导致主状态不再跳转,系统应能复位 通信模块并恢复运行。定期归档 通信错误日志,分析错误趋势,提前发现硬件老化或信号衰减问题。

暂无评论,快来抢沙发吧!