本文主要描述 - STM32 ADC NTC热敏电阻二分(折半)查表法测温功能的思路和代码实现
NTC的相关属性:R25=10K±3% B25/50=4100K±3% 10K上拉
STM32 ADC实现NTC测温的电路示意图如下:
STM32的ADC分辨率为12位,模数转换的范围 0~4095(0x000~0xFFF)
针对以上描述的NTC属性以及电路,对应的温度和测量的数字量的关系表:
static const uint16 R10K_TAB[] = { //R25=10K±3% B25/50=4100K±3% 10K上拉
3738,3719,3698,3677,3655,3631,3607,3582,3556,3530, //-20℃ ... -11℃
3502,3473,3443,3412,3381,3348,3314,3280,3244,3208, //-10℃ ... -1℃
3170,3132,3093,3053,3012,2970,2928,2885,2842,2797, // 0℃ ... 9℃
2752,2707,2661,2615,2568,2522,2474,2427,2379,2332, // 10℃ ... 19℃
2284,2237,2189,2142,2095,2048,2001,1954,1908,1863, // 20℃ ... 29℃
1818,1773,1729,1685,1642,1600,1558,1517,1477,1437, // 30℃ ... 39℃
1398,1360,1323,1286,1250,1215,1180,1147,1114,1082, // 40℃ ... 49℃
1051,1020, 990, 961, 933, 905, 878, 852, 827, 802, // 50℃ ... 59℃
778, 755, 732, 710, 688, 668, 647, 628, 609, 590, // 60℃ ... 69℃
572, 555, 538, 522, 506, 491, 476, 461, 447, 434, // 70℃ ... 79℃
421, 408, 395, 384, 372, 361, 350, 340, 330, 320, // 80℃ ... 89℃
311, 302, 293, 284, 276, 268, 261, 253, 246, 239, // 90℃ ... 99℃
233, 226, 220, 214, 209, 203 //100℃ ... 105℃
};
将数据表的数据放到Excel中查看曲线的形状
从图中可以看出,NTC的温度与数字量的关系比较接近一元线性关系,数字量越大,温度值越低,负相关。那么将两度之间的小数度数几乎可以认为就是线性的,下面介绍计算小数精度的方法按照线性处理。
在数据表中给出的数据,是整数温度对应的数字量,本次实验测量的精度为0.1℃,测量的数字量值很可能是在两个整数度中间,如ADC采样的数字量为 0x80C,十进制是2060,对应在数据表的2048(25℃)和2095(24℃)中间,计算方式按照线性处理如下:
关于二分(折半)查找的方法,肯定是要比逐次对比效率要高很多,至于效率高多少就不分析了,这里我按照二分查找的思路来进行。由于采样的ADC数字量可能在表中查找不到,但是可以查找出在哪个范围内,就如我上述举例的情况,采样是 2060,在2048和2095之间,至于怎么实现二分查找,参考一下下面的代码。
函数方法实现的思路,在第一图电路示意图中如果没有接NTC传感器,相当于是开路,ADC采样的值相当于电源电压;如果将NTC的两个端子用导线短接,ADC采样的值相当于GND;考虑到极限的情况温度低于-20℃,采样的数字量值不在数据表范围内,即大于3738;同样,如果实际测量温度高于105度,采样的数字量值不在数据表范围内,即小于203;其他的情况就属于正常可以查表的数字量。开路测量电源电压时,可能会稍低于0XFFF,我这里留出一些余量0X00F;短路测GND的电压数字量时,可能会稍微高于0X000,也留出余量0X00F。
用图来表示
下面是具体的代码实现:(代码中小数部分,通过*10的倍率来表示,即247代表24.7℃)
z_hardware_adc.c
#ifndef __Z_HARDWARE_ADC_H
#define __Z_HARDWARE_ADC_H
#include "z_hardware_adc.h"
#endif
#define SHORT_CIRCUIT_THRESHOLD 15
#define OPEN_CIRCUIT_THRESHOLD 4080
static const u16 R10K_TAB[] = { //R25=10K±3% B25/50=4100K±3% 10K??
3738,3719,3698,3677,3655,3631,3607,3582,3556,3530, //-20? ... -11?
3502,3473,3443,3412,3381,3348,3314,3280,3244,3208, //-10? ... -1?
3170,3132,3093,3053,3012,2970,2928,2885,2842,2797, // 0? ... 9?
2752,2707,2661,2615,2568,2522,2474,2427,2379,2332, // 10? ... 19?
2284,2237,2189,2142,2095,2048,2001,1954,1908,1863, // 20? ... 29?
1818,1773,1729,1685,1642,1600,1558,1517,1477,1437, // 30? ... 39?
1398,1360,1323,1286,1250,1215,1180,1147,1114,1082, // 40? ... 49?
1051,1020, 990, 961, 933, 905, 878, 852, 827, 802, // 50? ... 59?
778, 755, 732, 710, 688, 668, 647, 628, 609, 590, // 60? ... 69?
572, 555, 538, 522, 506, 491, 476, 461, 447, 434, // 70? ... 79?
421, 408, 395, 384, 372, 361, 350, 340, 330, 320, // 80? ... 89?
311, 302, 293, 284, 276, 268, 261, 253, 246, 239, // 90? ... 99?
233, 226, 220, 214, 209, 203 //100? ... 105?
};
void init_adc(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)
{
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;//PC4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
u16 func_get_adc_valve_ch7(void)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 1, ADC_SampleTime_55Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
u8 func_get_ntc_temp(u16 value_adc, s16* value_temp)
{
u8 index_l, index_r;
u8 r10k_tab_size = 126;
s32 temp = 0;
if(value_adc <= SHORT_CIRCUIT_THRESHOLD)
{
return 1;
}
else if(value_adc >= OPEN_CIRCUIT_THRESHOLD)
{
return 2;
}
else if(value_adc > R10K_TAB[0])
{
return 3;
}
else if(value_adc < R10K_TAB[r10k_tab_size - 1])
{
return 4;
}
index_l = 0;
index_r = r10k_tab_size - 1;
for(;index_r - index_l > 1;)
{
if((value_adc <= R10K_TAB[index_l]) && (value_adc > R10K_TAB[(index_r + index_l)%2 == 0 ? (index_r + index_l)/2 : (index_r + index_l)/2 + 1]))
{
index_r = (index_r + index_l) % 2 == 0 ? (index_r + index_l)/2 : (index_r + index_l)/2 + 1;
}
else
{
index_l = (index_r + index_l)/2;
}
}
if(R10K_TAB[index_l] == value_adc)
{
temp = (((s16)index_l) - 20)*10;//rate *10
}
else if(R10K_TAB[index_r] == value_adc)
{
temp = (((s16)index_r) - 20)*10;//rate *10
}
else
{
if(R10K_TAB[index_l] - R10K_TAB[index_r] == 0)
{
temp = (((s16)index_l) - 20)*10;//rate *10
}
else
{
temp = (((s16)index_l) - 20)*10 + ((R10K_TAB[index_l] - value_adc)*100 + 5)/10/(R10K_TAB[index_l] - R10K_TAB[index_r]);
}
}
*value_temp = temp;
return 0;
}
z_hardware_adc.h
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
void init_adc(void);
u16 func_get_adc_valve_ch7(void);
u8 func_get_ntc_temp(u16 value_adc, s16* value_temp);
测试的主函数代码:
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
#ifndef __Z_UTIL_TIME_H
#define __Z_UTIL_TIME_H
#include "z_util_time.h"
#endif
#ifndef __Z_HARDWARE_USART2_H
#define __Z_HARDWARE_USART2_H
#include "z_hardware_usart2.h"
#endif
#ifndef __Z_HARDWARE_ADC_H
#define __Z_HARDWARE_ADC_H
#include "z_hardware_adc.h"
#endif
#ifndef __Z_HARDWARE_LED_H
#define __Z_HARDWARE_LED_H
#include "z_hardware_led.h"
#endif
int main()
{
u8 buf[8];
u16 val;
s16 value_temp;
u8 res;
init_adc();
init_hardware_usart2_dma(9600);
init_led();
val = func_get_adc_valve_ch7();
for(;;)
{
val = func_get_adc_valve_ch7();
res = func_get_ntc_temp(val, &value_temp);
if(res == 0)
{
buf[0] = value_temp >> 8;
buf[1] = value_temp;
func_usart2_dma_send_bytes(buf, 2);
}
func_led1_on();
delay_ms(1000);
func_led1_off();
delay_ms(1000);
}
}
测试的效果,通过串口将16进制值打印出来如下: