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