第2章:开发环境搭建——STM32CubeMX工程创建、Keil/IAR编译器配置、CAN驱动移植准备

好,咱们正式开始动手了。上一章聊了CANopen的整体框架,这一章咱们把吃饭的家伙先支棱起来。说白了,就是搭一个能跑CANopen的工程环境。

我个人习惯,做嵌入式项目第一步永远是先搞定开发环境。环境不顺,后面全是坑。我记得刚入行那会儿,光配编译器就折腾了两天,后来发现是路径里有中文...嗯,这种低级错误咱们今天一次性避开。

2.1 STM32CubeMX工程创建——从零开始

STM32CubeMX是ST官方出的图形化配置工具。用它生成初始化代码,省时省力。我建议大家都用最新版,至少是6.x以上,对HAL库支持更完善。

咱们以STM32F407VGT6为例,选这个芯片是因为它带两个CAN控制器,做CANopen主从站都够用。你手头如果是F103、F429之类的,操作流程完全一样。

2.1.1 新建工程步骤

  1. 打开CubeMX,点击"New Project"
  2. 选择芯片:在Part Number搜索框输入"STM32F407VG",双击选中
  3. 配置时钟源:RCC → HSE → Crystal/Ceramic Resonator(外部晶振)
  4. 配置调试接口:SYS → Debug → Serial Wire(只用SWD,省两个引脚)
  5. 配置CAN外设:Connectivity → CAN1 → Mode: Activated

关键参数设置:

  • CAN1的波特率:我一般设500kbps。为什么?因为CANopen默认推荐就是这个值,兼容性最好。
  • Time Quantum:根据晶振和分频算,F407用8MHz晶振,设Prescaler=6,BS1=13,BS2=2,正好500k。

2.1.2 时钟树配置

时钟树这块容易乱。我教你一个口诀:先配HSE,再配PLL,最后看外设

具体操作:

  • HSE选8MHz(看你板子上的晶振)
  • PLLM=8,PLLN=336,PLLP=2 → 得到168MHz主频
  • APB1时钟=42MHz(CAN挂在APB1上,这个值决定了CAN时钟源)

你想想看,如果APB1时钟算错了,CAN波特率肯定不对。我在项目里遇到过,明明代码没问题,就是收不到数据,最后发现是时钟树配错了。所以这一步务必仔细核对。

2.2 Keil/IAR编译器配置——让代码跑起来

CubeMX生成代码后,咱们得用编译器打开。我个人偏爱Keil,但IAR也有它的优势。这里两个都讲,你根据习惯选。

2.2.1 Keil MDK配置

  1. 打开工程:在CubeMX里Project → Generate Code,选MDK-ARM V5
  2. 设置芯片型号:Project → Options for Target → Device → STM32F407VG
  3. 配置C/C++选项
// 在Define框里添加:
USE_HAL_DRIVER, STM32F407xx

// 优化等级:我建议调试阶段选-O0,发布时再改-O2
// 勾选:C99 Mode(CANopen源码需要)
  1. 配置调试器:Debug → Use: ST-Link Debugger → Settings → Flash Download → 勾选Reset and Run

小技巧:Keil里有个"Browse Information"选项,勾上后可以跳转到函数定义。我每次建工程必开这个,查代码方便很多。

2.2.2 IAR EWARM配置

如果你用IAR,流程也差不多:

  1. CubeMX生成时选"IAR Embedded Workbench"
  2. 打开.eww工程文件
  3. Project → Options → General Options → Target → Device → STM32F407VG
  4. C/C++ Compiler → Preprocessor → Defined symbols:添加USE_HAL_DRIVER, STM32F407xx
  5. Debugger → Setup → Driver: ST-Link

这里有个坑:IAR默认的堆栈大小可能不够。CANopen协议栈需要至少2KB的堆空间。我建议把Heap Size设到0x800(2KB),Stack Size设到0x1000(4KB)。

2.3 CAN驱动移植准备——让硬件说话

环境搭好了,接下来要准备CAN驱动。CANopen跑在CAN硬件之上,所以底层驱动必须稳。

2.3.1 HAL库CAN初始化流程

CubeMX生成的代码里,CAN初始化在MX_CAN1_Init()函数中。但默认配置只开了基本功能,咱们需要手动加一些东西。

// 在main.c里添加CAN过滤器配置
static void CAN_Filter_Config(void)
{
    CAN_FilterTypeDef sFilterConfig;

    sFilterConfig.FilterBank = 0;          // 使用过滤器组0
    sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  // 掩码模式
    sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位宽度
    sFilterConfig.FilterIdHigh = 0x0000;   // 标准ID高16位
    sFilterConfig.FilterIdLow = 0x0000;    // 标准ID低16位
    sFilterConfig.FilterMaskIdHigh = 0x0000;  // 掩码高16位
    sFilterConfig.FilterMaskIdLow = 0x0000;   // 掩码低16位
    sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;  // 关联到FIFO0
    sFilterConfig.FilterActivation = ENABLE;  // 使能过滤器
    sFilterConfig.SlaveStartFilterBank = 14;  // 从CAN的起始过滤器组

    HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
}

这段代码的意思是:不过滤任何ID,所有CAN报文都接收。为什么这么做?因为CANopen的SDO、PDO、NMT报文ID是动态的,一开始全收最保险。等后面协议栈跑起来了,再根据需要调整过滤器。

2.3.2 中断配置

CAN通信必须用中断,轮询方式在CANopen里根本跑不动。我见过有人用轮询做CANopen,结果CPU占用率飙到90%,啥也干不了。

// 使能CAN接收中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);

// 中断服务函数(在stm32f4xx_it.c中)
void CAN1_RX0_IRQHandler(void)
{
    HAL_CAN_IRQHandler(&hcan1);
}

注意:中断优先级要设好。CAN中断优先级建议设成1(0最高,15最低)。别设成0,否则会抢占系统滴答定时器,导致HAL_Delay()不准。我曾经因为这个bug查了两天...

2.3.3 收发测试

驱动移植完,先别急着上CANopen。做个简单的回环测试,确认硬件没问题。

// 发送一帧测试数据
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
uint32_t TxMailbox;

TxHeader.StdId = 0x123;      // 标准ID
TxHeader.ExtId = 0;          // 扩展ID(不用)
TxHeader.IDE = CAN_ID_STD;   // 标准帧
TxHeader.RTR = CAN_RTR_DATA; // 数据帧
TxHeader.DLC = 8;            // 数据长度

HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);

用CAN分析仪或者另一块板子接收,如果能收到0x123这帧数据,说明驱动移植成功了。收不到?检查波特率、时钟配置、还有物理层的终端电阻(120Ω别忘了接)。

2.4 工程目录结构建议

代码写多了,目录结构乱是最头疼的。我分享一个我用了多年的结构:

Project/
├── Core/           # 主程序、中断、系统文件
│   ├── Inc/
│   └── Src/
├── Drivers/        # HAL库、CMSIS
├── CANopen/        # CANopen协议栈源码(后面章节会加)
├── Middlewares/     # 其他中间件(比如FreeRTOS)
├── Doc/            # 文档、笔记
└── Output/         # 编译产物(.hex, .bin)

这样分层清晰,CANopen源码单独放一个文件夹,方便后续升级和维护。

2.5 本章小结

到这步,你的开发环境应该已经能跑起来了。总结一下咱们干了什么:

  • 用CubeMX创建了STM32F407的工程,配好了CAN外设和时钟
  • 在Keil或IAR里设置了编译选项,确保能正常编译下载
  • 写了CAN驱动初始化代码,做了收发测试

下一章,咱们开始移植CANopenNode协议栈。那才是真正的重头戏。不过别急,先把今天的环境跑通,有问题随时在群里问我。

课后作业:用两块开发板,互相发CAN数据。一块发0x123,另一块收到后回0x456。能实现这个,说明你的CAN驱动已经稳了。

好,今天就到这。记住:环境搭得好,后面少烦恼。