STM32内部温度传感器ADC+DMA采集实战:不占CPU的高效温度监控方案

发布时间:2026/10/14 1:10:39

STM32内部温度传感器ADC+DMA采集实战:不占CPU的高效温度监控方案 1. 项目缘起与整体设计思路搞嵌入式开发的朋友大概率都遇到过这样的场景板子跑起来了功能也正常但心里总不踏实——芯片到底热不热长时间跑会不会因为温升导致采样漂移尤其是做电机控制、电源管理、工业变送器这类应用MCU结温直接关系到系统稳定性和寿命。这时候如果手头没有外挂温度芯片STM32内部自带的温度传感器就成了一个非常实用的“体检工具”。这个项目的核心目标很明确用STM32的ADC采集内部温度传感器的数据并且通过DMA的方式把数据搬运到内存里全程不占用CPU。听起来简单但里面涉及的知识点其实不少——ADC的通道选择、采样时间配置、内部温度传感器的校准参数读取、DMA的循环模式设置、数据对齐方式等等。任何一个环节配错读出来的温度要么是乱码要么偏差十几度。先说清楚这个方案适合谁。如果你刚接触STM32的ADC和DMA想找一个完整的、能直接跑通的例程来学习那这篇内容就是为你准备的。如果你已经在做项目需要一个不占CPU资源的温度监控方案这里提供的配置思路和避坑经验也能直接参考。我用的芯片是STM32F103系列但核心逻辑在F4、H7、G0等系列上同样适用只是寄存器和库函数略有差异。为什么选择DMA而不是中断或者轮询这个问题值得展开说。轮询方式最简单但CPU要一直等着ADC转换完成效率极低。中断方式好一些但每次转换都要进中断如果采样频率高中断开销会吃掉大量CPU时间。DMA方式则是让ADC转换完成后自动把数据搬到指定内存地址CPU完全不用管只在需要读温度的时候去内存里取就行。对于需要连续监控温度的场景DMA加循环缓冲区的组合几乎是最优解。整个方案的架构可以这样理解ADC负责“测量”DMA负责“搬运”内存缓冲区负责“存放”CPU只在需要的时候去“查看”。这就像你请了一个专职测量员和一个专职快递员你只需要在办公室等结果就行不用亲自跑腿。2. 内部温度传感器的原理与关键参数2.1 温度传感器到底藏在哪STM32内部温度传感器其实是一个双极型晶体管构成的PN结它的基极-发射极电压随温度变化而线性变化。这个传感器在芯片内部连接到ADC的某个特定通道不同系列对应的通道号不一样。以STM32F103为例内部温度传感器连接的是ADC1的通道16而内部参考电压Vrefint连接的是通道17。这两个通道是内部固定的不需要外部引脚。这里有个容易踩的坑很多人以为温度传感器是独立的外设实际上它只是ADC的一个输入通道。你要用的时候得先使能ADC再配置通道最后启动转换。它不像外挂的DS18B20那样有独立的通信协议本质上就是一路模拟电压信号。2.2 采样时间为什么必须拉长内部温度传感器的输出阻抗比较高官方数据手册里明确写了要保证采样精度采样时间至少要设置为17.1微秒。这个数字怎么来的ADC的采样过程本质上是外部信号通过采样开关给内部采样电容充电如果信号源内阻大充电就慢采样时间不够的话电容上的电压还没稳定就被送去转换了结果自然不准。在STM32F103上ADC时钟通常配置为12MHzAPB2分频后一个采样周期是1/12MHz约等于83.3纳秒。要满足17.1微秒至少需要17.1us除以83.3ns约等于205个周期。所以采样时间要选239.5周期这一档这是F103里最长的采样时间。如果你用的是F4系列ADC时钟更高采样周期数需要相应调整但原则是一样的——宁长勿短。注意采样时间拉长会降低最大采样率但温度变化本身是慢过程几毫秒甚至几十毫秒采一次完全够用所以这个代价可以接受。2.3 校准值不读它温度必偏这是最容易被忽略的一步。STM32出厂时厂家会在特定温度通常是25摄氏度和特定电压通常是3.3V下测量温度传感器的输出电压并把对应的ADC原始值存在系统存储区的特定地址里。F103系列中这个值存在0x1FFFF7B8地址不同型号可能不同务必查对应型号的参考手册。同时还有一个Vrefint的校准值存在0x1FFFF7BA。如果你不读这个校准值直接用公式算温度偏差可能达到十几度甚至几十度。因为每个芯片的制造工艺有差异传感器特性不完全一致校准值就是用来消除这种个体差异的。计算公式是这样的实际温度等于当前ADC读数减去25度时的校准值除以斜率再加上25。斜率在F103手册里给出的是4.3毫伏每摄氏度但用ADC原始值计算时斜率要换算成“每摄氏度对应的ADC变化量”。具体换算方式是4.3mV除以3.3V再乘以4096约等于5.34。也就是说温度每升高1度ADC读数大约变化5.34。3. DMA传输配置与内存管理3.1 DMA通道选择与请求映射STM32的DMA控制器有多个通道每个通道可以响应特定的外设请求。ADC1的DMA请求在F103上固定映射到DMA1的通道1。这个映射关系是硬件决定的不能随意更改。如果你用的是ADC3那对应的就是DMA2的通道5。选错通道的话DMA根本不会响应ADC的请求数据永远搬不过来。配置DMA的时候有几个关键参数需要设置。外设地址设为ADC数据寄存器的地址内存地址设为你的缓冲区数组首地址。数据方向是外设到内存。数据宽度方面ADC是12位精度数据寄存器是16位的所以外设和内存的数据宽度都设为半字16位。模式要选循环模式这样DMA搬完一轮会自动从头开始不需要CPU干预。优先级可以设为中等或高取决于系统里还有没有其他DMA请求。3.2 缓冲区大小与数据对齐缓冲区大小设多少合适这取决于你的采样策略。如果只是监控温度缓冲区设10到20个元素就够了DMA循环填充CPU定期取平均值。如果要做温度趋势分析可以设大一些比如100个配合软件滤波。数据对齐方式要注意ADC转换结果是12位的右对齐时数据存在寄存器的低12位高4位是0。DMA搬运的时候直接搬16位所以内存里存的也是右对齐的数据。如果你开了过采样或者用了其他精度模式对齐方式可能不同要仔细看参考手册。#define TEMP_BUF_SIZE 20 uint16_t adc_temp_buffer[TEMP_BUF_SIZE]; // DMA配置结构体以标准外设库为例 DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_temp_buffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize TEMP_BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_Medium; DMA_Init(DMA1_Channel1, DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE);上面这段代码里外设地址不递增因为ADC只有一个数据寄存器内存地址递增这样每次搬来的数据会依次存入数组的不同位置。循环模式保证了缓冲区满了之后自动回卷不会溢出。3.3 ADC与DMA的联动配置ADC这边要开启DMA使能位否则ADC转换完成后不会发出DMA请求。在标准外设库里调用ADC_DMACmd(ADC1, ENABLE)就行。同时ADC的连续转换模式要打开这样ADC一次转换完成后自动开始下一次配合DMA循环模式整个系统就自动运转起来了。有个细节值得注意ADC的DMA请求是在每次转换完成后产生的。如果你开了扫描模式并且有多路通道DMA会把每个通道的结果依次搬走。但这里我们只采内部温度传感器一路所以扫描模式可以关掉规则组里只放通道16就行。4. 完整实操流程与代码实现4.1 硬件与软件环境准备硬件方面你只需要一块STM32开发板不需要任何外部传感器或电路。软件方面我用的开发环境是Keil MDK配合ST的标准外设库。如果你用HAL库或者LL库逻辑一样只是函数名不同。串口是必须的用来把温度值打印到电脑上观察。我习惯用USART1波特率115200。在开始写代码之前先把系统时钟配好。ADC时钟不能超过14MHzF103系列所以APB2分频后要确保ADC时钟在安全范围内。我通常把系统时钟设为72MHzAPB2不分频然后ADC预分频设为6分频这样ADC时钟是12MHz正好合适。4.2 初始化顺序与关键代码初始化顺序很重要顺序错了可能不工作。我的习惯是先配GPIO和串口再配DMA然后配ADC最后使能ADC和DMA。为什么DMA要在ADC之前配因为ADC使能后如果DMA还没准备好第一次转换完成时DMA请求可能丢失。虽然循环模式下后续会补上但第一组数据可能有问题。void ADC_Temp_Init(void) { ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 配置ADC通道16对应的引脚为模拟输入内部通道不需要外部引脚但习惯上配置一下 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // DMA初始化略见上文 // ADC初始化 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); // 配置通道16的采样时间为239.5周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5); // 使能内部温度传感器 ADC_TempSensorVrefintCmd(ENABLE); // 使能ADC的DMA请求 ADC_DMACmd(ADC1, ENABLE); // 使能ADC ADC_Cmd(ADC1, ENABLE); // ADC校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); // 启动ADC转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); }这段代码里ADC_TempSensorVrefintCmd(ENABLE)是关键它把内部温度传感器和参考电压通道的使能位打开。如果不调用这个函数通道16读出来的就是悬空值毫无意义。4.3 温度计算与串口输出DMA在后台自动搬运数据CPU只需要定期去缓冲区里取数据算温度。我一般用定时器或者简单的延时来触发读取比如每500毫秒读一次。float Get_Temperature(void) { uint32_t sum 0; uint16_t i; float voltage, temperature; uint16_t cal_value *(uint16_t*)0x1FFFF7B8; // 25度校准值 // 取缓冲区平均值简单滤波 for(i 0; i TEMP_BUF_SIZE; i) { sum adc_temp_buffer[i]; } uint16_t adc_value sum / TEMP_BUF_SIZE; // 计算温度 temperature (float)(adc_value - cal_value) / 5.34f 25.0f; return temperature; }这里用的5.34是前面算出来的斜率。实际使用中如果你发现温度偏差较大可以自己校准一下在已知温度环境下读一个值反推斜率。比如在25度时读数为cal_value在50度时读数为X那斜率就是(X - cal_value) / 25。串口输出就简单了用printf重定向或者自己写个发送函数都行。我习惯用printf在Keil里勾选Use MicroLIB然后重写fputc函数。5. 常见问题与排查技巧实录5.1 温度读数一直是零或者满量程这种情况通常是DMA没搬数据或者ADC根本没启动。排查步骤先看ADC的DR寄存器有没有变化如果有变化但缓冲区没数据那就是DMA配置问题。检查DMA通道对不对、使能位有没有打开、外设地址是不是ADC1-DR。如果DR寄存器本身就不变那检查ADC使能位、校准有没有完成、软件启动转换有没有调用。还有一种可能是采样时间太短ADC还没采到稳定值就转换了读出来接近零。把采样时间改成239.5周期试试。5.2 温度值跳动很大内部温度传感器本身精度有限官方给出的精度是正负1.5度左右加上ADC噪声跳动几个LSB很正常。解决办法是多次采样取平均或者用一阶滤波。我一般取20个样本平均效果就很稳了。如果还跳检查电源是否干净ADC参考电压是否稳定。5.3 温度偏差十几度九成是因为没读校准值或者校准值地址读错了。不同型号的STM32校准值地址不一样一定要查对应型号的参考手册。F103是0x1FFFF7B8F4系列通常在0x1FFF7A2C附近具体要看手册。另外如果你用的不是3.3V参考电压公式里的斜率也要相应调整。5.4 DMA传输影响其他外设DMA和CPU争抢总线带宽如果系统里还有别的DMA请求比如SPI、UART可能会互相影响。解决办法是合理设置优先级温度采集的DMA优先级可以设低一些因为它对实时性要求不高。另外缓冲区不要设太大20到50个元素足够了太大反而增加总线占用时间。5.5 常见问题速查表现象可能原因排查方法读数恒为0ADC未启动或DMA未使能检查ADC_Cmd和DMA_Cmd读数恒为4095通道选错或引脚悬空确认通道16且温度传感器已使能温度跳动大采样时间短或电源噪声加长采样时间增加平均次数温度偏差大未读校准值或斜率错误读取0x1FFFF7B8并核对公式DMA不搬运通道映射错误查手册确认ADC1对应DMA1通道1实操心得每次改完ADC或DMA配置一定要重新校准ADC。我遇到过好几次因为忘了重新校准导致数据异常的情况浪费了不少调试时间。6. 性能优化与扩展思路6.1 降低功耗的几种做法如果项目对功耗敏感温度采集不需要一直跑。可以改成定时采集用定时器触发ADC转换采完一组就关掉ADC和DMA需要的时候再开。STM32的ADC在关闭状态下功耗几乎为零DMA不工作时也不耗电。另外采样间隔可以拉长到几秒一次温度变化本来就慢没必要高频采集。6.2 多通道混合采集实际项目中你可能还需要采集外部电压、电流等信号。这时候可以把温度传感器和外部通道放在同一个规则组里用扫描模式加DMA一次性把所有通道的数据都搬走。注意每个通道的采样时间要单独配置温度通道用239.5周期其他通道可以根据信号源阻抗适当缩短。6.3 温度数据的进一步处理拿到温度值之后可以做很多事情。比如设置过温报警阈值超过85度就降频或者关断输出。也可以做温度补偿比如某些传感器的零漂随温度变化用内部温度值去修正。还可以把温度数据通过串口或者无线模块上传做远程监控。这些扩展都不复杂核心的采集和传输部分已经由ADC加DMA搞定了。6.4 不同系列芯片的移植要点从F1移植到F4主要改几个地方ADC通道号变了F4的温度传感器是ADC1_IN16或ADC1_IN18取决于具体型号校准值地址变了ADC时钟配置变了F4的ADC时钟最高36MHzDMA通道映射也变了。H7系列更复杂ADC和DMA的架构都不一样但基本思路是一致的——使能内部通道、配置采样时间、启动DMA循环搬运。我在实际使用中发现内部温度传感器最适合做趋势监控而不是精密测量。它的绝对精度有限但看温度变化趋势非常灵敏。比如电机启动后温度逐渐上升或者环境温度变化导致芯片温升这些场景下内部传感器完全够用。如果要精确到0.1度还是得外挂专用芯片。但对于大多数嵌入式项目来说能知道芯片大概热不热、有没有异常升温就已经解决大问题了。
延伸阅读

更多相关文章

2026/10/14 1:05:39

Java并行流顺序错乱揭秘:forEach与collect的保序机制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 2:30:44

量化软件推荐 参数搜索和独立验证三款工具怎样选

量化软件推荐,准备反复修改参数的人可以比较BigQuant、牛股王股票和青柠量化。愿意自己记录表达式及模型改动,可评估BigQuant;希望先限定现有因子与交易模型的少量调整,可检查牛股王股票;需要参数实验室及步进样本外验…

2026/10/14 2:30:44

【单片机课设毕设项目】基于单片机的智能婴儿床心率体温监测与图像远程监护装置设计 基于WIFI的婴儿监护生理环境监测与哄睡联动系统设计(032709)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/10/14 2:30:44

个人量化软件对比 三款工具如何解释少数交易拉高收益

个人量化软件对比,面对一条收益不错、却可能由少数交易贡献的曲线,可以比较米筐研究工具、牛股王股票和掘金量化。需要自行阅读头寸、流水并设计收益归因,可了解米筐RQAlpha Plus;希望从单次收益、盈亏比和历史明细开始复核&#…

2026/10/14 2:30:44

【单片机毕设案例分享】基于STM32的智能婴儿床多参数监测与远程联动控制装置设计 基于物联网的婴儿床生理环境监测与摄像头监护系统设计(032709)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于单片机,STM32单片机,51单片机,J…

2026/10/14 2:30:44

机械工程读研值不值?从产业周期到导师选择的全链条决策指南

每年到了保研、考研的时间点,总有一批机械工程专业的本科生在纠结同一个问题:到底要不要读硕士。问这个问题的学生,往往不是成绩最拔尖的那批,也不是完全不想读书的那批,而是处于中间状态的大多数——能找工作&#xf…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑