2. STM32基础回顾

说实话,很多初学者一上来就急着写代码,结果卡在环境搭建上。我见过太多人花了两三天都点不亮一个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 新建工程

  1. 打开Keil,点击 Project → New μVision Project
  2. 选择芯片型号:STM32F103C8
  3. 弹出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驱动安装

  1. 去SEGGER官网下载 J-Link Software and Documentation Pack
  2. 安装时选择 USB Driver 和 GDB Server
  3. 插上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,八成是固件太旧了。

升级方法:

  1. 打开 STM32CubeProgrammer
  2. 点击 Firmware Upgrade 按钮
  3. 选择最新的固件版本,点击 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 本章知识体系

下面这张图把开发环境搭建的核心流程串起来了,你可以对照着检查自己哪一步还没做:

开发环境搭建核心流程 开发环境搭建核心流程 选择IDE Keil MDK IAR EWARM J-Link / ST-Link J-Link / ST-Link printf重定向 无论选Keil还是IAR,最终都要完成:工程配置 → 调试器驱动 → printf输出

2.6 常见问题与排查

问题现象 可能原因 解决办法
Keil找不到芯片 Pack包未安装 打开Pack Installer,安装对应型号的DFP
J-Link连接失败 驱动问题或固件版本不匹配 重装驱动,或用J-Link Commander升级固件
printf无输出 未勾选MicroLIB,或串口未初始化 勾选Use MicroLIB,检查串口配置
编译报错:Undefined symbol 启动文件或系统文件未添加 检查工程文件列表,添加缺失的.c/.s文件
我的经验:环境搭建出问题,90%都是细节疏忽。比如忘记勾选MicroLIB、忘记添加头文件路径、或者调试器的SWD接线松了。别慌,按上面的表格一步步排查,基本都能解决。