发布时间:2026/9/1 20:28:17
STM32F103例程深度解析:从最小系统到Bootloader的实战避坑指南 简介这是一套基于STM32F103芯片的外设驱动例程合集主要面向嵌入式初学者、高校学生和需要快速搭建功能原型的开发者目标是帮助读者降低Cortex-M3平台入门门槛。压缩包内共548个文件以头文件、C源码和文本说明为主同时带有启动汇编、链接脚本、工程配置文件覆盖从系统启动到外设驱动的完整代码骨架整体约876KB便于下载和离线查阅。例程覆盖GPIO、定时器、ADC/DAC、UART、SPI、I2C、CAN、USB以及PWM等常见外设每类都提供可独立运行的示例能帮助理解外设初始化、中断服务、数据收发和时钟配置等关键步骤。目前已有431人学习下载配合STM32官方参考手册与HAL/LL库说明开发者可以较快上手STM32CubeMX代码生成方式并将例程中的配置思路迁移到实际项目中缩短开发调试周期整体目录结构清晰便于按外设模块快速检索。 网上随便一搜“stm32f103 例程”能扒下来上百个工程包。我见过不少新手下载下来挨个编译、下载、跑通然后就当完成任务了结果自己画板子或者换一个型号程序立刻翻车。我自己的看法是例程的真正价值不在“能跑”而在“能拆”。这篇文章不打算列一个大而全的例程清单而是挑出大家在搜stm32f103例程时最常遇到的几个场景最小系统、内部晶振、GPIO耐压、PWMDMA、多路ADC、低功耗保存、Bootloader和CDC多串口把背后的原理和坑位一次说清楚。1. 最小系统例程跑不起来的锅一半在硬件1.1 最小系统四件套的排查顺序很多人在开发板上跑例程没问题自己做了一块板子程序烧进去就是没反应。这时候别急着怀疑代码先把最小系统四件事查一遍电源、复位、时钟、启动模式。电源方面VDD和VDDA都要接3.3V每个电源引脚旁边加0.1uF去耦电容芯片的VCAP引脚记得接一个1uF到2.2uF的陶瓷电容这个电容漏了芯片会直接启动失败。复位引脚NRST要接10k上拉到3.3V再对地接一个0.1uF电容如果悬空电磁干扰可能让芯片不断复位。时钟方面如果例程默认用的是外部8MHz晶振板上必须有8M晶振和两个15到22pF负载电容晶振虚焊或电容不匹配都会导致系统卡死。启动模式方面BOOT0必须拉低BOOT1可以任意保证芯片从主Flash启动如果BOOT0悬空上电后可能进入不了用户程序。我自己第一次打样最小系统板时贴片晶振没焊牢程序烧进去就是不跑折腾了整整一下午最后用示波器量OSC_IN引脚发现完全没波形才想到是晶振虚焊。所以例程跑不起来的时候先拿万用表量电压、量复位电平再看时钟波形最后才是怀疑代码。1.2 把例程从外部晶振改到内部晶振有些板子为了省成本没有焊外部晶振但例程默认用HSE结果上电就黑屏。这种情况下可以把系统切换到内部HSI晶振工作。标准库里的SystemInit默认走外部HSE改成HSI需要操作RCC寄存器RCC_DeInit(); RCC_HSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) RESET); RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_16); RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() ! 0x08); FLASH_SetLatency(FLASH_Latency_2);这里有个关键点使用内部晶振时PLL的输入固定是HSI/2也就是4MHz最大倍频16倍所以系统主频最高只能到64MHz不能像外部晶振那样跑72MHz。想要满血72MHz就必须用外部8MHz晶振。另外HSI精度大约在-1%到2%之间跑115200这类高波特率串口时偶发乱码很常见USB应用更是基本不能用内部晶振必须用外部晶振才能保证48MHz USB时钟的精度。提示如果只是做简单的GPIO点灯可以直接把HSI 8MHz作为系统时钟跳过PLL代码更简单功耗也更低。2. GPIO的5V容忍与输入模式例程里最容易栽跟头的两个点2.1 FT引脚与5V容忍的边界STM32F103不是所有引脚都能直接接5V的数据手册上标注FT的引脚才具备5V容忍能力。很多人看到FT就以为“这个引脚随便接5V”这其实是个误解。FT引脚作为数字输入时确实可以接受5V高电平而不损坏芯片。但如果这个引脚同时被配置为ADC模拟输入输入电压就不能超过VDDA也就是3.3V。把5V信号直接接到ADC引脚上轻则采样值一直顶在4095重则长期使用损伤引脚。我有块板子就是踩了这个坑一个5V输出的传感器直接进PA0读出来的值永远是满量程换了分压电阻之后立刻正常。输出模式下的“5V容忍”又是一回事。推挽输出模式下引脚高电平只有3.3V没法输出5V但如果你把引脚配置为开漏输出外部上拉到5V输出高电平就是5V。这也是I2C总线常用的做法。很多新手在这里分不清以为开漏就是没输出能力其实开漏配合外部上拉正好是实现电平转换最简单的方式。2.2 按键输入、红外接收与UART帧错误按键例程看起来简单但输入模式配置错了照样翻车。按键一端接GND一端接GPIO内部上拉输入是标准做法按下时读到低电平。但如果你外部已经加了下拉电阻内部上拉就别再开否则上下拉分压引脚电平会处于不确定状态读出来的值忽高忽低。红外接收头是另一个高频踩坑点。像VS1838B这类红外接收头输出引脚是开漏结构需要外部上拉到3.3V空闲时为高电平收到红外信号时输出一串低脉冲。正确的解码方式是用外部中断下降沿触发配合定时器输入捕获测量脉宽。我看到有人在网上问红外解码时遇到HAL_UART_ERROR_FE帧错误怎么办实际上红外接收头根本不是UART设备之所以报FE多半是把红外输出接到了UART的RX引脚上或者串口波特率和电平配置跟红外的脉宽完全不匹配。我建议红外解码老老实实走外部中断加输入捕获别去蹭UART真正的串口调试出现FE先查波特率、查信号共地、查线上有没有干扰还要记得在HAL库回调里清掉错误标志否则会一直卡在错误状态。3. PWMDMA从呼吸灯到WS2812B全彩灯带3.1 呼吸灯例程的两种实现效率完全不同呼吸灯是PWM入门必做核心就是让占空比按三角波或正弦波规律变化。很多例程用定时器更新中断在中断里递增或递减比较寄存器CCR的值。这种方式确实直观但有一个问题1kHz的PWM频率中断就是1ms一次如果还要同时处理ADC、串口、按键扫描CPU忙得够呛。更好的做法是用DMA把所有占空比数据自动灌进CCR寄存器。先把一组波形表算好uint16_t breathTable[256]; for (uint16_t i 0; i 256; i) { breathTable[i] (uint16_t)((arm_sin_f32(2 * 3.14159f * i / 256) 1.0f) * ARR / 2); }然后把定时器PWM频率设为1kHz左右72MHz主频下PSC71ARR999配置DMA循环模式外设地址指向TIM的CCR寄存器内存地址就是breathTable数组传输大小256个半字。启动DMA之后CPU只需要在波形表算好的那一刻做一次启动后面完全不占处理时间呼吸灯自己会一直循环呼吸下去。3.2 WS2812B灯带PWMDMA是F103上最稳的方案WS2812B灯带用的是单线协议每一个bit用高低电平的持续时间来表示0码大概是350ns高电平加800ns低电平1码是700ns高电平加600ns低电平整个bit周期是1.25us对应800kbps。在STM32F103上驱动WS2812B最省心的方案就是把定时器频率设成800kHz把每一个bit都用PWM周期来表达。假设主频72MHzARR设为89PWM频率就是800kHz。0码的CCR设为28左右1码的CCR设为57左右这样每个PWM周期的占空比就对应了WS2812B的一个bit。然后把整条灯带所有灯珠的数据都转换成CCR值放到一个发送缓冲区里再用DMA循环搬运到CCR寄存器CPU只负责往缓冲区里填数据时序完全交给硬件。这个方案里最容易翻车的是引脚和DMA通道的映射关系。比如有人想同时用PA1和PA3输出两路PWMPA1对应TIM2_CH2PA3对应TIM2_CH4中间还隔着个PA2TIM2_CH3。配置复用功能时GPIO要设成AF_PP推挽复用同时还要开启AFIO时钟。至于DMA请求到底走哪个通道必须严格对照参考手册里的DMA请求映射表不是随便指定一个通道就能用的。我自己的排查经验是先写一个最简单的寄存器操作程序确认这个引脚能输出PWM波形用逻辑分析仪或示波器验证占空比和频率再往上叠加DMA。这样哪怕后面不亮也能把问题缩小到DMA配置而不是PWM本身。4. 多路ADC与相位差测量模拟量例程的精度陷阱4.1 4路ADC扫描DMA的配置顺序一步都不能错F103的ADC是12位单通道最高采样率大约1MHz。要做4路ADC扫描加DMA搬运配置顺序要按照固定的套路来ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; uint16_t adcBuf[4]; // ADC1配置 ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 4; ADC_Init(ADC1, ADC_InitStructure); // 通道顺序和采样时间注意每个通道都要单独配 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); // DMA配置外设地址固定为ADC1-DR DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adcBuf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 4; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_Init(DMA1_Channel1, DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_SoftwareStartConvCmd(ADC1, ENABLE);这里有两个细节容易忽略。一个是采样时间如果信号源内阻大采样时间一定要拉长到239.5个周期否则采样电容还没充满就开始转换读数会系统性偏低。另一个是通道串扰扫描模式下前一个通道的采样电容残留电荷会影响后续通道的读数表面上看是“通道2被通道1带偏了”实际解决办法就是拉长采样时间或者降低转换速率。还要泼一盆冷水在Proteus这类仿真软件里跑4路ADC程序电位器一拖数值稳如泰山看着很理想。但实际板子上如果VREF引脚滤波不好、地线噪声大ADC读数跳几个LSB是家常便饭。做产品的话建议ADC引脚前加RC低通PCB上做好模拟地和数字地分割软件里再做中值平均三重保险。4.2 用输入捕获测相位差比ADC过零检测更省事搜索“stm32f103 测量相位差”的人很多是想测两路工频或音频信号的相位。如果信号是方波最省事的方案是定时器输入捕获而不是ADC采样。用TIM2的两个通道分别接A信号和B信号捕获两路上升沿的时间戳差值除以整个周期就是相位差uint16_t ch1Val TIM_GetCapture1(TIM2); uint16_t ch2Val TIM_GetCapture2(TIM2); uint16_t diff (ch2Val ch1Val) ? (ch2Val - ch1Val) : (ch2Val TIM2-ARR 1 - ch1Val); float phaseAngle (float)diff * 360.0f / (float)(TIM2-ARR 1);用这个方法要注意定时器的时基必须能覆盖整个信号周期ARR要设置成比信号周期对应的计数值大否则会出现捕获值回绕算出来的相位差完全错误。另外输入捕获引脚要配成浮空或上拉输入最好加一个施密特触发器整形不然信号边沿抖动会让测量结果跳变。如果信号是正弦波没法直接用输入捕获再用ADC同步采样两路做互相关或者过零检测。F103的算力做FFT有点吃力但过零检测完全够用两路同时采样分别记录各自从负到正的过零时刻两个过零时刻的时间差就是相位差。5. 停机模式与掉电保存低功耗项目的最后一公里5.1 停机模式的进入与唤醒核心是时钟恢复F103的三低功耗模式里停机模式是性能和功耗平衡得最好的一个CPU停了外设时钟全停但是SRAM和寄存器内容全部保留唤醒速度又比待机模式快得多。进入停机的代码只要一行PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);但唤醒之后有个大坑系统时钟会自动切回HSI如果你原本用的是PLL输出的64MHz或72MHz必须重新初始化时钟。总之一句话唤醒代码的第一件事就是重新跑一遍时钟配置然后重新初始化用到的外设。还有一个功耗上的细节。直接调用上面的函数进停机电流可能还有好几毫安。原因是你根本没管GPIO。正确的做法是进入停机之前把不用的外设时钟关掉把GPIO全部配置为模拟输入避免引脚上的漏电流路径。我实测过同样一块板子GPIO什么都不做直接停机是2.8mA把GPIO全部设成模拟输入后能降到28uA左右差了整整100倍。5.2 掉电保存的三条路按场景选掉电保存数据F103有三种常见方案备份寄存器、内部Flash、外部EEPROM。备份寄存器最简单F103有10个16位的备份寄存器编号BKP_DR1到BKP_DR10。使用之前要先使能PWR和BKP时钟再调用PWR_BackupAccessCmd(ENABLE)打开备份区访问权限。它的优势是VBAT引脚接电池时主电源掉电数据不丢读写速度也快特别适合保存配置参数、校准值这种小数据。缺点就是容量小存不了多少东西。内部Flash适合保存开机次数、累计工作时长这种低频写入的数据。F103内部Flash的擦写寿命大约是1万次不能拿来频繁记录否则很快报废。写入前要先擦除整个页再执行编程操作。如果确实要频繁写就需要做磨损均衡轮流往多个页里写把整个Flash的寿命平摊开。如果要频繁保存比如掉电瞬间保存多个采样点建议用外部EEPROM比如M24C32I2C接口百万次擦写寿命完全不用担心磨损。配合PVD可编程电压检测器可以在主电源开始跌落时触发中断趁电压还没降到芯片最低工作电压之前把关键数据写进EEPROM。PVD阈值可以选2.8V左右注意EEPROM写入需要时间掉电跌落太快可能写不完所以硬件上要在电源输入端加大电容给写入操作争取几个毫秒的时间窗口。6. 系统级例程进阶Bootloader与CDC多串口6.1 Bootloader分区与跳转中断向量表必须重新定位在线升级是很多项目的刚需做IAP Bootloader最核心的就是分区和跳转。以STM32F103C8T6为例Flash共64KB我习惯把Boot区放在0x08000000到0x08007FFF占32KBApp区从0x08008000开始占剩下的32KB。Boot区代码负责通过串口接收固件写入App区然后跳转。跳转的核心代码很简单#define APP_ADDR 0x08008000 void JumpToApp(void) { uint32_t appStackAddr *(volatile uint32_t *)APP_ADDR; uint32_t appResetAddr *(volatile uint32_t *)(APP_ADDR 4); void (*appResetHandler)(void); // 检查栈顶地址是否合理防止跳到空Flash if ((appStackAddr 0x2FFE0000) ! 0x20000000) { return; } // 关闭全局中断跳转前清掉中断状态 __disable_irq(); NVIC_DeInit(); SysTick-CTRL 0; // 设置主栈指针跳转到App复位函数 __set_MSP(appStackAddr); appResetHandler (void (*)(void))appResetAddr; appResetHandler(); }很多新手在这里栽跟头用烧录器直接往0x08008000地址烧App跳转也成功了但程序一跑就进HardFault。原因多半是App工程的中断向量表还是默认从0x08000000开始中断来了之后CPU去0x08000000找向量表找到的是Boot区的内容自然就乱了。解决办法是在App的main函数最开始把中断向量表偏移量设置到App区SCB-VTOR 0x08008000;同时App工程里IROM1的起始地址要改成0x08008000大小改成0x08008000开始的剩余空间。这两个地方不改无论Boot写得再好App照样跑不起来。6.2 CDC多串口和FreeRTOS并行USB时钟必须准F103的USB是FS设备搞CDC虚拟串口是很多人的进阶目标。如果要做“多串口”有两条路。一条是做USB CDC复合设备通过IAD接口关联描述符把两个CDC接口合成一个复合设备本文还有配套的精品资源点击获取

相关新闻

2026/9/1 20:28:17

Matlab海杂波建模与目标检测:从K分布到CFAR实现

简介:本资源是一套面向雷达信号处理教学与科研的Matlab实践工具集,聚焦海杂波建模、滤波抑制与目标检测三大核心环节,适用于电子信息工程、计算机科学及数学等专业高年级本科生与研究生开展课程设计、综合实验或学位论文研究。资源包共50个文…

2026/9/1 20:28:17

无刷电机短路炸机根因解析:从原理到排查预防的完整指南

飞了很久的无人机,某天起飞时推油没反应,或者半空中突然“啪”一声,紧接着一股焦糊味,落地一看电机绕线已经发黑,电调也烧了。更头疼的是,明明没有炸过机、没有碰撞,电机却“莫名其妙”短路了。…

2026/9/1 20:38:18

微信小程序宠物美容预约系统开发实战:从需求到上线

微信小程序宠物美容预约系统,表面上看是一个课程设计或毕设项目,实际上是一个很典型的预约类业务完整案例。它要解决的核心问题不是把宠物美容项目展示出来,而是让用户能按自己的时间预约服务、让商家能管理档期和订单、让后台能追踪每一单的…

2026/9/1 20:38:18

Java自动类型转换全解析:从隐式转换到表达式提升的避坑指南

写代码报了“不兼容的类型”错误,查了半天才发现是自动类型转换在“做手脚”?如果你在 Java 入门阶段被这类编译错误逼疯过,或者面试时被问“1 2 "3"输出什么”而心里没底,那这篇内容就是为你准备的。自动类型转换&am…

2026/9/1 20:38:18

智能体开发Android出海应用:从最小链路到工程化落地

最近我经常被问到同一个问题:用智能体开发 Android 出海应用,到底靠不靠谱?我的回答是:靠谱,但和你想象的不太一样。它不是让 AI 替你生成一个完整 App,而是把需求理解、代码生成、工具链验证、本地化适配这…

2026/9/1 20:38:18

抖音本地生活:探店博主的“割韭菜”与“真红利”

抖音本地生活:探店博主的“割韭菜”与“真红利” 在抖音这一短视频巨头的生态中,本地生活服务板块正逐渐成为新的流量高地,其中探店内容作为连接商家与消费者的桥梁,吸引了大量博主涌入。这一领域既孕育着无限机遇,也伴…

2026/9/1 20:38:18

直播复盘怎么做?别只看GMV,这3个数据才是关键

直播复盘怎么做?别只看GMV,这3个数据才是关键 在当今新媒体蓬勃发展的时代,直播已成为众多博主和商家推广产品、与用户互动的重要方式。一场直播结束后,复盘是提升后续直播效果的关键环节。很多人在复盘时往往只关注GMV&#xff0…

2026/9/1 20:33:18

美团校招笔试复盘:从贪心到状态压缩的算法突破

1. 第2场笔试给我的整体印象:时间紧、样例弱、边界多1.1 试卷结构与时长带来的压迫感我参加的是美团2023校招技术岗的第2场在线笔试,整场下来最大的感受就是:题量不算夸张,但时间分配稍有不慎就会翻车。编程题部分通常是4道&#…

2026/9/1 16:02:17

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/1 8:27:47

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/1 7:04:43

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/1 0:00:42

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/1 0:00:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/1 0:00:42

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

2026/9/1 0:00:42

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/1 0:00:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/1 0:00:42

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…