STM32物联网项目:使用ADC采集实验板上NTC热敏电阻测量温度

STM32 ADC采集板载温度

STM32 ADC简介

​ STM32 拥有 1~3 个 ADC(STM32F101/102 系列只有 1 个 ADC),这些 ADC 可以独立使用, 也可以使用双重模式(提高采样率)。STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。 它有 18 个通道,可测量 16 个外部和 2 个内部信号源。各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。 模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。

​ STM32F103 系列最少都拥有 2 个 ADC,我们选择的 STM32F103ZET 包含有 3 个 ADC。 STM32 的 ADC 最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK=14M,采样周期 为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。

ADC规则通道组和注入通道组解析

​ STM32 将 ADC 的转换分为 2 个通道组:规则通道组注入通道组规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之 后,规则通道才得以继续转换。 通过一个形象的例子可以说明:假如你在家里的院子内放了 5 个温度探头,室内放了 3 个温度探头;你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组循环扫描室外的 5 个探头并显示 AD 转换结果,当你想看室内温度时,通过一个按钮启动注入转换组(3 个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则组和注入组的划分,当你按下按钮后,需要从新配置 AD 循环扫描的通道,然后在释放按钮后需再次配置 AD 循环扫描的通道。

​ 上面的例子因为速度较慢,不能完全体现这样区分(规则通道组和注入通道组)的好处,但在工业应用领域中有很多检测和监视探头需要较快地处理,这样对 AD 转换的分组将简化事件处理的程序并提高事件处理的速度。

​ STM32 其 ADC 的规则通道组最多包含 16 个转换,而注入通道组最多包含 4 个通道。STM32 的 ADC 可以进行很多种不同的转换模式。

ADC转换模式

单次转换模式

​STM32 的 ADC 在单次转换模式下,只执行一次转换,该模式可以通过 ADC_CR2 寄存器 的 ADON 位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道),这是 CONT 位为 0。 以规则通道为例,一旦所选择的通道转换完成,转换结果将被存在 ADC_DR 寄存器中, EOC(转换结束)标志将被置位,如果设置了 EOCIE,则会产生中断。然后 ADC 将停止,直到下次启动。

连续转换模式

在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换。此模式可通过外部触发启 动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1

扫描模式

此模式用来扫描一组模拟通道。 扫描模式可通过设置ADC_CR1寄存器的SCAN位来选择。一旦这个位被设置,ADC扫描所有被 ADC_SQRX寄存器(对规则通道)或ADC_JSQR(对注入通道)选中的所有通道。在每个组的每个通道上执行单次转换。在每个转换结束时,同一组的下一个通道被自动转换。如果设置了CONT 位,转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换

CubeMX配置

本次实验使用ADC1采集板载NTC(负温度系数热敏电阻)输入到单片机引脚的电压值,转化为数字量后用数码管显示对应的温度,并可通过串口发送温度信息到串口助手上

时钟配置

首先要设置ADC的时钟,因为不能超过14MHz,所以ADC分频系数选为6,得到12MHz的时钟给到ADC1,2,3

ADC时钟频率:72/6=12MHZ

采样周期设置为1.5时,另外因为采样到值以后进行转换需要12.5周期,那么实际转换时间为;

12MHZ /1.5 /12.5 = 640KHZ

频率换算周期等于1的倒数;

1s/640KHZ=1000 000us /640 000HZ =1.5625us

所以实际采样转换时间为1.5625us

ADC1参数配置

因为硬件电路中NTC输入引脚接到了单片机的PC13,使用的是ADC1的通道13,所以配置ADC1通道13的参数

因为你ADC1的通道0,1,2被其他模块占用了,所以这里显示红色,对配置通道13没影响

Scan Conversion Mode  ADC工作在扫描模式(多通道)还是单次(单通道)模式。可以设置这个参数为ENABLE 或者DISABLE。
Continuous Conversion Mode  ADC模数转换工作在连续模式。可以设置这个参数为 ENABLE或者DISABLE 。
Discontinuous Conversion Mode  ADC模数转换工作在不连续模式(单次模式)。可以设置这个参数为 ENABLE或者DISABLE 。

外部触发转换源可以选择定时器比较事件或者触发事件,这里选择软件触发,用代码启动ADC转换

数码管和串口的引脚也要配置

检测原理

因为NTC热敏电阻的阻值会随着温度的变化而变化,通过一个10k电阻分压后输入到单片机的ADC检测引脚,ADC检测的是输入的电压,并将电压转换为数字量,得到ADC的采集值;

因为热敏电阻不同温度对应的电阻值在数据手册中可以查到,所以根据公式可以算出输入到引脚的电压,将不同的电阻对应的电压做成表格

然后可以参考STC15的ADC采集值计算公式来计算不同的电压经过ADC转换后会得到的采集值

因为32单片机的ADC是12位的,所以上面公式的1024要改为4095( 212 – 1),因为计数值都是从0开始的,0 ~ 4095就刚好4096,所以ADC的采集值 = 4095 * Vin/Vcc

这样,就有了不同温度下的电阻值,电压值,ADC采集值,就可以做成一张表格

把四舍五入后的最小采集值和最大采集值写成一个二维数组,后面只要在程序中,查找ADC转换的采集值处于哪个数组值之间,就可以得知对应的温度值了

程序

程序与之前STC15实战的代码类型,因为同样是使用NTC热敏电阻检测温度

NTC.c

/*
* @name   Get_NTC_Voltage
* @brief  获取NTC的电压
* @param  None
* @retval None   
*/
static void Get_NTC_Voltage()
{
    //开启ADC
    HAL_ADC_Start(&hadc1);

    //等待ADC规则组转化完成
    if(HAL_ADC_PollForConversion(&hadc1,10) == HAL_OK)
    {
        NTC.usADC_Value = HAL_ADC_GetValue(&hadc1);     //获取ADC转换结果
        NTC.fNTC_Voltage = (NTC.usADC_Value*3.3)/4095;  //反向计算出输入ADC引脚的电压
        printf("ADC转换的原始值 = %d\r\n",NTC.usADC_Value);
        printf("计算得出的电压值 = %.2f\r\n",NTC.fNTC_Voltage);
    }
    else
    {
        printf("ADC转换错误或超时!\r\n");
    }
}
//NTC型号: B3950  10k  1%
//温度表,对应温度-30℃~70℃   
//Note:如果采集值 > 3867,则温度低于-30℃;采集值 < 589,则温度高于70℃
const uint16_t  NTC_Table[101][2] = 
{
	{3850,3867},{3837,3854},{3822,3840},{3807,3826},{3791,3810},
	{3774,3795},{3757,3778},{3739,3761},{3721,3743},{3701,3724},
	{3681,3704},{3660,3684},{3638,3663},{3616,3641},{3592,3618},
  {3568,3595},{3543,3570},{3517,3545},{3491,3519},{3463,3492},
	{3435,3464},{3405,3435},{3375,3405},{3344,3375},{3312,3343},
	{3280,3311},{3246,3279},{3212,3245},{3177,3211},{3141,3176},
	{3105,3140},{3067,3104},{3029,3066},{2991,3028},{2951,2990},
	{2911,2950},{2871,2910},{2829,2870},{2788,2828},{2745,2787},
  {2705,2744},{2660,2704},{2616,2659},{2572,2615},{2528,2571},
  {2484,2527},{2440,2483},{2395,2439},{2350,2394},{2305,2349},
	{2260,2304},  //对应20℃  --下标为50
  {2216,2259},{2171,2215},{2126,2170},{2082,2125},{2037,2081},
  {1992,2036},{1947,1991},{1903,1946},{1859,1902},{1815,1858},
  {1772,1814},{1729,1771},{1687,1728},{1646,1687},{1604,1645},
  {1564,1603},{1524,1563},{1484,1523},{1446,1483},{1408,1445},
  {1370,1407},{1334,1369},{1297,1333},{1262,1296},{1228,1261},
  {1194,1227},{1161,1193},{1128,1160},{1096,1129},{1062,1095},
  {1035,1067},{1006,1038},{977 ,1009},{949 , 981},{921 , 953},
  {895 , 926},{869 , 900},{843 , 875},{819 , 850},{795 , 826},
  {771 , 802},{749 , 779},{727 , 757},{705 , 735},{684 , 714},
  {664 , 694},{644 , 674},{625 , 654},{607 , 636},{589 , 617}
};
/*
* @name   Get_Temperature_Value
* @brief  获取温度值
* @param  None
* @retval None   
*/
static void Get_Temperature_Value()
{
    uint8_t temp;

    //获取NTC电压值
    NTC.Get_NTC_Voltage();

    //ADC采集值越界检测
    if(NTC.usADC_Value > 3867)  //最低-30度
    {
        NTC.usADC_Value = 3867;
        printf("Temperature is below -30℃\r\n");
    }
    if(NTC.usADC_Value < 589)   //最高 70度
    {
        NTC.usADC_Value = 589;
        printf("Temperature is higher than 70℃\r\n");
    }

    //二分法查表
    if(NTC.usADC_Value > NTC_Table[50][1])  //低于20度
    {
        //查询下标为0~49的一维数组,对应-30 ~ 19度
        for(temp=0;temp<=49;temp++)
        {
            if((NTC.usADC_Value >= NTC_Table[temp][0]) && (NTC.usADC_Value <= NTC_Table[temp][1]))
            {
                break;  //找到ADC采集值所在的一维数组,退出循环,得到下标数据temp
            }
        }
    }
    else if(NTC.usADC_Value < NTC_Table[50][0])  //高于20度
    {
        //查询下标为51~100的一维数组,对应21 ~ 70度
        for(temp=51;temp<=100;temp++)
        {
            if((NTC.usADC_Value >= NTC_Table[temp][0]) && (NTC.usADC_Value <= NTC_Table[temp][1]))
            {
                break;  //找到ADC采集值所在的一维数组,退出循环,得到下标数据temp
            }
        }
    }
    else
    {
        temp = 50;  //下标为50
    }

    //计算温度
    NTC.fTemperature = (float)temp  - 30;
    printf("Temperature is: %.1f℃ \r\n\r\n",NTC.fTemperature);
}

System.c

系统主函数中,每隔一秒在数码管上显示当前获得的温度值

/*
* @name   Run
* @brief  系统运行
* @param  None
* @retval None   
*/
static void Run()
{
  //获取温度值
  NTC.Get_Temperature_Value();

  //显示温度
  if(NTC.fTemperature < 0)  //如果温度是负数
  {
    Display.Disp_other(Disp_NUM_3,0x40,Disp_DP_OFF);  //3号数码管显示负号
  }
  else                      //否则是正数
  {
    Display.Disp_other(Disp_NUM_3,0x00,Disp_DP_OFF);  //3号数码管关闭
  }

  Display.Disp_Hex(Disp_NUM_1,(uint8_t)NTC.fTemperature%10,Disp_DP_OFF);
  Display.Disp_Hex(Disp_NUM_2,(uint8_t)NTC.fTemperature/10,Disp_DP_OFF);

  HAL_Delay(1000);  //每隔一秒采集一次
}

实验效果


物联沃分享整理
物联沃-IOTWORD物联网 » STM32物联网项目:使用ADC采集实验板上NTC热敏电阻测量温度

发表评论