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的复杂从站
我的建议:初学者先用NUCLEO-F407ZG开发板,板载ST-Link调试器,自带CAN收发器(需要跳线),到手就能用。

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模式自测
我曾经踩过的坑:波特率配成500kbps,但总线上的终端电阻没接对,导致通信时好时坏。后来用示波器一看,CAN_H和CAN_L的差分电压只有1.2V(正常应2V左右)。加上120Ω终端电阻后立刻恢复正常。所以,调试前先确认终端电阻。

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);
}
关键点:CANopenNode需要一个1ms的定时器中断来驱动协议栈。我用STM32的TIM3产生1ms中断,在中断里调用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),说明通信成功
快速验证技巧:没有CAN分析仪?用两个开发板对连,一个跑CANopenNode,另一个发NMT命令。我早期调试就是这么干的,省钱又方便。

2.5 本章知识体系总览

下面这张图把整个开发环境搭建的流程串起来了,你对照着看会更清楚:

开发环境搭建流程 开发环境搭建流程 硬件选型 STM32F407VG CubeMX配置 CAN外设 + 时钟 CANopenNode移植 CO_driver.c适配 编译 STM32CubeIDE 烧录 ST-Link 验证 CAN分析仪 💡 关键检查点 1. 终端电阻:CAN_H和CAN_L之间必须接120Ω 2. 波特率一致:所有节点波特率必须相同 3. 节点ID唯一:同一总线上不能有重复ID 图2-1 开发环境搭建流程与关键检查点

这张图把从硬件选型到验证的完整链路画出来了。你按这个顺序走,基本不会漏步骤。