4、开发环境搭建:Linux下编译CANopenNode、STM32平台移植、树莓派配置

好,咱们开始搭建干活的环境了。

说实话,做机器人控制,环境搭不好,后面全是坑。我见过太多人,代码写得挺溜,结果编译不过、烧录失败、通信不通,一查全是环境问题。所以这一章,咱们把Linux编译、STM32移植、树莓派配置这三个核心环节,一个一个捋清楚。

4.1 Linux下编译CANopenNode

我个人习惯在Ubuntu 20.04或22.04上做开发。CANopenNode本身是纯C写的,移植性很好。但编译时有些细节,不注意就会卡住。

4.1.1 获取源码

从GitHub拉下来就行:

git clone https://github.com/CANopenNode/CANopenNode.git
cd CANopenNode

嗯,这里要注意,CANopenNode依赖一个叫CANopenNode/stack的子模块。很多人忘了拉子模块,编译直接报错。

我曾经遇到过:拉完主仓库就开始编译,结果报了一堆头文件找不到。折腾了半天才发现是子模块没更新。

正确的做法:

git submodule update --init --recursive

4.1.2 编译选项与配置

CANopenNode用CMake构建。我个人建议用cmake-gui或者直接命令行:

mkdir build && cd build
cmake ..
make -j4

这里有几个关键选项,你想想看:

  • CAN_DRIVER:选哪个底层CAN驱动。Linux下常用socketCAN,也就是CAN_DRIVER_SOCKETCAN
  • CO_ENABLE_LSS:要不要LSS(Layer Setting Services)?如果你需要动态分配节点ID,就打开。我一般开着,省事。
  • CO_ENABLE_SDO_BLOCK:SDO块传输。大数据传输时有用,但会增加代码体积。
小技巧:如果你只是测试,可以用cmake -DCMAKE_BUILD_TYPE=Debug ..打开调试信息。出问题时能少掉不少头发。

4.1.3 编译测试

编译成功后,会生成一个叫canopend的可执行文件。跑一下:

./canopend -i 1 -s 0 -c "can0"

这个命令的意思是:节点ID为1,不启动SDO服务器,使用can0接口。如果看到终端打印出NMT: bootup,说明环境通了。

4.2 STM32平台移植

STM32是机器人控制里用得最多的MCU之一。CANopenNode移植到STM32,说白了就是搞定两件事:CAN驱动定时器

4.2.1 硬件准备

我建议用STM32F4或F7系列,带双CAN的型号。比如STM32F407VGT6,性价比很高。接线很简单:

  • CAN_TX → PA12 (CAN1)
  • CAN_RX → PA11 (CAN1)
  • CAN收发器:TJA1050或SN65HVD230,注意3.3V和5V电平匹配
我曾经在项目里直接用3.3V的STM32去驱动5V的CAN收发器,结果通信时好时坏。后来加了电平转换芯片,问题才解决。别省这个钱。

4.2.2 移植步骤

CANopenNode的移植,核心是填充三个回调函数:

  1. CAN发送CO_CANsend()
  2. CAN接收:中断里调用CO_CANreceive()
  3. 定时器:1ms的定时中断,调用CO_TMRreload()

我一般用HAL库,代码结构大概这样:

// CAN发送回调
static int8_t canSend(CAN_HandleTypeDef *hcan, uint16_t cob_id, uint8_t *data, uint8_t len) {
    CAN_TxHeaderTypeDef txHeader;
    txHeader.StdId = cob_id;
    txHeader.IDE = CAN_ID_STD;
    txHeader.RTR = CAN_RTR_DATA;
    txHeader.DLC = len;
    // 发送
    if (HAL_CAN_AddTxMessage(hcan, &txHeader, data, &txMailbox) != HAL_OK)
        return -1;
    return 0;
}

// 定时器中断(1ms)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM3) {
        CO_TMRreload(&canopenNode.TMR);
    }
}
我个人习惯:把CANopenNode的实例化放在一个单独的文件里,比如canopen_app.c。这样主循环里只调用canopen_app_process(),清爽很多。

4.2.3 时钟配置

CANopenNode要求一个1ms的定时器。我通常用TIM3,APB1时钟84MHz,分频后得到1ms中断:

// 定时器配置
TIM_HandleTypeDef htim3;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 84-1;   // 84MHz / 84 = 1MHz
htim3.Init.Period = 1000-1;    // 1MHz / 1000 = 1ms
HAL_TIM_Base_Start_IT(&htim3);

4.3 树莓派配置

树莓派在机器人里常作为上位机或网关。配置CANopenNode,主要靠SocketCAN。

4.3.1 启用CAN接口

树莓派本身没有CAN控制器,需要外接SPI转CAN模块,比如MCP2515。我推荐用mcp251x驱动:

# 在/boot/config.txt中添加
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25
dtoverlay=spi-bcm2835-overlay

重启后,用ip link show应该能看到can0接口。

我曾经遇到过:树莓派4B上,MCP2515的中断引脚和蓝牙冲突。后来换成了GPIO25才解决。如果你用树莓派3B+,这个问题少一些。

4.3.2 设置SocketCAN

启用并设置波特率:

sudo ip link set can0 up type can bitrate 250000
sudo ifconfig can0 up

验证是否成功:

candump can0

如果没报错,说明CAN接口已经活了。

4.3.3 编译并运行CANopenNode

在树莓派上编译CANopenNode,和Linux下一样。但要注意:树莓派是ARM架构,有些依赖库需要重新编译。我建议用apt安装基础工具:

sudo apt install cmake build-essential libsocketcan-dev

然后编译:

mkdir build && cd build
cmake .. -DCAN_DRIVER=socketcan
make -j4

运行:

./canopend -i 1 -s 0 -c "can0"
小提示:如果你想让树莓派开机自动启动CANopenNode,可以写一个systemd服务。我一般这样写:
[Unit]
Description=CANopenNode
After=network.target

[Service]
ExecStart=/home/pi/canopend -i 1 -s 0 -c "can0"
Restart=always

[Install]
WantedBy=multi-user.target

4.4 小结

环境搭建这块,说白了就是三个字:耐心试。Linux下编译,注意子模块;STM32移植,搞定CAN和定时器;树莓派配置,SocketCAN是核心。

我刚开始做的时候,光STM32的CAN中断就调了两天。后来发现是中断优先级没配好,导致接收丢帧。嗯,这些坑踩过了,你们就能少走弯路。

下一章,咱们开始写第一个CANopen节点,让电机转起来。