STM32 ADC NTC熱敏電阻二分(折半)查表法實現測溫功能

本文主要描述 - 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進制值打印出來如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章