1. CANopen与NMT协议基础
各位同学,咱们今天正式开始CANopenNode从站开发的实战之旅。第一节课,我打算先把CANopen协议栈和NMT状态机这两个最基础的概念讲透。你想想看,搞嵌入式通信,如果连设备的状态机都搞不清楚,后面调试起来那真是寸步难行。我个人习惯,每接触一个新协议栈,第一件事就是先把状态转换图画出来贴在工位上。
1.1 CANopen协议栈概述
CANopen,说白了就是基于CAN总线的一套高层应用协议。它定义了一套标准化的通信机制和设备描述方法。为什么工业现场这么多设备都用它?因为它的实时性好,而且主站从站分工明确,调试起来相对顺手。
一个典型的CANopen协议栈,从底往上大致分这么几层:
- 物理层:就是CAN收发器,比如TJA1050,负责电平转换。
- 数据链路层:CAN控制器,比如STM32内部的bxCAN或FDCAN,处理帧的收发和仲裁。
- 应用层:CANopen协议栈核心,包括对象字典、NMT、PDO、SDO等。
我在项目中遇到过不少新手,上来就拿着CANopenNode的源码硬啃,结果被对象字典的EDS文件搞得一头雾水。其实不用急,咱们先抓住主线——NMT状态机。只要状态机搞明白了,设备的行为你就掌握了七八成。
核心要点:CANopenNode是一个开源的CANopen协议栈实现,支持主站和从站模式。它的代码结构清晰,非常适合在STM32上做二次开发。咱们这门课,全程基于CANopenNode v4.x版本。
1.2 NMT状态机定义
NMT,全称Network Management,网络管理。它负责控制CANopen设备的状态。每个CANopen从站,都必须实现一个标准的状态机。这个状态机有四个核心状态:初始化、预操作、操作和停止。
嗯,这里要注意,有些资料会把“初始化”拆成“初始化”和“应用层复位”两个子状态,但咱们先按最标准的四状态模型来讲。
1.3 各状态详解
1.3.1 初始化状态
设备上电或硬件复位后,首先进入初始化状态。在这个状态里,设备做三件事:
- 初始化CAN控制器和收发器。
- 加载对象字典的默认值。
- 发送Boot-up报文(0x700 + Node-ID),告诉主站“我活了”。
初始化完成后,设备自动进入预操作状态。不需要主站发任何命令。我记得第一次调CANopenNode时,忘了配置Boot-up报文的发送,结果主站一直报“节点未上线”,查了半天才发现是初始化回调里少写了一行代码。
调试技巧:用CAN分析仪抓一下总线,如果上电后看到0x700 + Node-ID的报文,说明初始化成功了。没看到?先检查硬件复位和时钟配置。
1.3.2 预操作状态
预操作状态,是设备最常用的调试状态。在这个状态下:
- SDO通信正常:主站可以通过SDO读写对象字典,配置参数。
- PDO通信禁止:不会自动发送或接收过程数据。
- NMT命令响应:可以接收主站的NMT命令。
说白了,预操作就是“配置模式”。你想想看,如果一上电就开始跑PDO,参数都还没配好,那不乱套了吗?所以工业现场的标准流程是:上电→预操作→配置参数→切换到操作状态。
1.3.3 操作状态
操作状态,是设备的正常工作模式。在这个状态下:
- PDO通信使能:按照配置好的映射,周期或事件触发地收发过程数据。
- SDO通信仍然可用:但一般不建议在运行时频繁读写。
- NMT命令响应:可以接收主站的NMT命令。
我曾经在一个伺服驱动项目里,把PDO的发送周期设成了1ms,结果总线负载直接飙到80%以上。后来在预操作状态下把参数调好,再切到操作状态,问题就解决了。所以预操作状态真的是调试利器。
1.3.4 停止状态
停止状态,设备进入“静默”模式:
- 禁止SDO通信:不能读写对象字典。
- 禁止PDO通信:不发送任何过程数据。
- 只响应NMT命令:只能接收复位节点或复位通信命令。
这个状态一般用于紧急停止或故障后的安全隔离。嗯,这里要注意,停止状态下的设备虽然不通信,但CAN控制器还是在工作的,只是应用层不处理报文。
1.4 状态转换触发条件
状态转换由NMT主站通过发送特定的CAN报文来触发。报文格式如下:
| COB-ID | 字节0 (CS) | 字节1 | 说明 |
|---|---|---|---|
| 0x000 | 0x01 | Node-ID | 启动远程节点 → 操作状态 |
| 0x000 | 0x02 | Node-ID | 停止远程节点 → 停止状态 |
| 0x000 | 0x80 | Node-ID | 进入预操作 → 预操作状态 |
| 0x000 | 0x81 | Node-ID | 复位节点 → 初始化状态 |
| 0x000 | 0x82 | Node-ID | 复位通信 → 初始化状态 |
CS是Command Specifier,命令标识符。Node-ID为0时,表示广播给总线上所有节点。
避坑指南:我曾经在调试时,主站发了复位通信命令(CS=0x82),结果从站没有重新发送Boot-up报文。后来发现是CANopenNode的NMT回调函数里,复位通信后没有重新初始化对象字典的默认值。这个坑,大家移植时一定要检查。
1.5 CANopenNode中的NMT实现
在CANopenNode源码里,NMT状态机主要在CO_NMT.c和CO_NMT.h中实现。核心数据结构是CO_NMT_t,里面有一个state成员变量,记录当前状态。
// CANopenNode NMT状态枚举
typedef enum {
CO_NMT_INITIALISATION = 0x00,
CO_NMT_PRE_OPERATIONAL = 0x7F,
CO_NMT_OPERATIONAL = 0x05,
CO_NMT_STOPPED = 0x04
} CO_NMT_state_t;
状态转换的逻辑在CO_NMT_process()函数里。每次收到NMT报文,都会调用这个函数,根据当前状态和CS值,决定是否转换以及执行什么动作。
我个人习惯,在调试阶段会在CO_NMT_process()里加一个串口打印,把每次状态转换都打出来。这样主站发了什么命令,从站有没有正确响应,一目了然。
实战要点:在STM32上移植CANopenNode时,NMT状态机的核心代码基本不用改。你只需要确保:1) CAN中断能正确接收报文;2) 定时器能提供1ms的时基;3) 对象字典的默认值符合你的应用需求。
好了,这一节的内容就到这里。NMT状态机是CANopen的基石,后面的PDO、SDO、心跳报文,全都依赖这个状态机来协调。把这四个状态和它们的转换条件记熟,后面调试起来会顺手很多。