}
四、显示电路模块
1.显示电路设计:
这次我们用的均为两位共阳极七段数码管,管脚见图四
(1)
图四
(1)
显示模块电路图见图四
(2):
图四
(2)
2.显示电路原理分析:
我们选用静态显示控制电路,八位数据位作为74LS273数据锁存器的输入信号,273的输出信号作为七段数码管的输入信号,控制七个LED的亮灭。
地址数据A1和A2作为74LS138三八译码器的输入信号,138输出信号为锁存器时钟信号,VCC接G1端恒高有效,
和C1分别接译码器G2A和G2B端,恒低有效。
Y0控制选通锁存器1(即设定值高位),Y1控制选通锁存器2(即设定值低位),Y2控制选通锁存器3(即显示值高位),Y3控制选通锁存器4(即显示值低位)。
A1和A2按照程序设定的变化脉冲依次令Y0、Y1、Y2、Y3输出高脉冲,分别选通四个数码管,当8051芯片数据位输入信号时,四位数码管可以依照事先译好的数据表显示出相应的数值。
五、键盘控制电路模块
1.键盘控制电路设计:
见图五
(1)
图五
(1)
2.键盘控制电路原理分析:
我们采用阵列键盘读取方案,C4和
均输入低电平到74LS32或门,32输出到译码器选通。
同时32也输出到74LS244数据锁存器选通。
编写程序使138译码器依次输出低电平,扫描是否有键按下,当键盘上有键按下时,Yn为低电平,对应的Sn也为低电平(即对应的Dn也为低电平)。
六、系统调试及程序设计
系统调试:
第一步:
我们首先用调试台调试,测试开环,编写程序烧录进8051,使我们自己设计的显示模块显示位跟随调试台上LED显示模块的变化。
第二步:
编写PID闭环程序,编写程序烧录进8051,使系统能够从键盘上输入一个设定温度值并显示在系统显示模块设定位上。
随后按下调试台“Manual”按键,使系统能够将现在温度值与设定值做差,以确定需要升温还是降温,系统显示模块显示值会逐渐接近设定值并最终稳定在一个与设定值误差非常小的数值上。
第三步:
将完整系统(包括电源模块、系统模块、A/D模块和变送器、D/A模块、显示模块和键盘控制模块)移植到实验室真实模板上,先将模板设定为手动调节,设定一个值看系统显示位是否随动;再将模板还原为自动调节,从键盘输入一个设定值,打开调节按钮,观察系统显示位是否逐渐靠近设定位并最终稳定在一个非常相近的数值上,模板上加热(或制冷)提示灯是否点亮。
如果上述答案是肯定的,系统成功,能够满足教学要求,实现小型温度控制系统。
调试程序:
(自定义头函数见附录Ⅰ)
#include"C8051F020.h"
#include
#include"data_define.c"
#include"Init_Device.c"
#include"address.h"//设备地址列表
#include"keytable.h"//键盘的键值表
#include"digtaltable.h"//数码管的编码表
staticunsignedintDataT=0;//16进制的A/D数据
staticintDataTO=0;//10进制的温度数据
staticunsignedintDataTI=0;//10进制的目标温度(临时)
staticunsignedintTargetT=0;//10进制的目标温度
staticchartp=0;//目标温度和当前温度的差值
staticunsignedintsign=0;//表示tp的正负号
staticunsignedcharSwitch=0;//开关键是否按下的标志
staticunsignedintInput=0;//输入
voiddelay(void);
voiddelay1(void);
voidDisplay(constunsignedint,constunsignedint);
unsignedintConvert(constunsignedint);
unsignedintReadT(void);
unsignedintReadKey(void);
unsignedintReadKeyS(void);
unsignedintDecode(unsignedint);
unsignedintReadInput(void);
voidTask(void);
voidmain(void)
{
Init_Device();
while
(1)
{
ReadInput();//读取键盘
Task();//对温度进行调整(主任务)
}
}
unsignedintConvert(constunsignedinta)
{
return(int)95*a/0xff;//返回值=输入数据*95/255(取整数)返回值范围(0~95)
}
unsignedintReadT(void)
{
C2=0;//向A/D转换器发出指令
delay();//等待采样完成
returnC2;//返回采样数据数据
}
voidDisplay(constunsignedintx,constunsignedintz)
{
C13=DT3[z/10];//第三个数码管,显示第二个数字的个位
C14=DT4[z%10];//第四个数码管,显示第二个数字的十位
C11=DT1[x/10];//第一个数码管,显示第一个数字的个位
C12=DT2[x%10];//第二个数码管,显示第一个数字的十位
}
voiddelay(void)//延时子程序
{
longinti;
for(i=0;i<0x1f0;++i);
}
voiddelay1(void)
{
longinti;
for(i=0;i<0x81f0;++i);
}
unsignedintReadKeyS(void)
{
unsignedchartemp=0;
unsignedchartemp1=0;
unsignedchari=0;
temp=C43;//读取键盘第三行
temp=0x1f&C43;//屏蔽高3位
if(temp==0x1f)//如果没有按下,退出
{
return0xff;
}
temp1=temp;//如有按下,则延时后再读一次,看看是不是抖动
delay();
temp=0x1f&C43;
if(temp!
=temp1)//两次读取值不同,说明是抖动,退出
{
return0xff;
}
if(temp==0x1e)//是第三行第一列的键,则返回K11(在keytable定义)
{
returnK11;
}
if(temp==0x1d)//是第三行第二列的键,对Switch做求反操作,则返回K12(在keytable定义)
{
Switch=~Switch;
returnK12;
}
}
unsignedintReadKey(void)
{
unsignedinttemp=0;
unsignedinti=0;
while
(1)
{
temp=0x1f&C41;//扫描第一行,看有无按下
if(temp!
=0x1f)//第一行如有有按下,则跳出while循环
{
i=0x0;
break;
}
temp=0x1f&C42;//扫描第二行,看有无按下
if(temp!
=0x1f)//第二行如有有按下,则跳出while循环
{
i=0x40;
break;
}
temp=0x1f&C43;//扫描第三行,看有无按下
if(temp!
=0x1f)//第三行如有有按下,则跳出while循环
{
i=0x80;
break;
}
temp=0x1f&C44;//扫描第四行,看有无按下
if(temp!
=0x1f)//第四行如有有按下,则跳出while循环
{
i=0xC0;
break;
}
return0xff;//都没有按下,返回0xff
}
returni|temp;//将高三位和低五位合并
}
unsignedintDecode(unsignedinta)//对键值解码
{
switch(a)
{
caseK1:
return1;
caseK2:
return2;
caseK3:
return3;
caseK4:
return4;
caseK5:
return5;
caseK6:
return6;
caseK7:
return7;
caseK8:
return8;
caseK9:
return9;
caseK10:
return0;
caseK11:
return10;
default:
return0xff;
}
}
unsignedintReadInput(void)
{
unsignedintkey=0;
unsignedinti=0;
unsignedinttemp=0;
key=ReadKeyS();//读取开关键是否按下
i=Decode(ReadKeyS());//对读取的键值解码
if(i!
=10)//如果不等于开关键(10为开关键解码后的值),则不读数据,退出
{
return0xff;
}
Display(0,DataTO);//数码管的输入区清零
//delay1();
while
(1)
{
key=ReadKey();//读取第一个键值(十位)
i=Decode(key);//解码
if(i<10)//如果是前二行的键值,则更新DataTI(临时的输入数据)
{
DataTI=i;//更新DataTI
Display(DataTI,DataTO);//更新数码管,显示输入的数字
break;
}
Task();//和main()的那个Task()相同,目的是防止输入数据的时候单片机失去控温能力
}
while
(1)
{
key=ReadKey();//读取键值
if(key==0xff)//等键盘松开
break;
Task();
}
while
(1)
{
key=ReadKey();//读取第二个键值(个位)
i=Decode(key);//解码
if(i<10)//如果是前二行的键值,则更新DataTI(临时的输入数据)
{
DataTI=DataTI*10+i;//更新DateTI数据
Display(DataTI,DataTO);//更新数码管,显示输入的数字
TargetT=DataTI;//更新目标温度(正式)
break;
}
Task();
}
returnDataTI;//返回输入的数据
}
voidTask(void)
{
DataT=ReadT();//读取A/D的数据
DataTO=Convert(DataT);//把A/D数据转化为10进制的温度数据
if(DataTI>95)//如果温度大于95度,就修正为96度(95度时温度变送器为5V)
{
DataTI=95;
}
tp=TargetT-DataTO;//目标温度和目标温度的差值
if(tp<0)//如果为负
{
tp=~tp+1;//求差值绝对值
sign=1;//符号位标记为1(1为负数,0为正数)
}
else
{
sign=0;//符号位标记为0(1为负数,0为正数)
}
//分三段控制,0~30度,31~70度,71~95度
if(DataTO<31);//第一段
{
while
(1)
{
if(tp<1)//如果差值为0,则令驱动器空载(0V)
{
C3=0x80;
break;
}
if(tp<4&&sign==0)//如果差值小于4且为正数,令驱动器加热(但不是满载)
{
C3=170;
break;
}
if(tp<4&&sign==1)//如果差值小于4且为负数,令驱动器冷却(但不是满载)
{
C3=30;
break;
}
if(sign==1)//如果差值大于4且为负数,令驱动器冷却(满载)
{
C3=0;
break;
}
if(sign==0)//如果差值大于4且为正数,令驱动器加热(满载)
{
C3=255;
break;
}
break;
}
}
if(DataTO<71&&DataTO>30);//第二段
{
while
(1)
{
if(tp<1)//如果差值为0,则令驱动器空载(0V)
{
C3=0x80;
break;
}
if(tp<4&&sign==0)//如果差值小于4且为正数,令驱动器加热(但不是满载)
{
C3=220;
break;
}
if(tp<4&&sign==1)//如果差值小于4且为负数,令驱动器冷却(但不是满载)
{
C3=75;
break;
}
if(sign==1)//如果差值大于4且为负数,令驱动器冷却(满载)
{
C3=0;
break;
}
if(sign==0)//如果差值大于4且为正数,令驱动器加热(满载)
{
C3=255;
break;
}
break;
}
}
if(DataTO<96&&DataTO>70);//第三段
{
while
(1)
{
if(tp<1)//如果差值为0,则令驱动器空载(0V)
{
C3=0x80;
break;
}
if(tp<4&&sign==0)//如果差值小于4且为正数,令驱动器加热(但不是满载)
{
C3=255;
break;
}
if(tp<4&&sign==1)//如果差值小于4且为负数,令驱动器冷却(但不是满载)
{
C3=100;
break;
}
if(sign==1)//如果差值大于4且为负数,令驱动器冷却(满载)
{
C3=0;
break;
}
if(sign==0)/如果差值大于4且为正数,令驱动器加热(满载)
{
C3=255;
break;
}
break;
}
}
Display(DataTI,DataTO);//更新数码管显示
return;
}
七、出现的问题分析及解决方法
问题一:
我们在用老师的调试程序调试LED数码管时,发现无论怎么弄LED都显示乱码。
解决方法:
后来问老师,我得知老师的程序是按照Q1~Q7依次对应数码管的a~g,
同时将程序再按照四个数码管不同的对应线路译出四个独立的表。
问题二:
一开始调试闭环程序时我们用的是P调节,显示值确实很接近设定值,但是不能稳定在某一数值,而是不停地变换,比如85、84来回跳。
解决方法:
我们改进了程序,将P调节改进成PID调节,虽然在某些温度依然会来回跳,但是比改进算法前要稳定了很多。
问题三:
将系统移植到实验模板上时,我们发现最终显示值偏离设定值较大,最大偏差甚至达到6~7度。
解决方法:
我将0℃对应变送器输出0V,100℃对应5V。
然而当我用万用表测了100℃时变送器输出端的电压值