STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现)

2023-05-16

STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现)

  • 前言
  • 一、内部温度传感器的使用?
  • 二、代码操作讲解
    • 1.直接读取
    • 2.DMA处理
  • 总结


前言

STM32F1系列(本代码基于STM32F103C8T6芯片)MCU内置了一个温度传感器,供ADC_1的第16通道读取,它并非精确的温度计量会有实际性误差。本着对ADC功能的学习与理解,以下内容讲解将使用两种方式读取数据(直接获取/DMA方式两种,具体差异后面会说明)并用串口打印,提供工程文件,希望对初学者有着一定帮助。


PS:内容均为原创,转载需获取作者本人同意,如有侵权可联系删除。若对内容有疑问,欢迎指正与交流,由于最近即将处于研究生阶段有点忙碌,但尽力及时回复大家问题。

一、内部温度传感器的使用?

STM32芯片内部有一个温度传感器,已接入ADC1第16通道,它的测量范围为-40~125度。精度比较差,为±1.5℃左右。我们接下来的目的就使用ADC读取它的数据 ~

首先我们了解一下ADC(不是AD Carry~而是Analog-to-digital converter),大概就是将模拟信号转化为数字信号。常用的ADC有积分型、逐次比较型、并行比较型/串并行比较型、Σ-Δ调制型、电容阵列逐次比较型、压频变换型等(具体不做详细介绍,知道就好)。

我们密切关注的ADC的技术指标就两个(其他的目前不用急):
精度:反映转换器的实际输出接近理想输出的精确程度的物理量。
分辨率(Resolution): 指数字量变化一个最小量时模拟信号的变化量,定义为满刻度与2n的比值。分辨率又称精度,通常以数字信号的位数来表示。

一般把8位以下的A/D转换器称为低分辨率ADC,9~12位称为中分辨率ADC,13位以上为高分辨率。A/D器件的位数越高,分辨率越高,量化误差越小,能达到的精度越高。它的效果工作如下:
ADC示意
红色代表电压信号,根据一定时间周期采样(不失真应该满足采样定理,这里不做详细介绍,具体可百度)为离散的电压信号,这样当间距的离散电压时间足够小(不准确但是可以这样理解),那么就足够还原精度一定的红色模拟信号,则会产生大量之数据。

STM32f103系列ADC为逐次逼近型,总共有3个ADC,精度为12位,其中ADC1和ADC2都有16个外部通道, 2个内部通道,ADC3一般有8个外部通道。ADC的输入时钟不得超过14MHz,其时钟频率由PCLK2分频产生。因此我们只关注如何配置16通道可用就好。
在这里插入图片描述
还有一个是我们需要关注的,就是它的转换时间。我们可以配置它的采样时间,转换时间 = 采样时间 + 12.5个周期(固定时间)。如在14MHz和采样时间为1.5周期,则转换时间:

    TCONV = 1.5 + 12.5 = 14周期 = 14×(1 / (14 × 1000000)) = 1us

在这里插入图片描述
最后就关注它的转化模式:
1.单次转换模式:ADC只执行一次转换,然后停止。
2.连续转换模式:当前面ADC转换一结束,马上启动另一次转换。
3.扫描模式:扫描模式用来扫描一组模拟通道。在每个组的每个通道上执行单次转换,在每个转换结束时,同组的下一个通道开始转换。
在配置的时候详细注解!
由于ADC还有注入规则通道,这里没有涉及就不多赘述,最后得到的数据,根据参考手册的进行处理,大致如下:
在这里插入图片描述
具体参数在代码段里说明!

因此,我们归纳出配置的流程如下:
ADC转换步骤如下:
1.开启GPIO时钟,设置Pinx 为模拟输入。(由于是内部的温度传感器,所以不用开)
2 .使能ADC1 时钟,并设置分频因子:要使用ADC1,第一步要使能 ADC1 的时钟。再设置ADC1 的分频因子。分频因子要确保 ADC1 的时钟(ADCCLK)不要超过14Mhz 。
3 .设置ADC1 的工作模式:设置单次转换模式、触发方式选择、数据对齐方式等都在这一步实现。
4 .设置ADC1 规则序列的相关信息:如只有一个通道,设置规则序列中通道数为1 ,然后设置通道的采样周期。
5 .开启AD转换器,并校准(必须校准否则不准确):开启AD转换器,执行复位校准和AD校准。
6.读取ADC值校准完成后,ADC就算准备好了。启动ADC转换,在转换结束后,读取ADC1_DR 里面的值。

了解了这些预备知识,下面,我们就可以开始对它进行操作了(前往不要绝对上面麻烦,不然就算实现了,也云里雾里不是嘛)。

二、代码操作讲解

在讲解代码之前,有必要让大家知道为什么使用两种方式来操作(毕竟要以学到东西为主嘛),我们知道,MCU与外界通信基本上为这三种方式——
1:轮询
2:中断
3:DMA
三者有什么区别呢(如果懂直接跳过进入正题),在这言简意赅但不绝对术语化的说明一下,其实学习单片机的朋友都应该熟悉前两者,初学者却很少使用用后者DMA,那今天就弄懂一下它吧。

轮询咱们经常使用的呀,使用if/case等语句,加个while循环连续读取,一直就问问CPU内核,给厨师(外设)给我做的菜做的怎么样了呀,给我看看咯。然后CPU就一直忙忙忙这个事情~
轮询大概语句长这样:

while(1){
if(成立条件1){干成立条件1干的事情;}
else if(成立条件2){干成立条件2干的事情;}
else if(成立条件3){干成立条件3干的事情;}
......//一直寻找适合的条件
delay_ms(100);
}

中断呢就是单片机之精华所在,基本上以后工程许多案例都是中断来访问外设。大概就像是厨师在工作,然后有个按铃,做好了就叮~ ~ ~ 的一声告诉CPU,这个时候CPU才可以屁颠屁颠的跑过去给我们端菜啦,不用像轮询方式一直去问候他。
DMA其实手段跟中断差多,不过它的存在就是给CPU分担压力的,当处理数据过快过多到来的时候,CPU可能比较繁忙,这个时候就可能出现数据没有收集到或者处理太慢,那么DMA就可以帮助CPU来收集数据,由于DMA挂靠在总线上,可以直接代替CPU与外设亲密交流,这样就可以出现这样的情况,外面的厨师太多了,一下子就有很多的菜出锅,DMA这个小弟先帮忙分拣,整理好,跑个腿,CPU就可以省去很多很多的事情来做其他有意义的事情,嘿嘿嘿~(当然DMA也可以给出中断信号)

综上呢,我们理解了,轮询就一直干事情简单明了方便,但是占用cpu资源较多;中断呢就不会占用太多资源,CPU可以该干嘛干嘛,需要它的时候叫叫它;DMA呢就是当上面两种方式的数据大于CPU所能处理负载了,或者为了减轻CPU负担而存在的功能,大大降低CPU负担,但配置起来相对麻烦。

所以如果ADC处理多路输入,这一庞大数据来临时,前面两种方法好像招架不住了,因此DMA才是真正的应对手段;而当ADC处理的数据就这一路或者很少(比如本次的温度获取,只需要一个内部通道,数据少,丢了就丢了嘛),随便怎么用都可以。

(关于他们的专业解释可以可以百度,要是不太理解,我可以专门写一篇文章讲解他们的区别以及内在联系)

1.直接读取

首先配置ADC相关参数结构体:

	ADC_DeInit(ADC1);//重新来配置ADC1
	ADC_TempSensorVrefintCmd(ENABLE);//传感器这玩意必须打开,否则必定没数据
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //六分频72M/6=12M
    ADC_InitTypeDef ADCInitStruct;
	ADCInitStruct.ADC_Mode = ADC_Mode_Independent;	//设置独立模式
	ADCInitStruct.ADC_ScanConvMode = DISABLE;		//不开扫描
    ADCInitStruct.ADC_ContinuousConvMode = DISABLE;	//不循环(其实循环不循环不重要,反正就一个通道)
    ADCInitStruct.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
    ADCInitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发,不用硬件触发
    ADCInitStruct.ADC_NbrOfChannel = 1;//顺序转化的规则通道数目
    ADC_Init(ADC1,&ADCInitStruct);

然后需要校准:

	ADC_ResetCalibration(ADC1);			//初始化校准
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);			//开始校准
    while(ADC_GetCalibrationStatus(ADC1));
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发开始

然后可以开始进行读取数据:
通过简单的函数ADC_GetConversionValue(ADC1)来获取即可。

   	ADC_RegularChannelConfig(ADC1,ADC_Channel_16,1,ADC_SampleTime_239Cycles5);
    while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));
    return ADC_GetConversionValue(ADC1);

因为读出的值会有变化,我们可以取多次的平均值,一半都是取5~20次的平均值,在这里就直接使用20次的for循环求平均值

 {
 	u8 i;
    u32 averagedata = 0;
    for(i = 0;i < 20;i++)			//利用for循环读取20次的值
    {
        averagedata += ADC1_GetConvValue();	//这里就是每次读取的值
			delay_ms (5);
    }
    return averagedata/20;
}

获取到数值以后,我们来进行数据处理,即得到最后的温度结果

void GetTemperature(void)
{
    double VSense = (double)ADC1_GetAverageConvValue()*(3.3/4096.0);
    printf("VSense:%.2f; %.2f\r\n",VSense,((1.43 - VSense)/0.0043+25.0));
    //这个计算是涉及到浮点运算耗时间,可以扩大了计算更好,在这里就这样写吧也没问题也可便于理解

}
		

最后在主程序里面循环它就OK,大致如下:

	while(1){
		GetTemperature();
		delay_ms(500);
	}//这是不是很像我们的轮询方式呢~

最后通过串口看看我们的成果~
在这里插入图片描述
当手指按住它,则温度升高,前面数据为保留两位数据的ADC读取电压,后面则为获取到的温度值。

2.DMA处理

在这里插入图片描述
这里我们就用到了DMA1的通道一,在表中可以查看到,因此我们直接开始配置DMA就可。

代码如下(示例):

void MYDMA_Config(void)
{
	
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA时钟

		DMA_DeInit(DMA1_Channel1);//重设DMA为缺省值	
		DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;//外设地址
		DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&SendBuff1;//存储器地址
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设到存储器的传输模式
		DMA_InitStructure.DMA_BufferSize = 1; //数据量为1
		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //
		DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ; //16位!!!
		DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ; //16位!!!
		DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
		DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级高
		DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //(内存到内存禁止)
 
		DMA_Init(DMA_CHx,&DMA_InitStructure); //初始化
		DMA_SetCurrDataCounter(DMA1_Channel1,cndtr);//设置数据量
   		DMA_Cmd(DMA1_Channel1, ENABLE);                                       
}

同时,我们还需要开启ADC的DMAcmd使能才能让DMA接管CPU的任务。

		ADC_DMACmd(ADC1, ENABLE);

这样我们就可直接读取SendBuff1的值就知道啦~
当然我们这里也取了20次平均

u16 ADC1_GetAverageConvValue(void)
{
		u32 temp_val=0;
		u8 t;
		for(t=0;t<20;t++)
		{
				temp_val+=SendBuff1;
				delay_ms(5);
		}
		return temp_val/20;
} 	
	

然后在主程序中直接输出电压,温度即可。

循环读取:(因为cpu没有其他事情做,就让他反复读取这个值吧)
	adcx = ADC1_GetAverageConvValue();//这个函数相比起直接获取而言,大大降低了CPU的计算负担,大部分的数据传输任务都交给了DMA
	temp=(float)adcx*((float)3.3/4096);
	printf("(DMA)temperature:%.2f; %.2f\r\n",temp,((1.43 - temp)/0.0043+25.0));
	delay_ms(500);

最后的结果
在这里插入图片描述
基本上完成任务


总结

到此为止,已经演示了两种手段的处理思路,我们也发现了区别还是较大,使用了DMA外设(如果还不理解DMA,可以参考一下stm32的参考手册,上面写的相对详尽)。
对CPU资源占用的,轮询方式占用较大,中断其次,DMA最优。各有各的使用手段,在不同情况下调用不同的外设是嵌入式工程师必备的能力,希望对大家有所帮助,我也将把两份工程文件上传,供有需要的朋友学习。

工程文件(DMA获取温度方式):https://download.csdn.net/download/qq_40249327/13944700

工程文件(直接获取温度方式):
https://download.csdn.net/download/qq_40249327/13944690

码字不易,有问题可以提出交流,希望我们能在这条路走得更远,与君共勉!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现) 的相关文章

随机推荐

  • 目标跟踪之LTMU:High-Performance Long-Term Tracking with Meta-Updater环境配置及代码运行

    代码地址 xff1a https github com Daikenan LTMU 论文地址 xff1a High Performance Long Term Tracking with Meta Updater LTMU是CVPR2020
  • 英伟达NVIDIA Jetson系列产品刷机(SDK Manager)

    英伟达的TX NX AGX可以说是近几年边缘计算中非常牛的产品了 可Linux系统经常会出现安装某软件后系统环境崩掉的情况 xff0c 接着系统崩掉的机会 xff0c 记录一下Jetson AGX Xavier的刷机过程 准备 xff1a
  • seL4的编译和运行(OdroidXU3)

    seL4支持平台 seL4所在的git如下 xff1a https github com seL4 seL4 git 不过我们编译和运行seL4一般不只是用这个git xff0c 而是借助seL4test xff08 seL4的测试框架 x
  • 目标跟踪之Pysot系列代码训练(SiamRPN\SiamRPN++)

    代码地址 xff1a https github com STVIR pysot 环境配置参考博客 xff1a Siam系列跟踪算法工具包PySOT配置 一 数据集准备 Pysot系列跟踪器训练的时候 xff0c 首先将数据集进行裁剪 xff
  • 基于Paddle实现实例分割

    百度的Paddle这几年发展十分迅速 xff0c 而且文档十分齐全 xff0c 涉及到机器视觉的多个应用领域 xff0c 感觉还是非常牛的 xff0c 各种backbone xff0c 损失函数 数据增强手段以及NMS等 xff0c 整体感
  • 机器学习笔记: 时间序列 分解 STL

    1 前言 STL Seasonal and Trend decomposition using Loess 是以LOSS 作为平滑方式的时间序列分解 LOSS可以参考机器学习笔记 xff1a 局部加权回归 LOESS UQI LIUWJ的博
  • C++11 auto遍历

    C 43 43 11这次的更新带来了令很多C 43 43 程序员期待已久的for range循环 xff0c 每次看到javascript xff0c lua里的for range xff0c 心想要是C 43 43 能有多好 xff0c
  • C++ 文件的读写(fin && fout)

    如何让键盘输入字符保存在 txt文件中 如何让我们自己在键盘上输入的字符不仅仅在屏幕上显示 xff0c 而且还能保存在特定路径的文件中 xff0c 这让简单枯燥的控制台命令程序变得略有趣 首先 xff0c 先看看cin和cout对象 xff
  • 基本矩阵、本质矩阵和单应矩阵

    两幅视图存在两个关系 xff1a 第一种 xff0c 通过对极几何一幅图像上的点可以确定另外一幅图像上的一条直线 xff1b 另外一种 xff0c 通过上一种映射 xff0c 一幅图像上的点可以确定另外一幅图像上的一个点 xff0c 这个点
  • 矩阵零空间

    矩阵A的零空间就Ax 61 0的解的集合 零空间的求法 xff1a 对矩阵A进行消元求得主变量和自由变量 xff1b 给自由变量赋值得到特解 xff1b 对特解进行线性组合得到零空间 假设矩阵如下 xff1a 对矩阵A进行高斯消元得到上三角
  • VIO学习总结

    VIO xff08 visual inertial odometry xff09 即视觉惯性里程计 xff0c 有时也叫视觉惯性系统 xff08 VINS xff0c visual inertial system xff09 xff0c 是
  • 单应性(Homography)变换

    我们已经得到了像素坐标系和世界坐标系下的坐标映射关系 xff1a 其中 xff0c u v表示像素坐标系中的坐标 xff0c s表示尺度因子 xff0c fx fy u0 v0 xff08 由于制造误差产生的两个坐标轴偏斜参数 xff0c
  • senmantic slam mapping

    basicStructure hpp common h 定义一些常用的结构体 以及各种可能用到的头文件 xff0c 放在一起方便include 相机内参模型 增加了畸变参数 xff0c common headers h各种可能用到的头文件
  • Ubuntu 20.04 VNC 安装与设置

    原链接 VNC是一个远程桌面协议 按照本文的说明进行操作可以实现用VNC对Ubuntu 20 04进行远程控制 一般的VNC安装方式在主机没有插显示器的时候是无法使用的 下面的操作可以在主机有显示器和没有显示器时都能够正常工作 首先安装x1
  • opencv中类型转换问题

    记录一下最近困惑我的问题 方便以后查阅 在学习立体匹配算法中BM算法时 xff0c 出现在了关于类型转换的问题 xff1a disp convertTo disp8u CV 8U 255 numberOfDisparities 16 不知道
  • 最大似然估计MLE与贝叶斯估计

    最大似然估计 Maximum Likehood Estimation MLE 最大似然估计的核心思想是 xff1a 找到参数 的一个估计值 xff0c 使得当前样本出现的可能性最大 用当年博主老板的一句话来说就是 xff1a 谁大像谁 xf
  • 大疆Livox_mid 40雷达初体验

    为了解决无人车上镭神雷达FOV小而导致的车前3m内无法看到锥形桶问题 东家给公司邮寄了一台大疆的mid40雷达 不得不说 颜值真的高 光看颜值 就甩镭神几条街 昨天重新配置镭神的激光雷达 官方给的配置软件 真的是 用的我心碎啊 算了 不提了
  • 地铁供电系统的构成

    地铁供电系统一般划分为以下几部分 xff1a 外部电源 xff1b 主变电所 xff1b 牵引供电系统 xff1b 动力照明系统和杂散电流腐蚀防护系统 xff1b 电力监控系统 外部电源地铁供电系统的外部电源就是地铁供电系统主变电所供电的外
  • C++ Vector常用函数

    C 43 43 Vector常用函数 begin 函数 原型 xff1a iterator begin const iterator begin 功能 xff1a 返回一个当前vector容器中起始元素的迭代器 end 函数 原型 xff1
  • STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现)

    STM32使用ADC获取内部温度传感器数据输出 xff08 直接读取 DMA两种方式实现 xff09 前言一 内部温度传感器的使用 xff1f 二 代码操作讲解1 直接读取2 DMA处理 总结 前言 STM32F1系列 xff08 本代码基