news 2026/3/31 12:50:46

基于Keil的温度监控系统设计:入门必看

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Keil的温度监控系统设计:入门必看

从零开始:用Keil打造一个看得见温度的STM32监控系统

你有没有过这样的经历?
接好了电路,烧录了程序,MCU也在跑——可就是不知道传感器到底“读到了什么”。尤其是初学嵌入式时,面对一串串跳动的AD值,心里直打鼓:“这真的是当前温度吗?我是不是哪里接错了?”

别急。今天我们就来解决这个问题。

我们不讲大道理,也不堆术语,就手把手带你做一个能‘说话’的温度监控系统:芯片实时采集环境温度,通过串口发到电脑上,你在串口助手里就能清清楚楚看到“当前温度:24.6°C”——就像给单片机装上了嘴。

整个项目基于Keil μVision + STM32F103C8T6(蓝丸板)实现,全程使用标准外设库(Standard Peripheral Library),适合刚入门嵌入式开发的同学一步步跟着做。你会发现,原来“感知世界”的第一步,并没有想象中那么难。


为什么选Keil?它真适合新手吗?

市面上做嵌入式的IDE不少,VS Code搭PlatformIO、STM32CubeIDE、IAR……那为什么还要从Keil开始?

因为——它够稳、够全、够直观

Keil MDK(Microcontroller Development Kit)虽然不是免费的,但它对ARM Cortex-M系列的支持堪称教科书级别。特别是对于STM32F1这类经典芯片,Keil自带完整的设备支持包(Device Family Pack),你新建工程时选个型号,启动文件、寄存器定义、中断向量表全都自动配好,连时钟初始化都能帮你生成模板代码。

更重要的是,它的调试体验非常友好:

  • 可以直接看变量实时变化;
  • 能进汇编层单步执行;
  • 支持外设寄存器视图,比如打开ADC页面,你能一眼看到DR数据寄存器里的值是不是更新了;
  • 配合ST-Link下载器,几分钟就能把程序烧进去并开始调试。

所以如果你是第一次接触裸机编程,Keil是一个让你少踩坑、多看到结果的选择。

提示:学生可以申请Keil免费许可证(容量限制32KB),足够跑完本项目。


硬件怎么搭?只需要三个元件

我们的目标很简单:把环境温度变成数字,再打印出来

为此,硬件部分极其精简:

  • 主控:STM32F103C8T6 最小系统板(俗称“蓝丸”)
  • 传感器:NTC热敏电阻(如10kΩ @25°C)
  • 分压电阻:固定10kΩ电阻一只
  • 下载工具:ST-Link V2 或兼容仿真器

连接方式也超级简单:

NTC引脚1 ──┬── 接VCC(3.3V) ├── 接PA0(ADC通道0) └── 接10kΩ下拉电阻到GND

也就是说,NTC和固定电阻组成一个分压网络,中间节点接到STM32的PA0脚,这个脚要配置为模拟输入模式。

为什么不用DS18B20或I²C传感器?
因为我们要练的是最核心的能力——如何让MCU真正理解模拟世界。而这一切的起点,就是ADC。


ADC采集:让单片机“读懂”电压

STM32F103内置了一个12位ADC,意味着它可以将0~3.3V之间的电压量化成0到4095共4096个等级。精度约为0.8mV/LSB,用来测温度绰绰有余。

但关键问题来了:
怎么让这个数字变成“摄氏度”?

第一步:获取原始AD值

我们先来看最关键的初始化代码:

void ADC_Temp_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; // 使能GPIOA和ADC1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // 配置PA0为模拟输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 注意!必须设为AIN GPIO_Init(GPIOA, &GPIO_InitStructure); // ADC基本配置 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); // 设置通道0采样时间(越长越准) ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // 开启ADC并校准 ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }

这里有几个新手容易忽略的点:

  • GPIO_Mode_AIN必须设置,否则ADC无法正确采样;
  • 校准步骤不能省,尤其是在冷启动时,ADC内部偏移会影响精度;
  • 采样时间选55.5周期以上,给外部RC电路足够的充电时间,避免读数偏低。

接着写一个读取函数:

uint16_t Read_ADC_Value(void) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发 while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换完成 return ADC_GetConversionValue(ADC1); // 读取结果 }

现在你已经拿到了一个0~4095之间的数值。下一步才是重点:把它翻译成温度


温度计算:别被非线性吓住

NTC的阻值随温度呈指数变化,理想情况下应该用Steinhart-Hart公式:

$$
\frac{1}{T} = A + B \cdot \ln(R) + C \cdot (\ln(R))^3
$$

听上去很复杂?其实我们可以简化处理。

先根据分压原理算出NTC的实际阻值:

float voltage = (adc_raw_value / 4095.0f) * 3.3f; float r_ntc = 10.0f * voltage / (3.3f - voltage); // 单位kΩ

假设你的参考电阻是10kΩ,室温25°C时NTC也是10kΩ,那你可以在两个已知点进行线性拟合:

温度(°C)NTC阻值(kΩ)
0~33
2510
50~4.5

于是你可以粗略估算:

temperature = 25.0f + (10.0f - r_ntc) * 3.0f; // 每kΩ对应约±3°C

当然这不是高精度方案,但对于教学和原型验证完全够用。等你掌握了流程后,完全可以换成查表法或多项式拟合来提升准确性。

为了减少波动,建议连续采样16次取平均:

uint32_t sum = 0; for(int i = 0; i < 16; i++) { sum += Read_ADC_Value(); Delay_ms(5); // 小延时稳定信号 } adc_raw_value = sum >> 4; // 右移代替除法,提高效率

串口输出:让数据“开口说话”

有了温度值还不算完——得让人看得见才行。

我们使用USART1,TX接PA9,配置为115200波特率,异步通信模式。初始化如下:

void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // PA9 复用推挽输出(TX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); // PA10 浮空输入(RX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置串口参数 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); }

发送函数也很直观:

void USART_SendChar(USART_TypeDef* USARTx, char ch) { while (!USART_GetFlagStatus(USARTx, USART_FLAG_TXE)); USART_SendData(USARTx, ch); } void USART_SendString(USART_TypeDef* USARTx, const char* str) { while (*str) { USART_SendChar(USARTx, *str++); } }

最后在主循环中整合所有逻辑:

int main(void) { SystemInit(); // 系统时钟初始化(默认72MHz) ADC_Temp_Init(); USART1_Init(); while (1) { float temp = Calculate_Temperature(); // 包含滤波与换算 char buffer[32]; sprintf(buffer, "Temp: %.1f°C\r\n", temp); USART_SendString(USART1, buffer); Delay_ms(1000); // 每秒刷新一次 } }

打开XCOM或SSCOM之类的串口助手,选择正确的COM口和波特率,你就会看到一行行温度数据不断刷出:

Temp: 24.3°C Temp: 24.5°C Temp: 24.4°C

那一刻你会觉得:我真的让机器感知到了这个世界


常见问题与避坑指南

别以为一切顺利。我在第一次调试时也遇到一堆问题,总结几个典型的“坑”:

❌ AD值总是接近0或4095?

  • 检查PA0是否误设为普通输入或输出;
  • 查线路是否有虚焊,NTC是否接反;
  • 测一下实际分压点电压是否在合理范围(比如25°C应在1.65V左右);

❌ 温度跳变剧烈?

  • 加大采样次数(如32次)并加入滑动平均滤波;
  • 检查电源是否干净,可用万用表测VDD是否存在纹波;
  • PCB布线上,模拟走线尽量短,远离SWD、时钟线等高频路径;

❌ 串口收不到任何数据?

  • 确认TX/RX是否接反;
  • 检查USART时钟是否开启(RCC配置);
  • 使用示波器观察PA9是否有波形输出;

❌ Keil提示“no target connected”?

  • ST-Link驱动是否安装成功?
  • SWDIO/SWCLK是否接触良好?
  • 是否误开启了JTAG调试导致引脚冲突?

这些问题看似琐碎,但正是它们决定了你是“调通了”,还是“一直卡着”。


这个项目教会了我们什么?

表面上看,这只是个简单的温度显示系统。但背后藏着嵌入式开发的核心脉络:

  1. 从物理信号到数字世界的桥梁—— ADC 是你理解传感器的第一道关卡;
  2. 数据不是终点,表达才是—— 把AD值转成字符串发出去,完成了信息传递的闭环;
  3. 模块化思维养成—— 初始化、采集、处理、输出,每个环节独立又协同;
  4. 调试能力比编码更重要—— 学会看寄存器、查手册、用串口“问”单片机状态,是你成长为工程师的关键跃迁。

更进一步地,你可以在这个基础上加料:

  • 加个OLED屏幕,本地显示;
  • 设定阈值,超温点亮LED或蜂鸣报警;
  • 用定时器+中断实现精准周期采样,释放CPU;
  • 接ESP8266,把温度上传到手机或云平台。

但所有这些扩展的前提,是你亲手跑通了第一个“看得见”的系统。


写在最后:动手,是最好的老师

很多人学嵌入式,看书看视频看了几个月,始终不敢点“Download”按钮。怕烧芯片,怕接错线,怕程序跑不起来。

可我想说:错误不可怕,沉默才可怕

当你第一次看到串口助手里跳出“Temp: 24.6°C”,那种成就感,远胜于背下十页数据手册。

所以别等了。
插上你的蓝丸板,打开Keil,新建工程,照着上面的代码敲一遍。哪怕只改一个字母,也要让它属于你自己。

你会发现,那个曾经遥远的“智能世界”,其实就藏在这一个个AD值的背后。

如果你在实现过程中遇到了具体问题(比如编译报错、下载失败、数据异常),欢迎留言交流,我们一起排查。毕竟,每一个老手,都曾是个连PA0都找不到的新手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/29 10:22:52

对比试验:手动编码 vs AI生成线程池代码效率提升300%

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个线程池代码生成对比工具。功能&#xff1a;1. 传统方式手动编写线程池管理类&#xff1b;2. AI根据输入需求自动生成等效代码&#xff1b;3. 对比两者开发耗时和执行效率。…

作者头像 李华
网站建设 2026/3/19 12:47:27

.NET 10 New feature 新增功能介绍-Minimal APIs增强

上一篇给大家分享了 .NET 10 New feature 新增功能介绍-WebSocket功能增强 今天给大家继续分享.NET 10 中Minimal APIs 的增强。 一、复杂参数对象中空字符串按null处理 在使用复杂对象参数的 Minimal APIs 时&#xff0c;表单提交中的空字符串值现在将被转换为 /* by 01130.hk…

作者头像 李华
网站建设 2026/3/27 16:02:32

用 ADT 的 MIA Select Converter 快速迁移 Open SQL:把老式 SELECT 一键升级到 ABAP SQL 与 ABAP Cloud 语法

在把经典 ABAP 代码搬到 ABAP Cloud 或者做 S/4HANA 现代化改造时,最让人头疼的往往不是语法本身,而是那一大片历史遗留的 SELECT ...:有的写法还停留在早期 Open SQL 习惯,有的直接依赖传统透明表,有的混着旧式字段列表与过时的 INTO 结构。你当然可以手工逐条改,但在真…

作者头像 李华
网站建设 2026/3/29 3:05:34

从传统 ABAP 开发转型到 ABAP Cloud 开发,具体要学哪些东西?

很多团队在讨论 ABAP Cloud 时,常见的卡点并不是 RAP 或 CDS 本身有多难,而是学习目标太大、路径太长、角色太杂:有人要写业务逻辑,有人要做报表分析,有人要管架构与扩展治理,有人要做 Fiori 前端,还有人要把质量与安全的闸门立起来。把所有内容塞进一条 Roadmap,看上去…

作者头像 李华
网站建设 2026/3/27 11:04:51

AutoGLM-Phone-9B实战:移动端多语言翻译系统开发

AutoGLM-Phone-9B实战&#xff1a;移动端多语言翻译系统开发 随着移动设备在日常生活中的广泛应用&#xff0c;用户对实时、高效、跨语言沟通的需求日益增长。传统云端翻译服务虽然性能强大&#xff0c;但存在延迟高、隐私泄露风险和依赖网络等问题。为解决这一挑战&#xff0…

作者头像 李华
网站建设 2026/3/30 22:09:13

AutoGLM-Phone-9B零售终端:智能收银系统

AutoGLM-Phone-9B零售终端&#xff1a;智能收银系统 随着人工智能技术在消费场景中的深度渗透&#xff0c;传统零售终端正加速向智能化、自动化方向演进。其中&#xff0c;AutoGLM-Phone-9B 作为一款专为移动端优化的多模态大语言模型&#xff0c;凭借其轻量化设计与跨模态融合…

作者头像 李华