2. 开发环境搭建:STM32F4平台选择,CubeMX配置CAN外设,CANopenNode源码移植,编译与烧录验证
好,咱们正式开始动手。这一章说白了就是搭台子——把硬件平台、开发工具和CANopenNode源码全部就位,确保第一行代码能跑起来。
我个人习惯先把环境理清楚再写代码,不然出了问题都不知道是硬件还是软件的事。你想想看,CAN通信要是连收发器都没配对,后面PDO映射再漂亮也是白搭。
2.1 硬件平台选型:为什么是STM32F4?
市面上能跑CANopen的MCU不少,但我选STM32F4系列,原因很直接:
- 双CAN控制器:F405/F407都有两个CAN 2.0B接口,做网关或冗余通信很方便
- 主频够用:168MHz处理PDO同步报文绰绰有余
- 生态成熟:HAL库、CubeMX、各种调试工具一应俱全
- 成本可控:F4系列现在价格已经降下来了,量产不心疼
我在项目中用过F103做CANopen从站,但遇到高波特率(1Mbps)加多PDO的场景,CPU占用率直接飙到70%以上。换成F407之后,同样的负载降到20%左右。所以如果你要做实时控制,别在F1上省钱。
| 型号 | CAN接口数 | 主频 | RAM | Flash | 推荐场景 |
|---|---|---|---|---|---|
| STM32F103C8 | 1 | 72MHz | 20KB | 64KB | 简单IO从站 |
| STM32F405RG | 2 | 168MHz | 192KB | 1MB | 多PDO从站 |
| STM32F407VG | 2 | 168MHz | 192KB | 1MB | 带DSP/FPU的复杂从站 |
2.2 CubeMX配置CAN外设
打开STM32CubeMX,新建工程,选好你的芯片型号。我以STM32F407VG为例,一步步来。
2.2.1 时钟配置
先把时钟树搞定。F4的CAN外设挂在APB1总线上,最高时钟为42MHz。我习惯这样配:
- HSE:8MHz外部晶振
- PLL:168MHz主频
- APB1:42MHz(给CAN用)
- APB2:84MHz(给GPIO、SPI等用)
2.2.2 CAN引脚分配
CAN1的默认引脚是:
- CAN1_RX:PD0
- CAN1_TX:PD1
在Pinout视图里把这两个脚配成AF9复用功能。别忘了使能CAN1外设。
2.2.3 CAN参数设置
进入Parameter Settings,关键参数如下:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Baud Rate | 250 kbps | CANopen默认波特率,调试阶段最稳 |
| Time Quanta | 16 | 一个位时间分成16个时间片 |
| Sync Jump Width | 1 | 重同步跳转宽度,一般取1 |
| Mode | Normal | 调试时可用Loopback模式自测 |
2.2.4 生成代码
配好后点Project → Generate Code,选择MDK-ARM或STM32CubeIDE。生成后你会看到can.c和can.h,HAL库的CAN初始化代码已经自动写好了。
2.3 CANopenNode源码移植
CANopenNode是一个轻量级的CANopen协议栈,用纯C写的,移植性很好。我从GitHub上拉下来,然后手动整合到工程里。
2.3.1 源码结构
核心文件就这几个:
CANopen.c/CANopen.h:协议栈主逻辑CO_driver.c/CO_driver.h:硬件抽象层,需要你实现CO_Emergency.c:紧急报文处理CO_PDO.c:PDO收发逻辑CO_SDO.c:SDO服务器CO_NMT_Heartbeat.c:NMT和心跳
2.3.2 移植要点
说白了,移植就是填CO_driver.c里的几个函数空壳:
// 发送CAN帧
int16_t CO_CANsend(CANmodule_t *CANmodule, CO_CANtx_t *buffer) {
// 调用HAL_CAN_AddTxMessage
uint32_t mailbox;
HAL_CAN_AddTxMessage(&hcan1, &txHeader, buffer->data, &mailbox);
return 0;
}
// 检查是否有新报文
uint8_t CO_CANrxMsgRead(CANmodule_t *CANmodule, CO_CANrxMsg_t *buffer) {
// 从FIFO读取
HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &rxHeader, buffer->data);
return 1;
}
// 定时器中断回调(1ms)
void CO_TimerIRQHandler(void) {
CO_CANmodule_process(CANmodule);
CO_NMT_process(CANmodule, &OD, &SDO, &EM, &HB);
}
CO_TimerIRQHandler()。这个节奏不能乱,否则同步PDO会丢帧。
2.3.3 对象字典配置
对象字典(OD)是CANopen的核心。CANopenNode用CO_OD.c和CO_OD.h来定义。我一般这样配:
// 定义两个PDO:一个发送,一个接收
#define OD_ENTRY_H1001_ERROR_REGISTER 0x1001
#define OD_ENTRY_H1400_RPDO1_COMM_PARAM 0x1400
#define OD_ENTRY_H1600_RPDO1_MAP_PARAM 0x1600
#define OD_ENTRY_H1800_TPDO1_COMM_PARAM 0x1800
#define OD_ENTRY_H1A00_TPDO1_MAP_PARAM 0x1A00
嗯,这里要注意:对象字典的索引和子索引必须严格按照CiA 301标准来,否则主站会认不出来。
2.4 编译与烧录验证
2.4.1 编译
我用的是STM32CubeIDE,直接点锤子图标编译。第一次编译可能会报错,多半是:
- 头文件路径没加:把CANopenNode的
inc目录加进工程 - 中断优先级冲突:CAN接收中断和定时器中断优先级要错开
- 堆栈溢出:把
Heap_Size和Stack_Size改大一点,我一般设0x800
2.4.2 烧录
用ST-Link连接开发板,点Debug或Download。烧录成功后,打开串口助手(115200波特率),你会看到:
CANopenNode v4.0 initialized
Node ID: 0x01
Baudrate: 250 kbps
Pre-operational state
这说明协议栈已经跑起来了,节点处于预操作状态。
2.4.3 验证通信
用CAN分析仪(比如PCAN-USB或USB-CANmodul)发一个NMT启动命令:
- 发送:0x000(CAN ID),数据:0x01 0x01(启动节点1)
- 如果收到心跳报文(CAN ID 0x701,数据0x05),说明通信成功
2.5 本章知识体系总览
下面这张图把整个开发环境搭建的流程串起来了,你对照着看会更清楚:
这张图把从硬件选型到验证的完整链路画出来了。你按这个顺序走,基本不会漏步骤。