4. NMT网络管理与从站状态机
NMT(Network Management)是CANopen网络中负责节点生命周期管理的核心机制。它不传输过程数据,却决定了从站何时能参与PDO通信、何时只能响应SDO配置。理解NMT的关键在于两点:一是从站状态机及其迁移条件,二是NMT报文格式与节点监控机制(节点保护/心跳)。
一、从站状态机与迁移条件
CANopen从站上电后并非直接进入可通信状态,而是经历一个受控的状态序列。标准定义了四个主要状态:
- 初始化(Initialisation):上电或复位后进入,执行自检、加载对象字典默认值、配置CAN控制器。此阶段不参与任何通信(除自动进入预操作的内部迁移)。
- 预操作(Pre-operational):可进行SDO通信(参数配置、对象字典读写),可发送心跳/节点保护报文,但不能进行PDO通信。
- 操作(Operational):全部通信可用,包括PDO、SDO、心跳/节点保护。这是过程数据交换的正常工作状态。
- 停止(Stopped):仅响应NMT命令和心跳/节点保护,SDO和PDO均不可用。用于安全停机或故障隔离。
状态迁移由NMT主站发送的命令触发,命令通过NMT报文(COB-ID 0x000)广播或定向发送。迁移条件如下:
| 当前状态 | NMT命令 | 目标状态 |
|---|---|---|
| 初始化 | 自动(内部) | 预操作 |
| 预操作 | Start Remote Node (0x01) | 操作 |
| 预操作 | Stop Remote Node (0x02) | 停止 |
| 操作 | Stop Remote Node (0x02) | 停止 |
| 操作 | Enter Pre-operational (0x80) | 预操作 |
| 停止 | Start Remote Node (0x01) | 操作 |
| 停止 | Enter Pre-operational (0x80) | 预操作 |
| 任意 | Reset Node (0x81) | 初始化→预操作 |
| 任意 | Reset Communication (0x82) | 初始化→预操作 |
注意:Reset Node会重置应用参数和通信参数,Reset Communication仅重置通信参数(如PDO映射、心跳周期),应用参数保留。两者最终都回到预操作状态。
二、NMT报文格式
NMT报文使用CAN标识符0x000(最高优先级),数据长度固定为2字节:
字节0:NMT命令符(Command Specifier)
字节1:目标节点ID(Node-ID),0表示广播所有节点
命令符定义:
0x01 Start Remote Node → 进入操作状态
0x02 Stop Remote Node → 进入停止状态
0x80 Enter Pre-operational → 进入预操作状态
0x81 Reset Node → 复位节点(应用+通信)
0x82 Reset Communication → 复位通信
例如,主站要让节点3进入操作状态,发送CAN帧:ID=0x000,DLC=2,数据=01 03。若数据为01 00,则所有节点同时进入操作状态。
三、节点保护与心跳机制
NMT主站需要知道从站是否在线。CANopen提供两种监控机制,二者互斥,由对象字典0x1017(生产者心跳时间)和0x1016(消费者心跳时间/节点保护时间)配置。
节点保护(Node Guarding):主站周期性发送远程帧(RTR),从站响应1字节状态码。状态码bit7为触发位(Toggle),bit0~6表示NMT状态(0=初始化,4=停止,5=操作,127=预操作)。若从站在保护时间内未响应,主站判定其掉线。节点保护基于请求-响应模式,主站需维护每个节点的超时计时器。
心跳(Heartbeat):从站主动周期性发送心跳报文,COB-ID = 0x700 + Node-ID,数据为1字节状态码(与节点保护状态码相同)。主站通过监听心跳判断节点状态,若超过心跳时间未收到,则判定掉线。心跳机制无需主站轮询,更适合多节点网络。
四、从站状态机代码实现与调试
以下以C语言伪代码展示从站状态机核心逻辑,基于CAN接收中断处理NMT报文:
typedef enum {
STATE_INIT,
STATE_PRE_OP,
STATE_OP,
STATE_STOPPED
} nmt_state_t;
volatile nmt_state_t g_nmt_state = STATE_INIT;
void nmt_init(void) {
// 上电初始化:加载对象字典默认值、配置CAN控制器
od_load_defaults();
can_hw_init(BAUD_500K);
g_nmt_state = STATE_PRE_OP; // 自动进入预操作
heartbeat_start(1000); // 心跳周期1000ms
}
void can_rx_isr(uint32_t cob_id, uint8_t *data, uint8_t dlc) {
if (cob_id == 0x000 && dlc == 2) {
uint8_t cmd = data[0];
uint8_t node = data[1];
if (node != 0 && node != MY_NODE_ID) return; // 非本节点
switch (cmd) {
case 0x01: // Start
g_nmt_state = STATE_OP;
pdo_enable_all();
break;
case 0x02: // Stop
g_nmt_state = STATE_STOPPED;
pdo_disable_all();
break;
case 0x80: // Enter Pre-op
g_nmt_state = STATE_PRE_OP;
pdo_disable_all();
break;
case 0x81: // Reset Node
od_reset_application();
od_reset_communication();
g_nmt_state = STATE_PRE_OP;
break;
case 0x82: // Reset Communication
od_reset_communication();
g_nmt_state = STATE_PRE_OP;
break;
}
}
// 其他COB-ID处理...
}
调试时重点关注以下观察点:
- 状态迁移是否生效:用CAN分析仪抓取0x000报文后,观察从站心跳报文(0x700+ID)的状态码是否变化。例如发送
01 03后,节点3心跳数据应从7F(预操作)变为05(操作)。 - PDO是否按状态启停:进入操作状态后,TPDO应开始周期发送;进入停止或预操作后,TPDO应立即停止。若未停止,检查PDO使能逻辑是否与状态机耦合。
- 心跳超时判断:主站侧维护每个节点的心跳超时计时器,若连续2~3个心跳周期未收到,应触发节点掉线告警。从站侧可用示波器或CAN分析仪测量心跳报文间隔是否稳定在配置值附近。
- 复位行为验证:发送0x81后,从站应重新加载默认参数并回到预操作;发送0x82后,通信参数(如PDO映射、心跳周期)恢复默认,但应用参数(如传感器量程)保持不变。
实际工程中,建议将NMT状态机与通信服务层解耦:状态机仅维护当前状态并触发回调,PDO/SDO模块根据状态决定是否响应。这样便于单元测试和故障注入,也符合CANopen协议的分层设计思想。