3、开发环境搭建:选择合适的MCU、编译器配置、调试工具准备
好,咱们开始第三讲。前面两章我们把CANopen的协议框架和Node的软件结构理了一遍。说实话,光看代码是跑不起来的,你得有个能跑的环境。这一章我就带你手把手把开发环境搭起来。
我个人习惯是先把工具链理清楚,再动手写代码。你想想看,如果编译器版本不对,或者调试器连不上,后面排查问题会非常痛苦。我在项目中遇到过好几次,因为IDE版本不一致,折腾了一整天才发现是工具链的问题。
3.1 选择合适的MCU
选MCU这件事,说白了就是看你的项目需求。CANopen对MCU的要求其实不高,但有几个硬指标必须满足:
- CAN控制器:必须内置CAN外设,或者能外接CAN控制器芯片(比如MCP2515)。我个人建议用内置的,省事。
- RAM大小:至少8KB以上。CANopenNode的PDO、SDO缓冲区会占用不少内存。我见过有人用4KB的芯片跑CANopen,结果SDO传输稍微大一点就崩了。
- Flash大小:至少64KB。CANopenNode的协议栈本身大约30-40KB,加上你的应用代码,64KB是底线。
- 定时器资源:至少需要一个硬件定时器,用于产生SYNC信号和心跳报文的时间基准。
我常用的几款MCU给大家列一下:
| MCU型号 | 内核 | CAN外设 | RAM | Flash | 推荐理由 |
|---|---|---|---|---|---|
| STM32F103C8T6 | Cortex-M3 | bxCAN | 20KB | 64KB | 便宜、资料多、入门首选 |
| STM32F407VGT6 | Cortex-M4F | bxCAN x2 | 192KB | 1MB | 性能强、双CAN、适合复杂节点 |
| GD32F103C8T6 | Cortex-M3 | CAN | 20KB | 64KB | 国产替代、性价比高 |
| AT32F403AVGT7 | Cortex-M4F | CAN x2 | 224KB | 1MB | 主频高、适合做CANopen主站 |
3.2 编译器配置
编译器这块,我推荐用GCC工具链。为什么?因为CANopenNode本身就是用GCC编译的,你用IAR或者Keil也能跑,但移植起来会多出一些工作量。
我个人习惯用ARM GCC,版本选10.3.1或者更新的。太老的版本对C11标准支持不好,CANopenNode里用了一些C11的特性,比如_Static_assert。
下面是一个典型的GCC编译命令,我直接贴出来:
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb \
-O2 -g -Wall -Wextra \
-std=c11 -DUSE_HAL_DRIVER -DSTM32F103xE \
-I./Inc -I./Drivers/STM32F1xx_HAL_Driver/Inc \
-I./Middlewares/Third_Party/CANopenNode \
-c main.c -o main.o
这里有几个关键点:
-mcpu=cortex-m3:指定CPU内核,根据你的MCU型号调整。如果是M4就用cortex-m4。-O2:优化等级。调试阶段可以用-O0,但正式发布建议用-O2。-std=c11:必须指定C11标准,否则CANopenNode里的一些宏定义会报错。-DUSE_HAL_DRIVER:如果你用STM32的HAL库,这个宏必须定义。
-Os(优化体积)后,CANopenNode的心跳定时器居然不准了。排查了半天,发现是编译器把定时器中断里的一个volatile变量优化掉了。所以,如果你发现定时不准,先检查一下优化等级。
链接脚本也要注意。CANopenNode需要一块固定的RAM区域来存储对象字典。我一般会在链接脚本里单独划出一块内存:
/* 在链接脚本中定义CANopen对象字典区域 */
. = ALIGN(4);
_canopen_od_start = .;
*(.canopen_od)
_canopen_od_end = .;
然后在代码里用__attribute__((section(".canopen_od")))来指定对象字典的存放位置。这样做的好处是,对象字典的地址是固定的,方便调试器查看。
3.3 调试工具准备
调试工具这块,我分成两部分说:硬件调试器和软件调试环境。
3.3.1 硬件调试器
最常用的就是J-Link和ST-Link。我个人更推荐J-Link,虽然贵一点,但稳定性和速度都好很多。ST-Link也能用,但有时候会掉线,尤其是调试CAN中断的时候。
如果你预算有限,可以买一个国产的DAP-Link,十几块钱,配合OpenOCD也能用。但要注意,DAP-Link的SWD速度不能设太高,否则容易断连。
3.3.2 软件调试环境
我习惯用VSCode + Cortex-Debug插件。配置起来很简单,写一个launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "CANopen Debug",
"type": "cortex-debug",
"request": "launch",
"servertype": "jlink",
"device": "STM32F103C8",
"interface": "swd",
"svdFile": "./STM32F103xx.svd",
"executable": "./build/firmware.elf",
"runToMain": true
}
]
}
这里有个小技巧:svdFile一定要配上。SVD文件里包含了MCU所有外设寄存器的地址和位域定义,调试的时候可以直接看CAN寄存器的值,非常方便。
- 逻辑分析仪:至少8通道,采样率100MHz以上。用来抓CAN总线上的波形,排查物理层问题。
- CAN分析仪:比如PCAN-USB或者ZLG的USBCAN。用来发送和接收CAN报文,验证协议栈是否正确。
- 串口调试助手:用来打印调试信息。CANopenNode内部有
CO_DEBUG宏,打开后可以输出详细的协议栈运行日志。
3.4 环境验证:跑一个最小系统
环境搭好之后,怎么验证能不能用?我的做法是写一个最小系统:让MCU上电后,每隔1秒通过CAN发送一个心跳报文。
代码很简单:
#include "CANopen.h"
int main(void) {
// 初始化硬件
HAL_Init();
SystemClock_Config();
MX_CAN_Init();
// 初始化CANopen协议栈
CO_NMT_reset_cmd_t reset = CO_RESET_COMMAND;
CO_ReturnError_t err = CO_init(&CO_OD, &CO_CANmodule, &CO_NMT,
&CO_EM, &CO_SDO, &CO_TPDO, &CO_RPDO,
&CO_HBconsumer, &CO_SYNC, &CO_LSS,
CO_OD_CNT, CO_CAN_CNT, &reset);
if (err != CO_ERROR_NO) {
// 初始化失败,打印错误码
while(1);
}
// 主循环
while(1) {
CO_process(&CO_NMT, &CO_EM, &CO_SDO, &CO_TPDO, &CO_RPDO,
&CO_HBconsumer, &CO_SYNC, &CO_LSS, 1);
HAL_Delay(1);
}
}
编译下载后,用CAN分析仪看总线。如果每隔1秒收到一个ID为0x700+NodeID的报文,说明环境搭建成功了。
好了,这一章的内容就到这里。环境搭好之后,下一章我们就可以正式开始写CANopenNode的驱动代码了。记住,工具链和调试环境是基础,基础不牢,后面写代码会非常痛苦。我建议你花点时间把这一步做扎实。