2. 开发环境搭建:Keil/IAR工程配置、调试器驱动与printf重定向
说实话,很多初学者一上来就急着写代码,结果卡在环境搭建上。我见过太多人花了两三天都点不亮一个LED,最后发现是调试器驱动没装对。这一章,咱们就把这些基础工作一次性搞定。
2.1 开发工具的选择:Keil vs IAR
做STM32开发,主流就是这两家。我个人两个都用过,但平时主力还是Keil。为什么?因为生态好,网上能找到的例程、教程,十有八九都是Keil工程。
| 对比项 | Keil MDK | IAR EWARM |
|---|---|---|
| 编译器 | ARMCC v6 (基于Clang) | IAR C/C++ Compiler |
| 代码密度 | 中等,优化选项丰富 | 优秀,尤其对RAM敏感项目 |
| 调试体验 | 界面直观,断点管理方便 | 功能强大,但学习曲线陡 |
| License费用 | 较贵(有免费版限制32KB) | 更贵 |
| 社区资源 | 极其丰富 | 相对较少 |
我的建议:如果你刚入门,先用Keil。等做产品了,再考虑IAR的代码密度优势。我在一个量产项目里,IAR编译出来的固件比Keil小了将近15%,那会儿才觉得IAR真香。
2.2 Keil工程配置实战
咱们以STM32F103C8T6为例,走一遍标准流程。
2.2.1 新建工程
- 打开Keil,点击 Project → New μVision Project
- 选择芯片型号:
STM32F103C8 - 弹出Manage Run-Time Environment时,先点Cancel。咱们手动加文件,更清楚
2.2.2 添加启动文件和CMSIS核心
你需要从STM32Cube固件包里拷贝以下文件到工程目录:
startup_stm32f103xb.s— 启动文件system_stm32f103xe.c— 系统时钟配置stm32f1xx.h— 外设寄存器定义
注意:启动文件必须和芯片型号严格对应。F103C8是64KB Flash,用
startup_stm32f103xb.s;F103ZE是512KB,用startup_stm32f103xe.s。我曾经帮一个同事排查问题,他用了ZE的启动文件去跑C8的芯片,结果系统时钟死活不对。
2.2.3 配置魔法棒(Target & C/C++)
点击 Project → Options for Target(或者直接点魔法棒图标):
- Target标签:选择正确的ARM编译器,我一般用
ARM Compiler V6.9 - C/C++标签:在Define里加上
STM32F103xE(注意不是C8,因为头文件里xE覆盖了整个系列) - Include Paths:添加你的头文件路径,比如
.\Inc; .\Drivers\CMSIS\Include
2.3 调试器驱动安装与固件升级
调试器是连接电脑和开发板的桥梁。最常用的就是J-Link和ST-Link。
2.3.1 J-Link驱动安装
- 去SEGGER官网下载 J-Link Software and Documentation Pack
- 安装时选择 USB Driver 和 GDB Server
- 插上J-Link,打开设备管理器,应该能看到
J-Link driver
避坑指南:J-Link的盗版克隆版很多。如果你用的是几十块钱的“山寨版”,驱动可能装不上。我曾经遇到过,装完驱动后设备管理器显示感叹号。解决办法是手动替换驱动文件,或者用J-Link Commander强制更新固件。
2.3.2 ST-Link驱动与固件升级
ST-Link是STM32 Nucleo/Discovery板自带的调试器。驱动安装很简单:
- 安装 STM32CubeProgrammer,它会自动装好ST-Link驱动
- 或者单独安装 ST-Link USB driver
固件升级是个容易忽略的点。ST-Link的固件会随着IDE版本更新而更新。如果你发现Keil识别不到ST-Link,八成是固件太旧了。
升级方法:
- 打开 STM32CubeProgrammer
- 点击 Firmware Upgrade 按钮
- 选择最新的固件版本,点击 Upgrade
警告:升级过程中绝对不能拔掉USB线!我有个同事升级到一半手抖拔了,ST-Link直接变砖。后来只能用J-Link的SWD接口去救活它。
2.4 printf重定向:让调试信息飞起来
嵌入式调试最原始的方法就是点灯。但点灯只能告诉你“程序跑到了这里”,却说不清楚“变量值是多少”。printf重定向就是解决这个问题的。
2.4.1 原理简述
C库里的printf最终会调用一个底层函数:fputc。我们只需要重写这个函数,把字符输出到串口(UART)即可。
2.4.2 代码实现
在工程里新建一个 retarget.c 文件,写入以下代码:
#include <stdio.h>
#include "stm32f1xx.h"
// 重定向fputc到USART1
int fputc(int ch, FILE *f)
{
// 等待发送缓冲区空
while (!(USART1->SR & USART_SR_TXE));
// 发送一个字节
USART1->DR = (ch & 0xFF);
return ch;
}
// 如果需要scanf,还需要重定向fgetc
int fgetc(FILE *f)
{
while (!(USART1->SR & USART_SR_RXNE));
return (int)(USART1->DR & 0xFF);
}
注意:Keil MDK默认使用MicroLIB,它体积小但功能有限。如果你用了上面的代码发现printf没输出,检查一下是否勾选了 Target → Use MicroLIB。我一般会勾上,因为省空间。
2.4.3 使用示例
在主函数里初始化USART1后,就可以愉快地打印了:
int main(void)
{
// 初始化USART1,波特率115200
USART1_Init(115200);
printf("System started!\r\n");
printf("Current temperature: %d\r\n", temperature);
while(1)
{
// 主循环
}
}
2.5 本章知识体系
下面这张图把开发环境搭建的核心流程串起来了,你可以对照着检查自己哪一步还没做:
2.6 常见问题与排查
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| Keil找不到芯片 | Pack包未安装 | 打开Pack Installer,安装对应型号的DFP |
| J-Link连接失败 | 驱动问题或固件版本不匹配 | 重装驱动,或用J-Link Commander升级固件 |
| printf无输出 | 未勾选MicroLIB,或串口未初始化 | 勾选Use MicroLIB,检查串口配置 |
| 编译报错:Undefined symbol | 启动文件或系统文件未添加 | 检查工程文件列表,添加缺失的.c/.s文件 |
我的经验:环境搭建出问题,90%都是细节疏忽。比如忘记勾选MicroLIB、忘记添加头文件路径、或者调试器的SWD接线松了。别慌,按上面的表格一步步排查,基本都能解决。