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映射、心跳周期),应用参数保留。两者最终都回到预操作状态。

初始化 Initialisation 预操作 Pre-operational 操作 Operational 停止 Stopped 自动迁移 0x01 Start 0x80 Enter Pre-op 0x02 Stop 0x02 Stop 0x01 Start 0x80 Enter Pre-op 复位命令: 0x81 Reset Node → 重置应用+通信参数,回到初始化→预操作 0x82 Reset Communication → 仅重置通信参数,回到初始化→预操作 通信能力 SDO: ✗ PDO: ✗ 心跳: ✗ 通信能力 SDO: ✓ PDO: ✗ 心跳: ✓ 通信能力 SDO: ✓ PDO: ✓ 心跳: ✓ 通信能力 SDO: ✗ 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字节状态码(与节点保护状态码相同)。主站通过监听心跳判断节点状态,若超过心跳时间未收到,则判定掉线。心跳机制无需主站轮询,更适合多节点网络。

选择建议:新设计优先使用心跳(0x1017),因其为生产者主动发送,主站实现更简单;节点保护适用于已有旧设备兼容场景。两者不能同时启用。

四、从站状态机代码实现与调试

以下以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映射、心跳周期)恢复默认,但应用参数(如传感器量程)保持不变。
常见陷阱:从站上电后若未正确进入预操作状态,主站将无法通过SDO配置参数。调试时先确认心跳报文是否已发出,状态码是否为0x7F。若心跳未发出,检查0x1017是否被正确写入且心跳功能已使能。

实际工程中,建议将NMT状态机与通信服务层解耦:状态机仅维护当前状态并触发回调,PDO/SDO模块根据状态决定是否响应。这样便于单元测试和故障注入,也符合CANopen协议的分层设计思想。