4、开发环境搭建:Keil/IAR工程配置、STM32CubeMX初始化、LWIP协议栈移植、CAN驱动库集成
好,咱们进入第四章。这一章说白了就是“搭台子”。你想想看,后面所有的路由逻辑、协议转换,都得跑在这个台子上。台子搭不稳,后面全是白费功夫。
我个人习惯,做这种嵌入式网关项目,先把工具链理清楚。Keil 还是 IAR?STM32CubeMX 怎么配?LWIP 怎么塞进去?CAN 驱动从哪来?咱们一个一个来。
4.1 工具链选择:Keil MDK vs IAR EWARM
说实话,这两家我都用过。Keil 的社区资源多,网上例程一抓一把。IAR 的编译优化确实狠,代码密度能小 10%-15%。
我建议你根据团队习惯来选。如果项目就你一个人,用 Keil 省心。如果是团队协作,IAR 的工程管理更规范。
| 对比项 | Keil MDK | IAR EWARM |
|---|---|---|
| 编译器 | ARMCC v6 (基于 Clang) | IAR C/C++ Compiler |
| 调试器支持 | J-Link, ST-Link, ULINK | J-Link, I-jet, ST-Link |
| LWIP 集成 | 有 CMSIS-Driver 封装 | 需手动移植 |
| 代码优化 | 中等 | 高(尤其速度优化) |
| License 价格 | 约 $5000(Pro) | 约 $8000(Pro) |
4.2 STM32CubeMX 初始化配置
这一步是地基。我见过太多人上来就写代码,结果 Pin 脚冲突、时钟配错,折腾两天才发现是 CubeMX 没配好。
咱们这个网关,核心外设就这几个:ETH(以太网 MAC)、CAN(FD 或经典 CAN)、USART(调试用)、SPI(如果外接 CAN 控制器)。
- 时钟树配置: 外部晶振 25MHz,HSE 倍频到 400MHz(H7)或 216MHz(F4)。ETH 需要 50MHz 的 RMII 参考时钟。
- ETH 引脚: RMII 模式,PA1(REF_CLK),PG11(TX_EN),PG13(TX_D0),PG14(TX_D1),PA2(RX_D0),PC4(RX_D1),PA7(CRS_DV)。
- CAN 引脚: 以 STM32F407 为例,CAN1 用 PD0(RX)和 PD1(TX)。CAN2 用 PB5(RX)和 PB6(TX)。
- DMA 配置: ETH 的 DMA 描述符建议用 4 个 TX 和 4 个 RX。CAN 的 FIFO 用 DMA 传输,减轻 CPU 负担。
4.3 LWIP 协议栈移植
LWIP 移植,说白了就是让协议栈知道“怎么把数据塞进 ETH 的 DMA 描述符里”。
我习惯用 CMSIS-Driver 的方式移植。ST 官方已经封装好了 ETH 的 CMSIS 驱动,你只需要在 lwipopts.h 里配好参数。
// lwipopts.h 关键配置
#define NO_SYS 1 // 无 RTOS 模式
#define LWIP_SOCKET 0 // 不用 Socket API
#define LWIP_NETCONN 0 // 不用 Netconn API
#define LWIP_HTTPD 1 // 启用 HTTP 服务器(用于 Web 配置)
#define MEM_SIZE (16 * 1024) // 内存堆大小
#define PBUF_POOL_SIZE 16 // PBUF 池数量
#define PBUF_POOL_BUFSIZE 1536 // 每个 PBUF 大小(含以太网帧头)
移植的核心文件就三个:ethernetif.c(底层接口)、lwip.c(初始化)、lwipopts.h(配置)。
我建议你把 ethernetif.c 里的 low_level_output() 函数仔细看一遍。它负责把 LWIP 的 pbuf 链转换成 ETH 的 DMA 描述符链。这里容易出 Bug。
pbuf 是链表结构,而 ETH DMA 描述符是数组结构。转换时要注意内存对齐和字节序。
4.4 CAN 驱动库集成
CAN 驱动,我分两种情况讲。
情况一:STM32 内置 CAN 控制器(bxCAN)
直接用 STM32 HAL 库的 CAN 驱动。初始化流程很简单:
// CAN 初始化示例
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 6; // 分频系数
hcan1.Init.Mode = CAN_MODE_NORMAL; // 正常模式
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_13TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = ENABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = DISABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
HAL_CAN_Init(&hcan1);
情况二:外接 SPI 接口的 CAN 控制器(如 MCP2515)
这种就需要自己写 SPI 读写函数。我建议你封装一个 can_spi_driver.c,把 SPI 的 CS、SCK、MOSI、MISO 操作抽象出来。
| CAN 方案 | 优点 | 缺点 |
|---|---|---|
| 内置 bxCAN | 集成度高,延迟低 | FIFO 深度有限(3 个邮箱) |
| 外接 MCP2515 | FIFO 可扩展,支持 CAN FD | SPI 通信有额外延迟 |
| 外接 SJA1000 | 经典方案,资料多 | 不支持 CAN FD,已停产 |
4.5 整体架构图
下面这张图,是我做这个网关时的软件分层结构。你照着搭,基本不会错。
4.6 工程文件结构
最后,给你看看我习惯的工程目录结构。清晰的文件组织,能让你少掉不少头发。
Project_Gateway/
├── Core/
│ ├── Inc/ # 头文件
│ ├── Src/ # 主程序、中断、系统配置
│ └── Startup/ # 启动文件
├── Drivers/
│ ├── CMSIS/ # ARM CMSIS 核心
│ ├── STM32H7xx_HAL/ # STM32 HAL 库
│ └── BSP/ # 板级支持包(ETH PHY、CAN 收发器)
├── Middleware/
│ ├── LWIP/ # LWIP 协议栈源码
│ ├── CANopen/ # CANopen 协议栈(可选)
│ └── FreeRTOS/ # 实时操作系统(可选)
├── Application/
│ ├── can_route/ # CAN 路由引擎
│ ├── eth_route/ # 以太网路由引擎
│ ├── web_config/ # Web 配置页面
│ └── log/ # 日志管理
├── Tools/
│ ├── script/ # 编译脚本、烧录脚本
│ └── test/ # 单元测试代码
└── Doc/ # 设计文档、API 手册