STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现) 您所在的位置:网站首页 热敏传感器的功能 STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现)

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

2024-02-03 05:28| 来源: 网络整理| 查看: 265

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 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


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有