4、开发环境准备:Keil/IAR IDE配置、STM32CubeMX初始化、串口DMA配置
好,咱们正式开始动手了。
这一章,我把开发环境搭建的细节掰开揉碎讲清楚。你可能会觉得「装个IDE有什么好说的」,但我见过太多人在这一步卡住——不是编译器版本不对,就是DMA配置漏了一两个参数,最后调试时一脸懵。
我个人习惯,先把整个工具链理一遍,再动手。这样心里有底。
4.1 开发工具选型:Keil 还是 IAR?
先说结论:两个都能用,但我建议你根据项目团队来选。
- Keil MDK:国内用的人多,资料好找。如果你做STM32,Keil的Pack包支持很全。我早期做项目时一直用Keil,因为同事都用它,出了问题好互相看代码。
- IAR Embedded Workbench:编译优化更强,代码密度更小。我在做Bootloader时遇到过Keil编译出来的代码体积超标,换成IAR就过了。但IAR的界面风格偏老,上手需要适应。
我的建议:如果你刚入门,用Keil。如果你做量产产品、对代码体积有要求,可以考虑IAR。两个IDE的配置思路其实一样,只是菜单位置不同。
4.2 Keil IDE 配置要点
安装过程我就不啰嗦了,重点说配置。
4.2.1 工程模板创建
打开Keil,点 Project → New μVision Project。选好芯片型号,比如STM32F103C8T6。然后会弹出一个Manage Run-Time Environment窗口——这里我建议先关掉,后面用CubeMX生成初始化代码会更省事。
4.2.2 编译器与输出配置
右键工程,选 Options for Target,重点看这几个地方:
- Target 标签:ARM Compiler 选
V6.x(如果支持)。V6编译更快,但语法检查更严格。我遇到过V5编译通过、V6报错的情况,其实就是指针类型没写对。 - Output 标签:勾上
Create HEX File。做固件升级必须生成HEX或BIN文件。 - C/C++ 标签:在
Define里加上USE_HAL_DRIVER和STM32F103xE(根据你的芯片型号改)。
Include Paths 加上HAL库的路径。否则编译时会报找不到头文件。
4.2.3 调试器配置
在 Debug 标签里,选你的调试器(ST-Link、J-Link或DAP-Link)。我习惯用ST-Link,便宜且稳定。点 Settings,把 SWD 速度设为 4MHz,太高了容易掉线。
4.3 IAR IDE 配置要点
IAR的配置逻辑和Keil差不多,但菜单名不一样。
4.3.1 创建工程
点 Project → Create New Project,选 Empty Project。然后右键工程名,选 Options。
4.3.2 关键配置项
- General Options → Target:选你的芯片型号。IAR的芯片列表比Keil全,但搜索功能不太好用,建议直接输入型号前几位。
- C/C++ Compiler → Preprocessor:同样加上
USE_HAL_DRIVER和芯片宏定义。 - Linker → Output:勾上
Generate additional output,格式选Intel extended HEX。
说实话,IAR的界面布局我一开始很不习惯。但用久了发现它的错误提示比Keil详细,定位问题更快。
4.4 STM32CubeMX 初始化配置
这个工具是ST官方的图形化配置软件。说白了,就是帮你自动生成初始化代码,省得你手动写时钟、GPIO、外设配置。
4.4.1 新建工程
打开CubeMX,点 New Project,选芯片型号。然后进入Pinout & Configuration界面。
4.4.2 时钟树配置
点 Clock Configuration 标签。这里我直接说经验:
- HSE(外部高速晶振):选
Crystal/Ceramic Resonator。如果你板子上是8MHz晶振,就填8。 - PLL配置:一般把主频拉到最高。比如STM32F103最高72MHz,就设
HCLK = 72MHz。 - APB1和APB2时钟:APB1最高36MHz,APB2最高72MHz。串口通常挂在APB1上,注意别超频。
我曾经遇到过一个问题:串口波特率不准,数据收乱码。查了半天,发现是APB1时钟设成了72MHz,而串口外设最高只支持36MHz。嗯,这种低级错误犯一次就记住了。
4.4.3 串口引脚配置
在 Pinout & Configuration 里,找到 USART1(或其他串口),Mode 选 Asynchronous。然后看右边的引脚图,PA9(TX)和PA10(RX)会自动分配好。
参数设置里,我一般这样配:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Baud Rate | 115200 | 固件升级用这个速度,稳定且快 |
| Word Length | 8 Bits | 标准Modbus帧格式 |
| Parity | None | Modbus RTU自带CRC校验,不需要奇偶校验 |
| Stop Bits | 1 | 标准配置 |
4.5 串口DMA配置——这个最关键
做固件升级,数据量不小。如果用轮询方式接收,CPU会被占死。用中断方式,每个字节都进中断,效率也低。所以必须用DMA——直接内存访问,数据从串口外设自动搬到内存,CPU只管处理。
4.5.1 在CubeMX中开启DMA
在 USART1 配置里,点 DMA Settings 标签。点 Add,添加两个通道:
- USART1_RX:DMA通道选
DMA1 Channel 5(不同芯片可能不同),方向Peripheral to Memory,模式Circular(循环模式)。 - USART1_TX:DMA通道选
DMA1 Channel 4,方向Memory to Peripheral,模式Normal。
为什么RX用循环模式?因为固件升级数据是连续接收的,循环模式可以让DMA自动覆盖旧数据,你只需要在中断里判断接收了多少字节就行。
High。因为串口接收不能丢数据,优先级低了容易被其他中断打断。
4.5.2 生成代码后的手动调整
CubeMX生成的代码只是框架,有几个地方需要你手动补:
// 1. 启动DMA接收(放在main函数初始化里)
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
// 2. 在中断回调函数里处理接收完成事件
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART1)
{
// Size 就是本次接收到的数据长度
// 在这里处理一帧Modbus数据
process_modbus_frame(rx_buffer, Size);
// 重新启动DMA接收
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
}
这里有个坑:HAL_UARTEx_RxEventCallback 是HAL库1.8.0之后才有的函数。老版本用的是 HAL_UART_RxCpltCallback,但那个函数在DMA模式下不会自动传回接收长度。我刚开始用的时候没注意版本,折腾了一下午才发现是回调函数用错了。
4.5.3 缓冲区大小设计
DMA接收缓冲区的大小,我建议设成256字节或512字节。为什么?Modbus RTU最大帧长度是256字节(地址1 + 功能码1 + 数据最多252 + CRC2),设成256刚好够用。如果你用自定义协议,按最大帧长度来设。
4.6 验证环境是否正常
配置完成后,写个最简单的测试代码:
// 串口发送测试
uint8_t test_msg[] = "Hello from Bootloader!\r\n";
HAL_UART_Transmit_DMA(&huart1, test_msg, sizeof(test_msg) - 1);
while(1)
{
// 主循环里什么都不做,DMA自动处理收发
}
用串口助手连上你的板子,波特率115200。如果看到「Hello from Bootloader!」,说明环境配置成功了。
如果没看到,别慌。先检查这几个地方:
- 串口线有没有接反?TX接RX,RX接TX。
- 波特率对不对?115200,8N1。
- DMA时钟有没有开启?在CubeMX里确认DMA1的时钟是使能的。
我记得第一次调DMA时,板子死活没反应。查了半小时,发现是CubeMX里忘了勾DMA的中断使能。嗯,这种细节最容易漏。
好,环境准备好了。下一章我们开始写Bootloader的核心代码——串口接收与Modbus帧解析。