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的配置思路其实一样,只是菜单位置不同。

小提示: 无论用哪个IDE,都建议安装最新版本。老版本编译器对C11、C17标准的支持不好,写结构体指针时容易出坑。

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_DRIVERSTM32F103xE(根据你的芯片型号改)。
注意: 如果你用STM32CubeMX生成的代码,记得把 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自动覆盖旧数据,你只需要在中断里判断接收了多少字节就行。

重点: 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刚好够用。如果你用自定义协议,按最大帧长度来设。

警告: 缓冲区不要设太大,比如2048字节。DMA循环模式会覆盖数据,如果你处理速度跟不上,数据会被新数据冲掉。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帧解析。