资讯动态

STM32F1嵌入式入门实战:环境搭建、外设配置与避坑指南

发布时间:2026/10/23 7:06:48 来源:尧图企业网站定制
1. 为什么STM32F1至今仍是嵌入式入门的首选平台如果你在电子类专业的实验室或者创客空间里转一圈大概率会发现一个现象工作台上摆着的开发板十块里有六七块还是STM32F1系列的板子。这个系列从2007年前后推出到现在已经快二十年了按芯片行业的迭代速度早该被扫进历史垃圾堆但实际情况是它活得比很多后来者都滋润。我身边不少做工业控制、仪器仪表的朋友新项目选型时依然会优先考虑STM32F103这颗料原因很实在——便宜、好买、资料多、踩坑少。STM32F1系列是意法半导体基于ARM Cortex-M3内核推出的32位微控制器家族涵盖从低端的F100到高性价比的F103再到带USB OTG的F105/F107。其中F103是绝对的主力常见型号如STM32F103C8T6、STM32F103RCT6、STM32F103ZET6分别对应48脚、64脚、144脚封装Flash从64KB到512KB不等。这颗芯片的主频最高72MHz内置SRAM从20KB到64KB外设包括多路USART、SPI、I2C、CAN、USB、ADC、定时器等基本上你能想到的嵌入式常用接口它都有。为什么说它适合入门我总结下来有三点。第一是资料密度极高。你遇到任何问题搜索一下基本都能找到中文答案从寄存器配置到库函数调用从GPIO点灯到DMA传输前人已经把路踩烂了。第二是工具链成熟。无论是Keil MDK、IAR还是开源的GCCOpenOCD对F1的支持都非常完善ST-Link调试器的价格已经降到十几块钱烧录调试的门槛几乎为零。第三是容错空间大。F1的引脚大多耐5V虽然官方手册说FT引脚才耐5V但实际使用中很多普通IO短时间接5V也不会立刻烧这对新手接错线的情况比较友好——当然这不是让你故意去试只是说它的鲁棒性确实比某些3.3V-only的芯片强。不过话说回来F1也有明显的短板。它的内核是Cortex-M3没有FPU浮点运算单元做浮点运算全靠软件模拟速度慢没有硬件除法器除法指令要几十个周期外设版本比较老比如它的I2C模块有已知的硬件缺陷SPI速率也不算高。但这些短板在入门阶段基本感知不到等你真正需要做电机FOC控制或者音频处理的时候自然就该换F4或H7了。所以我的建议是如果你刚开始学嵌入式或者需要一个稳定可靠的8位机替代方案F1就是最优解如果你要做复杂算法或高速信号处理直接跳过F1上F4。2. 从零搭建STM32F1开发环境的完整路径2.1 硬件选型的几个关键决策点买开发板这件事看起来简单实际上坑不少。市面上F1的开发板从十几块到几百块都有核心差异在几个地方。首先是主控型号最常见的是C8T664KB Flash20KB RAM和RCT6256KB Flash48KB RAM。如果你只是点灯、串口通信、驱动OLEDC8T6完全够用但如果你要跑RTOS或者做文件系统建议直接上RCT6或ZET6Flash和RAM的余量会让你少很多焦虑。我见过太多人用C8T6做到一半发现空间不够又得重新买板子浪费时间和运费。其次是板载调试器。有些开发板集成了ST-Link或者DAP-Link插一根USB线就能下载调试非常方便。但要注意集成的调试器有时候会有兼容性问题比如某些廉价板子的DAP-Link在Keil下识别不稳定。如果你预算允许建议单独买一个原厂ST-Link V2或者国产的DAP-Link二十块钱左右稳定性和通用性都好很多。第三是外设布局。好的开发板会把常用的外设LED、按键、串口、OLED接口、传感器接口都引出来并且标注清晰。有些板子为了追求小巧把引脚排针做得非常密集接线的时候容易短路。我的经验是优先选引脚间距2.54mm、丝印清晰的板子哪怕大一点也无所谓调试阶段方便比美观重要得多。2.2 软件工具的安装与配置细节软件方面主流选择是Keil MDK和STM32CubeIDE。Keil的优势是编译速度快、调试体验好、中文资料多缺点是收费虽然很多人用社区版而且界面比较老旧。CubeIDE是ST官方推出的免费工具基于Eclipse集成了CubeMX配置工具适合新手快速生成初始化代码。我个人的建议是入门阶段用Keil标准外设库进阶后转CubeIDEHAL库。安装Keil之后需要做几件事。第一是安装器件支持包也就是Pack。你可以在Keil的Pack Installer里搜索STM32F1找到对应的DFPDevice Family Pack安装。第二是配置调试器如果你用的是ST-Link需要在Debug选项卡里选择ST-Link Debugger然后在Settings里确认能识别到芯片ID。第三是设置下载算法F1系列的Flash算法在Pack里自带一般不需要手动添加。这里有一个容易被忽略的细节Keil的编译优化等级。默认是Level 0不优化代码体积大但调试方便。如果你发现Flash不够用可以调到Level 1或Level 2但要注意优化后某些变量的值可能在调试时看不到因为被优化掉了。我一般是在开发阶段用Level 0发布时用Level 1兼顾调试和体积。2.3 第一个工程的创建与点灯验证创建工程的标准流程是新建Project → 选择芯片型号 → 添加启动文件 → 配置时钟 → 编写main函数。启动文件在标准外设库里叫startup_stm32f10x_hd.s大容量或md.s中容量根据你的芯片Flash大小选择。时钟配置是新手最容易出错的地方F1的时钟树比较复杂外部晶振一般是8MHz经过PLL倍频到72MHz。如果你用的是内部RC振荡器HSI默认是8MHz也能跑但精度不如外部晶振。点灯代码本身很简单核心是三步使能GPIO时钟、配置GPIO模式、设置输出电平。但这里有个坑F1的GPIO配置需要设置多个寄存器包括CRL/CRH模式寄存器、ODR输出数据寄存器、BSRR位设置/复位寄存器。用标准外设库的话代码大概是这样RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); GPIO_SetBits(GPIOC, GPIO_Pin_13);这段代码看起来简单但如果你忘了使能时钟GPIO就不会工作而且不会报错只是没反应。这是新手最常见的错误之一。另外GPIO_Mode_Out_PP是推挽输出适合驱动LED如果是开漏输出Out_OD需要外部上拉电阻才能输出高电平。这个区别在驱动I2C总线的时候特别重要因为I2C要求开漏输出。3. STM32F1核心外设的实战配置与避坑3.1 串口通信从轮询到中断再到DMA串口是嵌入式开发中最常用的外设没有之一。F1的USART功能很全支持同步/异步模式、硬件流控、多处理器通信等。但新手最常问的问题是为什么我的串口收不到数据或者为什么数据会丢先说轮询方式。轮询就是主循环里不断检查RXNE标志位有数据就读。这种方式最简单但效率极低而且如果主循环有其他耗时操作就会丢数据。我一般只在调试阶段用轮询打印一些简单信息正式代码里从来不用。中断方式就好很多。配置NVIC使能USART1_IRQn在中断服务函数里读DR寄存器同时清除标志位。这里有个细节读DR寄存器会自动清除RXNE标志但如果你用了库函数USART_ReceiveData它内部会读DR所以不需要额外清除。另外中断里不要做太耗时的操作比如浮点运算或者长循环否则会影响其他中断的响应。DMA方式是最优雅的。配置DMA通道让USART的DR寄存器地址作为外设地址内存缓冲区作为目标地址设置传输长度使能DMA请求。这样每收到一个字节DMA自动搬运到内存完全不占CPU。但DMA也有坑如果你不知道对方会发多少数据DMA的传输长度就没法预设。常见的解决方案是用“空闲中断DMA”即开启USART的IDLE中断当总线空闲时触发中断此时读取DMA的剩余传输数量就能算出实际接收了多少字节。这个方案我在多个项目里用过非常稳定。3.2 定时器PWM输出与输入捕获的精度问题F1的定时器资源很丰富高级定时器TIM1/TIM8有互补输出和死区插入通用定时器TIM2~TIM5有4个独立通道基本定时器TIM6/TIM7只能计数。PWM输出是定时器最常用的功能配置步骤是使能时钟、设置预分频器和自动重装载值、配置通道为PWM模式、使能输出。这里的关键参数是预分频器PSC和自动重装载值ARR。PWM频率的计算公式是Fpwm Fclk / ((PSC1) * (ARR1))。假设Fclk是72MHz你想要1kHz的PWM那么(PSC1)*(ARR1) 72000。你可以设PSC71ARR999这样PSC172ARR11000乘积正好72000。占空比则由CCR寄存器决定Duty CCR / (ARR1)。输入捕获用来测量脉冲宽度或频率。配置的时候要注意滤波器的设置。如果信号有毛刺不滤波会导致捕获值跳动。F1的输入捕获滤波器通过ICF位设置范围0~15值越大滤波越强但也会引入延迟。我一般根据信号质量设ICF4~8既能滤掉毛刺又不会明显影响测量精度。还有一个容易忽略的点定时器的时钟来源。APB1的定时器时钟是APB1总线时钟的2倍如果APB1预分频系数不为1APB2的定时器时钟是APB2总线时钟的2倍。所以TIM2~TIM7挂在APB1上时钟是72MHzTIM1/TIM8挂在APB2上时钟也是72MHz。但如果你改了系统时钟这些值都要重新算。3.3 I2C硬件缺陷与软件模拟的取舍F1的硬件I2C是出了名的“有毛病”。具体表现是在某些情况下总线会卡死SR2寄存器的BUSY位一直为1无法产生停止条件。这个问题在ST的官方勘误手册里有记载原因是硬件状态机在特定时序下会出错。虽然ST后来通过固件库打补丁缓解了一部分但并没有彻底解决。所以很多有经验的工程师在F1上直接用软件模拟I2C也就是用两个普通GPIO口通过控制SCL和SDA的高低电平来模拟I2C时序。软件模拟的优点是灵活、可控、不会卡死缺点是速度慢一般只能跑到100kHz左右而且占用CPU时间。但在大多数应用场景下I2C挂的都是低速外设EEPROM、传感器、OLED100kHz完全够用。如果你非要用硬件I2C我的建议是加上超时检测和总线恢复机制。具体做法是在每次I2C操作前检查BUSY位如果超过一定时间还是忙就手动发送9个时钟脉冲强制从设备释放SDA线然后发送停止条件复位总线。这个恢复流程我写在了一个函数里每次I2C初始化时调用一次能解决大部分卡死问题。3.4 ADC采样精度与稳定性的平衡F1的ADC是12位逐次逼近型最高采样率1MHzADC时钟14MHz采样周期1.5个周期时。但实际使用中你会发现采样值跳动很大尤其是采样高阻抗信号源的时候。原因有几个参考电压不稳定、采样保持时间不足、电源噪声。提高精度的措施包括第一在VREF和VDDA引脚附近加100nF和10uF的滤波电容越小越靠近引脚越好。第二降低采样率给采样保持电容足够的充电时间。F1的ADC采样时间可以设置为1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个周期信号源阻抗越高采样时间要越长。第三使用DMA多次采样求平均。我一般会连续采样16次去掉最大值和最小值剩下的求平均这样能有效抑制随机噪声。还有一个坑ADC的校准。F1的ADC在上电后需要执行一次校准否则精度会差很多。校准的步骤是使能ADC、等待稳定、设置CAL位、等待校准完成。这个过程在标准外设库里是ADC_ResetCalibration和ADC_StartCalibration两个函数别忘了调用。4. 调试与烧录中那些让人抓狂的问题4.1 ST-Link识别不到芯片的排查思路“No target connected”或者“Cannot access target”是STM32开发中最常见的报错。遇到这个问题不要慌按以下顺序排查。第一步检查硬件连接。SWD接口需要四根线VCC、GND、SWDIO、SWCLK。很多人只接了三根忘了接GND或者VCC接错了电压。F1的工作电压是2.0~3.6V一般用3.3V。如果你用ST-Link给板子供电要确认ST-Link的3.3V输出能力够不够有些板子功耗大ST-Link带不动。第二步检查芯片是否进入了低功耗模式或者被读保护。如果你之前的程序里设置了睡眠模式或者开启了读保护芯片可能不响应调试器。解决办法是按住复位键点击下载在复位释放的瞬间芯片会进入调试模式。或者用ST-Link Utility连接时选择“Connect Under Reset”。第三步检查BOOT引脚。F1的BOOT0和BOOT1决定了启动模式。BOOT00时从Flash启动BOOT01时从系统存储器启动用于串口下载。如果你不小心把BOOT0接高了芯片就不会运行Flash里的程序调试器也可能连不上。正常调试时BOOT0应该接地。第四步检查SWD引脚是否被复用。如果你在程序里把PA13SWDIO或PA14SWCLK配置成了普通GPIO调试器就连不上了。这种情况需要把BOOT0拉高让芯片从系统存储器启动然后用串口下载一个空程序或者正确的程序再恢复SWD功能。4.2 程序跑飞与HardFault的定位方法HardFault是Cortex-M内核的一种异常通常由非法内存访问、除零、未对齐访问等原因触发。F1没有MPU内存保护单元所以任何地址都可以访问但访问到不存在的地址或者外设保留区域就会触发HardFault。定位HardFault的方法有几种。最简单的是在HardFault_Handler里加一个死循环然后看LR寄存器的值。如果LR是0xFFFFFFF9说明是从线程模式进入的如果是0xFFFFFFFD说明是从中断模式进入的。然后查看压栈的PC值就能知道是哪条指令出的问题。更高级的方法是用Keil的Event Recorder或者Segger的RTT实时输出调试信息。但这两个都需要额外的配置新手可能觉得麻烦。我一般是在HardFault_Handler里把关键寄存器的值通过串口打印出来虽然土但有效。还有一个常见的问题是栈溢出。F1的默认栈大小在启动文件里定义一般是0x4001KB。如果你的程序里用了大数组或者递归调用栈可能会溢出导致HardFault。解决办法是增大栈大小或者把大数组改成全局变量放在堆或者静态区。4.3 串口下载的注意事项串口下载ISP是不用调试器就能烧录程序的方法只需要一个USB转TTL模块。步骤是BOOT0接高电平BOOT1接低电平复位芯片然后用Flash Loader Demonstrator或者STM32CubeProgrammer选择串口下载程序最后把BOOT0接回低电平再复位。这里有几个坑。第一串口模块的电平。F1的USART1是TTL电平如果你用RS232电平的模块会烧芯片。第二串口线的质量。有些廉价的CH340模块在高速波特率下会丢数据导致下载失败。建议用115200或者更低的波特率。第三下载完成后一定要把BOOT0接回低电平否则程序不会运行。我见过有人下载完忘了改BOOT0然后纳闷为什么板子没反应。5. 从F1进阶到更复杂项目的过渡策略5.1 什么时候该换平台F1虽然好用但它的天花板很明显。当你遇到以下情况时就该考虑换F4或者H7了需要浮点运算比如PID控制、FFT、需要更大的RAM比如跑GUI或者网络协议栈、需要高速外设比如SDIO、以太网、USB HS、需要更低的功耗比如电池供电的便携设备。换平台不是从头再来。F1上积累的GPIO操作、中断管理、定时器配置、串口通信等经验在F4上基本通用只是库函数从标准外设库换成了HAL库寄存器地址变了但概念是一样的。我建议的过渡路径是先在F1上把基础外设玩熟然后买一块F4的板子用CubeMX生成代码对比F1的配置看看哪些地方不一样通常一两天就能上手。5.2 代码复用与移植的经验如果你在F1上写了一些通用模块比如OLED驱动、按键扫描、软件定时器这些代码在换平台时大部分可以复用只需要改底层接口。我的做法是把硬件相关的代码抽象成一层接口比如void OLED_WR_Byte(uint8_t data, uint8_t cmd)上层逻辑不直接操作寄存器而是调用这个接口。这样换平台时只需要重写接口层上层逻辑不用动。另外标准外设库和HAL库的差异主要在初始化和中断处理上。标准库的初始化是直接填结构体HAL库是调用HAL_GPIO_Init之类的函数。中断处理上标准库需要自己写USART1_IRQHandlerHAL库提供了HAL_UART_IRQHandler你只需要实现回调函数。理解这些差异后移植工作会轻松很多。5.3 常见项目类型的选型建议如果你要做数据采集终端F1的ADC串口DMA方案完全够用成本低稳定性好。如果你要做电机控制F1没有FPU做FOC会很吃力建议直接上F4。如果你要做物联网节点F1加上ESP8266或者NB-IoT模块也能用但要注意F1的RAM比较小跑MQTT协议栈可能会紧张。如果你要做人机交互界面F1驱动小尺寸OLED或者TFT没问题但跑LVGL就有点勉强了建议上F4或者F7。我在实际项目中的一个体会是不要为了省钱而选性能不足的芯片。F1比F4便宜可能就几块钱但如果因为性能不够导致项目延期或者功能缩水损失远不止这几块钱。选型的时候留出30%的性能余量后面会从容很多。6. 那些年我在STM32F1上踩过的坑6.1 时钟配置错误导致串口乱码有一次我用F1做一个串口通信的项目代码在开发板上跑得好好的换到自己画的板子上就乱码。排查了半天发现是外部晶振的问题。开发板上用的是8MHz的晶振我自己的板子上焊的是12MHz的但代码里的PLL配置还是按8MHz算的结果系统时钟变成了108MHz串口波特率自然就不对了。F1的HSE范围是4~16MHz但PLL倍频系数和分频系数需要根据实际晶振频率重新计算。这个教训让我养成了一个习惯每次换板子先确认晶振频率再检查时钟配置。6.2 中断优先级配置不当导致程序卡死F1的NVIC支持16级中断优先级分为抢占优先级和响应优先级。如果两个中断的抢占优先级相同响应优先级高的先执行如果抢占优先级不同高抢占优先级可以打断低抢占优先级的中断。我曾经在一个项目里把串口中断和定时器中断的抢占优先级设成了相同结果串口中断里调用了printf而printf又依赖定时器中断做超时判断导致死锁。中断服务函数里尽量不要调用可能阻塞的函数也不要在中断里等待另一个中断的事件。如果确实需要把抢占优先级设成不同让高优先级的中断能打断低优先级的。6.3 Flash读写操作的注意事项F1的Flash写入需要先擦除擦除的最小单位是页1KB或2KB取决于型号。如果你要保存一些参数到Flash不能直接写必须先擦除整页。而且Flash擦写次数有限大概1万次左右如果你频繁写Flash芯片很快就会坏。我的做法是参数变化时不立即写而是等系统空闲或者断电前再写如果参数变化频繁就加一个EEPROM或者FRAM作为缓存。另外Flash操作期间不能执行Flash里的代码。也就是说如果你在Flash里运行程序同时又要擦写Flash程序会卡住。解决办法是把擦写Flash的代码放到RAM里执行或者用ST提供的Flash操作库它已经处理了这个问题。但如果你用的是自己写的Flash操作函数一定要注意这个细节。6.4 低功耗模式的唤醒问题F1支持睡眠、停止、待机三种低功耗模式。睡眠模式最简单任何中断都能唤醒停止模式需要外部中断或者RTC唤醒待机模式只能通过WKUP引脚、RTC闹钟或者复位唤醒。我用停止模式做过一个电池供电的传感器节点发现唤醒后串口的第一帧数据总是丢失。原因是停止模式唤醒后系统时钟需要重新稳定而串口在时钟稳定之前就开始接收数据了。解决办法是在唤醒后加一个短暂的延时等时钟稳定后再使能串口接收。还有一个坑是待机模式下的GPIO状态。进入待机模式后所有GPIO都变成高阻态如果你外接了上拉或下拉电阻可能会产生漏电流。我一般会在进入待机前把不用的GPIO配置成模拟输入或者输出低电平减少功耗。6.5 看门狗的使用时机看门狗是防止程序跑飞的重要手段但用不好也会带来麻烦。F1有独立看门狗IWDG和窗口看门狗WWDG。独立看门狗用内部RC振荡器精度不高适合对时间要求不严格的场景窗口看门狗用APB1时钟精度高但喂狗时间有窗口限制太早太晚都会复位。我的经验是看门狗只在正式发布的产品里开调试阶段关掉。因为调试的时候经常需要单步执行或者暂停看门狗会不断复位根本没法调试。另外喂狗的位置要放在主循环里不要放在中断里否则如果主循环卡死但中断还在跑看门狗就失效了。7. 关于STM32F1的一些个人体会写了这么多其实我想说的是STM32F1就像嵌入式领域的“大众捷达”——它不是最快的不是最先进的也不是最便宜的但它是你最容易上手、最容易修、最容易找到配件的那一个。你可以在它上面学到嵌入式开发的所有基本概念时钟树、中断、DMA、外设配置、调试技巧。这些知识在换到任何其他平台后都是通用的。我到现在还会在抽屉里放几块F103的核心板有时候需要快速验证一个电路或者写一个简单的控制逻辑直接拿F1来用十分钟就能跑起来。这种“随手可用”的感觉是很多高性能芯片给不了的。所以如果你刚开始学嵌入式不要纠结选什么芯片拿一块F103开发板把串口、定时器、ADC、I2C、SPI都玩一遍你就算入门了。剩下的都是在实际项目中慢慢积累的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑