目录:
-----------------------------------------------------------------------------------------------------------------
对于没有ADC的MCU,在测量外部电压时,使用它RC充放电是一种容易实现和低成本的方法。或者可以用于触摸方案,没有实际尝试过。
-----------------------------------------------------------------------------------------------------------------
这是使用单片机的一段IO口作温控程序,感温元件是NTC。功能是当温度低于一定值时开始加热,并随温度升高;当温度达到一定值时停止加热,然后开始冷却并重复。使用时注意RC常数和常数过大会导致16位计数溢出,无法得到正确结果。该程序是产品制作前的测试程序。当时调试通过了,可以做到±0.5。
;****************************************************** ;filename: IOTestNTC.asm ; mcu: MDT2005EP ; clock: 4 MHz for EXTXT ; date: 2006/03/17 ; writer: aLin ;******************************************************
;计算被测电阻。只做比较,不做计算。 ;计算公式:Rx = Rf * (Tx/Tf) ;Rx为被测电阻 ;Rf已知电阻,Rf=10K ;Tx对电容器进行测量电阻C放电计值为16位数 ;Tf对电容器进行测量电阻C放电计值为16位数 ;检查温度特性表(NTC型号为:CWF2-473F-3950K,大亚科技制造): 当下限温度为5摄氏度时,电阻为121545.44欧,即121.54544K ;转换为:Tx/Tf=Rx/Rf=12.154544 ;12154.544,约为12155,即2F76H 当上限温度为8℃时,电阻为104712.92欧,即104.71292K ;转换为:Tx/Tf=Rx/Rf=10.471292 ;扩大1000倍后,结果如下:10471.292,约为10471,即28E7H
;------------------------------------------------------------------- list p=pic16c54 #i nclude "p16c5x.inc"
;定义I/O口 #define vt portb,0 #define rf portb,1 #define rx portb,2 #define on portb,4 #define off portb,4 #define f0 user,0
;RAM 分配 CNT EQU 10H ;计数器
SOU1 EQU 11H ;四字节被除数最低,16位商人低8位 SOU2 EQU 12H SOU3 EQU 13H ;低8位被除数和16位余数 SOU4 EQU 14H ;四字节被除数最高位和16位余数的高8位 USER EQU 15H ;用户标记位
SOU EQU 16H ;被数低8位和积(四字节积的最低位)和RX电阻计数器低8位 SOUH EQU 17H ;被乘数高8位和积和RX电阻计数器高8位 RLT EQU 18H ;乘数低8位和积 RLTH EQU 19H ;乘数高8位和积(四字节积的最高位)
TEMP1 EQU 1AH ;临时寄存器1-4 TEMP2 EQU 1BH TEMP3 EQU 1CH TEMP4 EQU 1DH
RFCNTL EQU 1EH ;RF电阻计数器低8位 RFCNTH EQU 1FH ;RF电阻计数器高8位 ;--------------------------------- ORG 0000h
start:
bcf fsr,6 ;选择bank0 bcf fsr,5 movlw b'11101111' ;RB4定义为输出,其余输入,PortB4为一直输出 tris 06h ;--------------------------------------------------- ;停止加热处理程序 ;等待温度降到5摄氏度以下是,重新加热,跳到加热处理程序 ; ;负温度系电阻,阻值越大,温度越低 ;用5摄氏度时对应的电阻减去测出的电阻,为负数,表明实际温度已低过5摄氏度 ;需加热处理 ; HOT_DOWN bcf off ;停止加热 ; call d1s ;延时1秒 call io_rm ;调用测量电阻程序 call DUMUL ;调用16位无符号乖法程序 call DUDIV ;调用32位除以16位无符号除法程序 ;比较商大小 ;0E10 对应36K电阻,温度是31度 MOVLW 2FH MOVWF TEMP1 MOVF SOU2,W SUBWF TEMP1,W ;商高8位先减 BTFSS STATUS,C ;检查是否有借位,有借位时C=0 GOTO HOT_UP ;有借位,被减数小于减数,跳到加热程序 BTFSS STATUS,Z ;无借位,查相减结果是否为0 GOTO HOT_DOWN ;结果不为0,则被减数大于减数,跳到停止加热程序 MOVLW 76H ;商高8位相等,商低8位相减 MOVWF TEMP1 MOVF SOU1,W SUBWF TEMP1,W BTFSS STATUS,C GOTO HOT_UP BTFSS STATUS,Z GOTO HOT_DOWN
GOTO HOT_DOWN ;两数相等,返回 ;-------------------------------------- ; ;加热程序处理程序 ; ;当温度大于8摄氏度时跳到停止加热程序 ; HOT_UP
bsf on ;加热 ; call d1s ;延时1秒 call io_rm ;调用测量电阻程序 call DUMUL ;调用16位无符号乖法程序 call DUDIV ;调用32位除以16位无符号除法程序 ;比较商大小 ;07D0对应电阻20K,温度45度 MOVLW 28H SUBWF SOU2,W ;商高8位先减 BTFSS STATUS,C ;检查是否有借位,有借位时C=0 GOTO HOT_DOWN ;有借位,被减数小于减数,跳到停止加热程序 BTFSS STATUS,Z ;无借位,查相减结果是否为0 GOTO HOT_UP ;结果不为0,则被减数大于减数,跳到加热程序 MOVLW 0E7H ;商高8位相等,商低8位相减 SUBWF SOU1,W BTFSS STATUS,C GOTO HOT_DOWN BTFSS STATUS,Z GOTO HOT_UP GOTO HOT_UP ;温度未低过下限温度,返回继续等待
;---------------------------------------- ; io_rm clrf SOUH clrf SOU clrf RFCNTH clrf RFCNTL
call fullcharge ;让电容充电。 call rxdischarge ;调用测量rx放电时间子程序 call fullcharge ;让电容充电 call rfdischarge ;调用测量rf放电时间子程序。 call fulldischarge ;让电容完全放电。 retlw 00h ;----------------------------------------- ; ;电容充电子程序 ; fullcharge
movlw b'11101110' ;vt口转为输出,rf、rx为输入,PortB4一直输出 tris 06h bsf vt ;vt口输出高电平,让电容充电 movlw .40 ;延时,让电容有足够时间充满电至Voh。 movwf CNT decfsz CNT,F goto $-1 retlw 00h ;--------------------------------------- ; ;电容放电 ; fulldischarge movlw b'11101110' ;vt口转为输出,rf、rx为输入,PortB4一直输出 tris 06h bcf vt ;vt口输出低电平,让电容放电 retlw 00h ;----------------------------------------- ; ;测量rf放电时间子程序 ; rfdischarge movlw b'11101101' ;vt口转为输入,rf口转为输出,rx口输入,PortB4一直输出 tris 06h bcf rf ;rf口输出低电平,电容对rf电阻放电. _rfdis btfss vt goto _rfdisdone incf RFCNTL,f skpnz incf RFCNTH,f goto _rfdis _rfdisdone movlw b'11101111' ;断开rf电阻,由输出改为输入,PortB4一直输出 tris 06h retlw 00h ;-------------------------- ; ;测量rx放电时间子程序 ; rxdischarge movlw b'11101011' ;vt口转为输入,rx口转为输出,rf口输入,PortB4一直输出 tris 06h bcf rx ;rx口输出低电平,电容对rx电阻放电. _rxdis btfss vt goto _rxdisdone incf SOU,f skpnz incf SOUH,f goto _rxdis _rxdisdone movlw b'11101111' ;断开rx电阻,由输出改为输入,PortB4一直输出 tris 06h retlw 00h ;--------------------- ; ;本程序实现四字节除以双字节无符号数除法。 ; ;入口参数:被除数在SOU4~SOU1中,除数在RLTH、RLT中。 ;出口参数:商在SOU2、SOU1中,余数在SOU4、SOU3中. DUDIV MOVLW .16 ;循环16次 MOVWF CNT MOVF RLTH,W ;被除数,32位,最高8位 MOVWF SOU4 MOVF RLT,W MOVWF SOU3 MOVF SOUH,W MOVWF SOU2 MOVF SOU,W MOVWF SOU1 ;被除数,32位,最低8位 MOVF RFCNTH,W ;除数高8位 MOVWF RLTH MOVF RFCNTL,W ;除数低8位 MOVWF RLT LOOP BCF STATUS,C ;C清0 RLF SOU1,F RLF SOU2,F RLF SOU3,F RLF SOU4,F BTFSS STATUS,C GOTO CLR_F0 ;C=0, 跳到CLR_F0,清F0 BSF F0 ;C=1, 置F0 SUB_LO BCF STATUS,C MOVF RLT,W SUBWF SOU3,W ;SOU3-RLT -> W MOVWF TEMP1 ;送TEMP1保存 BTFSS STATUS,C ;有借位时C=0
GOTO SUB_HI ;低8位相减时有借位,被减数高8位要借1,相当于减数加1
MOVF RLTH,W SUBWF SOU4,W THAN BTFSC F0 ;若够减,跳到SAVE GOTO SAVE BTFSS STATUS,C GOTO NEXT ;若不够减,跳到NEXT SAVE MOVWF SOU4 ;保存相减结果 MOVF TEMP1,W MOVWF SOU3 INCF SOU1,F NEXT DECFSZ CNT,F GOTO LOOP RETLW 00H SUB_HI INCF RLTH,W SUBWF SOU4,W GOTO THAN CLR_F0 BCF F0 GOTO SUB_LO ;***************DUMUL*********** ;本程序实现双字节无符号数乘法。 ;入口参数:被乘数在SOUH、SOU中,乘数在RLTH、RLT中。 ;出口参数:结果在RLTH、RLT、SOUH、SOU中。 DUMUL MOVLW .16 MOVWF CNT
;设乘数RLTH、RLT=03E8H=1000D,即将SOUH、SOU扩大1000倍 MOVLW 03H MOVWF RLTH MOVLW 0E8H MOVWF RLT MOVF SOU,W MOVWF TEMP3 MOVF SOUH,W MOVWF TEMP4 CLRF SOU ;用于暂 CLRF SOUH ;存 CLRF TEMP1 ;结 CLRF TEMP2 ;果 BCF STATUS,C LOOP3 RRF TEMP4,F RRF TEMP3,F ;将被乘数的某一位送到C中 BTFSC STATUS,C GOTO DUADD ;将RLTH:RLT中的被乘数加上 BACK RRF SOUH,F RRF SOU,F RRF TEMP2,F RRF TEMP1,F ;被乘数右移 DECFSZ CNT,F GOTO LOOP3 MOVF SOUH,W ;保存结果 MOVWF RLTH MOVF SOU,W MOVWF RLT MOVF TEMP2,W MOVWF SOUH MOVF TEMP1,W MOVWF SOU RETLW 00H DUADD MOVF RLT,W ADDWF SOU,F MOVF RLTH,W BTFSC STATUS,C INCFSZ RLTH,W ADDWF SOUH,F GOTO BACK ;--------------------------- ;延时1S ; ; d1s movlw .16 ; .16时为1.000069S ; movwf temp1 ; movlw .100 ; movwf temp2 ; movlw .207 ; movwf temp3 ; decfsz temp3,f ; goto $-1 ; decfsz temp2,f ; goto $-5 ; decfsz temp1,f ; goto $-9 ; retlw 00h ;-------------------------------- END
-----------------------------------------------------------------------------------------------------------------
STC15F系列是1T的MCU,其IO口有OPEN-DRAIN模式,此模式可以很容易用一个IO口配合一个定时器实现RC充放电来测量外部未知电压。如果没有空余的定时器,也可以使用指令循环的方式实现。本例使用定时器。
本范例使用P3.2(INT0)来做RC测量,电路和波形示意图如下:
1、初始化程序将P3.2设置成OPEN-DRAIN模式, 并将P3.2输出0给电容放电。INT0设置成上升沿中断。Timer 0设置成16位自动重装定时器模式,时钟源为12T,允许中断。
2、测量时,先清Timer 0的TH0、TL0,然后将P3.2输出1开始对电容充电,接着设置TR0 = 1来启动Timer 0,然后在INT0中断里设置TR0 = 0来停止计数,并将P3.2输出0对电容放电。读出TH0、TL0的值就是RC充电时间。
由于MCU工作在5V时,IO口读到“1”的门限电压大约为2V,所以要求输入的电压高于2V,本例的测试数据从4~12.4V,测试结果参考后面的附录1。
假设输入电压为Ux,IO口门限电压为2V,则RC充电时间为:T = - R * C * ln ( 1 – 2 / Ux )
按图示参数,当输入为10V时,RC时间大约为446uS,附录1中实测为447uS。
由于RC时间跟R和C有关,而R的温漂一般较小,但普通电容的温漂较大,所以要使用温漂小并且漏电也小的电容。
由充电公式或曲线图可知,Ux和RC值的关系是非线性的,所以实际项目使用时,要根据自己的实际电路做一些标定,这样可以得
到比较准确的值。
本方法适用于对测量精度要求不是很高的场合。
#include "reg51.h"
#define MAIN_Fosc 22118400L //定义主时钟
#define uchar char
#define uint unsigned int
sfr AUXR = 0x8e; //Auxiliary register
sfr P3M1 = 0xB1; //P3M1.N,P3M0.N =00--->Standard, 01--->push-pull
sfr P3M0 = 0xB2; // =10--->pure input, 11--->open drain
sbit P_TXD1 = P3^1; //定义模拟串口发送脚,打印信息用
sbit P_RC = P3^2; //RC port
uchar SampleCnt; //发送结果的采样间隔计数
uchar LineCnt; //每行显示结果计数
bit B_Over; //超时标志
bit B_ADC_OK; //检测完成标志
uint adc; //RC做的ADC值
void RC_start(void);
void Tx1Send(uchar dat);
void InitTimer(void);
void delay_ms(unsigned char ms);
///
void main(void)
{
InitTimer(); //初始化Timer
P3M1 |= 1 << 2; //P3.2 config as Open-Drain
P3M0 |= 1 << 2;
P_RC = 0; //Clear RC port to 0
// TMOD |= 0x00; //T0 as 16 bits timer, auto reload
while (1)
{
delay_ms(5); //放电时间
B_ADC_OK = 0; //清除ADC结束标志
B_Over = 0; //清除超量程标志
RC_start(); //RC charge-decharge
while(!B_ADC_OK && !B_Over) ; //等待ADC结束或超时
if(B_ADC_OK)
{
if(++SampleCnt >= 100) //1秒钟发一个结果给串口
{
SampleCnt = 0;
Tx1Send(adc / 10000 + '0'); //send to PC from the UART
Tx1Send(adc % 10000 / 1000 + '0');
Tx1Send(adc % 1000 / 100 + '0');
Tx1Send(adc % 100 / 10 + '0');
Tx1Send(adc % 10 + '0');
Tx1Send(' ');
Tx1Send(' ');
if(++LineCnt >= 10) //10个结果后换行
{
LineCnt = 0;
Tx1Send(0x0d); //send CR
Tx1Send(0x0a);
}
}
}
}
}
///
//============================================================
// 函数: void delay_ms(unsigned char ms)
// 描述: 延时函数。
// 参数: ms,要延时的ms数.
// 返回: none.
// 版本: VER1.0
// 日期: 2010-12-15
// 备注:
//============================================================
void delay_ms(unsigned char ms)
{
unsigned int i;
do
{
i = MAIN_Fosc / 14000L; //1T
while(--i) ; //13T per loop
}while(--ms);
}
void RC_start(void)
{
//使用Timer 0 计时
TH0 = 0; //clear Timer 0
TL0 = 0;
B_Over = 0; //Clear Over flag
P_RC = 1; //RC charge
TR0 = 1; //enable Timer 0
IE0 = 0; //Clear INT0 flag
EX0 = 1; //INT0 Enable
IT0 = 0; //INT0 上升,下降沿中断
}
void INT0_int (void) interrupt 0 //
{
if(INT0 && !B_Over) //上升沿中断,无超时
{
TR0 = 0; //deable Timer 0
P_RC = 0; //decharge
adc = TH0; //read the RC time
adc = (adc << 8) + TL0;
B_ADC_OK = 1; //标志ADC结束
}
}
void InitTimer(void)
{
TMOD = 0; //for STC15Fxxx系列 Timer0 as 16bit reload timer.
TH0 = 0;
TL0 = 0;
ET0 = 1; //允许Timer0中断
TR0 = 0;
EA = 1; //允许总中断
}
void timer0 (void) interrupt 1
{
TR0 = 0; //超量程关闭
B_Over = 1; //标志超量程
}
void BitTime(void) //位时间函数
{
uint i;
i = ((MAIN_Fosc / 100) * 104) / 130000L - 1; //根据主时钟来计算位时间
while(--i);
}
//模拟串口发送
void Tx1Send(uchar dat) //9600,N,8,1 发送一个字节
{
uchar i;
EA = 0;
P_TXD1 = 0;
BitTime();
for(i=0; i<8; i++)
{
if(dat & 1) P_TXD1 = 1;
else P_TXD1 = 0;
dat >>= 1;
BitTime();
}
P_TXD1 = 1;
EA = 1;
BitTime();
BitTime();
}
-----------------------------------------------------------------------------------------------------------------