4. 软件环境搭建:安装CANopen协议栈与配置开发环境

好,咱们接着往下走。上一章我们把硬件平台搭起来了,板子有了,收发器也焊好了。但光有硬件不行,它就是个“植物人”——得把软件灌进去,它才能动起来。

这一章,我们就来搞定两件事:第一,把CANopen协议栈(我选CanFestival)装进你的工程里;第二,把Keil、IAR或者STM32CubeIDE这些开发环境配好,让代码能编译、能下载、能跑起来。

说实话,这一步是很多初学者最容易卡住的地方。我当年第一次搞CANopen,光是把CanFestival移植到STM32上就折腾了两天。后来发现,其实就是几个文件路径没搞对,宏定义没打开。嗯,咱们今天争取让你一次过。

4.1 为什么选CanFestival?

市面上CANopen协议栈不少,有商用的,有开源的。商用的一来贵,二来源码不公开,出了问题你只能干瞪眼。我个人习惯用开源方案,出了问题自己能查。

CanFestival是开源的,支持主站和从站,代码结构清晰。说白了,它就是一套C语言写的CANopen协议实现。你把它加到你的工程里,再配几个回调函数,就能让你的设备变成一个标准的CANopen从站(或者主站)。

我在项目中用过它做伺服驱动器的控制,也做过IO采集模块。稳定性没问题,跑几年不重启的都有。

核心要点: CanFestival 是开源、免费、成熟的 CANopen 协议栈,适合嵌入式系统。

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/ 这个文件夹。

小提示: 如果你用的MCU不在官方支持列表里,也不用慌。CanFestival的驱动层设计得很干净,你只需要实现几个底层接口(比如CAN发送、接收、定时器)就能移植过去。我曾在GD32上移植过,改改寄存器地址就搞定了。

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.ccanstm32.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/srcCanFestival/drivers 添加为源码目录。

嗯,这里有个坑。很多人加完路径后编译报错,说找不到 applicfg.h 或者 canfestival.h。其实就是路径没加全,或者路径写错了。检查一下,确保路径是相对于工程根目录的。

注意: 不同IDE的路径写法略有不同。Keil里是在C/C++选项卡的Include Paths里加,IAR是在Preprocessor的Additional include directories里加。别搞混了。

4.4 配置对象字典

CANopen的核心是对象字典。说白了,就是一张表,里面定义了你的设备有哪些参数、哪些数据可以读写。

CanFestival提供了一个图形化工具叫 objdictgen,在 objdictgen/ 目录下。你运行它,可以新建一个对象字典文件(.od或.eds)。

对于运动控制,你至少需要定义这些对象:

索引 名称 说明
0x1000 设备类型 比如伺服驱动器是0x00020192
0x1001 错误寄存器 记录设备错误状态
0x1017 心跳生产者时间 我一般设100ms
0x6040 控制字 启动、停止、使能等
0x6041 状态字 读取当前状态
0x6060 操作模式 位置模式、速度模式、转矩模式
0x607A 目标位置 位置模式下用
0x6081 轮廓速度 运动速度设定

用objdictgen生成好对象字典后,它会输出一个 .c 和一个 .h 文件。把这两个文件也加到你的工程里。

我的经验: 对象字典的配置直接决定了你的设备能不能被主站识别。我曾经因为设备类型码写错了,导致主站一直报错,查了半天才发现是0x1000的值不对。所以,建议你对照CiA 402标准来填,别自己瞎编。

4.5 编写底层驱动接口

CanFestival需要你提供三个底层函数:

  1. CAN发送函数 —— 把一帧CAN报文发出去
  2. CAN接收回调 —— 收到CAN报文时通知协议栈
  3. 定时器函数 —— 用于协议栈的时间管理(比如心跳、同步报文)

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也没问题。

避坑指南: 我曾经在IAR上遇到一个奇怪的问题——编译通过,但下载后程序跑不起来。查了半天,发现是IAR的优化等级太高,把某些关键变量优化掉了。所以,如果你遇到类似问题,先把优化等级降到 -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),恭喜你,环境搭建成功了!

验证标准: 收到Boot-up报文 = CANopen协议栈正常工作。收不到?检查CAN收发器供电、检查波特率、检查中断配置。

4.8 本章小结

这一章我们干了三件事:下载并集成了CanFestival协议栈,配置了对象字典,写好了底层驱动接口。最后还验证了环境是否搭建成功。

说实话,这一步确实有点繁琐。但只要你把路径配对了,三个底层接口写对了,剩下的就是水到渠成的事。下一章我们就要开始写真正的运动控制逻辑了——让电机转起来!

嗯,今天就到这里。有问题随时找我。


公众号:蓝海资料掘金营,微信deep3321