简介:STM32F103外设例程包面向嵌入式开发学习者与需要快速完成功能验证的工程师,覆盖GPIO、定时器、ADC/DAC、UART、SPI、I2C、CAN、USB及PWM等主流外设,每个模块均提供可直接编译的工程示例,有助于理解寄存器配置、时钟使能、中断处理与DMA传输等核心概念。压缩包内共548个文件,以214个头文件与197个C源文件为核心,辅以启动汇编asm、链接脚本lsl/ld/icf、Keil/IAR工程文件及说明文档,结构清晰便于直接对照学习或导入主流IDE,整体约876KB。已有431人学习/浏览。例程覆盖从初始化配置到数据收发的完整流程,包含TIM生成PWM、ADC多通道采集、SPI主从通信、I2C读写等典型场景;结合STM32官方参考手册,可深入理解引脚复用、定时器时基与协议时序,同时学习HAL/LL库的调用方式与查询、中断、DMA模式的选择技巧,快速搭建自己的工程模板,适应课程设计、竞赛备考及产品原型开发等需求。
1. 例程到底该怎么看:先搞懂需求和工程结构
STM32F103这颗芯片,放到今天依然有大量新项目在用,这本身就能说明问题:外设齐全、资料多、价格稳,最关键的是你随手一搜就能找到海量例程。但例程多了也有烦恼——从哪个开始看?哪个靠谱?尤其是刚接触的朋友,往往被五花八门的工程模板搞晕,光是"标准外设库"和"HAL库"之争就够纠结一阵子了。
我自己的习惯是,拿到一个陌生例程,先别急着编译下载,花十分钟把工程结构和启动流程捋一遍。一个规范的STM32F103例程,无论用什么库,核心骨架都差不多:启动文件、系统时钟初始化、外设驱动、主循环逻辑。启动文件决定了芯片上电后从哪里开始执行,时钟初始化决定了CPU能跑多快、外设能跑多准,这两块是地基,地基歪了后面全是空中楼阁。
还有个很多人忽略的细节:例程的芯片型号要和你手上的板子严格对应。F103系列里C8T6是64KB Flash、20KB RAM,ZET6是512KB Flash、64KB RAM,虽然引脚兼容性不错,但如果你用C8T6去编译ZET6的例程,Flash空间占用量很容易超,链接阶段直接报错。反过来用大容量芯片跑小容量例程倒是没问题,但浪费资源。所以拿到例程第一步,先看工程配置里的芯片型号,再看时钟树配置,最后才是外设代码。
对于初学者,我强烈建议先把"最小系统"这个概念吃透。STM32F103最小系统就几样东西:电源(3.3V)、晶振电路(典型8M外部晶振加两个负载电容)、复位电路、BOOT引脚配置,再加上必要的去耦电容。很多例程里所谓的"跑不起来",排查到最后发现是板子的晶振没起振,或者复位引脚悬空导致芯片一直在复位状态。例程软件写得再对,硬件基础不对,一切都是白搭。
1.1 库的选择:标准外设库、HAL库还是寄存器?
这几乎是每篇STM32F103例程相关文章绕不开的话题。我的观点很直接:如果你是在做产品、要长期维护、需要快速迭代,HAL库是主流选择,因为ST官方已经停止标准外设库的更新了;但如果你是学习原理、想彻底搞懂寄存器操作、或者维护老项目,标准外设库依然是很好的学习材料,它的代码风格直白,每个外设的初始化流程都写得清清楚楚,非常利于理解硬件逻辑。
有一种场景我特别推荐直接操作寄存器,那就是调试。比如你用HAL库配置了一个串口但死活不工作,这时候不如直接开寄存器手册,把USART_CR1、USART_BRR这几个关键寄存器翻出来,逐位核对配置是否正确。寄存器是硬件行为的最终体现,不管用哪层库,最后都是操作寄存器,所以"会看寄存器"是基本功,而不是可选项。
网上还有一个高频搜索词是"STM32F103在传统库函数中如何工作",这个说法其实不太准确。传统库函数(标准外设库)的工作方式是通过结构体封装寄存器组,然后调用库函数来配置这些结构体。比如GPIO初始化就是先定义一个GPIO_InitTypeDef结构体,填充Pin、Mode、Speed字段,再调用GPIO_Init()函数把这些值写入对应寄存器。理清了这个套路,你看任何标准外设库的例程都不会觉得陌生。
2. 例程的骨架:时钟、GPIO与串口是重中之重
不少朋友问过我,说例程下载了好几个,每个都能跑,但自己想改点什么就抓瞎。这种情况通常是只关注了现象(LED在闪、串口在打印),没有理解关键外设的初始化流程。STM32F103的例程不管是什么功能,十有八九都会涉及三个基础模块:时钟树、GPIO、串口。这三个模块吃透了,再去看PWM、ADC、DMA等高级外设,思路就顺了。
2.1 内部晶振与外部晶振:为什么系统时钟会影响一切
搜索热词里有一条很有代表性:"如何让STM32F103工作在内部晶振模式下"。这个问题背后通常有两种动机:一是硬件设计简化省掉了外部晶振,二是外部晶振出问题了想先确认芯片本身能不能跑。无论哪种,核心就是修改系统时钟源,把时钟从HSE(高速外部时钟)切换到HSI(高速内部时钟)。
在标准外设库中,SystemInit()函数会默认尝试启用HSE。如果你想只用内部8MHz RC振荡器,可以直接在SystemInit()里把时钟源切换相关的代码跳过,或者干脆用寄存器操作:RCC->CR |= RCC_CR_HSION; 然后等待HSERDY位不再是就绪状态,把时钟切换到HSI。注意HSI的精度不如外部晶振,出厂校准值在室温下大概是±1%左右,对于串口通信波特率这种对时钟精度敏感的场景,可能会导致误码率上升。所以我的建议是:功能验证可以用内部晶振,正式产品除非成本压力极大,否则还是用外部晶振稳妥。
还有一个容易踩的坑:很多例程在SystemInit()里设置了Flash等待周期,系统时钟从8MHz换到内部振荡器后,如果Flash配置没跟着改,程序在跑复杂逻辑时可能随机死机。因为内部晶振模式下系统时钟可能比外部晶振低,但Flash等待周期还是按高主频配置的,虽然大多数情况下没问题,但极端条件下会有隐患。所以改时钟源之后,务必核对FLASH_ACR寄存器。
2.2 GPIO输出与呼吸灯:一切外设操作的基础
GPIO是STM32F103例程里出场率最高的外设,而呼吸灯(PWM驱动LED亮度渐变)又是GPIO加定时器结合的经典入门例程。很多新手不理解,为什么呼吸灯能体现GPIO的要点?因为PWM波形输出涉及GPIO的复用功能配置(AFIO)、定时器的比较输出通道映射、以及输出模式的选择(推挽输出还是开漏输出)。
具体来说,呼吸灯的原理是定时器产生PWM波形,CCR(捕获比较寄存器)的值决定占空比,通过不断改变CCR的值,LED的亮度就随之变化。如果你用的是标准外设库,需要配置TIM_TimeBaseStructure设置定时器的周期和预分频,再配置TIM_OCInitStructure设置PWM模式、脉冲值、输出极性。很多人在这里搞混的概念是"频率"和"占空比":频率决定了PWM波形每秒重复多少次,占空比决定了LED亮多久暗多久。呼吸效果的好坏,关键是频率要足够高(一般建议1kHz以上),否则LED会明显闪烁,看起来就像老式荧光灯那样,完全没有"呼吸"的丝滑感。
搜索热词里还有"STM32F103 PWM + DMA PA1 PA3"这个组合,这就更进阶了。PWM加DMA的意义在于:你可以用DMA把内存中预先算好的占空比数据表自动搬运到定时器的CCR寄存器,而不需要CPU介入。这样做的收益是,你可以生成非常复杂、精确的波形序列,同时CPU可以歇着去干别的活。比如驱动WS2812B这类时序敏感的灯带——它的数据信号要求非常精确的脉冲宽度,用PWM+DMA就能把时序控制做到极致稳定。
2.3 串口与多串口:例程调试的必备工具
串口可以说是嵌入式开发者的"眼睛"。STM32F103的例程里,串口最常用于打印调试信息、跟PC上位机通信、或者跟其他模块交互数据。热词里有一项"STM32F103 CDC多串口编程",指的是用USB CDC类虚拟串口,让PC端通过USB口识别出多个串口设备。这个玩法需要配置USB外设的端点描述符,比较复杂,但一旦调通,调试体验比普通UART转USB线要方便得多,因为你不用额外准备USB转TTL模块,一根Type-C线就能搞定。
在传统UART例程中,有一个非常隐蔽的坑是波特率误差。STM32F103的UART波特率发生器是基于系统时钟分频的,如果你改了系统时钟却没同步调整波特率寄存器配置,串口通信就会出现乱码。比如系统时钟是72MHz时,USART_BRR的计算公式是:USARTDIV = 72000000 / (16 * 波特率),结果再转换成16进制写入BRR寄存器。很多人生搬硬套这个公式,但换了8MHz内部时钟后,还是用7200万去算,波特率自然就不对了。所以看串口例程时,先搞清楚它默认的外部晶振频率是多少,再核对你的板子是否一致。
3. 进阶例程的实用价值:低功耗、Bootloader与FreeRTOS
搜索热词中有几条明显属于进阶方向:停机模式(STOP Mode)、掉电保存数据、Flash Bootloader、FreeRTOS。这些例程在网上有不少,但质量参差不齐,很多只是跑通了一个最小功能,实际产品落地时需要大量补充和适配。这一节我挑三个最常见的进阶方向展开聊聊,结合我自己做项目时踩过的坑。
3.1 停机模式与掉电保存:低功耗设计的核心场景
"STM32F103停机模式"是很多做电池供电设备的人会搜的关键词。F103的停机模式(STOP Mode)在72MHz主频下,典型功耗可以降到微安级别(具体要看GPIO状态和外部电路漏电流),非常适合需要长时间待机的场景。
在标准外设库中,进入停机模式的代码非常简洁:PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);。但很多人不知道,进入停机模式之前必须做好两件事:一是把不需要的外设时钟关掉,尤其是调试用的串口和定时器,否则唤醒后这些外设可能处于异常状态;二是配置好唤醒源,F103从停机模式唤醒通常用外部中断(EXTI)或者RTC闹钟。
关于"掉电保存数据",F103片内没有EEPROM,所以掉电保存数据的常见方案有三种:一是用外部EEPROM(比如M24C32,I2C接口);二是用片内Flash模拟EEPROM,但要注意Flash擦写寿命大约1万次,不能频繁写入;三是靠备用寄存器(Backup Registers)和VBAT引脚供电,实现主电源掉电后数据依然保留,这个方案尤其适合保存RTC时间、系统配置等少量关键数据。
这里有一个实操细节值得注意:很多人写的掉电保存逻辑是"检测到掉电瞬间再写入Flash",但主电源电压降到Flash可写入阈值以下,写入就会失败,而且Flash写入需要时间,掉电过程可能只有几毫秒。更稳妥的做法是:用大电容(比如100uF~470uF)延长掉电时间,用电压监测(PVD)中断提前预警,在主电源还没完全消失时就启动数据保存流程。我在一个低功耗采集项目里实测过,470uF电容加上合适的PVD阈值,能给系统争取到几十毫秒的保存时间窗口,足够把关键数据写进Flash了。
3.2 Bootloader例程:量产与OTA升级的基础
"STM32F103C8T6 Bootloader例程"这个搜索词热度一直很高。Bootloader说白了就是芯片上电后先运行的一段小程序,它决定是进入用户应用程序还是执行固件更新。
F103的Bootloader方案常见有三种形式。第一种是芯片自带的系统存储器Bootloader,通过BOOT0/BOOT1引脚配置进入,配合ST官方的Flash Loader Demonstrator或者STM32CubeProgrammer,通过串口/USB/DFU(Device Firmware Upgrade)烧录固件,这种方式不需要你写任何代码,适合工厂量产或者现场维修。第二种是自定义Bootloader,你自己写一段程序放在Flash起始地址(比如0x08000000),用户应用放在0x08008000之后,Bootloader通过串口接收固件数据写入应用区,还可以加入固件版本校验、加密等功能,适合产品OTA升级。第三种是应用内跳转(IAP,In-Application Programming),用户程序运行过程中自己往Flash写新程序,写完自跳转,常用于远程升级。
自定义Bootloader例程里最难的部分可能是Flash扇区划分和中断向量表偏移。F103的Flash按扇区管理,比如C8T6的64KB Flash从0x08000000开始,扇区大小各不一样,如果你把Bootloader放在前16KB,用户程序就要从0x08004000开始,并且用户程序的启动文件里必须设置VECT_TAB_OFFSET为0x4000,否则中断向量表对不上,程序一进中断就飞。这个坑我在早期做IAP时踩过好几次,现象是主程序能跑,但一触发定时器中断或者串口中断就死机,最后查了半天才发现是向量表偏移没设置。
3.3 FreeRTOS例程:从裸机到RTOS的思路转变
FreeRTOS在F103上跑是非常成熟的方案,搜索热词里有"STM32F103 FreeRTOS",说明很多人在学习或者项目中准备引入RTOS。FreeRTOS在F103上只需要几KB的RAM,对于C8T6这种20KB RAM的小容量芯片也够用,只要你合理安排任务栈大小。
不过我在看很多FreeRTOS例程时发现一个问题:大多数例程只是把LED闪烁放在一个任务里,串口打印放在另一个任务里,看起来"跑通了",但实际上没有体现出RTOS的核心价值——任务调度、信号量、消息队列、中断与任务的交互。如果你只是想让两个LED交替闪烁,裸机用定时器就够了,没必要上RTOS。
RTOS真正有用的场景是:系统里有多个实时性要求不同的任务,比如一个任务要快速响应传感器数据,另一个任务要慢速处理界面显示,还有一个任务要处理通信协议。用裸机写状态机也能实现,但代码复杂度会随着功能增加而急剧上升,用FreeRTOS则可以把每个功能独立成任务,通过信号量和队列同步数据,清晰度和可维护性都好很多。我自己在做一个多传感器数据采集器时,用FreeRTOS把采集、处理、显示、存储拆成四个任务,整个代码结构一下子明朗了。建议你拿到FreeRTOS例程后,别急着跑demo,先花点时间理解task、queue、semaphore这三个核心概念,然后把例程里的任务删掉自己重新写,这样才能真正吃透。
4. 信号交互类例程的调试心得:从相位差到红外接收
搜索热词里还有几条很有意思,代表了STM32F103例程在信号处理领域的常见应用:"测量相位差"、"红外接收HAL_UART_ERROR_FE"、"WS2812B"、"4路AD程序仿真"。这些例程的共同特点是:它们不单是配置外设,还涉及信号采集、时序分析和算法处理,调试起来往往比单纯读写寄存器更费功夫。
4.1 测量相位差:定时器捕获模式是关键
测量两个同频信号之间的相位差,经典做法是用定时器的输入捕获功能。比如你用F103的两个定时器通道分别捕获两个信号的上升沿,记录下各自的时间戳,两次捕获的差值再换算成角度,就能得到相位差。F103的TIM1和TIM2都支持输入捕获,捕获模式配置在标准外设库中用TIM_ICInit()函数就能搞定。
相位差测量的精度主要取决于两点:一是定时器时钟频率,二是捕获中断的响应延迟。定时器时钟越高,计数值越精细,分辨率越好。比如72MHz定时器时钟下,每个计数周期约13.9ns,如果你测的是1kHz信号,一个周期是1ms,那么换算成角度,每个计数对应的相位分辨率大约是0.005度,精度相当可观。但要注意中断响应延迟,如果你在中断里做了太多事(比如打印、浮点运算),不同中断的响应时间不一致,会导致捕获时间戳抖动。我的经验是:捕获中断里只做时间存档和标志位置位,数据处理放到主循环或者RTOS任务里做。
还需要注意一个边界条件:如果两个信号的相位差接近360度,或者信号频率不稳定,捕获到的时间差可能会出现"回绕"现象,比如第一次捕获在计数器接近溢出时发生,第二次捕获在溢出后不久发生,这时你做减法会得到负数。解决办法是判断计数器的方向,或者把定时器设置为循环计数模式,然后用32位的时间戳做差(16位计数器扩展到32位)。
4.2 红外接收与串口错误:HAL库的一个隐蔽问题
"红外接收HAL_UART_ERROR_FE 例程"这个搜索词,对应的场景应该是用红外接收头(比如VS1838B)输出信号到单片机串口RX引脚,通过串口解析红外协议。这个方案本身是可行的,但当你用HAL库的串口接收时,可能会遇到一个非常头疼的问题:错误标志HAL_UART_ERROR_FE(Framing Error,帧错误)被置位,然后串口就停止接收了。
原因在于红外接收头输出的是脉冲信号,不是标准的UART电平时序。红外协议(比如NEC协议)的脉冲宽度和UART的位时序并不完全匹配,串口硬件可能无法正确识别起始位和停止位,导致帧错误。HAL库默认在检测到错误后会调用ErrorCallback,如果你没有在回调里做处理,串口接收状态会卡住。
解决这个问题的思路有几个方向。一个方向是提升硬件兼容性,增加信号整形电路,让红外接收头的输出尽量接近标准UART波形;另一个方向是调整串口参数,比如降低波特率,容忍一定程度的时序偏差;还有一个方向是程序层面,在HAL_UART_ErrorCallback回调里清除错误标志并重新启用接收。我个人的经验是:如果只是简单解析红外协议,与其用串口,不如直接用外部中断(EXTI)加定时器测量脉冲宽度,这样对红外信号的适应性更强,时序判断也更灵活。毕竟红外协议本身是自定义波形,用状态机去解析比硬套UART要靠谱得多。
4.3 WS2812B驱动与4路AD采集:考验时序与校准
WS2812B是单总线协议灯珠,数据信号只需要一根线,但每颗灯珠需要精确的800kHz左右的数据波形,高低电平的持续时间要求非常严格。F103驱动WS2812B的常见方案有几种:一是用SPI外设模拟时序,将每个bit映射为SPI数据位,靠SPI的硬件时钟保证时序准确;二是用PWM+DMA,通过调整DMA发送的占空比序列来生成WS2812B的波形;三是直接IO口翻转加延时,但这个方法对中断延迟敏感,不建议在复杂程序里用。搜索热词里那组"stm32f103 pwm + dma pa1 pa3"很可能就是用来干这个的。
我在驱动WS2812B时踩过一个很有代表性的坑:直接用HAL库的HAL_GPIO_WritePin加delay去刷灯带,灯带短(比如8颗灯珠)时完全正常,但接到60颗灯珠之后就出现颜色错误。排查后发现,因为600多us的刷屏期间中断频繁打断IO翻转,导致部分脉冲宽度超出WS2812B的容差范围。后来改用SPI方案,把每个像素数据预先组织成SPI字节序列,用DMA连续发送,彻底解决了时序抖动问题。
再看"STM32F103的4路AD程序仿真",这个例程的核心在于ADC的多通道扫描模式。F103的ADC1最多可以采样16个外部通道,把多个通道配置成扫描序列,然后通过ADC的中断或者DMA把结果搬运到内存数组。4路AD常见的使用场景是同时采集多路模拟传感器(比如电位器、温度传感器、光敏电阻)。
ADC例程调试时有个非常常见的坑:采样值跳动大。除了硬件上要处理好参考电压和滤波电容,软件上建议调整采样时间(ADC_SampleTime),老工程师通常在F103上把采样时间拉到最大(239.5个周期),实测下来能明显减小采样结果的跳变。如果你觉得还不够稳定,可以做软件滤波,比如连续采集5次求平均,或者用中值滤波去掉异常点。在4路甚至更多通道扫描时,还要注意每个通道的数据在结果寄存器里的位置,DMA搬运时如果通道顺序和内存布局不对应,采到的数据串通道,这种bug排查起来非常让人抓狂。
5. 例程常见的"伪问题"排查技巧
最后聊一个任何玩STM32F103的人几乎都会遇到的场景:例程编译通过、烧录成功,但程序就是跑不起来,或者运行一段时间后行为怪怪的。这种问题的根源往往不是逻辑错误,而是环境、配置、硬件这三者之间的隐性不匹配。这一节我整理几个典型的"伪问题",并给出我自己的排查思路。
5.1 工程环境类:编译链接与DLL错误
搜索热词里有一条奇怪的错误提示:"OSError: [WinError 1114] 动态链接库(DLL)初始化例程失败。Error loading C:\u..."。这个错误看起来是Python脚本在加载DLL时失败,但在STM32F103项目里,它通常会出现在用Python脚本做自动化下载、用openocd或者pyOCD调试的时候。常见原因是系统缺少VC++运行库,或者DLL依赖的另一个组件(比如libusb驱动)没有正确安装。如果你遇到类似问题,先别怀疑代码,先检查开发环境的依赖包是否完整,再用Dependency Walker看DLL的依赖关系。
5.2 Golang与数组越界:一个不典型的例子
有意思的是,搜索热词里虽然没有Golang,但有一条"Go 数组越界访问溢出避免方案"很值得拿出来说。为什么STM32F103例程会扯上数组越界?因为在嵌入式C代码里,数组越界是导致程序"神秘跑飞"的元凶之一。越界写数据会破坏相邻变量、栈,甚至破坏函数返回地址,程序看似随机死机,其实是数据被悄悄改了。
排查数组越界,我常用的笨办法是:在可疑数组的上下边界处放置"哨兵变量",并在程序的关键节点断言这些哨兵值没有被修改。比如定义一个全局变量int guard_value = 0x5A5A5A5A;,然后周期性地检查它。如果发现值变了,说明内存被非法篡改,再结合调试器查看具体是哪里写的。这个方法虽然土,但在没有芯片调试器的高级环境(比如纯命令行编译、远程部署固件)下特别管用。
5.3 GPIO输入与5V耐压问题
热词里有一条高频问题:"STM32F103的GPIO作为输入能不能承受5V电压"。答案是:大多数F103的GPIO引脚不是5V耐压的,数据手册标注的大多是"FT(Five-volt tolerant)"引脚才是5V兼容的,普通引脚即使标注为"TTL输入",也不能直接接5V。如果不小心把5V接到非FT引脚上,轻则引脚损坏,重则整个芯片烧掉,这个我亲眼见过好几回。
所以如果你需要让F103读取5V逻辑电平的信号,正确的做法是:要么选择FT引脚,要么用电平转换电路(分压电阻、电平转换芯片、MOS管单向转换)。不要在非FT引脚上冒险,这不是"能不能跑"的问题,而是"什么时候烧"的问题。FT引脚的列表在芯片数据手册的引脚定义表里有明确标注,看例程时顺便核对一下硬件连接里用到的GPIO是不是FT引脚,这是个好习惯。
5.4 M24C32例程:I2C总线的不稳定与上拉电阻
M24C32是ST的32Kbit I2C EEPROM,很多例程都会附带它的驱动代码。I2C例程最常见的"伪问题"是:单独测试EEPROM读写没问题,但一旦接了多个I2C设备,或者线缆长了一点,通信就时好时坏。
这里要特别强调一下上拉电阻的作用。I2C是开漏结构,总线必须有上拉电阻才能输出高电平。STM32F103内部虽然有上拉,但内部上拉阻值很大(约30~50kΩ),配上I2C总线的电容负载后,上升沿会变得很缓,高速通信时波形可能不够陡峭,导致从设备采样出错。外部加上拉电阻(典型值是2.2kΩ~10kΩ),总线性能会明显改善。如果你在例程里看到I2C初始化代码,记得看一眼板子上是不是已经有外部上拉电阻,没有的话补上,通常就能解决"时好时坏"的问题。
6. 把"跑通的例程"变成"能用的代码"
研究到这里,你会发现例程的真正价值不在于"能跑",而在于它能不能被改造成你的项目代码。很多人下载了一堆例程,每个都能跑,但到了自己要写功能时,依然不知道怎么下手。这里分享我的一个习惯:拿到例程后,先做三件事。
第一件事,把例程里所有和"演示"相关的代码删掉,只保留最小核心。比如LED闪烁、延时代码、串口打印欢迎信息,这些全是demo代码,不作为项目基础,删掉它们能让你关注核心外设的初始化逻辑。第二件事,把外设初始化代码单独拆成模块,不要全堆在main.c里。GPIO初始化一个文件、定时器初始化一个文件、ADC采集一个文件,这样后续扩展功能时,每个模块都独立可替换。第三件事,把你自己的硬件差异点标注出来,比如你用的晶振是多少、哪个引脚被占用、哪个外设不需要,写清楚这些,之后换板子或者移植到其他F103型号时效率会高很多。
有一些朋友喜欢在网上找"通用例程模板",找到之后拿来就用。我的建议是:模板可以拿来学,但最终你自己的项目代码应该是一行一行敲出来的。因为只有自己写过的代码,你才能了解每个配置项的含义、每个参数的选择理由。等你把GPIO、定时器、串口、ADC、I2C这些外设都亲手初始化过一遍之后,你会发现STM32F103的外设配置根本没有那么复杂,它们都是围绕一组寄存器做文章,套路高度相似。
最后再说一个很多人问的问题:例程里的注释是中文还是英文重要吗?不重要。重要的是你能不能通过读代码理解作者的意图。有些代码注释写得非常详细,但逻辑混乱;有些代码几乎没有注释,但函数命名清晰、结构简洁。真正高效的学习方式是:先把代码读一遍,用自己的话在纸上画出程序流程图,然后合上代码,自己重新实现一遍。能实现出来,说明你懂了;实现不出来,说明有些细节还没吃透,回头再翻代码,印象会极其深刻。我用这个方法带过不少新人,效果比反复看例程、抄例程好得多。
本文还有配套的精品资源,点击获取