概述
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器件的位数越高,分辨率越高,量化误差越小,能达到的精度越高。它的效果工作如下:
红色代表电压信号,根据一定时间周期采样(不失真应该满足采样定理,这里不做详细介绍,具体可百度)为离散的电压信号,这样当间距的离散电压时间足够小(不准确但是可以这样理解),那么就足够还原精度一定的红色模拟信号,则会产生大量之数据。
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; %.2frn",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; %.2frn",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
码字不易,有问题可以提出交流,希望我们能在这条路走得更远,与君共勉!
最后
以上就是落后山水为你收集整理的STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现)前言一、内部温度传感器的使用?二、代码操作讲解总结的全部内容,希望文章能够帮你解决STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现)前言一、内部温度传感器的使用?二、代码操作讲解总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复