简介:这份资源面向正在学习STM32嵌入式开发、需要动手实践外设驱动的初学者与进阶者,围绕野火STM32F103指南者开发板,提供一套简易温度读取的完整工程实现,可用于课程实验、自学练手或项目参考。压缩包共366个文件,约17.18MB,以C语言源文件与头文件为主体,涵盖stm32f10x_adc、tim、rcc、usart等标准外设库驱动,并包含Keil工程文件、编译中间产物、hex烧录文件及少量说明文档,目录结构保留了完整的工程组织方式,便于直接打开编译与二次修改。目前已有51人学习下载。读者可从中获得温度采集的完整代码框架、ADC采样与串口输出等模块的调用思路,以及标准库工程的搭建与调试参考,适合对照开发板逐步理解外设配置流程,快速上手STM32基础项目开发。
1. 从一份野火指南者工程包说起:STM32F103 读温度到底难在哪
很多人第一次拿到「基于 STM32 的简易温度读取」这类工程包,第一反应是打开 Keil 直接编译下载,结果要么卡在芯片包没装、要么串口助手一片乱码,最后怀疑代码本身有问题。实际上,STM32F103 读温度这件事,硬件链路和软件链路各有一半坑:硬件上要确认传感器是板载的还是外接的、供电和上拉电阻对不对;软件上要确认用的是标准库还是 HAL 库、时钟树配没配对、串口波特率是否一致。这份基于野火 STM32F103 指南者的工程包,价值就在于它把「最小可运行的温度采集 + 串口输出」这条链路完整封好了,适合刚上手 STM32F103 最小系统、想先跑通一个完整闭环再往深里做的从业者。它不追求功能多,追求的是你能在半小时内看到温度数值从串口稳定吐出来,而不是在环境配置上耗掉一整天。
2. 拆开工程包:文件结构、外设依赖与选型逻辑
2.1 工程目录里每个文件夹在干什么
拿到压缩包解压后,常见做法是先别急着打开工程文件,而是把目录结构扫一遍。野火系工程一般会分成几个固定区域:USER放main.c和中断服务函数,HARDWARE放外设驱动,SYSTEM放延时和串口底层,CORE放内核启动文件,STM32F10x_FWLib放标准库源码。这个划分不是随便定的,它对应的是「应用逻辑 / 硬件抽象 / 系统支撑 / 芯片底层」四层,改代码时能快速定位该动哪一层。
| 目录 | 典型内容 | 改动频率 |
|---|---|---|
| USER | main.c、stm32f10x_it.c | 高 |
| HARDWARE | 传感器驱动、LCD、LED | 中 |
| SYSTEM | delay.c、usart.c、sys.c | 低 |
| CORE | core_cm3.c、startup_stm32f10x_md.s | 几乎不动 |
| STM32F10x_FWLib | 标准外设库 | 不动 |
提示:如果解压后看到的是
.uvprojx而不是.uvproj,说明是较新的 Keil MDK 工程格式,用 MDK5 打开更稳。
2.2 温度传感器走的是哪条总线
STM32F103 读温度,常见方案有三种:片内温度传感器(接 ADC 内部通道)、DS18B20(单总线)、LM75 或 TMP102(I2C)。这份工程包标题写的是「简易温度读取」,结合野火指南者的板载资源,大概率走的是片内 ADC 温度传感器通道或者外接 DS18B20。片内方案的好处是不用额外接线,缺点是精度差、需要校准;DS18B20 精度够用,但时序要求严,延时函数稍微不准就读出 85 这个经典错误值。
判断方法很简单:打开HARDWARE目录,看有没有ds18b20.c或者adc_temperature.c这类文件。如果是 ADC 方案,重点看ADC_Channel_x选的是哪个通道;如果是 DS18B20,重点看delay_us的精度。这一步决定了后面调试时该盯哪里。
2.3 为什么用标准库而不是 HAL
野火 F103 指南者的老工程大多基于标准外设库(SPL),不是 HAL。原因很实际:F103 资源小、标准库代码量少、执行路径清晰,适合教学和快速验证。HAL 库抽象层厚,移植方便但代码体积大,在 F103 这种 64KB Flash 的片子上容易吃紧。如果你后续想换到 CubeMX 生成工程,注意标准库和 HAL 的初始化写法完全不兼容,不能混着抄。
常见做法是:先用这份标准库工程把温度读通,确认硬件没问题,再决定要不要迁到 HAL。迁移时重点重写GPIO_Init、USART_Init、ADC_Init这几块,业务逻辑基本可以保留。
3. 从零跑通:Keil 环境、时钟配置与串口输出
3.1 装芯片包和打开工程
第一步不是写代码,是把环境补齐。Keil MDK5 默认不带 STM32F1 的器件支持包,需要单独装Keil.STM32F1xx_DFP。装完之后打开工程,如果弹出「Device not found」,八成是包没装或者装错版本。
# 确认芯片包安装路径(Windows 默认) C:\Keil_v5\ARM\PACK\Keil\STM32F1xx_DFP\ # 目录下应能看到 .pdsc 文件和 Flash 算法文件夹逻辑说明:Keil 通过.pdsc文件识别器件型号和 Flash 烧写算法。如果这个目录为空,编译能过但下载会失败。参数上注意 DFP 版本和 MDK 版本的兼容性,太新的包在老 MDK 上可能不认。
3.2 时钟树:72MHz 是怎么来的
STM32F103 标准库工程一般把系统时钟配到 72MHz,路径是 HSE(8MHz 外部晶振)→ PLL 9 倍频 → 72MHz。这段代码在system_stm32f10x.c里,通过SetSysClockTo72()完成。如果外部晶振没起振,芯片会回退到 HSI(8MHz 内部 RC),此时串口波特率会整体偏掉,表现为乱码。
// system_stm32f10x.c 中的关键判断 if (HSEStatus == (uint32_t)0x01) { // HSE 起振成功,走 PLL 到 72MHz RCC->CFGR |= (uint32_t)RCC_CFGR_PLLMULL9; } else { // HSE 失败,保持 HSI,此时系统频率只有 8MHz }逻辑说明:HSEStatus来自RCC_CR寄存器的HSERDY位。如果这里走了 else 分支,后面所有基于 72MHz 算出来的延时和波特率全错。排查时可以先点灯确认程序在跑,再用示波器或逻辑分析仪看串口波形周期,反推实际时钟。
3.3 串口配置与 printf 重定向
温度值要能看到,靠的是串口。标准库工程一般用 USART1,PA9 做 TX、PA10 做 RX,波特率 115200。关键一步是重定向printf,否则你只能自己拼字符串。
// usart.c 中重定向 printf 到 USART1 int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }逻辑说明:USART_FLAG_TC是发送完成标志,等它置位再写下一个字节,避免数据覆盖。参数上注意在 Keil 的 Target 选项里勾选Use MicroLIB,否则printf重定向可能不生效。串口助手那边波特率、数据位 8、停止位 1、无校验,和代码里USART_InitStructure保持一致。
3.4 温度读取主循环
主循环通常很短:初始化外设,然后 while(1) 里读温度、格式化、打印、延时。
int main(void) { float temp; delay_init(); // 延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); uart_init(115200); // 串口初始化 DS18B20_Init(); // 或 ADC_Temp_Init() while (1) { temp = DS18B20_Get_Temp(); // 读温度 printf("Temp: %.1f C\r\n", temp); delay_ms(1000); } }逻辑说明:delay_init()必须在串口和传感器初始化之前,因为 DS18B20 的时序依赖微秒延时。printf里的%.1f在 MicroLIB 下支持浮点,如果没勾 MicroLIB,浮点打印可能输出空白或乱码,这时改成整数部分和小数部分分开打印更稳。
4. 避坑与排查:温度读不出、串口乱码、下载失败
4.1 串口助手全是乱码
现象:打开串口助手,收到的不是温度字符串,而是一堆不可读字符。原因通常是波特率不匹配或系统时钟不是 72MHz。先确认串口助手波特率和代码里uart_init参数一致,再检查外部晶振是否起振。如果 HSE 没起振,系统跑在 8MHz,实际波特率会变成设定值的约 1/9。解决方法是换一个 8MHz 晶振,或者临时改用 HSI 并在代码里重新计算波特率。
4.2 DS18B20 一直返回 85
现象:温度值恒定显示 85.0,不随环境变化。原因有两个:一是 DS18B20 上电默认输出就是 85,说明转换命令没发出去或时序不对;二是delay_us精度不够,单总线时序错位。解决方法是先用示波器看 DQ 线上的复位脉冲和存在脉冲,确认传感器有响应;再检查delay_us是否基于 SysTick 实现,如果是空循环延时,编译优化等级一变就会失准。
4.3 片内温度传感器读数偏高
现象:用 ADC 内部通道读温度,数值比实际环境高十几度。原因是片内传感器测的是芯片结温,不是环境温度,芯片自身发热会让读数偏高。解决方法是把它当相对值用,或者外接 DS18B20、LM75 这类独立传感器。如果一定要用片内通道,记得按参考手册里的公式做偏移校准,别直接拿原始 ADC 值当温度。
4.4 下载时提示 Flash 算法缺失
现象:编译通过,点下载报「Flash Download failed」。原因是 Keil 工程里选的 Flash 算法和芯片型号不匹配,或者 DFP 包没装全。解决方法是打开 Options → Debug → Settings → Flash Download,确认算法列表里有STM32F10x Med-density Flash,没有就手动添加。F103C8T6 属于中容量,选 128KB 的算法。
4.5 改了代码但现象没变
现象:修改了main.c里的打印内容,重新下载后串口输出还是旧的。原因是 Keil 没有重新编译,或者下载的是旧 axf 文件。解决方法是先 Build 再 Download,别直接点 Download。另外检查工程里有没有多个main.c被同时加入编译,这种情况链接器可能选了另一个。
5. 进阶:把温度数据用起来与工程迁移技巧
跑通串口打印只是起点。真正做项目时,温度数据要进缓冲区、要上传、要触发告警。一个实用技巧是在main.c里加一个环形缓冲区,把每次读到的温度存进去,主循环负责采集,另一个定时器中断负责发送,这样采集和通信解耦,不会因为串口阻塞导致采样周期抖动。
#define TEMP_BUF_SIZE 16 static float temp_buf[TEMP_BUF_SIZE]; static volatile uint8_t temp_wr = 0, temp_rd = 0; void temp_push(float t) { temp_buf[temp_wr] = t; temp_wr = (temp_wr + 1) % TEMP_BUF_SIZE; } float temp_pop(void) { float t = temp_buf[temp_rd]; temp_rd = (temp_rd + 1) % TEMP_BUF_SIZE; return t; }逻辑说明:temp_wr和temp_rd用volatile修饰,因为会在中断和主循环之间共享。缓冲区大小取 2 的幂可以用位与代替取模,但这里为了可读性用取模。注意读写指针相等时无法区分空和满,实际项目里可以加一个count变量或者留一个空位。
如果你要把这份标准库工程迁到 CubeMX + HAL,重点不是重写业务逻辑,而是重配时钟树和外设句柄。CubeMX 里把 USART1 配成 115200、ADC1 配好通道、GPIO 配好,生成代码后把DS18B20_Get_Temp这类驱动函数原样搬过去,只改底层读写 GPIO 的部分。迁移后验证方法很简单:先点灯,再串口发固定字符串,最后读温度,三步都过再往下做。
从那以后我每次拿到一份新的 STM32 工程包,都强制先做三件事:确认芯片包和 Flash 算法、用示波器或逻辑分析仪看一眼串口波形、把延时函数单独测一遍。这三步走完,后面九成的玄学问题都能提前排掉。希望帮到你。
本文还有配套的精品资源,点击获取