ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32F10x官方例程详解:从GPIO到DMA的工程实践指南

2026/9/2 4:27:48 拓冰建站 浏览量
STM32F10x官方例程详解:从GPIO到DMA的工程实践指南 简介STM32F10x官方例程是一套由意法半导体提供的嵌入式开发示例集合面向使用ARM Cortex-M3内核的STM32F10x系列微控制器的开发者与学习者。资源系统覆盖GPIO、定时器、ADC/DAC、USART/SPI、I2C、USB、CAN、DMA、RTC、EXTI及FFT等常见外设模块每个示例均含可编译工程与配置流程便于对照寄存器库理解芯片工作机制。压缩包共947个文件以C源文件348个、头文件275个、汇编启动文件44个及txt说明文档114个为主同时包含链接脚本、工程配置文件与CHM帮助文档整体约22.41MB。目前已有1496人学习下载。这套基于标准外设库V3.5.0的官方例程既适合初级嵌入式开发者系统入门也可作为实际项目中外设驱动移植和调试的重要参考能帮助读者快速上手STM32F10x开发。1. 为什么我建议你别急着扔了官方例程前阵子帮一个刚转嵌入式的小兄弟调板子他拿着某宝买的开发板第一件事就是把官方例程全部删掉理由是“代码太乱看不懂”。结果折腾了三天GPIO 翻转都点不亮 LED。后来我让他把官方例程重新拉回来对照着改十分钟就通了。这种事儿我见过太多次了。STM32F10x 官方例程也就是 ST 官方提供的 Standard Peripheral Library标准外设库配套示例代码是学习这颗芯片最好的起点没有之一。它解决的问题非常明确告诉你每个外设到底怎么配置、怎么用从最简单的 GPIO 点灯到 USART 通信、DMA 搬运、ADC 采集、定时器中断全都有现成可跑的工程。这套例程适合谁刚入坑 STM32 的新手靠它建立“寄存器配置看不懂但 API 调用能跑通”的信心做产品的工程师靠它快速验证某个外设在这个型号上有没有坑甚至资深开发者偶尔也要翻翻官方代码确认某个时序参数到底该怎么配。这篇文章我就把这套例程的目录结构、核心设计思路、典型外设的实现方式以及我踩过的那些坑一次性讲清楚。2. 官方例程的整体设计和目录结构2.1 为什么 ST 要把例程分类成三大块如果你下载过 STM32F10x 标准外设库现在 ST 主推 HAL 库但这套老库仍大量存在于存量项目和工业设备里解压后你会看到Project、Libraries、Utilities三个核心文件夹外加一堆说明文档。先说Libraries这里面装的是 CMSIS 内核头文件和标准外设驱动源码。CMSIS 是 ARM 制定的 Cortex-M 内核软件接口标准说人话就是不管你是 STM32 还是别的 Cortex-M 芯片内核寄存器访问方式都统一了这样你换芯片时底层代码不用从零写。外设驱动源码则是stm32f10x_gpio.c、stm32f10x_usart.c这类文件每个文件对应一个外设模块对外暴露的是一组以GPIO_Init、USART_SendData这种命名规则为主的 API 函数。再说Project这里放的是各个编译工具链的工程模板和官方例程。ARMCC、GCC、IAR 都有对应目录你用什么 IDE 就打开哪个。STM32F10x 官方例程的标准命名是STM32F10x_StdPeriph_Examples底下按外设又分了一堆子文件夹什么ADC、DMA、GPIO、TIM、USART每个文件夹里是一个个独立的小工程。最后是Utilities这块放的是 ST 官方评估板比如 STM32F10x-EVAL的板级驱动代码用来操作评估板上集成的 LCD、按键、LED、音频芯片等外设。如果你用的是自己画的板子这部分通常直接忽略但如果你手上正好有官方评估板这些代码能让你快速跑起一个带界面的演示程序。2.2 例程工程里那几个关键文件的真实作用随便打开一个官方例程的工程比如GPIO_IOToggle你会发现里面有一堆文件。很多新手看到头就大其实核心就三类第一类是启动文件名字一般是startup_stm32f10x_hd.s。不同的后缀代表不同容量的芯片hd是 high-density对应大容量型号md是 medium-density对应中等容量比如最常见的 STM32F103C8T6ld是 low-density对应小容量。选错启动文件程序很可能跑飞或者直接进 HardFault这是新手最容易忽略的问题。第二类是系统初始化文件包括system_stm32f10x.c和stm32f10x_it.c。前者负责配置系统时钟把外部晶振频率或者内部 HSI 时钟倍频到 72MHz后者是所有中断服务函数的默认入口你写中断处理逻辑就在这里加。第三类就是你自己要改的main.c和stm32f10x_conf.h。前者是用户代码后者是一个外设头文件的汇总配置文件你想用哪个外设就把对应头文件的#include注释打开同时把源文件加入工程编译。这里我想特别说一句官方例程的stm32f10x_conf.h默认会把所有外设的头文件都列出来你如果不裁剪编译时会把整个外设库全部编译一遍费时间不说Flash 占用也会大不少。实际项目里建议只保留用到的那几个外设比如只用 GPIO 和 USART就把其他的全注释掉。3. 核心外设例程的细节解析3.1 GPIO 例程一切外设操作的基本功GPIO 例程一般长这样初始化时钟、配置 Pin 的模式和速度、然后在主循环里翻转电平。官方例程里用的是GPIO_InitGPIO_WriteBit这种标准库 API代码如下/* 使能 GPIOC 时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); while (1) { GPIO_SetBits(GPIOC, GPIO_Pin_12); Delay(0xFFFF); GPIO_ResetBits(GPIOC, GPIO_Pin_12); Delay(0xFFFF); }这段代码看起来简单但里面有几个值得琢磨的点。RCC_APB2PeriphClockCmd这行是很多新手最容易漏掉的。STM32 的外设时钟默认是关闭的你不用 RCC 先打开直接配寄存器是无效的。这一点和 51 单片机完全不同51 的 IO 口上电就能用STM32 必须先开时钟。GPIO_Mode有输入、输出、复用、模拟四种大类输出又分为推挽和开漏。官方例程里默认用的是推挽输出这也是绝大多数数字信号输出的选择因为它能主动输出高电平和低电平驱动能力也强。开漏输出则常用于 I2C 总线或者需要外部上拉的场景。GPIO_Speed则决定了 IO 口的翻转速率官方例程常用 50MHz。有同学问过是不是速度配得越高越好不是。高速模式会带来更大的 EMI 噪声和功耗实际项目里建议按需配置点个 LED 用 2MHz 都绰绰有余。3.2 USART 例程串口通信的标准姿势USART 例程是官方例程里另外一组含金量很高的代码因为串口是调试阶段和外界打交道的主动脉。官方例程一般分两部分轮询方式和中断方式。轮询方式的核心逻辑是发送时用USART_SendData往数据寄存器丢数据然后等USART_GetFlagStatus(USARTx, USART_FLAG_TXE)变成 SET 再发下一个接收时则等在USART_FLAG_RXNE一旦置位就表示收到数据了调用USART_ReceiveData取出来。中断方式的代码则多了一个USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE)和对应的中断服务函数USART1_IRQHandler。在中断里判断中断标志位然后接收数据。很多人在串口例程上遇到的问题就一个乱码。乱码的根源一般不是代码逻辑问题而是波特率不匹配或者时钟配置不对。官方例程默认用的外部晶振是 8MHz倍频到 72MHz 系统时钟然后 USART 分频器按给定波特率反算分频系数。如果你的板子上晶振不是 8MHz比如是 12MHz 或者 25MHz那你必须改system_stm32f10x.c里的 PLL 配置参数否则串口波特率算出来是错的收发必然是乱码。我踩过最隐蔽的一个坑是有的板子用了 8MHz 晶振但内部 HSE 启动失败后自动切换到了 HSI内部 8MHz RC 振荡器而 HSI 的精度远不如晶振串口跑到 115200 时偶发乱码。这种问题很难查因为程序能跑、功能基本正常就是偶尔丢字节。最后是在system_stm32f10x.c里加了 HSE 启动检测加循环等待确保晶振起振后才继续执行。3.3 DMA 例程释放 CPU 的关键手段DMA直接存储器访问是 STM32F10x 里非常重要的一个外设它的核心价值就是“搬运数据不占用 CPU”。官方 DMA 例程通常会演示两件事内存到内存的数据拷贝以及外设到内存的数据搬运。内存到内存的例程比较直观定义一个源数组和一个目标数组配置好 DMA 通道、方向、数据宽度启动一次传输然后等传输完成标志最后比较两个数组内容。这类例程的价值在于让你建立对 DMA 工作流程的体感源地址、目标地址、传输长度、传输方向、优先级这五个核心要素是 DMA 配置的全部精髓。外设到内存的例程则更贴近实际应用比如 ADC 连续采样通过 DMA 把采样结果自动搬到内存缓冲区这样 CPU 完全不用管 ADC 数据的读取直接处理数组里的数据就行。官方例程里这个例程叫做ADC_DMA我建议每个学 STM32 的人都亲手跑一遍它打通了“模拟信号 - 数字量 - 内存数据”这条完整链路对理解整个系统的数据流非常有帮助。不过 DMA 例程有个注意点STM32F10x 的 DMA 有 DMA1 和 DMA2 两个控制器每个控制器下有多个通道每个通道能服务的外设是固定的。比如 USART1 的接收只对应 DMA1_Channel5你配到其他通道是触发不了的。官方例程里虽然把映射关系写进去了但很多新手不看注释直接照抄换了个外设就发现 DMA 不工作其实是通道映射错了。4. 实操过程从零跑通一个官方例程工程4.1 从下载到编译烧录的完整链路我以最常见的 MDK-ARMKeil环境为例讲一下怎么把一个官方例程工程跑起来。第一步下载标准外设库。去 ST 官网搜STM32F10x standard peripheral library下载 zip 包解压。国内网络环境不好的话可以找一些镜像站或者第三方托管不过建议还是尽量以官网版本为准避免被二次修改过的库坑到。第二步找到例程工程。以最常用的GPIO_IOToggle为例路径是STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\GPIO\GPIO_IOToggle\MDK-ARM里面的GPIO_IOToggle.uvproj就是 Keil 工程文件双击打开。第三步选择芯片型号。打开工程后在 Options for Target 里把 Device 选成你实际使用的型号。很多同学用的最小系统板是 STM32F103C8T6但官方工程默认可能是 STM32F103ZE这里要改掉。不改的话启动文件、Flash 大小、RAM 大小都对不上编译能过下载后大概率跑不起来。第四步确认调试器配置。在 Debug 选项卡里选择你手上实际的调试器比如 ST-Link 或者 J-Link同时在 Utilities 选项卡里勾选 Reset and Run这样烧录后程序会自动复位运行省去手动复位的麻烦。第五步编译下载。点击 Build确认没有错误然后 Load程序就进芯片了。到这里你就能看到 LED 在闪烁了。别小看这个流程我见过不少同学卡在“编译报错找不#包含文件”这一步。官方例程在工程里用的是相对路径如果你把整个工程移动位置或者改了目录结构头文件路径就失效了。解决办法是在 C/C 选项卡里的 Include Paths 里重新添加Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x等几个关键目录。4.2 把官方例程改造成自己的工程模板跑通一个例程只是第一步更有价值的操作是把官方例程改造成自己项目的基础模板。我自己的做法是这样的第一步复制整个示例工程目录重命名成项目名。第二步删掉不需要的示例代码保留Libraries和Project两个根目录。第三步在Project中新建User、Hardware、App三个文件夹分别放主函数、板级驱动、业务逻辑。第四步在Hardware里写自己的外设初始化文件比如led.c、uart.c、adc.c每个文件配套一个同名头文件。这里的写法完全参考官方例程的风格一个初始化函数 若干操作函数。第五步把自己写的驱动源文件加入工程编译同时在stm32f10x_conf.h里只保留需要的外设头文件。这个模板一旦建立起来后面写任何功能就都是在往上添砖加瓦而不是每次从零敲寄存器。我自己的经验是一套精心维护的模板能让新项目的启动时间从两天压缩到两个小时。4.3 官方例程在不同 Compiler 下的迁移要点除了 Keil还有不少开发者用 IAR 或者 GCC 工具链。官方例程虽然三种工具链的工程都给了但实际用起来有个明显的差异点需要留意就是启动文件里的堆栈配置和编译器指令。比如 Keil 工程里的启动文件由 ARMCC 的汇编器编译IAR 工程里的启动文件是 IAR 格式两者不能通用。如果你在 Keil 里写好了代码换到 IAR 编译必须切换成 IAR 的启动文件否则第一行汇编就报错。GCC 工具链用的人相对少一些一般配合 Makefile 或者 CMake 使用官方例程里没有为 GCC 提供现成的链接脚本需要你自己写.ld文件。不过现在很多人直接用 STM32CubeMX 生成 GCC 工程配合 HAL 库或者 LL 库已经不需要手写链接脚本了。我的建议是学习阶段专心用一套工具链吃透官方例程的代码逻辑生产阶段再根据自己的团队技术栈选择迁移方案。频繁切换工具链会浪费大量时间在环境配置上对学习本身没有太大帮助。5. 官方例程的常见问题与排查技巧5.1 例程编译报错的头文件路径问题官方例程最经典的问题就是编译报fatal error: stm32f10x.h: No such file or directory。它的根源在于 Keil 的头文件搜索路径配置里官方用的是相对路径一旦工程被移动或者从压缩包直接解压到中文路径下相对路径失效头文件就找不到了。排查思路很简单先在工程目录下确认stm32f10x.h实际位于哪个文件夹然后在 Options for Target - C/C - Include Paths 里添加一个绝对路径指向该目录。同时也要把Libraries\CMSIS\CM3\CoreSupport和DeviceSupport下的路径加进去因为内核头文件和设备头文件不在同一个目录。另外提醒一点工程路径不要有中文也不要带空格。如果你把工程放在D:\我的项目\STM32 测试\这种目录下编译器对中文和空格的支持在某些版本下是有问题的会报一些莫名其妙找不到文件的错误。5.2 例程烧录后无法运行的可能原因如果你把官方例程烧录进芯片后发现 LED 不闪、程序像死了一样可以从这几个方向排查按优先级从高到低第一启动文件选错。这是最常见的。STM32F103C8T6 是 64KB Flash对应中容量用startup_stm32f10x_md.s如果你的芯片是 STM32F103ZET6是 512KB Flash对应大容量用startup_stm32f10x_hd.s。启动文件里的向量表偏移量是和 Flash 容量挂钩的选错了直接跑飞。第二时钟启动失败。如果你的板子上晶振坏了或者没焊HSE 起振失败后如果代码里没有做超时检测程序会卡在while循环里。这个时候用调试器暂停程序看 PC 指针停在哪里如果是停在system_stm32f10x.c里的某个while那就是时钟问题。第三调试器连接不上。这个倒好判断把 BOOT0 拉高后再连接将芯片强制进入系统存储器模式就能绕过用户程序连上调试器然后擦除 Flash 再查问题。BOOT0 拉高这个操作是嵌入式调试里的万能恢复手段一定要记牢。5.3 官方例程和实际项目的差距到底在哪明确了官方例程的价值和使用方法以后我还想泼一盆冷水。官方例程的代码风格是“教学式”的它要保证任何时候读代码的人都能看懂因此牺牲了不少工程化特性。最典型的一点官方例程里大量使用轮询等待。比如我上面贴的 GPIO 翻转代码主循环里用了软件延时循环期间 CPU 什么都干不了。这在教学里没问题但在真正的产品里主循环往往要处理多个任务任何一个阻塞延时都是灾难。再比如官方例程的错误处理普遍很薄弱。很多外设例程里数据发送失败、接收超时、DMA 传输错误处理方式要么是死等要么是直接忽略。实际项目里这些异常都必须有合理的返回值和错误处理机制否则系统一运行就暴露各种隐性 bug。所以我的建议是官方例程是你学习外设工作原理的教科书但它不是你的产品代码。正确的方法是吃透官方例程的代码逻辑理解每个配置项的作用然后根据自己项目的需求写一套精简、有错误处理、有合理时间片分配的外设驱动代码。我在实际项目中一直用的就是这套策略——先跑官方例程验证外设没有硬件问题再写进自己的驱动框架。这个流程走下来开发稳定高效排查问题的时候也容易定位。最后再分享一个小技巧。官方例程里每个外设文件夹下都有一个readme.txt里面有这个例程的详细说明演示了什么功能、依赖哪些引脚、预期效果是什么。很多人从下载到放弃都没点开过这个文件。其实它比网上 90% 的教程写得都清楚尤其是引脚连接关系和硬件配置要求照着接线基本不会出错。本文还有配套的精品资源点击获取