4. 软件环境搭建:安装CANopen协议栈与配置开发环境
好,咱们接着往下走。上一章我们把硬件平台搭起来了,板子有了,收发器也焊好了。但光有硬件不行,它就是个“植物人”——得把软件灌进去,它才能动起来。
这一章,我们就来搞定两件事:第一,把CANopen协议栈(我选CanFestival)装进你的工程里;第二,把Keil、IAR或者STM32CubeIDE这些开发环境配好,让代码能编译、能下载、能跑起来。
说实话,这一步是很多初学者最容易卡住的地方。我当年第一次搞CANopen,光是把CanFestival移植到STM32上就折腾了两天。后来发现,其实就是几个文件路径没搞对,宏定义没打开。嗯,咱们今天争取让你一次过。
4.1 为什么选CanFestival?
市面上CANopen协议栈不少,有商用的,有开源的。商用的一来贵,二来源码不公开,出了问题你只能干瞪眼。我个人习惯用开源方案,出了问题自己能查。
CanFestival是开源的,支持主站和从站,代码结构清晰。说白了,它就是一套C语言写的CANopen协议实现。你把它加到你的工程里,再配几个回调函数,就能让你的设备变成一个标准的CANopen从站(或者主站)。
我在项目中用过它做伺服驱动器的控制,也做过IO采集模块。稳定性没问题,跑几年不重启的都有。
4.2 下载CanFestival源码
先到SourceForge或者GitHub上把CanFestival的源码拉下来。我建议你直接克隆GitHub上的版本,因为有些bug已经被社区修过了。
git clone https://github.com/canfestival/canfestival.git
下载下来后,你会看到这样的目录结构:
canfestival/
├── src/ # 核心协议栈源码
├── include/ # 头文件
├── drivers/ # 各种MCU的驱动适配层
├── examples/ # 示例工程
├── objdictgen/ # 对象字典生成工具
└── doc/ # 文档
这里要注意,drivers/ 目录下有针对STM32、LPC、AVR等不同MCU的驱动。我们用的是STM32,所以重点关注 drivers/stm32/ 这个文件夹。
4.3 把CanFestival集成到你的工程里
这一步是重点。我们以STM32CubeIDE为例,Keil和IAR的操作逻辑类似。
4.3.1 创建基础工程
先用STM32CubeMX生成一个基础工程。记得在Pinout & Configuration里把CAN外设打开,配置好波特率(CANopen标准是125k、250k、500k、1M,我一般用250k或500k)。
然后生成代码。这时候你得到一个能跑HAL库的工程,但还没有CANopen。
4.3.2 复制CanFestival源码
把CanFestival源码中的以下文件夹复制到你的工程目录下:
src/—— 全部复制include/—— 全部复制drivers/stm32/—— 复制里面的canstm32.c和canstm32.h
复制完后,你的工程目录大概长这样:
MyProject/
├── Core/ # STM32CubeMX生成的代码
├── Drivers/ # HAL库
├── CanFestival/ # 我们刚复制进来的
│ ├── src/
│ ├── include/
│ └── drivers/
└── ...
4.3.3 添加文件到工程
在STM32CubeIDE里,右键你的工程 -> Properties -> C/C++ Build -> Settings -> MCU GCC Compiler -> Include paths,把CanFestival的头文件路径加进去:
./CanFestival/include
./CanFestival/src
./CanFestival/drivers
然后在Source folders里把 CanFestival/src 和 CanFestival/drivers 添加为源码目录。
嗯,这里有个坑。很多人加完路径后编译报错,说找不到 applicfg.h 或者 canfestival.h。其实就是路径没加全,或者路径写错了。检查一下,确保路径是相对于工程根目录的。
4.4 配置对象字典
CANopen的核心是对象字典。说白了,就是一张表,里面定义了你的设备有哪些参数、哪些数据可以读写。
CanFestival提供了一个图形化工具叫 objdictgen,在 objdictgen/ 目录下。你运行它,可以新建一个对象字典文件(.od或.eds)。
对于运动控制,你至少需要定义这些对象:
| 索引 | 名称 | 说明 |
|---|---|---|
| 0x1000 | 设备类型 | 比如伺服驱动器是0x00020192 |
| 0x1001 | 错误寄存器 | 记录设备错误状态 |
| 0x1017 | 心跳生产者时间 | 我一般设100ms |
| 0x6040 | 控制字 | 启动、停止、使能等 |
| 0x6041 | 状态字 | 读取当前状态 |
| 0x6060 | 操作模式 | 位置模式、速度模式、转矩模式 |
| 0x607A | 目标位置 | 位置模式下用 |
| 0x6081 | 轮廓速度 | 运动速度设定 |
用objdictgen生成好对象字典后,它会输出一个 .c 和一个 .h 文件。把这两个文件也加到你的工程里。
4.5 编写底层驱动接口
CanFestival需要你提供三个底层函数:
- CAN发送函数 —— 把一帧CAN报文发出去
- CAN接收回调 —— 收到CAN报文时通知协议栈
- 定时器函数 —— 用于协议栈的时间管理(比如心跳、同步报文)
在 canstm32.c 里,这些函数已经写好了框架。你只需要把HAL库的CAN接口填进去就行。
举个例子,发送函数大概长这样:
unsigned char canSend(CAN_PORT notused, Message *m)
{
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
uint32_t TxMailbox;
TxHeader.StdId = m->cob_id;
TxHeader.ExtId = 0;
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = (m->rtr) ? CAN_RTR_REMOTE : CAN_RTR_DATA;
TxHeader.DLC = m->len;
for(int i = 0; i < m->len; i++)
TxData[i] = m->data[i];
if(HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox) != HAL_OK)
return 0;
return 1;
}
接收回调呢,一般在CAN的中断服务函数里调用:
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
Message m;
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData);
m.cob_id = RxHeader.StdId;
m.len = RxHeader.DLC;
m.rtr = (RxHeader.RTR == CAN_RTR_REMOTE) ? 1 : 0;
for(int i = 0; i < m.len; i++)
m.data[i] = RxData[i];
canDispatch(&m); // 交给CanFestival协议栈处理
}
定时器函数呢,一般用一个硬件定时器,每1ms产生一次中断。在中断里调用 TimeDispatch() 函数。
4.6 配置开发环境(Keil / IAR / STM32CubeIDE)
三种IDE的配置大同小异,我分别说一下关键点。
4.6.1 STM32CubeIDE
前面已经讲了大半。补充一点:在Project Settings里,把优化等级设为 -O2 或 -Os。CanFestival代码量不小,不开优化的话,Flash可能不够用。
4.6.2 Keil
在Keil里,你需要:
- 把CanFestival的源文件添加到工程组里
- 在Options for Target -> C/C++ -> Include Paths 里加头文件路径
- 在Target选项卡里选好芯片型号和Flash大小
- 注意:Keil默认的堆栈可能不够,建议把Heap Size设大一点,比如0x800
4.6.3 IAR
IAR的配置类似。在Project -> Options -> C/C++ Compiler -> Preprocessor 里加头文件路径。在Linker -> Stack/Heap Sizes 里把堆设大。
我个人习惯用STM32CubeIDE,因为免费,而且和CubeMX集成得好。但如果你公司买了正版Keil,用Keil也没问题。
-O0 试试。
4.7 验证环境是否搭建成功
代码写好了,怎么知道环境搭对了?
我的做法是:先写一个最简单的测试——让设备上电后发送一个Boot-up报文(COB-ID = 0x700 + Node-ID)。如果CAN分析仪上能收到这个报文,说明协议栈已经跑起来了。
测试代码大概这样:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_CAN_Init();
MX_TIM_Init();
// 初始化CANopen协议栈
canOpen(&can1, &canDispatch, &canSend);
setNodeId(&can1, 0x01); // 节点ID设为1
// 启动CANopen
can1.Node->State = Initialisation;
can1.Node->State = Pre_operational;
can1.Node->State = Operational;
while(1)
{
// 主循环里什么都不做,协议栈靠中断驱动
}
}
如果CAN分析仪上收到了0x701的报文(Boot-up),恭喜你,环境搭建成功了!
4.8 本章小结
这一章我们干了三件事:下载并集成了CanFestival协议栈,配置了对象字典,写好了底层驱动接口。最后还验证了环境是否搭建成功。
说实话,这一步确实有点繁琐。但只要你把路径配对了,三个底层接口写对了,剩下的就是水到渠成的事。下一章我们就要开始写真正的运动控制逻辑了——让电机转起来!
嗯,今天就到这里。有问题随时找我。
公众号:蓝海资料掘金营,微信deep3321