3. 状态转换实战:使用 ec_writestate() 和 ec_readstate() 实现从机状态切换
好,咱们直接进入正题。EtherCAT 从机状态机,说白了就是一条「单向车道」——从 Init 到 Pre-Op,再到 Safe-Op,最后到 Op。你只能往前走,不能跳级。我刚开始接触时,总想着能不能从 Init 直接蹦到 Op,结果从机根本不搭理我。
这一节,我们就用 SOEM 的两个核心函数——ec_writestate() 和 ec_readstate(),亲手把从机从 Init 推到 Op。代码跑通了,你才算真正入门。
3.1 状态机的基础认知
先看这张图,我把它画成了流程图,你一眼就能看明白状态怎么走:
这张图我画得比较直白。每个箭头代表一次 ec_writestate() 调用。注意那个红色的虚线箭头——那是错误恢复路径。从机如果出错了,会自己掉回 Init 或 Pre-Op,你得重新推上去。
3.2 ec_writestate() 到底干了什么?
这个函数,说白了就是往从机的 AL Control 寄存器(0x0120) 里写一个值。你告诉从机:「嘿,请切换到 XX 状态。」
函数原型很简单:
int ec_writestate(uint16_t slave, int state);
参数说明:
slave:从机编号,从 1 开始。0 表示所有从机。state:目标状态,用宏定义:EC_STATE_INIT、EC_STATE_PRE_OP、EC_STATE_SAFE_OP、EC_STATE_OP。
返回值:成功返回 0,失败返回 -1。
3.3 ec_readstate() 怎么用?
写完了状态,你得确认从机到底切没切过去。这就是 ec_readstate() 的活。
它读取从机的 AL Status 寄存器(0x0130),返回当前实际状态。
int ec_readstate(uint16_t slave);
返回的就是当前状态值。你可以跟目标状态做比较:
int current_state = ec_readstate(slave);
if (current_state == target_state) {
printf("状态切换成功!\n");
} else {
printf("状态不对,当前是 0x%02X\n", current_state);
}
EC_STATE_ERROR 宏来检测。
3.4 实战:从 Init 推到 Op
好,咱们直接上代码。这是一个完整的从机状态切换流程:
#include <stdio.h>
#include <osal.h>
#include <ethercat.h>
int main(int argc, char *argv[]) {
int slave = 1; // 操作第一个从机
int target_state;
int current_state;
int ret;
// 1. 初始化网络
if (ec_init("eth0") <= 0) {
printf("初始化失败!\n");
return -1;
}
printf("网络初始化成功\n");
// 2. 扫描从机
if (ec_config_init(FALSE) <= 0) {
printf("没有发现从机!\n");
ec_close();
return -1;
}
printf("发现 %d 个从机\n", ec_slavecount);
// 3. 配置映射(略,后续章节会讲)
// 4. 状态切换:Init -> Pre-Op
printf("正在切换到 Pre-Op...\n");
target_state = EC_STATE_PRE_OP;
ret = ec_writestate(slave, target_state);
if (ret != 0) {
printf("写入状态失败!\n");
ec_close();
return -1;
}
osal_usleep(10000); // 等待 10ms
current_state = ec_readstate(slave);
if (current_state != target_state) {
printf("切换到 Pre-Op 失败,当前状态: 0x%02X\n", current_state);
ec_close();
return -1;
}
printf("已切换到 Pre-Op\n");
// 5. Pre-Op -> Safe-Op
printf("正在切换到 Safe-Op...\n");
target_state = EC_STATE_SAFE_OP;
ret = ec_writestate(slave, target_state);
if (ret != 0) {
printf("写入状态失败!\n");
ec_close();
return -1;
}
osal_usleep(10000);
current_state = ec_readstate(slave);
if (current_state != target_state) {
printf("切换到 Safe-Op 失败,当前状态: 0x%02X\n", current_state);
ec_close();
return -1;
}
printf("已切换到 Safe-Op\n");
// 6. Safe-Op -> Op
printf("正在切换到 Op...\n");
target_state = EC_STATE_OP;
ret = ec_writestate(slave, target_state);
if (ret != 0) {
printf("写入状态失败!\n");
ec_close();
return -1;
}
osal_usleep(10000);
current_state = ec_readstate(slave);
if (current_state != target_state) {
printf("切换到 Op 失败,当前状态: 0x%02X\n", current_state);
ec_close();
return -1;
}
printf("已切换到 Op,从机正常运行!\n");
// 7. 运行主循环(略)
ec_close();
return 0;
}
这段代码我写得很啰嗦,但每一步都做了状态确认。你想想看,如果中间某一步失败了,你至少知道卡在哪。
3.5 错误恢复:从机掉线了怎么办?
实际项目中,从机不会永远乖乖听话。我遇到过好几次,从机在运行中突然报错,状态掉回 Init。这时候你得做错误恢复。
错误恢复的流程是这样的:
- 检测到从机状态异常(比如不是 Op)
- 读取 AL Status 寄存器,检查错误码
- 重新从 Init 开始,一步步推上去
看代码:
int recover_slave(int slave) {
int state;
int retry = 3; // 最多重试 3 次
while (retry--) {
printf("尝试恢复从机 %d...\n", slave);
// 先强制写 Init
ec_writestate(slave, EC_STATE_INIT);
osal_usleep(20000);
// 再推 Pre-Op
ec_writestate(slave, EC_STATE_PRE_OP);
osal_usleep(10000);
state = ec_readstate(slave);
if (state != EC_STATE_PRE_OP) continue;
// 推 Safe-Op
ec_writestate(slave, EC_STATE_SAFE_OP);
osal_usleep(10000);
state = ec_readstate(slave);
if (state != EC_STATE_SAFE_OP) continue;
// 推 Op
ec_writestate(slave, EC_STATE_OP);
osal_usleep(10000);
state = ec_readstate(slave);
if (state == EC_STATE_OP) {
printf("从机 %d 恢复成功!\n", slave);
return 0;
}
}
printf("从机 %d 恢复失败,请检查硬件!\n", slave);
return -1;
}
3.6 状态切换的常见问题
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| ec_writestate() 返回 -1 | 网络不通或从机未响应 | 检查网线、电源、从机地址 |
| 状态卡在 Init 不动 | 邮箱通信未配置好 | 检查 SDO 配置或从机 EEPROM |
| Safe-Op 推不到 Op | PDO 映射或过程数据有问题 | 检查 DC 配置和同步模式 |
| 状态来回跳变 | 看门狗超时或通信干扰 | 调整看门狗参数,检查布线 |
嗯,这张表是我多年踩坑的总结。你如果遇到状态切换的问题,先对照这张表排查,大概率能解决。
3.7 总结
这一节我们干了三件事:
- 理解了 EtherCAT 状态机的单向转换规则
- 学会了用 ec_writestate() 写状态、用 ec_readstate() 读状态
- 写了一个完整的从 Init 到 Op 的切换代码,还加了错误恢复
我个人觉得,状态切换是 EtherCAT 开发中最基础也最容易出问题的一环。你只要把这一节吃透了,后面配置 PDO、跑过程数据,都会顺手很多。
好,代码都在上面了。你拿去跑一跑,有问题随时回来翻这张表。