使用单片机非AD方式实现温度测测量
最近在一个新的开发项目中,因一个需要一个简小的外联功能模块,考虑到体积尺寸和单一的功能需求,故而思琢采用性价比较高的STC51单片机。项目需求中需要用到温度测量,但是后来发现,自身未带AD口,无奈,遂想到之前在图书馆借阅的老外著作的《模拟电路》中有讲解到使用GPIO+TIMER+RC实现温度测量的原理步骤,当时觉得就蛮新奇的,在此之前也听闻过网友使用此法实现了温度测量,效果说也不错,于是就决定尝试使用该方法。
原理如下:
已知电容充电放电时间计算公式:
设,V0 为电容上的初始电压值;
Vu 为电容充满终止电压值;
Vt 为任意时刻t,电容上的电压值。
则,
Vt=V0+(Vu-V0)* [1-exp(-t/RC)] -----1
如果,电压为E的电池通过电阻R向初值为0的电容C充电
V0=0,充电极限Vu=E,
故,任意时刻t,电容上的电压为:
Vt=E*[1-exp(-t/RC)] -----2
t=RCLn[E/(E-Vt)] -----3
如果已知某时刻电容上的电压Vt,根据常数可以计算出时间t。
分别计算两路的充电时间Tntc;Tref;
据公式3有:Rntc=Rref*(Tntc/Tref)
然后根据手册查表即可得到当前温度。
可能当时记漏啦,后来PCB样板回来后,发现我画的原理,使用的是两个GPIO口,如图1,
但是后来翻看那本书,上面使用的是三个GPIO口,如图2。
为了保证先实现,遂在PCB上找一个空GPIO引脚飞一下。按照书上的步骤,最后总算实现了对环境温度的测量,无论是精度,稳定度,效果还是不错的。
在实现完这个之后,我就想了想,应该是可以用两个GPIO口实现温度测量,因为我那个漏掉的GPIO口,按书上逻辑是直接用来给电容C直接充放电通道的。
但是实际应该也可以使用上面两个自身的GPIO口就可以进行充放电动作。故而我更改了一下思路步骤,嘿嘿,最后还真实现啦,效果跟三线制一样的效果。
当然该方法可以在AD资源匮乏的情况下使用,如有AD建议使用AD采集方式更为可靠。
具体实现代码,可参阅如下:
两线制法
//-----two wire
#define Rntc_CAP_DISCHARGE() {P34_PP_MODE();GP1=0;Delay1ms(5);} //GP1 //use for charging and discharging port//the capitor discharge equel to zero;
#define Rntc_CAP_CHARGE() {P34_PP_MODE();GP1=1;}
#define Rref_CAP_DISCHARGE() {P35_PP_MODE();GP2=0;Delay1ms(5);}
#define Rref_CAP_CHARGE() {P35_PP_MODE();GP2=1;}
void two_element_charge_action()
{
ntc_charge_over_flag=0;//NTC路 充电完成标志置初值0
ntc_charge_t=0;//NTC路 充电时间置初值0
P35_HZ_MODE();//GPIO设置为高阻抗模式
Rntc_CAP_DISCHARGE();//NTC路 电容放完电
Rntc_CAP_CHARGE();//对电容充电
while(0==GP2)//开启计时并检测GPIO电平的翻转
{
T0_ON();
}
T0_OFF(); //关闭计时
ntc_charge_over_flag=1;//充电完成标志置位
/////////////////////////////////////////////////////
ref_charge_over_flag=0;//REF路 充电完成标志置初值0
ref_charge_t=0;//REF路 充电时间置初值0
P34_HZ_MODE();//GPIO设置为高阻抗模式
Rref_CAP_DISCHARGE();//REF路 电容放完电
Rref_CAP_CHARGE();//对电容充电
while(0==GP1)//开启计时并检测GPIO电平的翻转
{
T0_ON();
}
T0_OFF(); //关闭计时
ref_charge_over_flag=1; //充电完成标志置位
}
三线制法
//-----three wire
#define CAP_INIT_CLEAR() {P12_PP_MODE();GP0=0;Delay1ms(1);}//set the GPIO mode as PP;//as discharge channal of the capitor
#define NTC_TO_CAP_CHARGE() {P34_PP_MODE();GP1=1;}//the capitor start to charge
#define REF_TO_CAP_CHARGE() {P35_PP_MODE();GP2=1;}
void three_element_charge_action(void)//
{
ntc_charge_over_flag=0;//NTC路 充电完成标志置初值0
ntc_charge_t=0;//NTC路 充电时间置初值0
P34_HZ_MODE();//GPIO设置为高阻抗模式
P35_HZ_MODE();//GPIO设置为高阻抗模式
CAP_INIT_CLEAR() ;//电容先放完电
P12_HZ_MODE();//GPIO设置为高阻抗模式
NTC_TO_CAP_CHARGE();//对电容充电
while(0==GP0)//开启计时并检测GPIO电平的翻转
{
T0_ON();
}
T0_OFF(); //关闭计时
ntc_charge_over_flag=1;//充电完成标志置位
////////////////////ntc resistor test over!////////////////////////////
ref_charge_over_flag=0;//对比路 充电完成标志置初值0
ref_charge_t=0;//对比路 充电时间置初值0
P35_HZ_MODE();//GPIO设置为高阻抗模式
P34_HZ_MODE();//GPIO设置为高阻抗模式
CAP_INIT_CLEAR() ;//电容先放完电
P12_HZ_MODE();//GPIO设置为高阻抗模式
REF_TO_CAP_CHARGE();//对电容充电
while(0==GP0)//开启计时并检测GPIO电平的翻转
{
T0_ON();
}
T0_OFF(); //关闭计时
ref_charge_over_flag=1; //充电完成标志置位
P35_HZ_MODE();//GPIO设置为高阻抗模式
//////////////////ref resistor test over!////////////////////////////
}
主函数
void main(void)
{
EA=0;
P37_PP_MODE();
P36_PP_MODE();
timer0_Init();
timer1_Init();
EA = 1;
while(1)
{
two_element_charge_action();
// three_element_charge_action();
}
}
定时时基配置
//////////////////////////////////////////////////////////////////////////////
// t_base=(2^16-X)/f0/12(us)
//set:f0=12MHz
//x=65535 t_base=1us;
#define T0_ON() {TR0 = 1;ET0 =1;}
#define T0_OFF() {TR0 = 0;ET0 =0;}
void timer0_Init(void) //use for charge timer
{
TMOD |= 0x00; //模式0
TL0 = 0xff;
TH0 = 0xff;
T0_OFF();
}
//////////////////////////////////////////////////////////////////////////////
// t_base=(2^16-X)/f0/12(us)
//set:f0=12MHz
//x=65535 t_base=1ms;
#define T1_ON() {TR1 = 1;ET1 =1;}
#define T1_OFF() {TR1 = 0;ET1 =0;}
void timer1_Init(void)
{
TMOD|= 0x00; //模式0
TL1 = 0x66;
TH1 = 0xfc;
T1_ON();
}
定时中断处理
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TM0_Isr() interrupt 1
{
if(0==ntc_charge_over_flag) ntc_charge_t++;
if(0==ref_charge_over_flag) ref_charge_t++;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TM1_Isr() interrupt 3
{
seg_fresh_t++;
if(0==seg_fresh_t%5)//
{
seg_fresh_t=0;
if(ntc_charge_over_flag&&ref_charge_over_flag)
cur_temp_equel_resistor_val=(float)(ntc_charge_t*1.0/ref_charge_t*1.0)*10.0;//10.0即Rref的阻值(KR)
cur_temp_val=Find_Tab(cur_temp_equel_resistor_val,tempH_tab,69);
DisplayScanHc595();
}
}
注意这个是NTC热敏电阻datasheet中自带的R-T对应表,不同于AD采集方法变换后的ADC-R对应表
const float xdata tempH_tab[] = {
//0 1 2 3 4 5 6 7 8 9
32.116,30.570,29.105,27.716,26.399,25.150,23.965,22.842,21.776,20.764,
//10 11 12 13 14 15 16 17 18 19
19.783,18.892,18.026,17.204,16.423,15.681,14.976,14.306,13.669,13.063,
//20 21 22 23 24 25 26 27 28 29
12.487,11.939,11.418,10.449,10,9.571,9.164,8.775,8.405,
//30 31 32 33 34 35 36 37 38 39
8.052,7.716,7.396,7.090,6.798,6.520,6.255,6.002,5.760,5.529,
//40 41 42 43 44 45 46 47 48 49
5.309,5.098,4.897,4.704,4.521,4.345,4.177,4.016,3.863,3.716,
//50 51 52 53 54 55 56 57 58 59
3.588,3.440,3.311,3.188,3.069,2.956,2.848,2.744,2.644,2.548,
//60 61 62 63 64 65 66 67 68 69
2.457,2.369,2.284,2.204,2.126,2.051,1.980,1.911,1.845,1.782,
};
相关变量定义及声明
float cur_temp_equel_resistor_val=0;
unsigned int ntc_charge_t=0; //ntc resistor charge time
unsigned int ref_charge_t=0; //ref resistor charge time
unsigned int seg_fresh_t=0;//segment fresh time
bit ntc_charge_over_flag=0;
bit ref_charge_over_flag=0;
如有上文纰漏遗误,请自行查证。。。。