#include "REG52.H"
#define const_voice_short 40 //蜂鸣器短叫的持续时间
#define const_key_time1 20 //按键去抖动延时的时间
#define const_key_time2 20 //按键去抖动延时的时间
#define const_key_time3 20 //按键去抖动延时的时间
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(); //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5; //作为中途暂停指示灯 亮的时候表示中途暂停
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucKeySec=0; //被触发的按键编号
unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
unsigned char ucDigShow8; //第8位数码管要显示的内容
unsigned char ucDigShow7; //第7位数码管要显示的内容
unsigned char ucDigShow6; //第6位数码管要显示的内容
unsigned char ucDigShow5; //第5位数码管要显示的内容
unsigned char ucDigShow4; //第4位数码管要显示的内容
unsigned char ucDigShow3; //第3位数码管要显示的内容
unsigned char ucDigShow2; //第2位数码管要显示的内容
unsigned char ucDigShow1; //第1位数码管要显示的内容
unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned int uiSetData1=0; //本程序中需要被设置的参数1
unsigned int uiSetData2=0; //本程序中需要被设置的参数2
unsigned int uiSetData3=0; //本程序中需要被设置的参数3
unsigned int uiSetData4=0; //本程序中需要被设置的参数4
unsigned char ucTemp1=0; //中间过渡变量
unsigned char ucTemp2=0; //中间过渡变量
unsigned char ucTemp3=0; //中间过渡变量
unsigned char ucTemp4=0; //中间过渡变量
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f, //0 序号0
0x06, //1 序号1
0x5b, //2 序号2
0x4f, //3 序号3
0x66, //4 序号4
0x6d, //5 序号5
0x7d, //6 序号6
0x07, //7 序号7
0x7f, //8 序号8
0x6f, //9 序号9
0x00, //无 序号10
0x40, //- 序号11
0x73, //P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
display_service(); //显示的窗口菜单服务程序
}
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/
void display_service() //显示的窗口菜单服务程序
{
switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示P--1窗口的数据
if(ucWd1Update==1) //窗口1要全部更新显示
{
ucWd1Update=0; //及时清零标志,避免一直进来扫描
ucDigShow8=12; //第8位数码管显示P
ucDigShow7=11; //第7位数码管显示-
ucDigShow6=1; //第6位数码管显示1
ucDigShow5=10; //第5位数码管显示无
//先分解数据
ucTemp4=uiSetData1/1000;
ucTemp3=uiSetData1%1000/100;
ucTemp2=uiSetData1%100/10;
ucTemp1=uiSetData1%10;
//再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
/* 注释二:
* 就是在这里略作修改,把高位为0的去掉不显示。
*/
if(uiSetData1<1000)
{
ucDigShow4=10; //如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4; //第4位数码管要显示的内容
}
if(uiSetData1<100)
{
ucDigShow3=10; //如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3; //第3位数码管要显示的内容
}
if(uiSetData1<10)
{
ucDigShow2=10; //如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2; //第2位数码管要显示的内容
}
ucDigShow1=ucTemp1; //第1位数码管要显示的内容
}
break;
case 2: //显示P--2窗口的数据
if(ucWd2Update==1) //窗口2要全部更新显示
{
ucWd2Update=0; //及时清零标志,避免一直进来扫描
ucDigShow8=12; //第8位数码管显示P
ucDigShow7=11; //第7位数码管显示-
ucDigShow6=2; //第6位数码管显示2
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData2/1000; //分解数据
ucTemp3=uiSetData2%1000/100;
ucTemp2=uiSetData2%100/10;
ucTemp1=uiSetData2%10;
if(uiSetData2<1000)
{
ucDigShow4=10; //如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4; //第4位数码管要显示的内容
}
if(uiSetData2<100)
{
ucDigShow3=10; //如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3; //第3位数码管要显示的内容
}
if(uiSetData2<10)
{
ucDigShow2=10; //如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2; //第2位数码管要显示的内容
}
ucDigShow1=ucTemp1; //第1位数码管要显示的内容
}
break;
case 3: //显示P--3窗口的数据
if(ucWd3Update==1) //窗口3要全部更新显示
{
ucWd3Update=0; //及时清零标志,避免一直进来扫描
ucDigShow8=12; //第8位数码管显示P
ucDigShow7=11; //第7位数码管显示-
ucDigShow6=3; //第6位数码管显示3
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData3/1000; //分解数据
ucTemp3=uiSetData3%1000/100;
ucTemp2=uiSetData3%100/10;
ucTemp1=uiSetData3%10;
if(uiSetData3<1000)
{
ucDigShow4=10; //如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4; //第4位数码管要显示的内容
}
if(uiSetData3<100)
{
ucDigShow3=10; //如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3; //第3位数码管要显示的内容
}
if(uiSetData3<10)
{
ucDigShow2=10; //如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2; //第2位数码管要显示的内容
}
ucDigShow1=ucTemp1; //第1位数码管要显示的内容
}
break;
case 4: //显示P--4窗口的数据
if(ucWd4Update==1) //窗口4要全部更新显示
{
ucWd4Update=0; //及时清零标志,避免一直进来扫描
ucDigShow8=12; //第8位数码管显示P
ucDigShow7=11; //第7位数码管显示-
ucDigShow6=4; //第6位数码管显示4
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData4/1000; //分解数据
ucTemp3=uiSetData4%1000/100;
ucTemp2=uiSetData4%100/10;
ucTemp1=uiSetData4%10;
if(uiSetData4<1000)
{
ucDigShow4=10; //如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4; //第4位数码管要显示的内容
}
if(uiSetData4<100)
{
ucDigShow3=10; //如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3; //第3位数码管要显示的内容
}
if(uiSetData4<10)
{
ucDigShow2=10; //如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2; //第2位数码管要显示的内容
}
ucDigShow1=ucTemp1; //第1位数码管要显示的内容
}
break;
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1; //自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1; //自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1; //自锁按键置位,避免一直触发
ucKeySec=3; //触发3号键
}
}
}
void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1++;
if(uiSetData1>9999) //最大值是9999
{
uiSetData1=9999;
}
ucWd1Update=1; //窗口1更新显示
break;
case 2:
uiSetData2++;
if(uiSetData2>9999) //最大值是9999
{
uiSetData2=9999;
}
ucWd2Update=1; //窗口2更新显示
break;
case 3:
uiSetData3++;
if(uiSetData3>9999) //最大值是9999
{
uiSetData3=9999;
}
ucWd3Update=1; //窗口3更新显示
break;
case 4:
uiSetData4++;
if(uiSetData4>9999) //最大值是9999
{
uiSetData4=9999;
}
ucWd4Update=1; //窗口4更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1--;
if(uiSetData1>9999)
{
uiSetData1=0; //最小值是0
}
ucWd1Update=1; //窗口1更新显示
break;
case 2:
uiSetData2--;
if(uiSetData2>9999)
{
uiSetData2=0; //最小值是0
}
ucWd2Update=1; //窗口2更新显示
break;
case 3:
uiSetData3--;
if(uiSetData3>9999)
{
uiSetData3=0; //最小值是0
}
ucWd3Update=1; //窗口3更新显示
break;
case 4:
uiSetData4--;
if(uiSetData4>9999)
{
uiSetData4=0; //最小值是0
}
ucWd4Update=1; //窗口4更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
ucWd++; //切换窗口
if(ucWd>4)
{
ucWd=1;
}
switch(ucWd) //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
{
case 1:
ucWd1Update=1; //窗口1更新显示
break;
case 2:
ucWd2Update=1; //窗口2更新显示
break;
case 3:
ucWd3Update=1; //窗口3更新显示
break;
case 4:
ucWd4Update=1; //窗口4更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive()
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1: //显示第1位
ucDigShowTemp=dig_table[ucDigShow1];
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2: //显示第2位
ucDigShowTemp=dig_table[ucDigShow2];
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3: //显示第3位
ucDigShowTemp=dig_table[ucDigShow3];
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4: //显示第4位
ucDigShowTemp=dig_table[ucDigShow4];
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5: //显示第5位
ucDigShowTemp=dig_table[ucDigShow5];
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6: //显示第6位
ucDigShowTemp=dig_table[ucDigShow6];
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7: //显示第7位
ucDigShowTemp=dig_table[ucDigShow7];
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8: //显示第8位
ucDigShowTemp=dig_table[ucDigShow8];
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time() interrupt 1
{
TF0=0; //清除中断标志
TR0=0; //关中断
key_scan(); //按键扫描函数
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
// beep_dr=1; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
// beep_dr=0; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
display_drive(); //数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1; //开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++) //内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself() //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
led_dr=0; //关闭独立LED灯
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
数码管通过切换窗口来设置参数,这里的窗口类似于一级菜单,在一级菜单下,还可以分解出二级菜单。一级菜单的特点是整屏数码管的显示内容全部都改变,而二级菜单的特点是只改变其中一部分数码管的内容。二级菜单的程序怎么编写?欲知详情,请听下回分解-----数码管通过闪烁来设置数据。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-2-28 15:54
本帖最后由 jianhong_wu 于 2014-2-28 16:34 编辑
第三十节:数码管通过闪烁来设置数据。
开场白:
上一节讲了一级菜单,这一节要教会大家两个知识点:
第一个:二级菜单的程序的程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键
(2)实现功能:
通过按键设置4个不同的参数。
只有1个窗口。这个窗口显示4个参数。
第8,7位数码管显示第1个参数。第6,5位数码管显示第2个参数。第4,3位数码管显示第3个参数。第2,1位数码管显示第4个参数。每个参数的范围是从0到99。
有三个按键。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下5种状态中循环:只有第8,7位数码管闪烁---只有第6,5位数码管闪烁---只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short 40 //蜂鸣器短叫的持续时间
#define const_key_time1 20 //按键去抖动延时的时间
#define const_key_time2 20 //按键去抖动延时的时间
#define const_key_time3 20 //按键去抖动延时的时间
#define const_dpy_time_half 200 //数码管闪烁时间的半值
#define const_dpy_time_all 400 //数码管闪烁时间的全值 一定要比const_dpy_time_half 大
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(); //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5; //作为中途暂停指示灯 亮的时候表示中途暂停
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucKeySec=0; //被触发的按键编号
unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
unsigned char ucDigShow8; //第8位数码管要显示的内容
unsigned char ucDigShow7; //第7位数码管要显示的内容
unsigned char ucDigShow6; //第6位数码管要显示的内容
unsigned char ucDigShow5; //第5位数码管要显示的内容
unsigned char ucDigShow4; //第4位数码管要显示的内容
unsigned char ucDigShow3; //第3位数码管要显示的内容
unsigned char ucDigShow2; //第2位数码管要显示的内容
unsigned char ucDigShow1; //第1位数码管要显示的内容
unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。
unsigned char ucWd1Part1Update=0; //在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志
unsigned char ucWd1Part4Update=0; //在窗口1中,局部4的更新显示标志
unsigned int uiSetData1=0; //本程序中需要被设置的参数1
unsigned int uiSetData2=0; //本程序中需要被设置的参数2
unsigned int uiSetData3=0; //本程序中需要被设置的参数3
unsigned int uiSetData4=0; //本程序中需要被设置的参数4
unsigned char ucTemp1=0; //中间过渡变量
unsigned char ucTemp2=0; //中间过渡变量
unsigned char ucTemp3=0; //中间过渡变量
unsigned char ucTemp4=0; //中间过渡变量
unsigned char ucTemp5=0; //中间过渡变量
unsigned char ucTemp6=0; //中间过渡变量
unsigned char ucTemp7=0; //中间过渡变量
unsigned char ucTemp8=0; //中间过渡变量
unsigned int uiDpyTimeCnt=0; //数码管的闪烁计时器,放在定时中断里不断累加
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f, //0 序号0
0x06, //1 序号1
0x5b, //2 序号2
0x4f, //3 序号3
0x66, //4 序号4
0x6d, //5 序号5
0x7d, //6 序号6
0x07, //7 序号7
0x7f, //8 序号8
0x6f, //9 序号9
0x00, //无 序号10
0x40, //- 序号11
0x73, //P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
display_service(); //显示的窗口菜单服务程序
}
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/
void display_service() //显示的窗口菜单服务程序
{
switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示窗口1的数据
if(ucWd1Part1Update==1) //仅仅参数1局部更新
{
ucWd1Part1Update=0; //及时清零标志,避免一直进来扫描
ucTemp8=uiSetData1/10; //第1个参数
ucTemp7=uiSetData1%10;
if(uiSetData1<10)
{
ucDigShow8=10;
}
else
{
ucDigShow8=ucTemp8;
}
ucDigShow7=ucTemp7;
}
if(ucWd1Part2Update==1) //仅仅参数2局部更新
{
ucWd1Part2Update=0; //及时清零标志,避免一直进来扫描
ucTemp6=uiSetData2/10; //第2个参数
ucTemp5=uiSetData2%10;
if(uiSetData2<10)
{
ucDigShow6=10;
}
else
{
ucDigShow6=ucTemp6;
}
ucDigShow5=ucTemp5;
}
if(ucWd1Part3Update==1) //仅仅参数3局部更新
{
ucWd1Part3Update=0; //及时清零标志,避免一直进来扫描
ucTemp4=uiSetData3/10; //第3个参数
ucTemp3=uiSetData3%10;
if(uiSetData3<10)
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
}
if(ucWd1Part4Update==1) //仅仅参数4局部更新
{
ucWd1Part4Update=0; //及时清零标志,避免一直进来扫描
ucTemp2=uiSetData4/10; //第4个参数
ucTemp1=uiSetData4%10;
if(uiSetData4<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
/* 注释二:
* 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
* 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
*/
if(ucWd1Update==1) //窗口1要全部更新显示
{
ucWd1Update=0; //及时清零标志,避免一直进来扫描
ucTemp8=uiSetData1/10; //第1个参数
ucTemp7=uiSetData1%10;
ucTemp6=uiSetData2/10; //第2个参数
ucTemp5=uiSetData2%10;
ucTemp4=uiSetData3/10; //第3个参数
ucTemp3=uiSetData3%10;
ucTemp2=uiSetData4/10; //第4个参数
ucTemp1=uiSetData4%10;
if(uiSetData1<10)
{
ucDigShow8=10;
}
else
{
ucDigShow8=ucTemp8;
}
ucDigShow7=ucTemp7;
if(uiSetData2<10)
{
ucDigShow6=10;
}
else
{
ucDigShow6=ucTemp6;
}
ucDigShow5=ucTemp5;
if(uiSetData3<10)
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
if(uiSetData4<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
//数码管闪烁
switch(ucPart) //根据局部变量的值,使对应的参数产生闪烁的动态效果。
{
case 0: //4个参数都不闪烁
break;
case 1: //第1个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData1<10) //数码管显示内容
{
ucDigShow8=10;
}
else
{
ucDigShow8=ucTemp8;
}
ucDigShow7=ucTemp7;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow8=10; //数码管显示空,什么都不显示
ucDigShow7=10;
}
break;
case 2: //第2个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData2<10) //数码管显示内容
{
ucDigShow6=10;
}
else
{
ucDigShow6=ucTemp6;
}
ucDigShow5=ucTemp5;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow6=10; //数码管显示空,什么都不显示
ucDigShow5=10;
}
break;
case 3: //第3个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData3<10) //数码管显示内容
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow4=10; //数码管显示空,什么都不显示
ucDigShow3=10;
}
break;
case 4: //第4个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData4<10) //数码管显示内容
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow2=10; //数码管显示空,什么都不显示
ucDigShow1=10;
}
break;
}
break;
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1; //自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1; //自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1; //自锁按键置位,避免一直触发
ucKeySec=3; //触发3号键
}
}
}
void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
{
case 0:
break;
case 1:
uiSetData1++;
if(uiSetData1>99) //最大值是99
{
uiSetData1=99;
}
ucWd1Part1Update=1; //局部更新显示参数1
break;
case 2:
uiSetData2++;
if(uiSetData2>99) //最大值是99
{
uiSetData2=99;
}
ucWd1Part2Update=1; //局部更新显示参数2
break;
case 3:
uiSetData3++;
if(uiSetData3>99) //最大值是99
{
uiSetData3=99;
}
ucWd1Part3Update=1; //局部更新显示参数3
break;
case 4:
uiSetData4++;
if(uiSetData4>99) //最大值是99
{
uiSetData4=99;
}
ucWd1Part4Update=1; //局部更新显示参数4
break;
}
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
{
case 0:
break;
case 1:
uiSetData1--;
if(uiSetData1>99) //0减去1溢出肯定大于99
{
uiSetData1=0;
}
ucWd1Part1Update=1; //局部更新显示参数1
break;
case 2:
uiSetData2--;
if(uiSetData2>99) //0减去1溢出肯定大于99
{
uiSetData2=0;
}
ucWd1Part2Update=1; //局部更新显示参数2
break;
case 3:
uiSetData3--;
if(uiSetData3>99) //0减去1溢出肯定大于99
{
uiSetData3=0;
}
ucWd1Part3Update=1; //局部更新显示参数3
break;
case 4:
uiSetData4--;
if(uiSetData4>99) //0减去1溢出肯定大于99
{
uiSetData4=0;
}
ucWd1Part4Update=1; //局部更新显示参数4
break;
}
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1: //在窗口1下,切换"光标闪烁"
ucPart++;
if(ucPart>4)
{
ucPart=0;
}
ucWd1Update=1; //窗口1全部更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive()
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1: //显示第1位
ucDigShowTemp=dig_table[ucDigShow1];
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2: //显示第2位
ucDigShowTemp=dig_table[ucDigShow2];
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3: //显示第3位
ucDigShowTemp=dig_table[ucDigShow3];
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4: //显示第4位
ucDigShowTemp=dig_table[ucDigShow4];
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5: //显示第5位
ucDigShowTemp=dig_table[ucDigShow5];
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6: //显示第6位
ucDigShowTemp=dig_table[ucDigShow6];
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7: //显示第7位
ucDigShowTemp=dig_table[ucDigShow7];
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8: //显示第8位
ucDigShowTemp=dig_table[ucDigShow8];
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time() interrupt 1
{
TF0=0; //清除中断标志
TR0=0; //关中断
key_scan(); //按键扫描函数
uiDpyTimeCnt++; //数码管的闪烁计时器
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
// beep_dr=1; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
// beep_dr=0; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
display_drive(); //数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1; //开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++) //内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself() //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
led_dr=0; //关闭独立LED灯
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了数码管通过闪烁来设置数据的基本程序,但是该程序只有一个窗口。实际应用中,有些项目会有几个窗口,而且每个窗口都要设置几个参数,这样的程序该怎么写?欲知详情,请听下回分解-----数码管通过一二级菜单来设置数据的综合程序。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-2-28 17:07
第三十一节:数码管通过一二级菜单来设置数据的综合程序。
开场白:
上一节讲了二级菜单,这一节要教会大家两个知识点:
第一个:数码管通过一二级菜单来设置数据的综合程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键,切换窗口按键对应S13键。
(2)实现功能:
通过按键设置4个不同的参数。
有2个窗口。每个窗口显示2个参数。
第8,7,6,5位数码管显示”P-1 ”代表第1个窗口,显示”P-2 ”代表第2个窗口。第4,3位数码管显示该窗口下其中一个参数,第2,1位数码管显示该窗口下其中另外一个参数。每个参数的范围是从0到99。
有四个按键。
一个是切换窗口按键,依次按下此按键,会依次切换窗口显示。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下3种状态中循环:只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short 40 //蜂鸣器短叫的持续时间
#define const_key_time1 20 //按键去抖动延时的时间
#define const_key_time2 20 //按键去抖动延时的时间
#define const_key_time3 20 //按键去抖动延时的时间
#define const_key_time4 20 //按键去抖动延时的时间
#define const_dpy_time_half 200 //数码管闪烁时间的半值
#define const_dpy_time_all 400 //数码管闪烁时间的全值 一定要比const_dpy_time_half 大
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(); //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5; //作为中途暂停指示灯 亮的时候表示中途暂停
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucKeySec=0; //被触发的按键编号
unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt4=0; //按键去抖动延时计数器
unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
unsigned char ucDigShow8; //第8位数码管要显示的内容
unsigned char ucDigShow7; //第7位数码管要显示的内容
unsigned char ucDigShow6; //第6位数码管要显示的内容
unsigned char ucDigShow5; //第5位数码管要显示的内容
unsigned char ucDigShow4; //第4位数码管要显示的内容
unsigned char ucDigShow3; //第3位数码管要显示的内容
unsigned char ucDigShow2; //第2位数码管要显示的内容
unsigned char ucDigShow1; //第1位数码管要显示的内容
unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。
unsigned char ucWd1Part1Update=0; //在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd2Part1Update=0; //在窗口2中,局部1的更新显示标志
unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志
unsigned int uiSetData1=0; //本程序中需要被设置的参数1
unsigned int uiSetData2=0; //本程序中需要被设置的参数2
unsigned int uiSetData3=0; //本程序中需要被设置的参数3
unsigned int uiSetData4=0; //本程序中需要被设置的参数4
unsigned char ucTemp1=0; //中间过渡变量
unsigned char ucTemp2=0; //中间过渡变量
unsigned char ucTemp3=0; //中间过渡变量
unsigned char ucTemp4=0; //中间过渡变量
unsigned char ucTemp5=0; //中间过渡变量
unsigned char ucTemp6=0; //中间过渡变量
unsigned char ucTemp7=0; //中间过渡变量
unsigned char ucTemp8=0; //中间过渡变量
unsigned int uiDpyTimeCnt=0; //数码管的闪烁计时器,放在定时中断里不断累加
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f, //0 序号0
0x06, //1 序号1
0x5b, //2 序号2
0x4f, //3 序号3
0x66, //4 序号4
0x6d, //5 序号5
0x7d, //6 序号6
0x07, //7 序号7
0x7f, //8 序号8
0x6f, //9 序号9
0x00, //无 序号10
0x40, //- 序号11
0x73, //P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
display_service(); //显示的窗口菜单服务程序
}
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/
void display_service() //显示的窗口菜单服务程序
{
switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示窗口1的数据
if(ucWd1Part1Update==1) //仅仅参数1局部更新
{
ucWd1Part1Update=0; //及时清零标志,避免一直进来扫描
ucTemp4=uiSetData1/10; //第1个参数
ucTemp3=uiSetData1%10;
if(uiSetData1<10)
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
}
if(ucWd1Part2Update==1) //仅仅参数2局部更新
{
ucWd1Part2Update=0; //及时清零标志,避免一直进来扫描
ucTemp2=uiSetData2/10; //第2个参数
ucTemp1=uiSetData2%10;
if(uiSetData2<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
/* 注释二:
* 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
* 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
*/
if(ucWd1Update==1) //窗口1要全部更新显示
{
ucWd1Update=0; //及时清零标志,避免一直进来扫描
ucTemp8=12; //显示P
ucTemp7=11; //显示-
ucTemp6=1; //显示1
ucTemp5=10; //显示空
ucTemp4=uiSetData1/10; //第1个参数
ucTemp3=uiSetData1%10;
ucTemp2=uiSetData2/10; //第2个参数
ucTemp1=uiSetData2%10;
ucDigShow8=ucTemp8;
ucDigShow7=ucTemp7;
ucDigShow6=ucTemp6;
ucDigShow5=ucTemp5;
if(uiSetData1<10)
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
if(uiSetData2<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
//数码管闪烁
switch(ucPart) //根据局部变量的值,使对应的参数产生闪烁的动态效果。
{
case 0: //2个参数都不闪烁
break;
case 1: //第1个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData1<10) //数码管显示内容
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow4=10; //数码管显示空,什么都不显示
ucDigShow3=10;
}
break;
case 2: //第2个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData2<10) //数码管显示内容
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow2=10; //数码管显示空,什么都不显示
ucDigShow1=10;
}
break;
}
break;
case 2: //显示窗口2的数据
if(ucWd2Part1Update==1) //在窗口2中,仅仅参数1局部更新
{
ucWd2Part1Update=0; //及时清零标志,避免一直进来扫描
ucTemp4=uiSetData3/10; //第3个参数
ucTemp3=uiSetData3%10;
if(uiSetData3<10)
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
}
if(ucWd2Part2Update==1) //在窗口2中,仅仅参数2局部更新
{
ucWd2Part2Update=0; //及时清零标志,避免一直进来扫描
ucTemp2=uiSetData4/10; //第4个参数
ucTemp1=uiSetData4%10;
if(uiSetData4<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
if(ucWd2Update==1) //窗口2要全部更新显示
{
ucWd2Update=0; //及时清零标志,避免一直进来扫描
ucTemp8=12; //显示P
ucTemp7=11; //显示-
ucTemp6=2; //显示2
ucTemp5=10; //显示空
ucTemp4=uiSetData3/10; //第3个参数
ucTemp3=uiSetData3%10;
ucTemp2=uiSetData4/10; //第4个参数
ucTemp1=uiSetData4%10;
ucDigShow8=ucTemp8;
ucDigShow7=ucTemp7;
ucDigShow6=ucTemp6;
ucDigShow5=ucTemp5;
if(uiSetData3<10)
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
if(uiSetData4<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
//数码管闪烁
switch(ucPart) //根据局部变量的值,使对应的参数产生闪烁的动态效果。
{
case 0: //2个参数都不闪烁
break;
case 1: //第3个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData3<10) //数码管显示内容
{
ucDigShow4=10;
}
else
{
ucDigShow4=ucTemp4;
}
ucDigShow3=ucTemp3;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow4=10; //数码管显示空,什么都不显示
ucDigShow3=10;
}
break;
case 2: //第4个参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData4<10) //数码管显示内容
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow2=10; //数码管显示空,什么都不显示
ucDigShow1=10;
}
break;
}
break;
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1; //自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1; //自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1; //自锁按键置位,避免一直触发
ucKeySec=3; //触发3号键
}
}
if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock4=0; //按键自锁标志清零
uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt4++; //累加定时中断次数
if(uiKeyTimeCnt4>const_key_time4)
{
uiKeyTimeCnt4=0;
ucKeyLock4=1; //自锁按键置位,避免一直触发
ucKeySec=4; //触发4号键
}
}
}
void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
{
case 0:
break;
case 1:
uiSetData1++;
if(uiSetData1>99) //最大值是99
{
uiSetData1=99;
}
ucWd1Part1Update=1; //局部更新显示参数1
break;
case 2:
uiSetData2++;
if(uiSetData2>99) //最大值是99
{
uiSetData2=99;
}
ucWd1Part2Update=1; //局部更新显示参数2
break;
}
break;
case 2:
switch(ucPart) //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
{
case 0:
break;
case 1:
uiSetData3++;
if(uiSetData3>99) //最大值是99
{
uiSetData3=99;
}
ucWd2Part1Update=1; //局部更新显示参数1
break;
case 2:
uiSetData4++;
if(uiSetData4>99) //最大值是99
{
uiSetData4=99;
}
ucWd2Part2Update=1; //局部更新显示参数2
break;
}
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
{
case 0:
break;
case 1:
uiSetData1--;
if(uiSetData1>99) //0减去1溢出肯定大于99
{
uiSetData1=0;
}
ucWd1Part1Update=1; //局部更新显示参数1
break;
case 2:
uiSetData2--;
if(uiSetData2>99) //0减去1溢出肯定大于99
{
uiSetData2=0;
}
ucWd1Part2Update=1; //局部更新显示参数2
break;
}
break;
case 2:
switch(ucPart) //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
{
case 0:
break;
case 1:
uiSetData3--;
if(uiSetData3>99) //0减去1溢出肯定大于99
{
uiSetData3=0;
}
ucWd2Part1Update=1; //局部更新显示参数1
break;
case 2:
uiSetData4--;
if(uiSetData4>99) //0减去1溢出肯定大于99
{
uiSetData4=0;
}
ucWd2Part2Update=1; //局部更新显示参数2
break;
}
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1: //在窗口1下,切换"光标闪烁"
ucPart++;
if(ucPart>2)
{
ucPart=0;
}
ucWd1Update=1; //窗口1全部更新显示
break;
case 2: //在窗口2下,切换"光标闪烁"
ucPart++;
if(ucPart>2)
{
ucPart=0;
}
ucWd2Update=1; //窗口2全部更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 4:// 切换窗口按键 对应朱兆祺学习板的S13键
ucWd++;
if(ucWd>2)
{
ucWd=1;
}
ucPart=0; //强行把局部变量复位,让新切换的窗口不闪烁
switch(ucWd) //在不同的窗口下,更新显示不同的窗口
{
case 1: //在窗口1下
ucWd1Update=1; //窗口1全部更新显示
break;
case 2: //在窗口2下
ucWd2Update=1; //窗口2全部更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive()
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1: //显示第1位
ucDigShowTemp=dig_table[ucDigShow1];
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2: //显示第2位
ucDigShowTemp=dig_table[ucDigShow2];
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3: //显示第3位
ucDigShowTemp=dig_table[ucDigShow3];
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4: //显示第4位
ucDigShowTemp=dig_table[ucDigShow4];
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5: //显示第5位
ucDigShowTemp=dig_table[ucDigShow5];
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6: //显示第6位
ucDigShowTemp=dig_table[ucDigShow6];
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7: //显示第7位
ucDigShowTemp=dig_table[ucDigShow7];
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8: //显示第8位
ucDigShowTemp=dig_table[ucDigShow8];
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time() interrupt 1
{
TF0=0; //清除中断标志
TR0=0; //关中断
key_scan(); //按键扫描函数
uiDpyTimeCnt++; //数码管的闪烁计时器
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
// beep_dr=1; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
// beep_dr=0; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
display_drive(); //数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1; //开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++) //内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself() //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
led_dr=0; //关闭独立LED灯
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了数码管通过一二级菜单来设置数据的综合程序,鸿哥的人机界面程序框架基本上都涉及到了,为了继续加深熟悉鸿哥的“一二级菜单显示理论”,下一节会继续讲一个常用的数码管项目小程序,这个项目小程序鸿哥是怎么写的?欲知详情,请听下回分解-----数码管中的倒计时程序。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-3-9 20:21
第三十二节:数码管中的倒计时程序。
开场白:
上一节讲了一二级菜单的综合程序,这一节要教会大家三个知识点:
第一个:通过本程序,继续加深理解按键与数码管的关联方法。
第二个:复习一下我在第五节教给大家的时间校正法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。
(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short 40 //蜂鸣器短叫的持续时间
#define const_voice_long 200 //蜂鸣器长叫的持续时间
#define const_key_time1 20 //按键去抖动延时的时间
#define const_key_time2 20 //按键去抖动延时的时间
#define const_dpy_time_half 200 //数码管闪烁时间的半值
#define const_dpy_time_all 400 //数码管闪烁时间的全值 一定要比const_dpy_time_half 大
/* 注释一:
* 如何知道1秒钟需要多少个定时中断?
* 这个需要编写一段小程序测试,得到测试的结果后再按比例修正。
* 步骤:
* 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
* 第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。
* 如果单片机倒计时跑完了99秒,而手机上的秒表才走了45秒。
* 第三步:那么最终得出1秒钟需要的定时中断次数是:const_1s=(200*99)/45=440
*/
#define const_1s 440 //大概一秒钟所需要的定时中断次数
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(); //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5; //作为中途暂停指示灯 亮的时候表示中途暂停
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucKeySec=0; //被触发的按键编号
unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
unsigned char ucDigShow8; //第8位数码管要显示的内容
unsigned char ucDigShow7; //第7位数码管要显示的内容
unsigned char ucDigShow6; //第6位数码管要显示的内容
unsigned char ucDigShow5; //第5位数码管要显示的内容
unsigned char ucDigShow4; //第4位数码管要显示的内容
unsigned char ucDigShow3; //第3位数码管要显示的内容
unsigned char ucDigShow2; //第2位数码管要显示的内容
unsigned char ucDigShow1; //第1位数码管要显示的内容
unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucCountDown=99; //倒计时的当前值
unsigned char ucStartFlag=0; //暂停与启动的标志位
unsigned int uiTimeCnt=0; //倒计时的时间计时器
unsigned char ucTemp1=0; //中间过渡变量
unsigned char ucTemp2=0; //中间过渡变量
unsigned char ucTemp3=0; //中间过渡变量
unsigned char ucTemp4=0; //中间过渡变量
unsigned char ucTemp5=0; //中间过渡变量
unsigned char ucTemp6=0; //中间过渡变量
unsigned char ucTemp7=0; //中间过渡变量
unsigned char ucTemp8=0; //中间过渡变量
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f, //0 序号0
0x06, //1 序号1
0x5b, //2 序号2
0x4f, //3 序号3
0x66, //4 序号4
0x6d, //5 序号5
0x7d, //6 序号6
0x07, //7 序号7
0x7f, //8 序号8
0x6f, //9 序号9
0x00, //无 序号10
0x40, //- 序号11
0x73, //P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
display_service(); //显示的窗口菜单服务程序
}
}
/* 注释二:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/
void display_service() //显示的窗口菜单服务程序
{
//由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示窗口1的数据
if(ucWd1Update==1) //窗口1要全部更新显示
{
ucWd1Update=0; //及时清零标志,避免一直进来扫描
ucTemp8=10; //显示空
ucTemp7=10; //显示空
ucTemp6=10; //显示空
ucTemp5=10; //显示空
ucTemp4=10; //显示空
ucTemp3=10; //显示空
ucTemp2=ucCountDown/10; //倒计时的当前值
ucTemp1=ucCountDown%10;
ucDigShow8=ucTemp8;
ucDigShow7=ucTemp7;
ucDigShow6=ucTemp6;
ucDigShow5=ucTemp5;
ucDigShow4=ucTemp4;
ucDigShow3=ucTemp3;
if(ucCountDown<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
break;
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1; //自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1; //自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
}
void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键
//由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
if(ucStartFlag==0) //如果原来处于暂停的状态,则启动
{
ucStartFlag=1; //启动
}
else //如果原来处于启动的状态,则暂停
{
ucStartFlag=0; //暂停
}
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 复位按键 对应朱兆祺学习板的S5键
//由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
ucStartFlag=0; //暂停
ucCountDown=99; //恢复倒计时的默认值99
uiTimeCnt=0; //倒计时的时间计时器清零
ucWd1Update=1; //窗口1更新显示标志 只要ucCountDown变化了,就要更新显示一次
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive()
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1: //显示第1位
ucDigShowTemp=dig_table[ucDigShow1];
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2: //显示第2位
ucDigShowTemp=dig_table[ucDigShow2];
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3: //显示第3位
ucDigShowTemp=dig_table[ucDigShow3];
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4: //显示第4位
ucDigShowTemp=dig_table[ucDigShow4];
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5: //显示第5位
ucDigShowTemp=dig_table[ucDigShow5];
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6: //显示第6位
ucDigShowTemp=dig_table[ucDigShow6];
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7: //显示第7位
ucDigShowTemp=dig_table[ucDigShow7];
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8: //显示第8位
ucDigShowTemp=dig_table[ucDigShow8];
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time() interrupt 1
{
TF0=0; //清除中断标志
TR0=0; //关中断
key_scan(); //按键扫描函数
if(ucStartFlag==1) //启动倒计时的计时器
{
uiTimeCnt++;
if(uiTimeCnt>=const_1s) //1秒钟的时间到
{
if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
{
ucCountDown--; //倒计时当前显示值减1
}
if(ucCountDown==0) //倒计时结束
{
ucStartFlag=0; //暂停
uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
}
ucWd1Update=1; //窗口1更新显示标志
uiTimeCnt=0; //计时器清零,准备从新开始计时
}
}
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
// beep_dr=1; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
// beep_dr=0; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
display_drive(); //数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1; //开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++) //内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself() //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
led_dr=0; //关闭独立LED灯
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了数码管中的倒计时程序。如果要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位,该怎么编写这个程序?欲知详情,请听下回分解-----能设置速度档位的数码管倒计时程序。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-3-9 20:22
第三十三节:能设置速度档位的数码管倒计时程序。
开场白:
上一节讲了数码管中的倒计时程序。这节要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位。这一节要教会大家三个知识点:
第一个:把一个按键的短按与长按复合应用在项目中的程序结构。
第二个:通过本程序,继续加深理解按键与数码管的关联方法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。加键对应S9键,减键对应S13键。
(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。如果长按复位按键,在数码管会切换到第2个闪烁窗口,用来设置速度档位,修改完速度档位后,再一次按下复位按键,或者直接按启动暂停按键,会切换回窗口1显示倒计时的当前数据。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short 40 //蜂鸣器短叫的持续时间
#define const_voice_long 200 //蜂鸣器长叫的持续时间
#define const_key_time1 20 //按键去抖动延时的时间
#define const_key_time2 20 //按键去抖动延时的时间
#define const_key_time3 20 //按键去抖动延时的时间
#define const_key_time4 20 //按键去抖动延时的时间
#define const_key_long_time 200 //长按复位按键的时间
#define const_dpy_time_half 200 //数码管闪烁时间的半值
#define const_dpy_time_all 400 //数码管闪烁时间的全值 一定要比const_dpy_time_half 大
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(); //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5; //作为中途暂停指示灯 亮的时候表示中途暂停
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucKeySec=0; //被触发的按键编号
unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int uiKeyTimeCnt4=0; //按键去抖动延时计数器
unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
unsigned char ucDigShow8; //第8位数码管要显示的内容
unsigned char ucDigShow7; //第7位数码管要显示的内容
unsigned char ucDigShow6; //第6位数码管要显示的内容
unsigned char ucDigShow5; //第5位数码管要显示的内容
unsigned char ucDigShow4; //第4位数码管要显示的内容
unsigned char ucDigShow3; //第3位数码管要显示的内容
unsigned char ucDigShow2; //第2位数码管要显示的内容
unsigned char ucDigShow1; //第1位数码管要显示的内容
unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=1; //窗口2更新显示标志
unsigned char ucCountDown=99; //倒计时的当前值
unsigned char ucStartFlag=0; //暂停与启动的标志位
unsigned int uiTimeCnt=0; //倒计时的时间计时器
unsigned int uiDpyTimeCnt=0; //数码管的闪烁计时器,放在定时中断里不断累加
unsigned int uiSetData1=50; //速度档位
unsigned int uiSpeedCnt=400; //影响速度变量,它跟速度档位uiSetData1有关联
unsigned char ucTemp1=0; //中间过渡变量
unsigned char ucTemp2=0; //中间过渡变量
unsigned char ucTemp3=0; //中间过渡变量
unsigned char ucTemp4=0; //中间过渡变量
unsigned char ucTemp5=0; //中间过渡变量
unsigned char ucTemp6=0; //中间过渡变量
unsigned char ucTemp7=0; //中间过渡变量
unsigned char ucTemp8=0; //中间过渡变量
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f, //0 序号0
0x06, //1 序号1
0x5b, //2 序号2
0x4f, //3 序号3
0x66, //4 序号4
0x6d, //5 序号5
0x7d, //6 序号6
0x07, //7 序号7
0x7f, //8 序号8
0x6f, //9 序号9
0x00, //无 序号10
0x40, //- 序号11
0x73, //P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
display_service(); //显示的窗口菜单服务程序
}
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/
void display_service() //显示的窗口菜单服务程序
{
switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示窗口1的数据
if(ucWd1Update==1) //窗口1要全部更新显示
{
ucWd1Update=0; //及时清零标志,避免一直进来扫描
ucTemp8=10; //显示空
ucTemp7=10; //显示空
ucTemp6=10; //显示空
ucTemp5=10; //显示空
ucTemp4=10; //显示空
ucTemp3=10; //显示空
ucTemp2=ucCountDown/10; //倒计时的当前值
ucTemp1=ucCountDown%10;
ucDigShow8=ucTemp8;
ucDigShow7=ucTemp7;
ucDigShow6=ucTemp6;
ucDigShow5=ucTemp5;
ucDigShow4=ucTemp4;
ucDigShow3=ucTemp3;
if(ucCountDown<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
break;
case 2: //显示窗口2的数据
if(ucWd2Update==1) //窗口2要全部更新显示
{
ucWd2Update=0; //及时清零标志,避免一直进来扫描
ucTemp8=10; //显示空
ucTemp7=10; //显示空
ucTemp6=10; //显示空
ucTemp5=10; //显示空
ucTemp4=10; //显示空
ucTemp3=10; //显示空
ucTemp2=uiSetData1/10; //倒计时的速度档位
ucTemp1=uiSetData1%10;
ucDigShow8=ucTemp8;
ucDigShow7=ucTemp7;
ucDigShow6=ucTemp6;
ucDigShow5=ucTemp5;
ucDigShow4=ucTemp4;
ucDigShow3=ucTemp3;
if(uiSetData1<10)
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
//数码管闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
if(uiSetData1<10) //数码管显示内容
{
ucDigShow2=10;
}
else
{
ucDigShow2=ucTemp2;
}
ucDigShow1=ucTemp1;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDigShow2=10; //数码管显示空,什么都不显示
ucDigShow1=10;
}
break;
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1; //自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
/* 注释二:
* 请注意以下长按复位按键与短按复位按键的写法。在本程序中,每次长按复位按键必然
* 触发一次短按复位按键。
*/
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1; //自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
else if(uiKeyTimeCnt2<const_key_long_time) //长按复位按键
{
uiKeyTimeCnt2++;
if(uiKeyTimeCnt2==const_key_long_time)
{
ucKeySec=17; //触发17号长按复位键
}
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1; //自锁按键置位,避免一直触发
ucKeySec=3; //触发3号键
}
}
if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock4=0; //按键自锁标志清零
uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt4++; //累加定时中断次数
if(uiKeyTimeCnt4>const_key_time4)
{
uiKeyTimeCnt4=0;
ucKeyLock4=1; //自锁按键置位,避免一直触发
ucKeySec=4; //触发4号键
}
}
}
void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1:
if(ucStartFlag==0) //如果原来处于暂停的状态,则启动
{
ucStartFlag=1; //启动
}
else //如果原来处于启动的状态,则暂停
{
ucStartFlag=0; //暂停
}
break;
}
ucWd=1; //不管在哪个窗口,强行切换回窗口1
ucWd1Update=1; //窗口1更新显示标志
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 复位按键 对应朱兆祺学习板的S5键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1: //在窗口1中
ucStartFlag=0; //暂停
ucCountDown=99; //恢复倒计时的默认值99
uiTimeCnt=0; //倒计时的时间计时器清零
ucWd1Update=1; //窗口1更新显示标志 只要ucCountDown变化了,就要更新显示一次
break;
case 2: //在窗口2中
ucWd=1; //切换回窗口1
ucWd1Update=1; //窗口1更新显示标志
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 加按键 对应朱兆祺学习板的S9键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 2: //在窗口2中
uiSetData1++; //速度档位累加,档位越大,速度越快.
if(uiSetData1>99)
{
uiSetData1=99;
}
uiSpeedCnt=440-(uiSetData1*2); //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快
ucWd2Update=1; //窗口2更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 4:// 减按键 对应朱兆祺学习板的S13键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 2: //在窗口2中
if(uiSetData1>0) //加此条件判断,避免0减1
{
uiSetData1--; //速度档位累减,档位越小,速度越慢.
}
uiSpeedCnt=440-(uiSetData1*2); //速度档位越小,累计中断数uiSpeedCnt越大,从而倒计时的时间越慢
ucWd2Update=1; //窗口2更新显示
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 17:// 长按复位按键 对应朱兆祺学习板的S5键
switch(ucWd) //在不同的窗口下,设置不同的参数
{
case 1: //窗口1下
ucWd=2; //切换到闪烁窗口2 进行设置速度档位显示
ucWd2Update=1; //窗口2更新显示标志
break;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive()
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1: //显示第1位
ucDigShowTemp=dig_table[ucDigShow1];
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2: //显示第2位
ucDigShowTemp=dig_table[ucDigShow2];
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3: //显示第3位
ucDigShowTemp=dig_table[ucDigShow3];
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4: //显示第4位
ucDigShowTemp=dig_table[ucDigShow4];
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5: //显示第5位
ucDigShowTemp=dig_table[ucDigShow5];
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6: //显示第6位
ucDigShowTemp=dig_table[ucDigShow6];
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7: //显示第7位
ucDigShowTemp=dig_table[ucDigShow7];
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8: //显示第8位
ucDigShowTemp=dig_table[ucDigShow8];
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09; //先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01; //再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time() interrupt 1
{
TF0=0; //清除中断标志
TR0=0; //关中断
key_scan(); //按键扫描函数
if(ucStartFlag==1) //启动倒计时的计时器
{
uiTimeCnt++;
if(uiTimeCnt>=uiSpeedCnt) //时间到
{
if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
{
ucCountDown--; //倒计时当前显示值减1
}
if(ucCountDown==0) //倒计时结束
{
ucStartFlag=0; //暂停
uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
}
ucWd1Update=1; //窗口1更新显示标志
uiTimeCnt=0; //计时器清零,准备从新开始计时
}
}
uiDpyTimeCnt++; //数码管的闪烁计时器
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
// beep_dr=1; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
// beep_dr=0; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
display_drive(); //数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1; //开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++) //内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself() //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
led_dr=0; //关闭独立LED灯
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
uiSpeedCnt=440-(uiSetData1*2); //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了能设置速度档位的数码管倒计时程序。现在很多人用iphone4S的手机,这个手机每次开机显示的时候,都要通过4个密码开锁,如果我们要用4位数码管来实现这个密码锁功能,该怎么编写这个程序?欲知详情,请听下回分解-----在数码管中实现iphone4S开机密码锁的程序。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-3-15 10:11
第三十四节:在数码管中实现iphone4S开机密码锁的程序。
开场白:
这一节要教会大家四个知识点:
第一个:类似手机上10秒钟内无按键操作将自动进入锁屏的程序。
第二个:如何用一个数组来接收按键的一串数字输入。
第三个:矩阵键盘中,数字按键的输入,由于这部分按键的代码相似度非常高,因此把它封装在一个函数里可以非常简洁方便。
第四个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键,数字0键对应S10键。其他的按键不用。
(2)实现功能:
本程序有3个窗口。
开机显示第1个密码登录框窗口“----”,在这个窗口下输入密码,如果密码等于”9922”表示密码正确,将会切换到第2个显示按键值的窗口。在窗口2下,按不同的按键会显示不同的按键值,如果10秒内没有按键操作,将会自动切换到第1个密码登录窗口,类似手机上的自动锁屏操作。在密码登录窗口1下,如果密码不正确,会自动清除密码的数字,继续在窗口1下显示”----”。
窗口3是用来停留0.5秒显示全部密码的信息,然后根据密码的正确与否自动切换到对应的窗口。
(3
)源代码讲解如下:
- #include "REG52.H"
- #define const_no_key_push 4400 //大概10秒内无按键按下的时间
- #define const_0_1s 220 //大概0.5秒的时间
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time 20 //按键去抖动延时的时间
- void initial_myself();
- void initial_peripheral();
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(); //显示数码管字模的驱动函数
- void display_service(); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(); //定时中断函数
- void number_key_input(unsigned char ucWhichKey); //由于数字按键的代码相似度高,因此封装在这个函数里
- void key_service(); //按键服务的应用程序
- void key_scan();//按键扫描函数 放在定时中断里
- sbit key_sr1=P0^0; //第一行输入
- sbit key_sr2=P0^1; //第二行输入
- sbit key_sr3=P0^2; //第三行输入
- sbit key_sr4=P0^3; //第四行输入
- sbit key_dr1=P0^4; //第一列输出
- sbit key_dr2=P0^5; //第二列输出
- sbit key_dr3=P0^6; //第三列输出
- sbit key_dr4=P0^7; //第四列输出
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit led_dr=P3^5; //作为中途暂停指示灯 亮的时候表示中途暂停
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucKeyStep=1; //按键扫描步骤变量
- unsigned int uiKeyTimeCnt=0; //按键去抖动延时计数器
- unsigned char ucKeyLock=0; //按键触发后自锁的变量标志
- unsigned char ucRowRecord=1; //记录当前扫描到第几列了
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Update=1; //窗口1更新显示标志
- unsigned char ucWd2Update=0; //窗口2更新显示标志
- unsigned char ucWd3Update=0; //窗口3更新显示标志
- unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- unsigned char ucInputPassword[4]; //在第1个窗口下,显示输入的4个密码
- unsigned char ucPasswordCnt=0; //记录当前已经输入到哪一位密码了
- unsigned char ucKeyNumber=1; //在第2个窗口下,显示当前被按下的按键
- unsigned int uiNoKeyPushTimer=const_no_key_push; //10秒内无按键按下的计时器
- unsigned int uiPasswordTimer=const_0_1s; //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- display_service(); //显示的窗口菜单服务程序
- }
- }
- /* 注释一:
- *鸿哥首次提出的"一二级菜单显示理论":
- *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
- *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
- *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
- *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
- */
- void display_service() //显示的窗口菜单服务程序
- {
- switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- {
- case 1: //显示输入密码的登录框
- if(ucWd1Update==1) //窗口1要全部更新显示
- {
- ucWd1Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=10; //第8位数码管显示无
- ucDigShow7=10; //第7位数码管显示无
- ucDigShow6=10; //第6位数码管显示无
- ucDigShow5=10; //第5位数码管显示无
- ucDigShow4=ucInputPassword[0]; //第4位数码管显示输入的密码
- ucDigShow3=ucInputPassword[1]; //第3位数码管显示输入的密码
- ucDigShow2=ucInputPassword[2]; //第2位数码管显示输入的密码
- ucDigShow1=ucInputPassword[3]; //第1位数码管显示输入的密码
- }
- break;
- case 2: //显示被按下的键值
- if(ucWd2Update==1) //窗口2要全部更新显示
- {
- ucWd2Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=10; //第8位数码管显示无
- ucDigShow7=10; //第7位数码管显示无
- ucDigShow6=10; //第6位数码管显示无
- ucDigShow5=10; //第5位数码管显示无
- ucDigShow4=10; //第4位数码管显示无
- ucDigShow3=10; //第3位数码管显示无
- ucDigShow2=10; //第2位数码管显示无
- ucDigShow1=ucKeyNumber; //第1位数码管显示被按下的键值
- }
- break;
- case 3: //当输入完4个密码后,显示1秒钟的密码登录框,
- if(ucWd3Update==1) //窗口3要全部更新显示
- {
- ucWd3Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=10; //第8位数码管显示无
- ucDigShow7=10; //第7位数码管显示无
- ucDigShow6=10; //第6位数码管显示无
- ucDigShow5=10; //第5位数码管显示无
- ucDigShow4=ucInputPassword[0]; //第4位数码管显示输入的密码
- ucDigShow3=ucInputPassword[1]; //第3位数码管显示输入的密码
- ucDigShow2=ucInputPassword[2]; //第2位数码管显示输入的密码
- ucDigShow1=ucInputPassword[3]; //第1位数码管显示输入的密码
- }
- break;
- }
-
- }
- void key_scan()//按键扫描函数 放在定时中断里
- {
- switch(ucKeyStep)
- {
- case 1: //按键扫描输出第ucRowRecord列低电平
- if(ucRowRecord==1) //第一列输出低电平
- {
- key_dr1=0;
- key_dr2=1;
- key_dr3=1;
- key_dr4=1;
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- key_dr1=1;
- key_dr2=0;
- key_dr3=1;
- key_dr4=1;
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- key_dr1=1;
- key_dr2=1;
- key_dr3=0;
- key_dr4=1;
- }
- else //第四列输出低电平
- {
- key_dr1=1;
- key_dr2=1;
- key_dr3=1;
- key_dr4=0;
- }
- uiKeyTimeCnt=0; //延时计数器清零
- ucKeyStep++; //切换到下一个运行步骤
- break;
- case 2: //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
- uiKeyTimeCnt++;
- if(uiKeyTimeCnt>1)
- {
- uiKeyTimeCnt=0;
- ucKeyStep++; //切换到下一个运行步骤
- }
- break;
- case 3:
- if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
- {
- ucKeyStep=1; //如果没有按键按下,返回到第一个运行步骤重新开始扫描
- ucKeyLock=0; //按键自锁标志清零
- uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
-
- ucRowRecord++; //输出下一列
- if(ucRowRecord>4)
- {
- ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
- }
- }
- else if(ucKeyLock==0) //有按键按下,且是第一次触发
- {
- if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=1; //触发1号键 对应朱兆祺学习板的S1键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=2; //触发2号键 对应朱兆祺学习板的S2键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=3; //触发3号键 对应朱兆祺学习板的S3键
- }
- else //第四列输出低电平
- {
- ucKeySec=4; //触发4号键 对应朱兆祺学习板的S4键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=5; //触发5号键 对应朱兆祺学习板的S5键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=6; //触发6号键 对应朱兆祺学习板的S6键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=7; //触发7号键 对应朱兆祺学习板的S7键
- }
- else //第四列输出低电平
- {
- ucKeySec=8; //触发8号键 对应朱兆祺学习板的S8键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=9; //触发9号键 对应朱兆祺学习板的S9键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=10; //触发10号键 对应朱兆祺学习板的S10键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=11; //触发11号键 对应朱兆祺学习板的S11键
- }
- else //第四列输出低电平
- {
- ucKeySec=12; //触发12号键 对应朱兆祺学习板的S12键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=13; //触发13号键 对应朱兆祺学习板的S13键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=14; //触发14号键 对应朱兆祺学习板的S14键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=15; //触发15号键 对应朱兆祺学习板的S15键
- }
- else //第四列输出低电平
- {
- ucKeySec=16; //触发16号键 对应朱兆祺学习板的S16键
- }
- }
-
- }
-
- }
- break;
- }
- }
- void key_service() //第三区 按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 1号键 对应朱兆祺学习板的S1键
- number_key_input(1); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 2:// 2号键 对应朱兆祺学习板的S2键
- number_key_input(2); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 3号键 对应朱兆祺学习板的S3键
- number_key_input(3); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 4:// 4号键 对应朱兆祺学习板的S4键
- number_key_input(4); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 5:// 5号键 对应朱兆祺学习板的S5键
- number_key_input(5); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 6:// 6号键 对应朱兆祺学习板的S6键
- number_key_input(6); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 7:// 7号键 对应朱兆祺学习板的S7键
- number_key_input(7); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 8:// 8号键 对应朱兆祺学习板的S8键
- number_key_input(8); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 9:// 9号键 对应朱兆祺学习板的S9键
- number_key_input(9); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 10:// 把这个按键专门用来输入数字0 对应朱兆祺学习板的S10键
- number_key_input(0); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 11:// 11号键 对应朱兆祺学习板的S11键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 12:// 12号键 对应朱兆祺学习板的S12键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 13:// 13号键 对应朱兆祺学习板的S13键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 14:// 14号键 对应朱兆祺学习板的S14键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 15:// 15号键 对应朱兆祺学习板的S15键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 16:// 16号键 对应朱兆祺学习板的S16键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- }
- }
- void number_key_input(unsigned char ucWhichKey) //由于数字按键的代码相似度高,因此封装在这个函数里
- {
- switch(ucWd)
- {
- case 1: //在显示密码登录框的窗口下
- ucInputPassword[ucPasswordCnt]=ucWhichKey; //输入的密码值显示
- ucPasswordCnt++;
- if(ucPasswordCnt>=4)
- {
- ucPasswordCnt=0;
- ucWd=3;//切换到第3个的窗口,停留显示1秒钟全部密码
- ucWd3Update=1; //更新显示窗口3
- uiPasswordTimer=const_0_1s; //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失
- }
- ucWd1Update=1; //更新显示窗口1
- uiNoKeyPushTimer=const_no_key_push; //10秒内无按键按下的计时器赋新值
- break;
- case 2: //在显示按键值的窗口下
- ucKeyNumber=ucWhichKey; //输入的按键数值显示
- ucWd2Update=1; //更新显示窗口2
- uiNoKeyPushTimer=const_no_key_push; //10秒内无按键按下的计时器赋新值
- break;
- }
- }
- void display_drive()
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time() interrupt 1
- {
- unsigned int i;
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(ucWd==3) //在窗口3下
- {
- if(uiPasswordTimer>0)
- {
- uiPasswordTimer--;
- }
- if(uiPasswordTimer==0)
- {
- if(ucInputPassword[0]==9&&ucInputPassword[1]==9&&ucInputPassword[2]==2&&ucInputPassword[3]==2)
- { //如果密码等于9922,则正确
- ucWd=2;//切换到第2个显示按键的窗口
- ucWd2Update=1; //更新显示窗口2
- }
- else //如果密码不正确,则继续显示----
- {
- for(i=0;i<4;i++)
- {
- ucInputPassword[i]=11; //开机默认密码全部显示"----"
- }
- ucWd=1;
- ucWd1Update=1; //更新显示窗口1
- }
- }
- }
- if(ucWd==2) //在窗口2下
- {
- if(uiNoKeyPushTimer>0)
- {
- uiNoKeyPushTimer--;
- }
- if(uiNoKeyPushTimer==0)//如果10秒内无按键按下,则自动切换到显示密码登录框的界面
- {
- for(i=0;i<4;i++)
- {
- ucInputPassword[i]=11; //开机默认密码全部显示"----"
- }
- ucWd=1;
- ucWd1Update=1; //更新显示窗口1
- }
- }
- key_scan(); //按键扫描函数
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
- // beep_dr=1; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
- // beep_dr=0; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
- }
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself() //第一区 初始化单片机
- {
- led_dr=0; //关闭独立LED灯
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral() //第二区 初始化外围
- {
- unsigned int i; //个人的变量命名习惯,i,j,k等单个字母的变量名只用在for循环里
- for(i=0;i<4;i++)
- {
- ucInputPassword[i]=11; //开机默认密码全部显示"----"
- }
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词: 这节讲了iphone4S开机密码锁的程序。2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用,这个程序该怎么编写?欲知详情,请听下回分解-----带数码管显示的象棋比赛专用计时器。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-3-21 01:34
第三十五节:带数码管显示的象棋比赛专用计时器。
开场白:
2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用。这一节要教会大家两个知识点:
第一个:按键服务程序操作的精髓在于根据当前系统处于什么窗口状态下就执行什么操作。紧紧围绕着不同的窗口ucWd来执行不同的操作。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。
刚上电开机时,红棋加时键对应S1键,红棋减时键对应S2键.。
刚上电开机时,黑棋加时键对应S3键,黑棋减时键对应S4键.。
比赛中途暂停双方计时的暂停按键对应S6键。刚上电时,复位双方默认20分时间的复位按键对应S7按键。
红棋的抢时按键对应S13键,黑棋的抢时按键对应S16按键。
(2)实现功能:
棋类计时器有点像抢答器,本质上有两个计时器。比赛的时候对弈的两个棋友各用一个不同的按键抢时间,红棋走一步棋后,就按一下自己的抢时按键,这个时候红棋的计时器停止计时,而黑棋的计时器开始计时,黑棋走了一步棋后,按一下自己的计时器,黑棋停止计时,红棋继续计时,依次循环,谁的时间最先用完谁就输,蜂鸣器也会发出长鸣的声音提示时间到。
上电开机默认双方各有20分钟的时间,左边显示的是红棋的时间,右边显示的是黑棋的时间。此时可以通过S1,S2.,S3,S4的加减按键来设置各自的最大倒计时时间。此时如果按下复位按键S7,会自动把双方的时间设置为默认的20分钟。
设置好最大倒计时的时间后,此时任意一方按下各自的抢时按键(S13或者S16),则自己的计时器停止计时,而对方开始倒计时。此时数码管显示的是对方的时间,而自己的时间屏蔽不显示。
在开始倒计时的时候,如果中途有棋友要接听电话或者忙别的事情,需要暂时暂停一下双方的时间,这个时候可以按S6暂停按键来暂停双方的计时,忙完后再次按下暂停按键会继续倒计时。任何一方的时间走完,都会蜂鸣器长鸣提示。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_voice_long 900 //蜂鸣器长叫的持续时间
- #define const_key_time 10 //按键去抖动延时的时间
- #define const_1s 422 //产生一秒钟的时间基准
- void initial_myself();
- void initial_peripheral();
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- void T0_time(); //定时中断函数
- void key_service();
- void key_scan(); //按键扫描函数 放在定时中断里
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(); //放在定时中断里的数码管驱动函数
- void time_service(); //放在定时中断里的时间应用程序
- void display_service();
- sbit key_sr1=P0^0; //第一行输入
- sbit key_sr2=P0^1; //第二行输入
- sbit key_sr3=P0^2; //第三行输入
- sbit key_sr4=P0^3; //第四行输入
- sbit key_dr1=P0^4; //第一列输出
- sbit key_dr2=P0^5; //第二列输出
- sbit key_dr3=P0^6; //第三列输出
- sbit key_dr4=P0^7; //第四列输出
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit led_dr=P3^5; //作为中途暂停指示灯 亮的时候表示中途暂停
- sbit dig_hc595_sh_dr=P2^0; //数码管 的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucKeyStep=1; //按键扫描步骤变量
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt=0; //按键去抖动延时计数器
- unsigned char ucKeyLock=0; //按键触发后自锁的变量标志
- unsigned char ucRowRecord=1; //记录当前扫描到第几列了
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucDigShow8=0; //第8位数码管要显示的内容
- unsigned char ucDigShow7=0; //第7位数码管要显示的内容
- unsigned char ucDigShow6=0; //第6位数码管要显示的内容
- unsigned char ucDigShow5=0; //第5位数码管要显示的内容
- unsigned char ucDigShow4=0; //第4位数码管要显示的内容
- unsigned char ucDigShow3=0; //第3位数码管要显示的内容
- unsigned char ucDigShow2=0; //第2位数码管要显示的内容
- unsigned char ucDigShow1=0; //第1位数码管要显示的内容
- unsigned char ucDigDot3=1; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot7=1; //数码管7的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned int uiRedTimeCnt=0; //红棋产生秒基准的时间计时器
- unsigned int uiBlackTimeCnt=0; //黑棋产生秒基准的时间计时器
- unsigned int uiRedTotal=1200; //红棋的总时间
- unsigned int uiBlackTotal=1200; //黑棋的总时间
- unsigned char ucRedFlag=0; //红棋是否开始计时的标志
- unsigned char ucBlackFlag=0; //黑棋是否开始计时的标志
- unsigned char ucDisplayUpdate=1; //更新显示标志
- /* 注释一:
- * ucWd变量是本程序最核心的变量,代表显示哪一个窗口和系统处于当前哪种状态
- */
- unsigned char ucWd=1;
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //不显示 序号10
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service();
- display_service();
- }
- }
- void time_service() //放在定时中断里的时间应用程序
- {
- if(ucRedFlag==1) //1代表红棋在运行中
- {
- uiRedTimeCnt++;
- if(uiRedTimeCnt>const_1s)
- {
- uiRedTimeCnt=0;
- if(uiRedTotal>0)
- {
- uiRedTotal--;
- }
- else //时间到
- {
- ucRedFlag=0; //红棋和黑棋同时停止计时
- ucBlackFlag=0;
- ucWd=1; //切换到第一个窗口的状态
- uiVoiceCnt=const_voice_long; //报警声音触发
- }
-
- ucDisplayUpdate=1; //更新显示
- }
- }
- if(ucBlackFlag==1) //1代表黑棋在运行中
- {
- uiBlackTimeCnt++;
- if(uiBlackTimeCnt>const_1s)
- {
- uiBlackTimeCnt=0;
- if(uiBlackTotal>0)
- {
- uiBlackTotal--;
- }
- else //时间到
- {
- ucRedFlag=0; //红棋和黑棋同时停止计时
- ucBlackFlag=0;
- ucWd=1; //切换到第一个窗口的状态
- uiVoiceCnt=const_voice_long; //报警声音触发
- }
-
- ucDisplayUpdate=1; //更新显示
- }
- }
- }
- void display_service() //放在定时中断里的显示应用程序
- {
- if(ucDisplayUpdate==1) //有数据更新显示
- {
- ucDisplayUpdate=0;
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电或者复位后的状态
- //红棋分解出分
- ucDigShowTemp=uiRedTotal/60;
- ucDigShow8=ucDigShowTemp/10;
- ucDigShow7=ucDigShowTemp%10;
- //红棋分解出秒
- ucDigShowTemp=uiRedTotal%60;
- ucDigShow6=ucDigShowTemp/10;
- ucDigShow5=ucDigShowTemp%10;
- ucDigDot7=1; //数码管7的小数点显示
- //黑棋分解出分
- ucDigShowTemp=uiBlackTotal/60;
- ucDigShow4=ucDigShowTemp/10;
- ucDigShow3=ucDigShowTemp%10;
- //黑棋分解出秒
- ucDigShowTemp=uiBlackTotal%60;
- ucDigShow2=ucDigShowTemp/10;
- ucDigShow1=ucDigShowTemp%10;
- ucDigDot3=1; //数码管3的小数点显示
- led_dr=1; //计时器处于停止状态,LED亮
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- //红棋全部不显示
- ucDigShow8=10;
- ucDigShow7=10;
- ucDigShow6=10;
- ucDigShow5=10;
- ucDigDot7=0; //数码管7的小数点不显示
- //黑棋分解出分
- ucDigShowTemp=uiBlackTotal/60;
- ucDigShow4=ucDigShowTemp/10;
- ucDigShow3=ucDigShowTemp%10;
- //黑棋分解出秒
- ucDigShowTemp=uiBlackTotal%60;
- ucDigShow2=ucDigShowTemp/10;
- ucDigShow1=ucDigShowTemp%10;
- ucDigDot3=1; //数码管3的小数点显示
- led_dr=0; //计时器处于计时状态,LED灭
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- //红棋全部不显示
- ucDigShow8=10;
- ucDigShow7=10;
- ucDigShow6=10;
- ucDigShow5=10;
- ucDigDot7=0; //数码管7的小数点不显示
- //黑棋分解出分
- ucDigShowTemp=uiBlackTotal/60;
- ucDigShow4=ucDigShowTemp/10;
- ucDigShow3=ucDigShowTemp%10;
- //黑棋分解出秒
- ucDigShowTemp=uiBlackTotal%60;
- ucDigShow2=ucDigShowTemp/10;
- ucDigShow1=ucDigShowTemp%10;
- ucDigDot3=1; //数码管3的小数点显示
- led_dr=1; //计时器处于暂停状态,LED亮
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- //红棋分解出分
- ucDigShowTemp=uiRedTotal/60;
- ucDigShow8=ucDigShowTemp/10;
- ucDigShow7=ucDigShowTemp%10;
- //红棋分解出秒
- ucDigShowTemp=uiRedTotal%60;
- ucDigShow6=ucDigShowTemp/10;
- ucDigShow5=ucDigShowTemp%10;
- ucDigDot7=1; //数码管7的小数点显示
- //黑棋全部不显示
- ucDigShow4=10;
- ucDigShow3=10;
- ucDigShow2=10;
- ucDigShow1=10;
- ucDigDot3=0; //数码管3的小数点不显示
- led_dr=0; //计时器处于倒计时状态,LED灭
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- //红棋分解出分
- ucDigShowTemp=uiRedTotal/60;
- ucDigShow8=ucDigShowTemp/10;
- ucDigShow7=ucDigShowTemp%10;
- //红棋分解出秒
- ucDigShowTemp=uiRedTotal%60;
- ucDigShow6=ucDigShowTemp/10;
- ucDigShow5=ucDigShowTemp%10;
- ucDigDot7=1; //数码管7的小数点显示
- //黑棋全部不显示
- ucDigShow4=10;
- ucDigShow3=10;
- ucDigShow2=10;
- ucDigShow1=10;
- ucDigDot3=0; //数码管3的小数点不显示
- led_dr=1; //计时器处于暂停状态,LED亮
- break;
- }
- }
- }
- void display_drive() //放在定时中断里的数码管驱动函数
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- /* 注释二:
- * 注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
- */
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void key_scan()//按键扫描函数 放在定时中断里
- {
- switch(ucKeyStep)
- {
- case 1: //按键扫描输出第ucRowRecord列低电平
- if(ucRowRecord==1) //第一列输出低电平
- {
- key_dr1=0;
- key_dr2=1;
- key_dr3=1;
- key_dr4=1;
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- key_dr1=1;
- key_dr2=0;
- key_dr3=1;
- key_dr4=1;
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- key_dr1=1;
- key_dr2=1;
- key_dr3=0;
- key_dr4=1;
- }
- else //第四列输出低电平
- {
- key_dr1=1;
- key_dr2=1;
- key_dr3=1;
- key_dr4=0;
- }
- uiKeyTimeCnt=0; //延时计数器清零
- ucKeyStep++; //切换到下一个运行步骤
- break;
- case 2: //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
- uiKeyTimeCnt++;
- if(uiKeyTimeCnt>1)
- {
- uiKeyTimeCnt=0;
- ucKeyStep++; //切换到下一个运行步骤
- }
- break;
- case 3:
- if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
- {
- ucKeyStep=1; //如果没有按键按下,返回到第一个运行步骤重新开始扫描
- ucKeyLock=0; //按键自锁标志清零
- uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
-
- ucRowRecord++; //输出下一列
- if(ucRowRecord>4)
- {
- ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
- }
- }
- else if(ucKeyLock==0) //有按键按下,且是第一次触发
- {
- if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=1; //触发1号键 对应朱兆祺学习板的S1键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=2; //触发2号键 对应朱兆祺学习板的S2键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=3; //触发3号键 对应朱兆祺学习板的S3键
- }
- else //第四列输出低电平
- {
- ucKeySec=4; //触发4号键 对应朱兆祺学习板的S4键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=5; //触发5号键 对应朱兆祺学习板的S5键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=6; //触发6号键 对应朱兆祺学习板的S6键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=7; //触发7号键 对应朱兆祺学习板的S7键
- }
- else //第四列输出低电平
- {
- ucKeySec=8; //触发8号键 对应朱兆祺学习板的S8键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=9; //触发9号键 对应朱兆祺学习板的S9键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=10; //触发10号键 对应朱兆祺学习板的S10键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=11; //触发11号键 对应朱兆祺学习板的S11键
- }
- else //第四列输出低电平
- {
- ucKeySec=12; //触发12号键 对应朱兆祺学习板的S12键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=13; //触发13号键 对应朱兆祺学习板的S13键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=14; //触发14号键 对应朱兆祺学习板的S14键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=15; //触发15号键 对应朱兆祺学习板的S15键
- }
- else //第四列输出低电平
- {
- ucKeySec=16; //触发16号键 对应朱兆祺学习板的S16键
- }
- }
-
- }
-
- }
- break;
- }
- }
- /* 注释三:
- * 按键服务程序操作的精髓在于根据当前系统处于什么窗口下就执行什么操作。
- * 紧紧围绕着不同的窗口ucWd来执行不同的操作。
- */
- void key_service() //第三区 放在定时中断里的按键服务应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 1号键 对应朱兆祺学习板的S1键 红棋加分 按键
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- uiRedTotal=uiRedTotal+60; //加红棋分的时间,此处60秒代表一分
- if(uiRedTotal>5940)
- {
- uiRedTotal=5940;
- }
- uiRedTotal=uiRedTotal-(uiRedTotal%60); //去秒取整分
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 2:// 2号键 对应朱兆祺学习板的S2键 红棋减分 按键
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- if(uiRedTotal>=60)
- {
- uiRedTotal=uiRedTotal-60; //减红棋分的时间,此处60秒代表一分
- }
- uiRedTotal=uiRedTotal-(uiRedTotal%60); //去秒取整分
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 3号键 对应朱兆祺学习板的S3键 黑棋加分 按键
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- uiBlackTotal=uiBlackTotal+60; //加黑棋分的时间,此处60秒代表一分
- if(uiBlackTotal>5940)
- {
- uiBlackTotal=5940;
- }
- uiBlackTotal=uiBlackTotal-(uiBlackTotal%60); //去秒取整分
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 4:// 4号键 对应朱兆祺学习板的S4键 黑棋减分 按键
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- if(uiBlackTotal>=60)
- {
- uiBlackTotal=uiBlackTotal-60; //减黑棋分的时间,此处60秒代表一分
- }
- uiBlackTotal=uiBlackTotal-(uiBlackTotal%60); //去秒取整分
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 5:// 5号键 对应朱兆祺学习板的S5键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 6:// 6号键 对应朱兆祺学习板的S6键 中途暂停和启动按键
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- ucRedFlag=0; //暂停计时
- ucBlackFlag=0;//暂停计时
- ucWd=3; //切换到黑棋中途暂停的状态
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- ucRedFlag=0; //红棋暂停计时
- ucBlackFlag=1; //黑棋继续计时
- ucWd=2; //切换到黑棋正在运行中的状态
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- ucRedFlag=0; //暂停计时
- ucBlackFlag=0;//暂停计时
- ucWd=5; //切换到红棋中途暂停的状态
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- ucRedFlag=1; //红棋继续计时
- ucBlackFlag=0; //黑棋暂停计时
- ucWd=4; //切换到红棋正在运行中的状态
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 7:// 7号键 对应朱兆祺学习板的S7键 在第一个窗口下,把计时器的值恢复为开机时的默认值20分钟
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- uiRedTotal=1200; //红棋的总时间
- uiBlackTotal=1200; //黑棋的总时间
- ucDisplayUpdate=1; //更新显示
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 8:// 8号键 对应朱兆祺学习板的S8键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 9:// 9号键 对应朱兆祺学习板的S9键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 10:// 10号键 对应朱兆祺学习板的S10键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 11:// 11号键 对应朱兆祺学习板的S11键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 12:// 12号键 对应朱兆祺学习板的S12键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 13:// 13号键 对应朱兆祺学习板的S13键 红棋按下
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- ucRedFlag=0; //红棋暂停计时
- ucBlackFlag=1; //黑棋继续计时
- ucWd=2; //切换到黑棋正在运行中的状态
- ucDisplayUpdate=1; //更新显示
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- ucRedFlag=0; //红棋暂停计时
- ucBlackFlag=1; //黑棋继续计时
- ucWd=2; //切换到黑棋正在运行中的状态
- ucDisplayUpdate=1; //更新显示
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 14:// 14号键 对应朱兆祺学习板的S14键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 15:// 15号键 对应朱兆祺学习板的S15键
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 16:// 16号键 对应朱兆祺学习板的S16键 黑棋按下
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1,代表刚上电,完成或者复位后的状态
- ucRedFlag=1; //红棋继续计时
- ucBlackFlag=0; //黑棋暂停计时
- ucWd=4; //切换到红棋正在运行中的状态
- ucDisplayUpdate=1; //更新显示
- break;
- case 2: //窗口2,代表黑棋正在运行中的状态
- ucRedFlag=1; //红棋继续计时
- ucBlackFlag=0; //黑棋暂停计时
- ucWd=4; //切换到红棋正在运行中的状态
- ucDisplayUpdate=1; //更新显示
- break;
- case 3: //窗口3,代表黑棋在中途暂停的状态
- break;
- case 4: //窗口4,代表红棋正在运行中的状态
- break;
- case 5: //窗口5,代表红棋在中途暂停的状态
- break;
- }
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- }
- }
- void T0_time() interrupt 1
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- key_scan(); //放在定时中断里的按键扫描函数
- time_service(); //放在定时中断里的时间应用程序
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
- }
- display_drive(); //放在定时中断里的数码管驱动函数
- /* 注释四:
- * 注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
- */
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself() //第一区 初始化单片机
- {
- led_dr=1;
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00);
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral() //第二区 初始化外围
- {
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
这节讲了象棋比赛专用计时器的项目程序。为了继续加深读者理解按键和显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----带数码管显示的加法简易计算器。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-3-27 12:54
第三十六节:带数码管显示的加法简易计算器。
开场白:
这一节要做一个简单的计算器。这个计算器不带小数点,只能进行不超过8位数据的加法运算,它麻雀虽小但是五脏俱全,它能清晰地勾勒出商业计算器的程序框架和思路。读者只要看懂本节程序框架的规律,以后自己想做一个复杂一点的计算器应该是没问题的。复杂的计算器在算法上要用数组进行特殊处理,不能简单地直接用C语言的+,-,*,/运算符,这方面的内容我会在以后的章节中跟大家分享。
这一节要教会大家两个知识点:
第一个:数字按键的输入和十进制数值的移位方法。
第二个:继续加深理解按键与数码管的关联程序框架。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。加号键对应S13,等于号键对应S14,清除复位按键对应S16。其它按键不用。
(2)实现功能:
常用的加法计算器功能。有连加功能。
本程序有2个窗口。
第1个窗口:原始数据和运算结果窗口。 比如加法运算中的被加数
第2个窗口:第二个参与运行的数据窗口。比如加法运算中的加数
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_voice_long 900 //蜂鸣器长叫的持续时间
- #define const_key_time 10 //按键去抖动延时的时间
- #define const_1s 422 //产生一秒钟的时间基准
- void initial_myself();
- void initial_peripheral();
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- void T0_time(); //定时中断函数
- void key_service();
- void key_scan(); //按键扫描函数 放在定时中断里
- void number_key_input(unsigned long ucWhichKey); //由于数字按键的代码相似度高,因此封装在这个函数里
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(); //放在定时中断里的数码管驱动函数
- void display_service();
- sbit key_sr1=P0^0; //第一行输入
- sbit key_sr2=P0^1; //第二行输入
- sbit key_sr3=P0^2; //第三行输入
- sbit key_sr4=P0^3; //第四行输入
- sbit key_dr1=P0^4; //第一列输出
- sbit key_dr2=P0^5; //第二列输出
- sbit key_dr3=P0^6; //第三列输出
- sbit key_dr4=P0^7; //第四列输出
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit led_dr=P3^5; //LED指示灯
- sbit dig_hc595_sh_dr=P2^0; //数码管 的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucKeyStep=1; //按键扫描步骤变量
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt=0; //按键去抖动延时计数器
- unsigned char ucKeyLock=0; //按键触发后自锁的变量标志
- unsigned char ucRowRecord=1; //记录当前扫描到第几列了
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucDigShow8=0; //第8位数码管要显示的内容
- unsigned char ucDigShow7=0; //第7位数码管要显示的内容
- unsigned char ucDigShow6=0; //第6位数码管要显示的内容
- unsigned char ucDigShow5=0; //第5位数码管要显示的内容
- unsigned char ucDigShow4=0; //第4位数码管要显示的内容
- unsigned char ucDigShow3=0; //第3位数码管要显示的内容
- unsigned char ucDigShow2=0; //第2位数码管要显示的内容
- unsigned char ucDigShow1=0; //第1位数码管要显示的内容
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucDisplayUpdate=1; //更新显示标志
- unsigned long ulSource=0; //原始数据 比如在加法运算中的被加数
- unsigned long ulOther=0; //另外一个参与运算的数据 比如在加法运算中的加数
- unsigned long ulResult=0; //运算结果
- unsigned char ucOperator=0; //运行符号。0代表当前没有选择运行符号。1代表当前的运算符是加法。
- /* 注释一:
- * ucWd变量是本程序最核心的变量,代表数码管显示哪一个窗口
- * 本程序只有两个窗口,他们分别是:
- * 第一个窗口:原始数据和运算结果窗口。 比如加法运算中的被加数
- * 第二个窗口:第二个参与运行的数据窗口。比如加法运算中的加数
- */
- unsigned char ucWd=1;
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //不显示 序号10
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service();
- display_service();
- }
- }
- void display_service() //放在定时中断里的显示应用程序
- {
- if(ucDisplayUpdate==1) //有数据更新显示
- {
- ucDisplayUpdate=0;
- switch(ucWd) //本程序最核心的变量ucWd
- {
- case 1: //窗口1 原始数据和运算结果窗口
- if(ulSource>=10000000)
- {
- ucDigShow8=ulSource/10000000;
- }
- else
- {
- ucDigShow8=10;//数据显示空
- }
- if(ulSource>=1000000)
- {
- ucDigShow7=ulSource%10000000/1000000;
- }
- else
- {
- ucDigShow7=10;//数据显示空
- }
- if(ulSource>=100000)
- {
- ucDigShow6=ulSource%1000000/100000;
- }
- else
- {
- ucDigShow6=10;//数据显示空
- }
- if(ulSource>=10000)
- {
- ucDigShow5=ulSource%100000/10000;
- }
- else
- {
- ucDigShow5=10;//数据显示空
- }
- if(ulSource>=1000)
- {
- ucDigShow4=ulSource%10000/1000;
- }
- else
- {
- ucDigShow4=10;//数据显示空
- }
- if(ulSource>=100)
- {
- ucDigShow3=ulSource%1000/100;
- }
- else
- {
- ucDigShow3=10;//数据显示空
- }
- if(ulSource>=10)
- {
- ucDigShow2=ulSource%100/10;
- }
- else
- {
- ucDigShow2=10;//数据显示空
- }
- ucDigShow1=ulSource%10;
- break;
- case 2: //窗口2 第二个参与运算数据的窗口 比如加法运算中的加数
- if(ulOther>=10000000)
- {
- ucDigShow8=ulOther/10000000;
- }
- else
- {
- ucDigShow8=10;//数据显示空
- }
- if(ulOther>=1000000)
- {
- ucDigShow7=ulOther%10000000/1000000;
- }
- else
- {
- ucDigShow7=10;//数据显示空
- }
- if(ulOther>=100000)
- {
- ucDigShow6=ulOther%1000000/100000;
- }
- else
- {
- ucDigShow6=10;//数据显示空
- }
- if(ulOther>=10000)
- {
- ucDigShow5=ulOther%100000/10000;
- }
- else
- {
- ucDigShow5=10;//数据显示空
- }
- if(ulOther>=1000)
- {
- ucDigShow4=ulOther%10000/1000;
- }
- else
- {
- ucDigShow4=10;//数据显示空
- }
- if(ulOther>=100)
- {
- ucDigShow3=ulOther%1000/100;
- }
- else
- {
- ucDigShow3=10;//数据显示空
- }
- if(ulOther>=10)
- {
- ucDigShow2=ulOther%100/10;
- }
- else
- {
- ucDigShow2=10;//数据显示空
- }
- ucDigShow1=ulOther%10;
- break;
- }
- }
- }
- void display_drive() //放在定时中断里的数码管驱动函数
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- /* 注释二:
- * 注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
- */
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void key_scan()//按键扫描函数 放在定时中断里
- {
- switch(ucKeyStep)
- {
- case 1: //按键扫描输出第ucRowRecord列低电平
- if(ucRowRecord==1) //第一列输出低电平
- {
- key_dr1=0;
- key_dr2=1;
- key_dr3=1;
- key_dr4=1;
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- key_dr1=1;
- key_dr2=0;
- key_dr3=1;
- key_dr4=1;
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- key_dr1=1;
- key_dr2=1;
- key_dr3=0;
- key_dr4=1;
- }
- else //第四列输出低电平
- {
- key_dr1=1;
- key_dr2=1;
- key_dr3=1;
- key_dr4=0;
- }
- uiKeyTimeCnt=0; //延时计数器清零
- ucKeyStep++; //切换到下一个运行步骤
- break;
- case 2: //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
- uiKeyTimeCnt++;
- if(uiKeyTimeCnt>1)
- {
- uiKeyTimeCnt=0;
- ucKeyStep++; //切换到下一个运行步骤
- }
- break;
- case 3:
- if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
- {
- ucKeyStep=1; //如果没有按键按下,返回到第一个运行步骤重新开始扫描
- ucKeyLock=0; //按键自锁标志清零
- uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
-
- ucRowRecord++; //输出下一列
- if(ucRowRecord>4)
- {
- ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
- }
- }
- else if(ucKeyLock==0) //有按键按下,且是第一次触发
- {
- if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=1; //触发1号键 对应朱兆祺学习板的S1键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=2; //触发2号键 对应朱兆祺学习板的S2键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=3; //触发3号键 对应朱兆祺学习板的S3键
- }
- else //第四列输出低电平
- {
- ucKeySec=4; //触发4号键 对应朱兆祺学习板的S4键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=5; //触发5号键 对应朱兆祺学习板的S5键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=6; //触发6号键 对应朱兆祺学习板的S6键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=7; //触发7号键 对应朱兆祺学习板的S7键
- }
- else //第四列输出低电平
- {
- ucKeySec=8; //触发8号键 对应朱兆祺学习板的S8键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=9; //触发9号键 对应朱兆祺学习板的S9键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=10; //触发10号键 对应朱兆祺学习板的S10键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=11; //触发11号键 对应朱兆祺学习板的S11键
- }
- else //第四列输出低电平
- {
- ucKeySec=12; //触发12号键 对应朱兆祺学习板的S12键
- }
- }
-
- }
- else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
- {
- uiKeyTimeCnt++; //去抖动延时计数器
- if(uiKeyTimeCnt>const_key_time)
- {
- uiKeyTimeCnt=0;
- ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
- if(ucRowRecord==1) //第一列输出低电平
- {
- ucKeySec=13; //触发13号键 对应朱兆祺学习板的S13键
- }
- else if(ucRowRecord==2) //第二列输出低电平
- {
- ucKeySec=14; //触发14号键 对应朱兆祺学习板的S14键
- }
- else if(ucRowRecord==3) //第三列输出低电平
- {
- ucKeySec=15; //触发15号键 对应朱兆祺学习板的S15键
- }
- else //第四列输出低电平
- {
- ucKeySec=16; //触发16号键 对应朱兆祺学习板的S16键
- }
- }
-
- }
-
- }
- break;
- }
- }
- /* 注释三:
- * 按键服务程序操作的精髓在于根据当前系统处于什么窗口下,在此窗口下的运算符处于
- * 什么状态,然后紧紧围绕着不同的窗口ucWd,不同的ucOperator来执行不同的操作。
- */
- void key_service() //第三区 按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 1号键 对应朱兆祺学习板的S1键
- number_key_input(1); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 2:// 2号键 对应朱兆祺学习板的S2键
- number_key_input(2); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 3号键 对应朱兆祺学习板的S3键
- number_key_input(3); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 4:// 4号键 对应朱兆祺学习板的S4键
- number_key_input(4); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 5:// 5号键 对应朱兆祺学习板的S5键
- number_key_input(5); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 6:// 6号键 对应朱兆祺学习板的S6键
- number_key_input(6); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 7:// 7号键 对应朱兆祺学习板的S7键
- number_key_input(7); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 8:// 8号键 对应朱兆祺学习板的S8键
- number_key_input(8); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 9:// 9号键 对应朱兆祺学习板的S9键
- number_key_input(9); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 10:// 把这个按键专门用来输入数字0 对应朱兆祺学习板的S10键
- number_key_input(0); //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 11:// 11号键 对应朱兆祺学习板的S11键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 12:// 12号键 对应朱兆祺学习板的S12键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 13:// 13号键 加号按键 对应朱兆祺学习板的S13键
- switch(ucWd)
- {
- case 1: //在原始数据和运算结果的窗口下
- ucOperator=1; //加法
- ulOther=ulSource; //第二个运算数默认等于原始数
- ucDisplayUpdate=1; //刷新显示窗口
- break;
- case 2: //在第二个参与运算数据的窗口下
- ulResult=ulSource+ulOther;//连加
- ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
- ucWd=1; //切换到第一个窗口
- ucDisplayUpdate=1; //刷新显示窗口
- break;
- }
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 14:// 14号键 等于号按键 对应朱兆祺学习板的S14键
- switch(ucWd)
- {
- case 1: //在原始数据和运算结果的窗口下
- switch(ucOperator) //根据不同的运算符号进行不同的操作
- {
- case 0: //无运算符号
- break;
- case 1: //加法
- ulResult=ulSource+ulOther;//连加
- ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
- ucDisplayUpdate=1; //刷新显示窗口
- break;
- case 2: //减法 本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去
- break;
-
- }
- break;
- case 2: //在第二个参与运算数据的窗口下
- switch(ucOperator) //根据不同的运算符号进行不同的操作
- {
- case 1: //加法
- ulResult=ulSource+ulOther;//连加
- ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
- ucWd=1; //切换到第一个窗口
- ucDisplayUpdate=1; //刷新显示窗口
- break;
- case 2: //减法 本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去
- break;
-
- }
- break;
- }
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 15:// 15号键 对应朱兆祺学习板的S15键
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 16:// 16号键 清除按键 相当于复位的功能。重新输入数据 对应朱兆祺学习板的S16键
- ulSource=0;
- ulOther=0;
- ulResult=0;
- ucOperator=0;
- ucWd=1; //切换到第一个窗口
- ucDisplayUpdate=1; //刷新显示窗口
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- }
- }
- /* 注释四:
- * 此处参与运算的输入数字ucWhichKey记得用最大变量类型unsigned long,可以避免数据溢出等错误
- */
- void number_key_input(unsigned long ucWhichKey) //由于数字按键的代码相似度高,因此封装在这个函数里
- {
- switch(ucWd)
- {
- case 1: //在原始数据和运算结果的窗口下
- switch(ucOperator) //根据不同的运算符号进行不同的操作
- {
- case 0: //无运算符号 按键输入原始数据,比如被加输
- if(ulSource<=9999999) //最大只能输入8位数
- {
- ulSource=ulSource*10+ucWhichKey; //十进制的数值移位方法。
- }
- break;
- default: //在已经按下了运算符号的情况下
- ulOther=0; //第二个运算数先清零,再输入新的数据,然后马上切换到第2个窗口下
- ulOther=ucWhichKey;
- ucWd=2; //马上切换到第二个窗口下
- break;
-
- }
- ucDisplayUpdate=1; //刷新显示窗口
- break;
- case 2: //在第二个参与运算数据的窗口下 按键输入第二个参与运算的数据
- if(ulOther<=9999999) //最大只能输入8位数
- {
- ulOther=ulOther*10+ucWhichKey; //十进制的数值移位方法。
- }
- ucDisplayUpdate=1; //刷新显示窗口
- break;
- }
- }
- void T0_time() interrupt 1
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- key_scan(); //放在定时中断里的按键扫描函数
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
- }
- display_drive(); //放在定时中断里的数码管驱动函数
- /* 注释五:
- * 注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
- */
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself() //第一区 初始化单片机
- {
- led_dr=0;
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00);
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral() //第二区 初始化外围
- {
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
这节讲了加法简易计算器的程序项目。为了让读者理解运动,按键,显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----数码管作为仪表盘显示跑马灯的方向,速度和运行状态。
(未完待续,下节更精彩,不要走开哦)
作者: 东东 时间: 2014-3-28 18:59
不错,赞一个!
作者: jianhong_wu 时间: 2014-4-3 01:39
第三十七节:数码管作为仪表盘显示跑马灯的方向,速度和运行状态。
开场白:
我在第24节中讲过按键控制跑马灯的方向,速度和运行状态的项目程序,只可惜那个程序不能直观地显示运行中的三种状态,这节我决定在24节的基础上,增加一个数码管显示作为类似汽车仪表盘的界面,实时显示跑马灯的方向,速度,和运行状态。
这一节要教会大家一个知识点:继续加深理解运动,按键与数码管三者之间的关联程序框架。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。用S1键作为控制跑马灯的方向按键,S5键作为控制跑马灯方向的加速度按键,S9键作为控制跑马灯方向的减速度按键,S13键作为控制跑马灯方向的启动或者暂停按键。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。
(2)实现功能:
跑马灯运行:第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。用S1来改变方向。用S5和S9来改变速度,每按一次按键的递增或者递减以10为单位。
数码管显示:本程序只有1个窗口,这个窗口分成3个局部显示。8,7,6位数码管显示运行状态,启动时显示“on”,停止时显示“oFF”。5位数码管显示数码管方向,正向显示“n”,反向显示“U”。4,3,2,1位数码管显示速度。数值越大速度越慢,最慢的速度是550,最快的速度是50。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time1 20 //按键去抖动延时的时间
- #define const_key_time2 20 //按键去抖动延时的时间
- #define const_key_time3 20 //按键去抖动延时的时间
- #define const_key_time4 20 //按键去抖动延时的时间
- void initial_myself();
- void initial_peripheral();
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(); //显示数码管字模的驱动函数
- void display_service(); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
- void led_update(); //LED更新函数
- void T0_time(); //定时中断函数
- void key_service(); //按键服务的应用程序
- void key_scan();//按键扫描函数 放在定时中断里
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
- sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
- sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
- sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
- sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
- sbit led_dr=P3^5;
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
- unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
- unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
- unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt4=0; //按键去抖动延时计数器
- unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucLed_dr1=0; //代表16个灯的亮灭状态,0代表灭,1代表亮
- unsigned char ucLed_dr2=0;
- unsigned char ucLed_dr3=0;
- unsigned char ucLed_dr4=0;
- unsigned char ucLed_dr5=0;
- unsigned char ucLed_dr6=0;
- unsigned char ucLed_dr7=0;
- unsigned char ucLed_dr8=0;
- unsigned char ucLed_dr9=0;
- unsigned char ucLed_dr10=0;
- unsigned char ucLed_dr11=0;
- unsigned char ucLed_dr12=0;
- unsigned char ucLed_dr13=0;
- unsigned char ucLed_dr14=0;
- unsigned char ucLed_dr15=0;
- unsigned char ucLed_dr16=0;
- unsigned char ucLed_update=0; //刷新变量。每次更改LED灯的状态都要更新一次。
- unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
- unsigned int uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器
- unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量
- unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量
- unsigned char ucLedDirFlag=0; //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
- unsigned int uiSetTimeLevel_09_16=300; //速度变量,此数值越大速度越慢,此数值越小速度越快。
- unsigned char ucLedStartFlag=1; //启动和暂停的变量,0代表暂停,1代表启动
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Part1Update=1; //窗口1的局部1更新显示变量
- unsigned char ucWd1Part2Update=1; //窗口1的局部2更新显示变量
- unsigned char ucWd1Part3Update=1; //窗口1的局部3更新显示变量
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- 0x5c, //o 序号13
- 0x71, //F 序号14
- 0x3e, //U 序号15
- 0x37, //n 序号16
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- display_service(); //显示的窗口菜单服务程序
- led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
- led_update(); //LED更新函数
- }
- }
- /* 注释一:
- * 由于本程序只有1个窗口,而这个窗口又分成3个局部,因此可以省略去窗口变量uWd,
- * 只用三个局部变量ucWdxPartyUpdate就可以了。
- */
- void display_service() //显示的窗口菜单服务程序
- {
- if(ucWd1Part1Update==1) //更新显示当前系统是处于运行还是暂停的状态
- {
- ucWd1Part1Update=0; //及时把更新变量清零,防止一直进来更新
- if(ucLedStartFlag==1) //启动,显示on
- {
- ucDigShow8=13; //显示o
- ucDigShow7=16; //显示n
- ucDigShow6=10; //显示空
- }
- else //暂停,显示oFF
- {
- ucDigShow8=13; //显示o
- ucDigShow7=14; //显示F
- ucDigShow6=14; //显示F
- }
- }
- if(ucWd1Part2Update==1) //更新显示当前系统是处于正方向还是反方向
- {
- ucWd1Part2Update=0; //及时把更新变量清零,防止一直进来更新
- if(ucLedDirFlag==0) //正方向,向上,显示n
- {
- ucDigShow5=16; //显示n
- }
- else //反方向,向下,显示U
- {
- ucDigShow5=15; //显示U
- }
- }
- if(ucWd1Part3Update==1) //更新显示当前系统的速度,此数值越大速度越慢,此数值越小速度越快。
- {
- ucWd1Part3Update=0; //及时把更新变量清零,防止一直进来更新
- ucDigShow4=10; //显示空 这一位不用,作为空格
- if(uiSetTimeLevel_09_16>=100)
- {
- ucDigShow3=uiSetTimeLevel_09_16/100; //显示速度的百位
- }
- else
- {
- ucDigShow3=10; //显示空
- }
- if(uiSetTimeLevel_09_16>=10)
- {
- ucDigShow2=uiSetTimeLevel_09_16%100/10; //显示速度的十位
- }
- else
- {
- ucDigShow2=10; //显示空
- }
- ucDigShow1=uiSetTimeLevel_09_16%10; //显示速度的个位
- }
-
- }
- void key_scan()//按键扫描函数 放在定时中断里
- {
- if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock1=0; //按键自锁标志清零
- uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定时中断次数
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1; //自锁按键置位,避免一直触发
- ucKeySec=1; //触发1号键
- }
- }
- if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock2=0; //按键自锁标志清零
- uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定时中断次数
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1; //自锁按键置位,避免一直触发
- ucKeySec=2; //触发2号键
- }
- }
- if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock3=0; //按键自锁标志清零
- uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt3++; //累加定时中断次数
- if(uiKeyTimeCnt3>const_key_time3)
- {
- uiKeyTimeCnt3=0;
- ucKeyLock3=1; //自锁按键置位,避免一直触发
- ucKeySec=3; //触发3号键
- }
- }
- if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock4=0; //按键自锁标志清零
- uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt4++; //累加定时中断次数
- if(uiKeyTimeCnt4>const_key_time4)
- {
- uiKeyTimeCnt4=0;
- ucKeyLock4=1; //自锁按键置位,避免一直触发
- ucKeySec=4; //触发4号键
- }
- }
- }
- void key_service() //按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键
- if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
- {
- ucLedDirFlag=1;
- }
- else
- {
- ucLedDirFlag=0;
- }
- ucWd1Part2Update=1; //及时更新显示方向
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
- uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
- if(uiSetTimeLevel_09_16<50) //最快限定在50
- {
- uiSetTimeLevel_09_16=50;
- }
- ucWd1Part3Update=1; //及时更新显示速度
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 减速按键 对应朱兆祺学习板的S9键 uiSetTimeLevel_09_16越大速度越慢
- uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
- if(uiSetTimeLevel_09_16>550) //最慢限定在550
- {
- uiSetTimeLevel_09_16=550;
- }
- ucWd1Part3Update=1; //及时更新显示速度
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键 ucLedStartFlag为0时代表暂停,为1时代表启动
- if(ucLedStartFlag==1) //启动和暂停两种状态循环切换
- {
- ucLedStartFlag=0;
- }
- else //启动和暂停两种状态循环切换
- {
- ucLedStartFlag=1;
- }
- ucWd1Part1Update=1; //及时更新显示系统的运行状态,是运行还是暂停.
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- }
- }
- void led_update() //LED更新函数
- {
- if(ucLed_update==1)
- {
- ucLed_update=0; //及时清零,让它产生只更新一次的效果,避免一直更新。
- if(ucLed_dr1==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x01;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0xfe;
- }
- if(ucLed_dr2==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x02;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0xfd;
- }
- if(ucLed_dr3==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x04;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0xfb;
- }
- if(ucLed_dr4==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x08;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0xf7;
- }
- if(ucLed_dr5==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x10;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0xef;
- }
- if(ucLed_dr6==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x20;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0xdf;
- }
- if(ucLed_dr7==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x40;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0xbf;
- }
- if(ucLed_dr8==1)
- {
- ucLedStatus08_01=ucLedStatus08_01|0x80;
- }
- else
- {
- ucLedStatus08_01=ucLedStatus08_01&0x7f;
- }
- if(ucLed_dr9==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x01;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0xfe;
- }
- if(ucLed_dr10==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x02;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0xfd;
- }
- if(ucLed_dr11==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x04;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0xfb;
- }
- if(ucLed_dr12==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x08;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0xf7;
- }
- if(ucLed_dr13==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x10;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0xef;
- }
- if(ucLed_dr14==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x20;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0xdf;
- }
- if(ucLed_dr15==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x40;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0xbf;
- }
- if(ucLed_dr16==1)
- {
- ucLedStatus16_09=ucLedStatus16_09|0x80;
- }
- else
- {
- ucLedStatus16_09=ucLedStatus16_09&0x7f;
- }
- hc595_drive(ucLedStatus16_09,ucLedStatus08_01); //74HC595底层驱动函数
- }
- }
- void display_drive()
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
- {
- if(ucLedStartFlag==1) //此变量为1时代表启动
- {
- switch(ucLedStep_09_16)
- {
- case 0:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr16=0; //第16个灭
- ucLed_dr9=1; //第9个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=1; //切换到下一个步骤
- }
- else //反方向
- {
- ucLed_dr15=1; //第15个亮
- ucLed_dr16=0; //第16个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=7; //返回上一个步骤
- }
- }
- break;
- case 1:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr9=0; //第9个灭
- ucLed_dr10=1; //第10个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=2; //切换到下一个步骤
- }
- else //反方向
- {
- ucLed_dr16=1; //第16个亮
- ucLed_dr9=0; //第9个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=0; //返回上一个步骤
- }
- }
- break;
- case 2:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr10=0; //第10个灭
- ucLed_dr11=1; //第11个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=3; //切换到下一个步骤
- }
- else //反方向
- {
- ucLed_dr9=1; //第9个亮
- ucLed_dr10=0; //第10个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=1; //返回上一个步骤
- }
- }
- break;
- case 3:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr11=0; //第11个灭
- ucLed_dr12=1; //第12个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=4; //切换到下一个步骤
- }
- else //反方向
- {
- ucLed_dr10=1; //第10个亮
- ucLed_dr11=0; //第11个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=2; //返回上一个步骤
- }
- }
- break;
- case 4:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr12=0; //第12个灭
- ucLed_dr13=1; //第13个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=5; //切换到下一个步骤
- }
- else //反方向
- {
- ucLed_dr11=1; //第11个亮
- ucLed_dr12=0; //第12个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=3; //返回上一个步骤
- }
- }
- break;
- case 5:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr13=0; //第13个灭
- ucLed_dr14=1; //第14个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=6; //切换到下一个步骤
- }
- else //反方向
- {
- ucLed_dr12=1; //第12个亮
- ucLed_dr13=0; //第13个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=4; //返回上一个步骤
- }
- }
- break;
- case 6:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr14=0; //第14个灭
- ucLed_dr15=1; //第15个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=7; //切换到下一个步骤
- }
- else //反方向
- {
- ucLed_dr13=1; //第13个亮
- ucLed_dr14=0; //第14个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=5; //返回上一个步骤
- }
- }
- break;
- case 7:
- if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
- {
- uiTimeCnt_09_16=0; //时间计数器清零
- if(ucLedDirFlag==0) //正方向
- {
- ucLed_dr15=0; //第15个灭
- ucLed_dr16=1; //第16个亮
- ucLed_update=1; //更新显示
- ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
- }
- else //反方向
- {
- ucLed_dr14=1; //第14个亮
- ucLed_dr15=0; //第15个灭
- ucLed_update=1; //更新显示
- ucLedStep_09_16=6; //返回上一个步骤
- }
- }
- break;
-
- }
- }
- }
- void T0_time() interrupt 1
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(uiTimeCnt_09_16<0xffff) //设定这个条件,防止uiTimeCnt超范围。
- {
- if(ucLedStartFlag==1) //此变量为1时代表启动
- {
- uiTimeCnt_09_16++; //累加定时中断的次数,
- }
- }
- key_scan(); //按键扫描函数
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
- // beep_dr=1; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
- // beep_dr=0; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
- }
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself() //第一区 初始化单片机
- {
- /* 注释二:
- * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
- * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
- * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
- */
- key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
- led_dr=0; //关闭独立LED灯
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral() //第二区 初始化外围
- {
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
前面花了大量的章节在讲数码管显示,按键,运动的关联程序框架,从下一节开始,我将会用八节内容来讲我常用的串口程序框架,内容非常精彩和震撼,思路非常简单而又实用
。欲知详情,请听下回分解-----判断数据尾来接收一串数据的串口通用程序框架。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-4-5 11:19
本帖最后由 jianhong_wu 于 2014-4-6 22:29 编辑
第三十八节:判断数据尾来接收一串数据的串口通用程序框架。
开场白:
在实际项目中,串口通讯不可能一次通讯只发送或接收一个字节,大部分的项目都是一次发送或者接受一串的数据。我们还要在这一串数据里解析数据协议,提取有用的数据。
这一节要教会大家三个知识点:
第一个:如何识别一串数据已经发送接收完毕。
第二个:如何在已经接收到的一串数据中解析数据尾协议并且提取有效数据。
第三个:接收一串数据的通用程序框架涉及到main循环里的串口服务程序,定时器的计时程序,串口接收中断程序的密切配合。大家要理解它们三者之间是如何关联起来的。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600 。
通讯协议:XX YY EB 00 55
其中后三位 EB 00 55就是我所说的数据尾,它的有效数据XX YY在数据尾的前面。
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字前面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据尾和有效数据都是正确的。
(3)源代码讲解如下:
总结陈词:
这一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这样的程序该怎么写?欲知详情,请听下回分解-----判断数据头来接收一串数据的串口通用程序框架。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-4-6 12:27
本帖最后由 jianhong_wu 于 2014-4-7 08:58 编辑
第三十九节:判断数据头来接收一串数据的串口通用程序框架。
开场白:
上一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这一节要教会大家两个知识点:
第一个:如何在已经接收到的一串数据中解析数据头协议并且提取有效数据。
第二个:无论是判断数据头还是判断数据尾,无论是单片机还是上位机,最好在固定协议前多发送一个填充的无效字节0x00,因为硬件原因,第一个字节往往容易丢失。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600 。
通讯协议:EB 00 55 XX YY
加无效填充字节后,上位机实际上应该发送:00 EB 00 55 XX YY
其中第1位00是无效填充字节,防止由于硬件原因丢失第一个字节。
其中第2,3,4位EB 00 55就是数据头
后2位XX YY就是有效数据
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字后面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。
(3)源代码讲解如下:
总结陈词:
这一节讲了常用的判断数据头来接收一串数据的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,有效数据长度,有效数据,数据校验的通讯协议。这样的程序该怎么写?欲知详情,请听下回分解-----常用的自定义串口通讯协议。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-4-7 13:14
本帖最后由 jianhong_wu 于 2014-7-21 00:17 编辑
第四十节:常用的自定义串口通讯协议。
开场白:
上一节讲了判断数据头的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,数据地址,有效数据长度,有效数据,数据校验的通讯协议。这一节要教会大家三个知识点:
第一个:常用自定义串口通讯协议的程序框架。
第二个:累加校验和的校验方法。累加和的意思是前面所有字节的数据相加,超过一个字节的溢出部分会按照固定的规则自动丢弃,不用我们管。比如以下数据:
eb 00 55 01 00 02 0028 6b
其中eb 00 55为数据头,01为数据类型,00 02为有效数据长度,00 28 分别为具体的有效数据,6b为前面所有字节的累加和。累加和可以用电脑系统自带的计算器来验证。打开电脑上的计算器,点击“查看”下拉的菜单,选“科学型”,然后选左边的“十六进制”,最后选右边的“字节”,然后把前面所有的字节相加,它们的和就是6b,没错吧。
第三个:原子锁的使用方法,实际上是借鉴了"红金龙吸味"关于原子锁的建议,专门用来保护中断与主函数的共享数据。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600.
通讯协议:EB 00 55 GG HH HH XX XX …YYYY CY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表驱动奉命,02代表驱动Led灯。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中从第7位开始,到最后一个字节Cy之前,XX..YY都是具体的有效数据。
在本程序中,当数据类型是01时,有效数据代表蜂鸣器鸣叫的时间长度。当数据类型是02时,有效数据代表Led灯点亮的时间长度。
最后一个字节CY是累加和,前面所有字节的累加。
发送以下测试数据,将会分别控制蜂鸣器和Led灯的驱动时间长度。
蜂鸣器短叫发送:eb 00 55 01 00 02 00 28 6b
蜂鸣器长叫发送:eb 00 55 01 00 02 00 fa 3d
Led灯短亮发送:eb 00 55 02 00 02 00 28 6c
Led灯长亮发送:eb 00 55 02 00 02 00 fa3e
(3)源代码讲解如下:
总结陈词:
这一节讲了常用的自定义串口通讯协议的程序框架,这种框架在判断一串数据是否接收完毕的时候,都是靠“超过规定的时间内,没有发现串口数据”来判定的,这是我做绝大多数项目的串口程序框架,但是在少数要求实时反应非常快的项目中,我会用另外一种响应速度更快的串口程序框架,这种程序框架是什么样的?欲知详情,请听下回分解-----在串口接收中断里即时解析数据头的特殊程序框架。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-4-19 11:51
第四十一节:在串口接收中断里即时解析数据头的特殊程序框架。
开场白:
上一节讲了常用的自定义串口通讯协议的程序框架,这种框架在判断一串数据是否接收完毕的时候,都是靠“超过规定的时间内,没有发现串口数据”来判定的,这是我做绝大多数项目的串口程序框架,但是在少数要求实时反应非常快的项目中,这样的程序框架可能会满足不了系统对速度的要求,这一节就是要介绍另外一种响应速度更加快的串口程序框架,要教会大家一个知识点:在串口接收中断里即时解析数据头的特殊程序框架。我在这种程序框架里,会尽量简化数据头和数据尾,同时也简化校验,目的都是为了提高响应速度。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600.
通讯协议:EB GG XX XX XX XX ED
其中第1位EB就是数据头.
其中第2位GG就是数据类型。01代表驱动蜂鸣器,02代表驱动Led灯。
其中第3,4,5,6位XX就是有效数据长度。高位在左,低位在右。
其中第7位ED就是数据尾,在这里也起一部分校验的作用,虽然不是累加和的方式。
在本程序中,
当数据类型是01时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么蜂鸣器就鸣叫一声表示正确。
当数据类型是02时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么LED灯就会闪烁一下表示正确。
十进制的123456789等于十六进制的75bcd15 。
发送以下测试数据,将会分别控制蜂鸣器Led灯。
控制蜂鸣器发送:eb 01 07 5b cd 15 ed
控制LED灯发送:eb 02 07 5b cd 15 ed
(3)源代码讲解如下:
总结陈词:
前面花了4节内容仔细讲了各种串口接收数据的常用框架,从下一节开始,我开始讲串口发送数据的程序框架,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用delay延时方式发送一串数据。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-4-22 10:57
第四十二节:通过串口用delay延时方式发送一串数据。
开场白:
上一节讲了在串口接收中断里即时解析数据头的特殊程序框架。这节开始讲串口发送数据需要特别注意的地方和程序框架,要教会大家一个知识点:根据我个人的经验,在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。当然,也有一些朋友可能不增加延时,直接靠单片机自带的发送完成标志位来判断,但是我以前在做项目中,感觉单单靠发送完成标志位来判断还是容易出错(当然也有可能是我自身程序的问题),所以后来在大部分的项目中我就干脆靠延时来等待它发送完成。我在51,PIC单片机中都是这么做的。但是,凭我的经验,在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600.
按一次按键S1,单片机就往上位机发送以下一串数据:
eb 00 55 01 00 00 00 00 41
(3)源代码讲解如下:
总结陈词:
这节在每个字节之间都添加了delay延时来等待每个字节的发送完成,由于delay(400)这个时间还不算很长,所以可以应用在很多简单任务的系统中。但是在某些任务量很多的系统中,实时运行的主任务不允许被长时间和经常性地中断,这个时候就需要用计数延时来替代delay延时,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用计数延时方式发送一串数据。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-4-25 11:02
本帖最后由 jianhong_wu 于 2014-4-26 10:25 编辑
第四十三节:通过串口用计数延时方式发送一串数据。
开场白:
上一节讲了通过串口用delay延时方式发送一串数据,这种方式要求发送一串数据的时候一气呵成,期间不能执行其它任务,由于delay(400)这个时间还不算很长,所以可以应用在很多简单任务的系统中。但是在某些任务量很多的系统中,实时运行的主任务不允许被长时间和经常性地中断,这个时候就需要用计数延时来替代delay延时。本节要教会大家两个知识点:
第一个:用计数延时方式发送一串数据的程序框架。
第二个:环形消息队列的程序框架。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600.
用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
按一次按键S1,发送EB 00 55 01 00 00 00 00 41
按一次按键S5,发送EB 00 55 02 00 00 00 00 42
按一次按键S9,发送EB 00 55 03 00 00 00 00 43
按一次按键S13,发送EB 00 55 04 00 00 00 00 44
(3)源代码讲解如下:
总结陈词:
前面几个章节中,每个章节要么独立地讲解串口收数据,要么独立地讲解发数据,实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。读者只要把我的串口收发程序结合起来,就很容易实现这样的功能,我就不再详细讲解了。从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-5-3 08:45
本帖最后由 jianhong_wu 于 2014-7-21 00:20 编辑
第四十四节:从机的串口收发综合程序框架
开场白:
根据上一节的预告,本来这一节内容打算讲“利用AT24C02进行掉电后的数据保存”的,但是由于网友“261854681”强烈建议我讲一个完整的串口收发程序实例,因此我决定再花两节篇幅讲讲这方面的内容。
实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
这节先讲从机的收发端程序实例。要教会大家三个知识点:
第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:从机端的收发端程序框架。
第三个:从机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
一共有4个窗口。每个窗口显示一个参数。有两种更改参数的方式:
第一种:按键更改参数:
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是复位按键,当通讯超时蜂鸣器报警时,可以按下此键清除报警。
第二种:通过串口来更改参数:
波特率是:9600.
通讯协议:EB 00 55 GG 00 02 XX XX CY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表更改参数1,02代表更改参数2,03代表更改参数3,04代表更改参数4,
其中第5,6位00 02就是有效数据长度。高位在左,低位在右。
其中从第7,8位XX XX是被更改的参数。高位在左,低位在右。
第9位CY是累加和,前面所有字节的累加。
一个完整的通讯必须接收完4串数据,每串数据之间的间隔时间不能超过10秒钟,否则认为通讯超时出错引发蜂鸣器报警。如果接收到得数据校验正确,
则返回校验正确应答:eb 00 55 f5 00 00 35,
否则返回校验出错应答::eb 00 55 fa 00 00 3a。
系统处于待机状态时,LED灯一直亮,
系统处于非待机状态时,LED灯闪烁,
系统处于通讯超时出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。
通过电脑的串口助手,依次发送以下测试数据,将会分别更改参数1,参数2,参数3,参数4。注意,每串数据之间的时间最大不能超过10秒,否则系统认为通讯超时报警。
把参数1更改为十进制的1: eb 00 55 01 00 02 00 01 44
把参数2更改为十进制的12: eb 00 55 02 00 02 00 0c 50
把参数3更改为十进制的123: eb 00 55 03 00 02 00 7b c0
把参数4更改为十进制的1234:eb 00 55 04 00 02 04 d2 1c
(3)源代码讲解如下:- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time1 20 //按键去抖动延时的时间
- #define const_key_time2 20 //按键去抖动延时的时间
- #define const_key_time3 20 //按键去抖动延时的时间
- #define const_key_time4 20 //按键去抖动延时的时间
- #define const_led_0_5s 200 //大概0.5秒的时间
- #define const_led_1s 400 //大概1秒的时间
- #define const_send_time_out 4000 //通讯超时出错的时间 大概10秒
- #define const_rc_size 20 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- #define const_send_size 10 //串口发送数据的缓冲区数组大小
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口服务程序,在main函数里
- void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时
- void key_service(void); //按键服务的应用程序
- void key_scan(void);//按键扫描函数 放在定时中断里
- void status_service(void); //状态显示的应用程序
- sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
- sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
- sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
- sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
- sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit led_dr=P3^5; //作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- unsigned char ucSendCntLock=0; //串口计时器的原子锁
- unsigned char ucRcType=0; //数据类型
- unsigned int uiRcSize=0; //数据长度
- unsigned char ucRcCy=0; //校验累加和
- unsigned int uiLedCnt=0; //控制Led闪烁的延时计时器
- unsigned int uiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
- unsigned char ucSendTimeOutLock=0; //原子锁
- unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
- unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
- unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
- unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt4=0; //按键去抖动延时计数器
- unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucVoiceLock=0; //蜂鸣器鸣叫的原子锁
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Update=1; //窗口1更新显示标志
- unsigned char ucWd2Update=0; //窗口2更新显示标志
- unsigned char ucWd3Update=0; //窗口3更新显示标志
- unsigned char ucWd4Update=0; //窗口4更新显示标志
- unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- unsigned int uiSetData1=0; //本程序中需要被设置的参数1
- unsigned int uiSetData2=0; //本程序中需要被设置的参数2
- unsigned int uiSetData3=0; //本程序中需要被设置的参数3
- unsigned int uiSetData4=0; //本程序中需要被设置的参数4
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- usart_service(); //串口服务程序
- display_service(); //显示的窗口菜单服务程序
- status_service(); //状态显示的应用程序
- }
- }
- void status_service(void) //状态显示的应用程序
- {
- if(ucStatus!=0) //处于非待机的状态,Led闪烁
- {
- if(uiLedCnt<const_led_0_5s) //大概0.5秒
- {
- led_dr=1; //前半秒亮
- if(ucStatus==2) //处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
- {
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- }
- }
- else if(uiLedCnt<const_led_1s) //大概1秒
- {
- led_dr=0; //前半秒灭
- }
- else
- {
- uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
- }
-
- }
- else //处于待机状态,Led一直亮
- {
- led_dr=1;
-
- }
- }
- void usart_service(void) //串口服务程序,在main函数里
- {
- unsigned int i;
-
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
- ucRcType=ucRcregBuf[uiRcMoveIndex+3]; //数据类型 一个字节
- uiRcSize=ucRcregBuf[uiRcMoveIndex+4]; //数据长度 两个字节
- uiRcSize=uiRcSize<<8;
- uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
-
- ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]; //记录最后一个字节的校验
- ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0; //清零最后一个字节的累加和变量
- for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
- {
- ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
- }
- if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize]) //如果校验正确,则进入以下数据处理
- {
- switch(ucRcType) //根据不同的数据类型来做不同的数据处理
- {
- case 0x01: //设置参数1
- ucStatus=1; //从设置参数1开始,表示当前处于正在发送数据的状态
- uiSetData1=ucRcregBuf[uiRcMoveIndex+6]; //把两个字节合并成一个int类型的数据
- uiSetData1=uiSetData1<<8;
- uiSetData1=uiSetData1+ucRcregBuf[uiRcMoveIndex+7];
- ucWd1Update=1; //窗口1更新显示
- break;
-
- case 0x02: //设置参数2
- uiSetData2=ucRcregBuf[uiRcMoveIndex+6]; //把两个字节合并成一个int类型的数据
- uiSetData2=uiSetData2<<8;
- uiSetData2=uiSetData2+ucRcregBuf[uiRcMoveIndex+7];
- ucWd2Update=1; //窗口2更新显示
- break;
- case 0x03: //设置参数3
- uiSetData3=ucRcregBuf[uiRcMoveIndex+6]; //把两个字节合并成一个int类型的数据
- uiSetData3=uiSetData3<<8;
- uiSetData3=uiSetData3+ucRcregBuf[uiRcMoveIndex+7];
- ucWd3Update=1; //窗口3更新显示
- break;
- case 0x04: //设置参数4
- ucStatus=0; //从设置参数4结束发送数据的状态,表示发送数据的过程成功,切换回待机状态
- uiSetData4=ucRcregBuf[uiRcMoveIndex+6]; //把两个字节合并成一个int类型的数据
- uiSetData4=uiSetData4<<8;
- uiSetData4=uiSetData4+ucRcregBuf[uiRcMoveIndex+7];
- ucWd4Update=1; //窗口4更新显示
- break;
-
- }
- ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0xf5; //代表校验正确
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x00;
- ucSendregBuf[6]=0x35;
- for(i=0;i<7;i++) //返回校验正确的应答指令
- {
- eusart_send(ucSendregBuf[i]); //发送一串数据给上位机
- }
- }
- else
- {
- ucSendTimeOutLock=1; //原子锁加锁
- uiSendTimeOutCnt=0; //超时计时器计时清零
- ucSendTimeOutLock=0; //原子锁解锁
- ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0xfa; //代表校验错误
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x00;
- ucSendregBuf[6]=0x3a;
- for(i=0;i<7;i++) //返回校验错误的应答指令
- {
- eusart_send(ucSendregBuf[i]); //发送一串数据给上位机
- }
-
- }
- ucSendTimeOutLock=1; //原子锁加锁
- uiSendTimeOutCnt=0; //超时计时器计时清零
- ucSendTimeOutLock=0; //原子锁解锁
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- {
- case 1: //显示P--1窗口的数据
- if(ucWd1Update==1) //窗口1要全部更新显示
- {
- ucWd1Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=1; //第6位数码管显示1
- ucDigShow5=10; //第5位数码管显示无
- //先分解数据
- ucTemp4=uiSetData1/1000;
- ucTemp3=uiSetData1%1000/100;
- ucTemp2=uiSetData1%100/10;
- ucTemp1=uiSetData1%10;
-
- //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
- if(uiSetData1<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData1<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData1<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 2: //显示P--2窗口的数据
- if(ucWd2Update==1) //窗口2要全部更新显示
- {
- ucWd2Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=2; //第6位数码管显示2
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData2/1000; //分解数据
- ucTemp3=uiSetData2%1000/100;
- ucTemp2=uiSetData2%100/10;
- ucTemp1=uiSetData2%10;
- if(uiSetData2<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData2<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData2<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 3: //显示P--3窗口的数据
- if(ucWd3Update==1) //窗口3要全部更新显示
- {
- ucWd3Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=3; //第6位数码管显示3
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData3/1000; //分解数据
- ucTemp3=uiSetData3%1000/100;
- ucTemp2=uiSetData3%100/10;
- ucTemp1=uiSetData3%10;
- if(uiSetData3<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData3<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData3<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 4: //显示P--4窗口的数据
- if(ucWd4Update==1) //窗口4要全部更新显示
- {
- ucWd4Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=4; //第6位数码管显示4
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData4/1000; //分解数据
- ucTemp3=uiSetData4%1000/100;
- ucTemp2=uiSetData4%100/10;
- ucTemp1=uiSetData4%10;
- if(uiSetData4<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData4<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData4<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- }
-
- }
- void key_scan(void)//按键扫描函数 放在定时中断里
- {
- if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock1=0; //按键自锁标志清零
- uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定时中断次数
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1; //自锁按键置位,避免一直触发
- ucKeySec=1; //触发1号键
- }
- }
- if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock2=0; //按键自锁标志清零
- uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定时中断次数
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1; //自锁按键置位,避免一直触发
- ucKeySec=2; //触发2号键
- }
- }
- if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock3=0; //按键自锁标志清零
- uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt3++; //累加定时中断次数
- if(uiKeyTimeCnt3>const_key_time3)
- {
- uiKeyTimeCnt3=0;
- ucKeyLock3=1; //自锁按键置位,避免一直触发
- ucKeySec=3; //触发3号键
- }
- }
- if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock4=0; //按键自锁标志清零
- uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt4++; //累加定时中断次数
- if(uiKeyTimeCnt4>const_key_time4)
- {
- uiKeyTimeCnt4=0;
- ucKeyLock4=1; //自锁按键置位,避免一直触发
- ucKeySec=4; //触发4号键
- }
- }
- }
- void key_service(void) //按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 加按键 对应朱兆祺学习板的S1键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1++;
- if(uiSetData1>9999) //最大值是9999
- {
- uiSetData1=9999;
- }
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2++;
- if(uiSetData2>9999) //最大值是9999
- {
- uiSetData2=9999;
- }
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3++;
- if(uiSetData3>9999) //最大值是9999
- {
- uiSetData3=9999;
- }
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4++;
- if(uiSetData4>9999) //最大值是9999
- {
- uiSetData4=9999;
- }
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 2:// 减按键 对应朱兆祺学习板的S5键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1--;
- if(uiSetData1>9999)
- {
- uiSetData1=0; //最小值是0
- }
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2--;
- if(uiSetData2>9999)
- {
- uiSetData2=0; //最小值是0
- }
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3--;
- if(uiSetData3>9999)
- {
- uiSetData3=0; //最小值是0
- }
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4--;
- if(uiSetData4>9999)
- {
- uiSetData4=0; //最小值是0
- }
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
- ucWd++; //切换窗口
- if(ucWd>4)
- {
- ucWd=1;
- }
- switch(ucWd) //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
- {
- case 1:
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 4:// 复位按键 对应朱兆祺学习板的S13键
- switch(ucStatus) //在不同的状态下,进行不同的操作
- {
- case 0: //处于待机状态
- break;
- case 1: //处于正在通讯的过程
- break;
- case 2: //发送数据出错,比如中间超时没有接收到数据
- ucStatus=0; //切换回待机的状态
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- if(ucSendCntLock==0) //原子锁判断
- {
- ucSendCntLock=1; //加锁
- uiSendCnt=0; //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
- ucSendCntLock=0; //解锁
- }
-
- }
- else //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
- {
- TI = 0; //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
- }
-
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- /* 注释一:
- * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
- */
- if(ucSendCntLock==0) //原子锁判断
- {
- ucSendCntLock=1; //加锁
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- ucSendCntLock=0; //解锁
- }
- if(ucVoiceLock==0) //原子锁判断
- {
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
-
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
-
- }
- }
- if(ucStatus!=0) //处于非待机的状态,Led闪烁
- {
- uiLedCnt++; //Led闪烁计时器不断累加
- }
- if(ucStatus==1) //处于正在通讯的状态,
- {
- if(ucSendTimeOutLock==0) //原子锁判断
- {
- uiSendTimeOutCnt++; //超时计时器累加
- if(uiSendTimeOutCnt>const_send_time_out) //超时出错
- {
- uiSendTimeOutCnt=0;
- ucStatus=2; //切换到出错报警状态
- }
- }
- }
- key_scan(); //按键扫描函数
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- /* 注释二:
- * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
- * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
- * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
- */
- key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
- led_dr=1; //点亮独立LED灯
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- /* 注释三:
- * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
- * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
- */
- IP =0x10; //把串口中断设置为最高优先级,必须的。
- TH1=TL1=-(11059200L/12/32/9600); //串口波特率为9600。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
这节详细讲了从机收发端的程序框架,而主机端的程序则用电脑的串口助手来模拟。实际上,主机端的程序也有很多内容,它包括依次发送每一串数据,根据返回的应答来决定是否需要重发数据,重发三次如果没反应则进行报错,以及超时没接收到数据等等内容。主机收发端的程序框架是什么样的?欲知详情,请听下回分解-----主机的串口收发综合程序框架
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-5-5 12:10
本帖最后由 jianhong_wu 于 2014-11-29 22:52 编辑
第四十五节:主机的串口收发综合程序框架
开场白:
在大部分的项目中,串口都需要“一收一应答”的握手协议,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
上一节已经讲了从机,这节就讲主机的收发端程序实例。要教会大家四个知识点:
第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:主机端的收发端程序框架。包括重发,超时检测等等。
第三个:主机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。
第四个:其实上一节的LED灯闪烁的时间里,我忘了加原子锁,不加原子锁的后果是,闪烁的时间有时候会不一致,所以这节多增加一个原子锁变量ucLedLock,再次感谢“红金龙吸味”关于原子锁的建议,真的很好用。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
一共有4个窗口。每个窗口显示一个参数。串口可以把当前设置的4个数据发送给从机。从机端可以用电脑的串口助手来模拟。
按键更改参数:
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是启动发送数据和复位按键,当系统处于待机状态时,按下此按键会启动发送数据,注意,单片机每发送一串数据,必须在电脑串口助手端发送应答信号,否则单片机重发3次后会引发超时报警;当通讯超时蜂鸣器报警时,可以按下此键清除报警,返回到待机的状态。
通过电脑的串口助手来模拟从机,返回不同的应答
从机返回校验正确应答:eb 00 55 f5 00 00 35
从机返回校验出错应答:eb 00 55 fa 00 00 3a
系统处于待机状态时,LED灯一直亮,
系统处于非待机状态时,LED灯闪烁,
系统处于出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。
(3)源代码讲解如下:- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time1 20 //按键去抖动延时的时间
- #define const_key_time2 20 //按键去抖动延时的时间
- #define const_key_time3 20 //按键去抖动延时的时间
- #define const_key_time4 20 //按键去抖动延时的时间
- #define const_led_0_5s 200 //大概0.5秒的时间
- #define const_led_1s 400 //大概1秒的时间
- #define const_send_time_out 4000 //通讯超时出错的时间 大概10秒
- #define const_rc_size 20 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- #define const_send_size 10 //串口发送数据的缓冲区数组大小
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口接收服务程序,在main函数里
- void communication_service(void); //一发一收的通讯服务程序
- void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时
- void key_service(void); //按键服务的应用程序
- void key_scan(void);//按键扫描函数 放在定时中断里
- void status_service(void); //状态显示的应用程序
- sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
- sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
- sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
- sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
- sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit led_dr=P3^5; //作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- unsigned char ucSendCntLock=0; //串口计时器的原子锁
- unsigned char ucRcType=0; //数据类型
- unsigned int uiRcSize=0; //数据长度
- unsigned char ucRcCy=0; //校验累加和
- unsigned char ucLedLock=0; //原子锁
- unsigned int uiLedCnt=0; //控制Led闪烁的延时计时器
- unsigned int uiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
- unsigned char ucSendTimeOutLock=0; //原子锁
- unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错
- unsigned char ucSendStep=0; //发送数据的过程步骤
- unsigned char ucErrorCnt=0; //累计错误总数
- unsigned char ucSendTotal=0; //记录当前已经发送了多少串数据
- unsigned char ucReceiveStatus=0; //返回的数据状态 0代表待机 1代表校验正确 2代表校验出错
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
- unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
- unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
- unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt4=0; //按键去抖动延时计数器
- unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucVoiceLock=0; //蜂鸣器鸣叫的原子锁
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Update=1; //窗口1更新显示标志
- unsigned char ucWd2Update=0; //窗口2更新显示标志
- unsigned char ucWd3Update=0; //窗口3更新显示标志
- unsigned char ucWd4Update=0; //窗口4更新显示标志
- unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- unsigned int uiSetData1=0; //本程序中需要被设置的参数1
- unsigned int uiSetData2=0; //本程序中需要被设置的参数2
- unsigned int uiSetData3=0; //本程序中需要被设置的参数3
- unsigned int uiSetData4=0; //本程序中需要被设置的参数4
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- usart_service(); //串口接收服务程序
- communication_service(); //一发一收的通讯服务程序
- display_service(); //显示的窗口菜单服务程序
- status_service(); //状态显示的应用程序
- }
- }
- void communication_service(void) //一发一收的通讯服务程序
- {
- unsigned int i;
- if(ucStatus==1) //处于正在通讯的过程中
- {
- switch(ucSendStep)
- {
- case 0: //通讯过程0 发送一串数据
- switch(ucSendTotal) //根据当前已经发送到第几条数据来决定发送哪些参数
- {
- case 0: //发送参数1
- ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x01; //代表发送参数1
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x02; //代表发送2个字节的有效数据
- ucSendregBuf[6]=uiSetData1>>8; //把int类型的参数分解成两个字节的数据
- ucSendregBuf[7]=uiSetData1;
- break;
- case 1: //发送参数2
- ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x02; //代表发送参数2
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x02; //代表发送2个字节的有效数据
- ucSendregBuf[6]=uiSetData2>>8; //把int类型的参数分解成两个字节的数据
- ucSendregBuf[7]=uiSetData2;
- break;
- case 2: //发送参数3
- ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x03; //代表发送参数3
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x02; //代表发送2个字节的有效数据
- ucSendregBuf[6]=uiSetData3>>8; //把int类型的参数分解成两个字节的数据
- ucSendregBuf[7]=uiSetData3;
- break;
- case 3: //发送参数4
- ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x04; //代表发送参数4
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x02; //代表发送2个字节的有效数据
- ucSendregBuf[6]=uiSetData4>>8; //把int类型的参数分解成两个字节的数据
- ucSendregBuf[7]=uiSetData4;
- break;
- }
-
- ucSendregBuf[8]=0x00;
- for(i=0;i<8;i++) //最后一个字节是校验和,是前面所有字节累加,溢出部分不用我们管,系统会有规律的自动处理
- {
- ucSendregBuf[8]=ucSendregBuf[8]+ucSendregBuf[i];
- }
- for(i=0;i<9;i++)
- {
- eusart_send(ucSendregBuf[i]); //把一串完整的数据发送给下位机
- }
- ucSendTimeOutLock=1; //原子锁加锁
- uiSendTimeOutCnt=0; //超时计时器计时清零
- ucSendTimeOutLock=0; //原子锁解锁
- ucReceiveStatus=0; //返回的数据状态清零
- ucSendStep=1; //切换到下一个步骤,等待返回的数据
- break;
- case 1: //通讯过程1 判断返回的指令
- if(ucReceiveStatus==1) //校验正确
- {
- ucErrorCnt=0; //累计校验错误总数清零
- ucSendTotal++; //累加当前发送了多少串数据
- if(ucSendTotal>=4) //已经发送完全部4串数据,结束
- {
- ucStatus=0; //切换到结束时的待机状态
- }
- else //还没发送完4串数据,则继续发送下一串新数据
- {
- ucSendStep=0; //返回上一个步骤,继续发送新数据
- }
- }
- else if(ucReceiveStatus==2||uiSendTimeOutCnt>const_send_time_out) //校验出错或者超时出错
- {
- ucErrorCnt++; //累计错误总数
- if(ucErrorCnt>=3) //累加重发次数3次以上,则报错
- {
- ucStatus=2; //切换到出错报警状态
- }
- else //重发还没超过3次,继续返回重发
- {
- ucSendStep=0; //返回上一个步骤,重发一次数据
- }
- }
- break;
-
- }
-
- }
- }
- void status_service(void) //状态显示的应用程序
- {
- if(ucStatus!=0) //处于非待机的状态,Led闪烁
- {
- if(uiLedCnt<const_led_0_5s) //大概0.5秒
- {
- led_dr=1; //前半秒亮
- if(ucStatus==2) //处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
- {
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- }
- }
- else if(uiLedCnt<const_led_1s) //大概1秒
- {
- led_dr=0; //前半秒灭
- }
- else
- {
- ucLedLock=1; //原子锁加锁
- uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
- ucLedLock=0; //原子锁解锁
- }
-
- }
- else //处于待机状态,Led一直亮
- {
- led_dr=1;
-
- }
- }
- void usart_service(void) //串口接收服务程序,在main函数里
- {
- unsigned int i;
-
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
- ucRcType=ucRcregBuf[uiRcMoveIndex+3]; //数据类型 一个字节
- uiRcSize=ucRcregBuf[uiRcMoveIndex+4]; //数据长度 两个字节
- uiRcSize=uiRcSize<<8;
- uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
-
- ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]; //记录最后一个字节的校验
- ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0; //清零最后一个字节的累加和变量
- for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
- {
- ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
- }
- if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize]) //如果一串数据校验正确,则进入以下数据指令的判断
- {
- switch(ucRcType) //根据不同的数据类型来做不同的数据处理
- {
- case 0xf5: //返回的是正确的校验指令
- ucReceiveStatus=1;//代表校验正确
- break;
-
- case 0xfa: //返回的是错误的校验指令
- ucReceiveStatus=2;//代表校验错误
- break;
- }
- }
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- {
- case 1: //显示P--1窗口的数据
- if(ucWd1Update==1) //窗口1要全部更新显示
- {
- ucWd1Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=1; //第6位数码管显示1
- ucDigShow5=10; //第5位数码管显示无
- //先分解数据
- ucTemp4=uiSetData1/1000;
- ucTemp3=uiSetData1%1000/100;
- ucTemp2=uiSetData1%100/10;
- ucTemp1=uiSetData1%10;
-
- //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
- if(uiSetData1<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData1<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData1<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 2: //显示P--2窗口的数据
- if(ucWd2Update==1) //窗口2要全部更新显示
- {
- ucWd2Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=2; //第6位数码管显示2
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData2/1000; //分解数据
- ucTemp3=uiSetData2%1000/100;
- ucTemp2=uiSetData2%100/10;
- ucTemp1=uiSetData2%10;
- if(uiSetData2<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData2<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData2<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 3: //显示P--3窗口的数据
- if(ucWd3Update==1) //窗口3要全部更新显示
- {
- ucWd3Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=3; //第6位数码管显示3
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData3/1000; //分解数据
- ucTemp3=uiSetData3%1000/100;
- ucTemp2=uiSetData3%100/10;
- ucTemp1=uiSetData3%10;
- if(uiSetData3<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData3<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData3<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 4: //显示P--4窗口的数据
- if(ucWd4Update==1) //窗口4要全部更新显示
- {
- ucWd4Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=4; //第6位数码管显示4
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData4/1000; //分解数据
- ucTemp3=uiSetData4%1000/100;
- ucTemp2=uiSetData4%100/10;
- ucTemp1=uiSetData4%10;
- if(uiSetData4<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData4<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData4<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- }
-
- }
- void key_scan(void)//按键扫描函数 放在定时中断里
- {
- if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock1=0; //按键自锁标志清零
- uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定时中断次数
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1; //自锁按键置位,避免一直触发
- ucKeySec=1; //触发1号键
- }
- }
- if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock2=0; //按键自锁标志清零
- uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定时中断次数
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1; //自锁按键置位,避免一直触发
- ucKeySec=2; //触发2号键
- }
- }
- if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock3=0; //按键自锁标志清零
- uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt3++; //累加定时中断次数
- if(uiKeyTimeCnt3>const_key_time3)
- {
- uiKeyTimeCnt3=0;
- ucKeyLock3=1; //自锁按键置位,避免一直触发
- ucKeySec=3; //触发3号键
- }
- }
- if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock4=0; //按键自锁标志清零
- uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt4++; //累加定时中断次数
- if(uiKeyTimeCnt4>const_key_time4)
- {
- uiKeyTimeCnt4=0;
- ucKeyLock4=1; //自锁按键置位,避免一直触发
- ucKeySec=4; //触发4号键
- }
- }
- }
- void key_service(void) //按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 加按键 对应朱兆祺学习板的S1键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1++;
- if(uiSetData1>9999) //最大值是9999
- {
- uiSetData1=9999;
- }
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2++;
- if(uiSetData2>9999) //最大值是9999
- {
- uiSetData2=9999;
- }
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3++;
- if(uiSetData3>9999) //最大值是9999
- {
- uiSetData3=9999;
- }
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4++;
- if(uiSetData4>9999) //最大值是9999
- {
- uiSetData4=9999;
- }
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 2:// 减按键 对应朱兆祺学习板的S5键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1--;
- if(uiSetData1>9999)
- {
- uiSetData1=0; //最小值是0
- }
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2--;
- if(uiSetData2>9999)
- {
- uiSetData2=0; //最小值是0
- }
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3--;
- if(uiSetData3>9999)
- {
- uiSetData3=0; //最小值是0
- }
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4--;
- if(uiSetData4>9999)
- {
- uiSetData4=0; //最小值是0
- }
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
- ucWd++; //切换窗口
- if(ucWd>4)
- {
- ucWd=1;
- }
- switch(ucWd) //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
- {
- case 1:
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 4:// 启动发送数据和复位按键 对应朱兆祺学习板的S13键
- switch(ucStatus) //在不同的状态下,进行不同的操作
- {
- case 0: //处于待机状态,则启动发送数据
- ucErrorCnt=0; //累计错误总数清零
- ucSendTotal=0; //已经发送串数据总数清零
- ucSendStep=0; //发送数据的过程步骤清零,返回开始的步骤待命
- ucStatus=1; //启动发送数据,1代表正在通讯过程
- break;
- case 1: //处于正在通讯的过程
- break;
- case 2: //发送数据出错,比如中间超时没有接收到数据
- ucStatus=0; //切换回待机的状态
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- if(ucSendCntLock==0) //原子锁判断
- {
- ucSendCntLock=1; //加锁
- uiSendCnt=0; //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
- ucSendCntLock=0; //解锁
- }
-
- }
- else //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
- {
- TI = 0; //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
- }
-
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- /* 注释一:
- * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
- */
- if(ucSendCntLock==0) //原子锁判断
- {
- ucSendCntLock=1; //加锁
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- ucSendCntLock=0; //解锁
- }
- if(ucVoiceLock==0) //原子锁判断
- {
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
-
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
-
- }
- }
- if(ucStatus!=0) //处于非待机的状态,Led闪烁
- {
- if(ucLedLock==0)//原子锁判断
- {
- uiLedCnt++; //Led闪烁计时器不断累加
- }
- }
- if(ucStatus==1) //处于正在通讯的状态,
- {
- if(ucSendTimeOutLock==0) //原子锁判断
- {
- uiSendTimeOutCnt++; //超时计时器累加
- }
- }
- key_scan(); //按键扫描函数
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- /* 注释二:
- * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
- * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
- * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
- */
- key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
- led_dr=1; //点亮独立LED灯
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- /* 注释三:
- * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
- * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
- */
- IP =0x10; //把串口中断设置为最高优先级,必须的。
- TH1=TL1=-(11059200L/12/32/9600); //串口波特率为9600。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
前面花了大量篇幅详细地讲解了串口收发数据的程序框架,从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-5-12 13:33
第四十六节:利用AT24C02进行掉电后的数据保存。
开场白:
一个AT24C02可以存储256个字节,地址范围是(0至255)。利用AT24C02存储数据时,要教会大家六个知识点:
第一个:单片机操作AT24C02的通讯过程也就是IIC的通讯过程, IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此在整个通讯过程中应该先关闭总中断,完成之后再开中断。
第二个:在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,应该适当继续把这个时间延长,尤其是在写入数据时。
第三个:如何初始化EEPROM数据的方法。系统第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。
第四个:在时序中,发送ACK确认信号时,要记得把数据线eeprom_sda_dr_s设置为输入的状态。对于51单片机来说,只要把eeprom_sda_dr_s=1就可以。而对于PIC或者AVR单片机来说,它们都是带方向寄存器的,就不能直接eeprom_sda_dr_s=1,而要直接修改方向寄存器,把它设置为输入状态。在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
第五个: 提醒各位读者在硬件上应该注意的问题,单片机跟AT24C02通讯的2根IO口都要加上一个4.7K左右的上拉电阻。凡是在IIC通讯场合,都要加上拉电阻。AT24C02的WP引脚一定要接地,否则存不进数据。
第六个:旧版的坚鸿51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的坚鸿51学习板已经改过来了。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9作为独立按键。
一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time1 20 //按键去抖动延时的时间
- #define const_key_time2 20 //按键去抖动延时的时间
- #define const_key_time3 20 //按键去抖动延时的时间
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void start24(void); //开始位
- void ack24(void); //确认位
- void stop24(void); //停止位
- unsigned char read24(void); //读取一个字节的时序
- void write24(unsigned char dd); //发送一个字节的时序
- unsigned char read_eeprom(unsigned int address); //从一个地址读取出一个字节数据
- void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
- unsigned int read_eeprom_int(unsigned int address); //从一个地址读取出一个int类型的数据
- void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据
- void T0_time(void); //定时中断函数
- void key_service(void); //按键服务的应用程序
- void key_scan(void);//按键扫描函数 放在定时中断里
- sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
- sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
- sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
- sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit eeprom_scl_dr=P3^7; //时钟线
- sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
- unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
- unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
- unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucVoiceLock=0; //蜂鸣器鸣叫的原子锁
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Update=1; //窗口1更新显示标志
- unsigned char ucWd2Update=0; //窗口2更新显示标志
- unsigned char ucWd3Update=0; //窗口3更新显示标志
- unsigned char ucWd4Update=0; //窗口4更新显示标志
- unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- unsigned int uiSetData1=0; //本程序中需要被设置的参数1
- unsigned int uiSetData2=0; //本程序中需要被设置的参数2
- unsigned int uiSetData3=0; //本程序中需要被设置的参数3
- unsigned int uiSetData4=0; //本程序中需要被设置的参数4
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- display_service(); //显示的窗口菜单服务程序
- }
- }
- //AT24C02驱动程序
- void start24(void) //开始位
- {
- eeprom_sda_dr_sr=1;
- eeprom_scl_dr=1;
- delay_short(15);
- eeprom_sda_dr_sr=0;
- delay_short(15);
- eeprom_scl_dr=0;
- }
- void ack24(void) //确认位时序
- {
- eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入
- eeprom_scl_dr=1;
- delay_short(15);
- eeprom_scl_dr=0;
- delay_short(15);
- //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
- //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
- }
- void stop24(void) //停止位
- {
- eeprom_sda_dr_sr=0;
- eeprom_scl_dr=1;
- delay_short(15);
- eeprom_sda_dr_sr=1;
- }
- unsigned char read24(void) //读取一个字节的时序
- {
- unsigned char outdata,tempdata;
- outdata=0;
- eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
- delay_short(2);
- for(tempdata=0;tempdata<8;tempdata++)
- {
- eeprom_scl_dr=0;
- delay_short(2);
- eeprom_scl_dr=1;
- delay_short(2);
- outdata<<=1;
- if(eeprom_sda_dr_sr==1)outdata++;
- eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
- delay_short(2);
- }
- return(outdata);
-
- }
- void write24(unsigned char dd) //发送一个字节的时序
- {
- unsigned char tempdata;
- for(tempdata=0;tempdata<8;tempdata++)
- {
- if(dd>=0x80)eeprom_sda_dr_sr=1;
- else eeprom_sda_dr_sr=0;
- dd<<=1;
- delay_short(2);
- eeprom_scl_dr=1;
- delay_short(4);
- eeprom_scl_dr=0;
- }
- }
- unsigned char read_eeprom(unsigned int address) //从一个地址读取出一个字节数据
- {
- unsigned char dd,cAddress;
- cAddress=address; //把低字节地址传递给一个字节变量。
- /* 注释一:
- * IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此
- * 在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新
- * 问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在操作EEPROM时,
- * 数码管就会出现闪烁的现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管
- * 的方案,应该用静态显示的方案。那么程序上还有没有改善的方法?有的,下一节我会讲这个问题
- * 的改善方法。
- */
- EA=0; //禁止中断
- start24(); //IIC通讯开始
- write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
- //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
- ack24(); //发送应答信号
- write24(cAddress); //发送读取的存储地址(范围是0至255)
- ack24(); //发送应答信号
- start24(); //开始
- write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
- //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
- ack24(); //发送应答信号
- dd=read24(); //读取一个字节
- ack24(); //发送应答信号
- stop24(); //停止
- /* 注释二:
- * 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,
- * 写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。
- * 否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,
- * 应该适当继续把这个时间延长,尤其是在写入数据时。
- */
- delay_short(800); //此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
- EA=1; //允许中断
- return(dd);
- }
- void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
- {
- unsigned char cAddress;
- cAddress=address; //把低字节地址传递给一个字节变量。
- EA=0; //禁止中断
- start24(); //IIC通讯开始
- write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
- //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
- ack24(); //发送应答信号
- write24(cAddress); //发送写入的存储地址(范围是0至255)
- ack24(); //发送应答信号
- write24(dd); //写入存储的数据
- ack24(); //发送应答信号
- stop24(); //停止
- delay_short(2000); //此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
- EA=1; //允许中断
- }
- unsigned int read_eeprom_int(unsigned int address) //从一个地址读取出一个int类型的数据
- {
- unsigned char ucReadDataH;
- unsigned char ucReadDataL;
- unsigned int uiReadDate;
- ucReadDataH=read_eeprom(address); //读取高字节
- ucReadDataL=read_eeprom(address+1); //读取低字节
- uiReadDate=ucReadDataH; //把两个字节合并成一个int类型数据
- uiReadDate=uiReadDate<<8;
- uiReadDate=uiReadDate+ucReadDataL;
- return uiReadDate;
- }
- void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
- {
- unsigned char ucWriteDataH;
- unsigned char ucWriteDataL;
- ucWriteDataH=uiWriteData>>8;
- ucWriteDataL=uiWriteData;
- write_eeprom(address,ucWriteDataH); //存入高字节
- write_eeprom(address+1,ucWriteDataL); //存入低字节
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- {
- case 1: //显示P--1窗口的数据
- if(ucWd1Update==1) //窗口1要全部更新显示
- {
- ucWd1Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=1; //第6位数码管显示1
- ucDigShow5=10; //第5位数码管显示无
- //先分解数据
- ucTemp4=uiSetData1/1000;
- ucTemp3=uiSetData1%1000/100;
- ucTemp2=uiSetData1%100/10;
- ucTemp1=uiSetData1%10;
-
- //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
- if(uiSetData1<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData1<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData1<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 2: //显示P--2窗口的数据
- if(ucWd2Update==1) //窗口2要全部更新显示
- {
- ucWd2Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=2; //第6位数码管显示2
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData2/1000; //分解数据
- ucTemp3=uiSetData2%1000/100;
- ucTemp2=uiSetData2%100/10;
- ucTemp1=uiSetData2%10;
- if(uiSetData2<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData2<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData2<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 3: //显示P--3窗口的数据
- if(ucWd3Update==1) //窗口3要全部更新显示
- {
- ucWd3Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=3; //第6位数码管显示3
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData3/1000; //分解数据
- ucTemp3=uiSetData3%1000/100;
- ucTemp2=uiSetData3%100/10;
- ucTemp1=uiSetData3%10;
- if(uiSetData3<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData3<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData3<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 4: //显示P--4窗口的数据
- if(ucWd4Update==1) //窗口4要全部更新显示
- {
- ucWd4Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=4; //第6位数码管显示4
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData4/1000; //分解数据
- ucTemp3=uiSetData4%1000/100;
- ucTemp2=uiSetData4%100/10;
- ucTemp1=uiSetData4%10;
- if(uiSetData4<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData4<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData4<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- }
-
- }
- void key_scan(void)//按键扫描函数 放在定时中断里
- {
- if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock1=0; //按键自锁标志清零
- uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定时中断次数
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1; //自锁按键置位,避免一直触发
- ucKeySec=1; //触发1号键
- }
- }
- if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock2=0; //按键自锁标志清零
- uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定时中断次数
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1; //自锁按键置位,避免一直触发
- ucKeySec=2; //触发2号键
- }
- }
- if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock3=0; //按键自锁标志清零
- uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt3++; //累加定时中断次数
- if(uiKeyTimeCnt3>const_key_time3)
- {
- uiKeyTimeCnt3=0;
- ucKeyLock3=1; //自锁按键置位,避免一直触发
- ucKeySec=3; //触发3号键
- }
- }
- }
- void key_service(void) //按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 加按键 对应朱兆祺学习板的S1键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1++;
- if(uiSetData1>9999) //最大值是9999
- {
- uiSetData1=9999;
- }
- write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2++;
- if(uiSetData2>9999) //最大值是9999
- {
- uiSetData2=9999;
- }
- write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3++;
- if(uiSetData3>9999) //最大值是9999
- {
- uiSetData3=9999;
- }
- write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4++;
- if(uiSetData4>9999) //最大值是9999
- {
- uiSetData4=9999;
- }
- write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 2:// 减按键 对应朱兆祺学习板的S5键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1--;
- if(uiSetData1>9999)
- {
- uiSetData1=0; //最小值是0
- }
- write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2--;
- if(uiSetData2>9999)
- {
- uiSetData2=0; //最小值是0
- }
- write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3--;
- if(uiSetData3>9999)
- {
- uiSetData3=0; //最小值是0
- }
- write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4--;
- if(uiSetData4>9999)
- {
- uiSetData4=0; //最小值是0
- }
- write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
- ucWd++; //切换窗口
- if(ucWd>4)
- {
- ucWd=1;
- }
- switch(ucWd) //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
- {
- case 1:
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- /* 注释三:
- * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
- */
- if(ucVoiceLock==0) //原子锁判断
- {
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
-
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
-
- }
- }
- key_scan(); //按键扫描函数
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- /* 注释四:
- * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
- * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
- * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
- */
- key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- /* 注释五:
- * 如何初始化EEPROM数据的方法。在使用EEPROM时,这一步初始化很关键!
- * 第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。
- * 这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。另外,
- * 由于int类型数据占用2个字节,所以以下4个数据挨着的地址是0,2,4,6.
- */
- uiSetData1=read_eeprom_int(0); //读取uiSetData1,内部占用2个字节地址
- if(uiSetData1>9999) //不在范围内
- {
- uiSetData1=0; //填入一个初始化数据
- write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
- }
- uiSetData2=read_eeprom_int(2); //读取uiSetData2,内部占用2个字节地址
- if(uiSetData2>9999)//不在范围内
- {
- uiSetData2=0; //填入一个初始化数据
- write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
- }
- uiSetData3=read_eeprom_int(4); //读取uiSetData3,内部占用2个字节地址
- if(uiSetData3>9999)//不在范围内
- {
- uiSetData3=0; //填入一个初始化数据
- write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
- }
- uiSetData4=read_eeprom_int(6); //读取uiSetData4,内部占用2个字节地址
- if(uiSetData4>9999)//不在范围内
- {
- uiSetData4=0; //填入一个初始化数据
- write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
- }
- }
复制代码
总结陈词:
IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此,在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在按键更改参数,内部操作EEPROM时,数码管就会出现短暂明显的闪烁现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管的方案,应该用静态显示的方案。那么在程序上还有没有改善这种现象的方法?当然有。欲知详情,请听下回分解-----操作AT24C02时,利用“一气呵成的定时器方式”改善数码管的闪烁现象。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-5-15 12:55
第四十七节:操作AT24C02时,利用“一气呵成的定时器延时”改善数码管的闪烁现象。
开场白:
上一节在按键更改参数时,会出现短暂明显的数码管闪烁现象。这节通过教大家使用新型延时函数可以有效的改善闪烁现象。要教会大家三个知识点:
第一个:如何编写一气呵成的定时器延时函数。
第二个:如何编写检查EEPROM芯片是否存在短路,虚焊或者芯片坏了的监控程序。
第三个:经过网友“cjseng”的提醒,我建议大家以后在用EEPROM芯片时,如果单片机IO口足够多,WP引脚应该专门接一个IO口,并且加一个上拉电阻,需要更改EEPROM存储数据时置低,其他任何一个时刻都置高,这样可以更加有效地保护EEPROM内部数据不会被意外更改。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。旧版的坚鸿51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的坚鸿51学习板已经改过来了。
(2)实现功能:
4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。如果AT24C02短路,虚焊,或者坏了,系统可以检查出来,并且蜂鸣器会间歇性鸣叫报警。按更改参数按键时,数码管比上一节大大降低了闪烁现象。
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9作为独立按键。
一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time1 20 //按键去抖动延时的时间
- #define const_key_time2 20 //按键去抖动延时的时间
- #define const_key_time3 20 //按键去抖动延时的时间
- #define const_eeprom_1s 400 //大概1秒的时间
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- void delay_timer(unsigned int uiDelayTimerTemp); //一气呵成的定时器延时方式
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void start24(void); //开始位
- void ack24(void); //确认位
- void stop24(void); //停止位
- unsigned char read24(void); //读取一个字节的时序
- void write24(unsigned char dd); //发送一个字节的时序
- unsigned char read_eeprom(unsigned int address); //从一个地址读取出一个字节数据
- void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
- unsigned int read_eeprom_int(unsigned int address); //从一个地址读取出一个int类型的数据
- void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据
- void T0_time(void); //定时中断函数
- void key_service(void); //按键服务的应用程序
- void key_scan(void);//按键扫描函数 放在定时中断里
- void eeprom_alarm_service(void); //EEPROM出错报警
- sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
- sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
- sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
- sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit eeprom_scl_dr=P3^7; //时钟线
- sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
- unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
- unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
- unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucVoiceLock=0; //蜂鸣器鸣叫的原子锁
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Update=1; //窗口1更新显示标志
- unsigned char ucWd2Update=0; //窗口2更新显示标志
- unsigned char ucWd3Update=0; //窗口3更新显示标志
- unsigned char ucWd4Update=0; //窗口4更新显示标志
- unsigned char ucWd=1; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- unsigned int uiSetData1=0; //本程序中需要被设置的参数1
- unsigned int uiSetData2=0; //本程序中需要被设置的参数2
- unsigned int uiSetData3=0; //本程序中需要被设置的参数3
- unsigned int uiSetData4=0; //本程序中需要被设置的参数4
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- unsigned char ucDelayTimerLock=0; //原子锁
- unsigned int uiDelayTimer=0;
- unsigned char ucCheckEeprom=0; //检查EEPROM芯片是否正常
- unsigned char ucEepromError=0; //EEPROM芯片是否正常的标志
- unsigned char ucEepromLock=0;//原子锁
- unsigned int uiEepromCnt=0; //间歇性蜂鸣器报警的计时器
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- display_service(); //显示的窗口菜单服务程序
- eeprom_alarm_service(); //EEPROM出错报警
- }
- }
- void eeprom_alarm_service(void) //EEPROM出错报警
- {
- if(ucEepromError==1) //EEPROM出错
- {
- if(uiEepromCnt<const_eeprom_1s) //大概1秒钟蜂鸣器响一次
- {
- ucEepromLock=1; //原子锁加锁
- uiEepromCnt=0; //计时器清零
- ucEepromLock=0; //原子锁解锁
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- }
- }
- }
- //AT24C02驱动程序
- void start24(void) //开始位
- {
- eeprom_sda_dr_sr=1;
- eeprom_scl_dr=1;
- delay_short(15);
- eeprom_sda_dr_sr=0;
- delay_short(15);
- eeprom_scl_dr=0;
- }
- void ack24(void) //确认位时序
- {
- eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入
- eeprom_scl_dr=1;
- delay_short(15);
- eeprom_scl_dr=0;
- delay_short(15);
- //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
- //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
- }
- void stop24(void) //停止位
- {
- eeprom_sda_dr_sr=0;
- eeprom_scl_dr=1;
- delay_short(15);
- eeprom_sda_dr_sr=1;
- }
- unsigned char read24(void) //读取一个字节的时序
- {
- unsigned char outdata,tempdata;
- outdata=0;
- eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
- delay_short(2);
- for(tempdata=0;tempdata<8;tempdata++)
- {
- eeprom_scl_dr=0;
- delay_short(2);
- eeprom_scl_dr=1;
- delay_short(2);
- outdata<<=1;
- if(eeprom_sda_dr_sr==1)outdata++;
- eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
- delay_short(2);
- }
- return(outdata);
-
- }
- void write24(unsigned char dd) //发送一个字节的时序
- {
- unsigned char tempdata;
- for(tempdata=0;tempdata<8;tempdata++)
- {
- if(dd>=0x80)eeprom_sda_dr_sr=1;
- else eeprom_sda_dr_sr=0;
- dd<<=1;
- delay_short(2);
- eeprom_scl_dr=1;
- delay_short(4);
- eeprom_scl_dr=0;
- }
- }
- unsigned char read_eeprom(unsigned int address) //从一个地址读取出一个字节数据
- {
- unsigned char dd,cAddress;
- cAddress=address; //把低字节地址传递给一个字节变量。
- EA=0; //禁止中断
- start24(); //IIC通讯开始
- write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
- //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
- ack24(); //发送应答信号
- write24(cAddress); //发送读取的存储地址(范围是0至255)
- ack24(); //发送应答信号
- start24(); //开始
- write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
- //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
- ack24(); //发送应答信号
- dd=read24(); //读取一个字节
- ack24(); //发送应答信号
- stop24(); //停止
- EA=1; //允许中断
- delay_timer(2); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管
- return(dd);
- }
- void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
- {
- unsigned char cAddress;
- cAddress=address; //把低字节地址传递给一个字节变量。
- EA=0; //禁止中断
- start24(); //IIC通讯开始
- write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
- //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
- ack24(); //发送应答信号
- write24(cAddress); //发送写入的存储地址(范围是0至255)
- ack24(); //发送应答信号
- write24(dd); //写入存储的数据
- ack24(); //发送应答信号
- stop24(); //停止
- EA=1; //允许中断
- delay_timer(4); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管
- }
- unsigned int read_eeprom_int(unsigned int address) //从一个地址读取出一个int类型的数据
- {
- unsigned char ucReadDataH;
- unsigned char ucReadDataL;
- unsigned int uiReadDate;
- ucReadDataH=read_eeprom(address); //读取高字节
- ucReadDataL=read_eeprom(address+1); //读取低字节
- uiReadDate=ucReadDataH; //把两个字节合并成一个int类型数据
- uiReadDate=uiReadDate<<8;
- uiReadDate=uiReadDate+ucReadDataL;
- return uiReadDate;
- }
- void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
- {
- unsigned char ucWriteDataH;
- unsigned char ucWriteDataL;
- ucWriteDataH=uiWriteData>>8;
- ucWriteDataL=uiWriteData;
- write_eeprom(address,ucWriteDataH); //存入高字节
- write_eeprom(address+1,ucWriteDataL); //存入低字节
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- {
- case 1: //显示P--1窗口的数据
- if(ucWd1Update==1) //窗口1要全部更新显示
- {
- ucWd1Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=1; //第6位数码管显示1
- ucDigShow5=10; //第5位数码管显示无
- //先分解数据
- ucTemp4=uiSetData1/1000;
- ucTemp3=uiSetData1%1000/100;
- ucTemp2=uiSetData1%100/10;
- ucTemp1=uiSetData1%10;
-
- //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
- if(uiSetData1<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData1<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData1<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 2: //显示P--2窗口的数据
- if(ucWd2Update==1) //窗口2要全部更新显示
- {
- ucWd2Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=2; //第6位数码管显示2
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData2/1000; //分解数据
- ucTemp3=uiSetData2%1000/100;
- ucTemp2=uiSetData2%100/10;
- ucTemp1=uiSetData2%10;
- if(uiSetData2<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData2<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData2<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 3: //显示P--3窗口的数据
- if(ucWd3Update==1) //窗口3要全部更新显示
- {
- ucWd3Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=3; //第6位数码管显示3
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData3/1000; //分解数据
- ucTemp3=uiSetData3%1000/100;
- ucTemp2=uiSetData3%100/10;
- ucTemp1=uiSetData3%10;
- if(uiSetData3<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData3<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData3<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- case 4: //显示P--4窗口的数据
- if(ucWd4Update==1) //窗口4要全部更新显示
- {
- ucWd4Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow8=12; //第8位数码管显示P
- ucDigShow7=11; //第7位数码管显示-
- ucDigShow6=4; //第6位数码管显示4
- ucDigShow5=10; //第5位数码管显示无
- ucTemp4=uiSetData4/1000; //分解数据
- ucTemp3=uiSetData4%1000/100;
- ucTemp2=uiSetData4%100/10;
- ucTemp1=uiSetData4%10;
- if(uiSetData4<1000)
- {
- ucDigShow4=10; //如果小于1000,千位显示无
- }
- else
- {
- ucDigShow4=ucTemp4; //第4位数码管要显示的内容
- }
- if(uiSetData4<100)
- {
- ucDigShow3=10; //如果小于100,百位显示无
- }
- else
- {
- ucDigShow3=ucTemp3; //第3位数码管要显示的内容
- }
- if(uiSetData4<10)
- {
- ucDigShow2=10; //如果小于10,十位显示无
- }
- else
- {
- ucDigShow2=ucTemp2; //第2位数码管要显示的内容
- }
- ucDigShow1=ucTemp1; //第1位数码管要显示的内容
- }
- break;
- }
-
- }
- void key_scan(void)//按键扫描函数 放在定时中断里
- {
- if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock1=0; //按键自锁标志清零
- uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定时中断次数
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1; //自锁按键置位,避免一直触发
- ucKeySec=1; //触发1号键
- }
- }
- if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock2=0; //按键自锁标志清零
- uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定时中断次数
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1; //自锁按键置位,避免一直触发
- ucKeySec=2; //触发2号键
- }
- }
- if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock3=0; //按键自锁标志清零
- uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt3++; //累加定时中断次数
- if(uiKeyTimeCnt3>const_key_time3)
- {
- uiKeyTimeCnt3=0;
- ucKeyLock3=1; //自锁按键置位,避免一直触发
- ucKeySec=3; //触发3号键
- }
- }
- }
- void key_service(void) //按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 加按键 对应朱兆祺学习板的S1键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1++;
- if(uiSetData1>9999) //最大值是9999
- {
- uiSetData1=9999;
- }
- write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2++;
- if(uiSetData2>9999) //最大值是9999
- {
- uiSetData2=9999;
- }
- write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3++;
- if(uiSetData3>9999) //最大值是9999
- {
- uiSetData3=9999;
- }
- write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4++;
- if(uiSetData4>9999) //最大值是9999
- {
- uiSetData4=9999;
- }
- write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 2:// 减按键 对应朱兆祺学习板的S5键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- uiSetData1--;
- if(uiSetData1>9999)
- {
- uiSetData1=0; //最小值是0
- }
- write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- uiSetData2--;
- if(uiSetData2>9999)
- {
- uiSetData2=0; //最小值是0
- }
- write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- uiSetData3--;
- if(uiSetData3>9999)
- {
- uiSetData3=0; //最小值是0
- }
- write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- uiSetData4--;
- if(uiSetData4>9999)
- {
- uiSetData4=0; //最小值是0
- }
- write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
- ucWd++; //切换窗口
- if(ucWd>4)
- {
- ucWd=1;
- }
- switch(ucWd) //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
- {
- case 1:
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- ucWd2Update=1; //窗口2更新显示
- break;
- case 3:
- ucWd3Update=1; //窗口3更新显示
- break;
- case 4:
- ucWd4Update=1; //窗口4更新显示
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(ucVoiceLock==0) //原子锁判断
- {
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
-
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
-
- }
- }
- if(ucDelayTimerLock==0) //原子锁判断
- {
- if(uiDelayTimer>0)
- {
- uiDelayTimer--; //一气呵成的定时器延时方式的计时器
- }
-
- }
- if(ucEepromError==1) //EEPROM出错
- {
- if(ucEepromLock==0)//原子锁判断
- {
- uiEepromCnt++; //间歇性蜂鸣器报警的计时器
- }
- }
- key_scan(); //按键扫描函数
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void delay_timer(unsigned int uiDelayTimerTemp)
- {
- ucDelayTimerLock=1; //原子锁加锁
- uiDelayTimer=uiDelayTimerTemp;
- ucDelayTimerLock=0; //原子锁解锁
- /* 注释一:
- *延时等待,一直等到定时中断把它减到0为止.这种一气呵成的定时器方式,
- *可以在延时的时候动态扫描数码管,改善数码管的闪烁现象
- */
- while(uiDelayTimer!=0); //一气呵成的定时器方式延时等待
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- /* 注释二:
- * 检查AT24C02芯片是否存在短路,虚焊,芯片坏了等不工作现象。
- * 在一个特定的地址里把数据读出来,如果发现不等于0x5a,则重新写入0x5a,再读出来
- * 判断是不是等于0x5a,如果不相等,则芯片有问题,出错报警提示。
- */
- ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
- if(ucCheckEeprom!=0x5a) //如果不等于特定内容。则重新写入数据再判断一次
- {
- write_eeprom(254,0x5a); //重新写入标志数据
- ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
- if(ucCheckEeprom!=0x5a) //如果还是不等于特定数字,则芯片不正常
- {
- ucEepromError=1; //表示AT24C02芯片出错报警
- }
- }
- uiSetData1=read_eeprom_int(0); //读取uiSetData1,内部占用2个字节地址
- if(uiSetData1>9999) //不在范围内
- {
- uiSetData1=0; //填入一个初始化数据
- write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
- }
- uiSetData2=read_eeprom_int(2); //读取uiSetData2,内部占用2个字节地址
- if(uiSetData2>9999)//不在范围内
- {
- uiSetData2=0; //填入一个初始化数据
- write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
- }
- uiSetData3=read_eeprom_int(4); //读取uiSetData3,内部占用2个字节地址
- if(uiSetData3>9999)//不在范围内
- {
- uiSetData3=0; //填入一个初始化数据
- write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
- }
- uiSetData4=read_eeprom_int(6); //读取uiSetData4,内部占用2个字节地址
- if(uiSetData4>9999)//不在范围内
- {
- uiSetData4=0; //填入一个初始化数据
- write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
- }
- }
复制代码
总结陈词:
下一节开始讲关于单片机驱动实时时钟芯片的内容,欲知详情,请听下回分解-----利用DS1302做一个实时时钟 。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-5-21 11:16
第四十八节:利用DS1302做一个实时时钟 。
开场白:
DS1302有两路独立电源输入,我们只要在其中一路电源上挂一个纽扣电池就可以实现掉电时钟继续跑的功能,纽扣电池作为备用电源必须比主电源的电压低一点。DS1302还给我们预留了一片RAM区,我们可以把一些数据存入到DS1302,只要DS1302的电池有电,那么它就相当于一个EEPROM。这个RAM区有什么用呢?因为RAM区的数据只要一掉电,所有的数据都会变成0x00或者0xff,也就是数据掉电会丢失,我们可以利用这个特点,可以在里面存入标志位数据,一旦发现这个数据改变了,就知道时钟的数据需要重新设置过,或者说明电池没电了。
在移植DS1302驱动程序中,有一个地方最容易出错,就是DS1302芯片的数据线DIO。我们编程时要特别留意这个IO口什么时候作为数据输入,什么时候作为数据输出,以便及时更改方向寄存器。对于51单片机,IO口在读取数据之前,要先置1。
这一节要教会大家六个知识点:
第一个:DS1302做实时时钟时,修改时间和读取时间的常见程序框架。
第二个:如何编写监控备用电池电量耗尽的监控程序。
第三个:在往DS1302写入数据修改时间之前,必须注意调整一下“日”的变量,因为每个月的最大天数是不一样的,有的一个月28天,有的一个月29天,有的一个月30天,有的一个月31天。
第四个:本程序第一次出现了电平按键,跟之前讲的下降沿按键不一样,请留意我是何如用软件滤波的,以及它具体的实现代码。
第五个:本程序第一次出现了一个按键按下去后,如果不松手就会触发两次事件,第一次是短按,第二次是长按3秒。请留意我是如何在之前的按键上略做修改就实现此功能的具体代码。
第六个:继续加深了解按键与显示是如何紧密关联起来的程序框架。
具体内容,请看源代码讲解。
(1) 硬件平台.
基于坚鸿51单片机学习板。
旧版的坚鸿51学习板在硬件上有一个bug,DS1302芯片附近的R43,R42两个电阻应该去掉,并且把R41的电阻换成0欧姆的电阻,或者直接短接起来。新版的坚鸿51学习板已经改过来了。
(2)实现功能:
本程序有2两个窗口。
第1个窗口显示日期。显示格式“年-月-日”。注意中间有“-”分开。
第2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性写入DS1302芯片内部,达到修改日期时间的目的。
S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。
本程序在使用过程中的注意事项:
(a)第一次上电时,蜂鸣器会报警,只要DS1302芯片的备用电池电量充足,这个时候断电再重启一次,就不会报警了。
(b)第一次上电时,时间没有走动,需要重新设置一下日期时间才可以。长按S9按键可以进入修改日期时间的状态。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_dpy_time_half 200 //数码管闪烁时间的半值
- #define const_dpy_time_all 400 //数码管闪烁时间的全值 一定要比const_dpy_time_half 大
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time1 20 //按键去抖动延时的时间
- #define const_key_time2 20 //按键去抖动延时的时间
- #define const_key_time3 20 //按键去抖动延时的时间
- #define const_key_time4 20 //按键去抖动延时的时间
- #define const_key_time17 1200 //长按超过3秒的时间
- #define const_ds1302_0_5s 200 //大概0.5秒的时间
- #define const_ds1302_sampling_time 360 //累计主循环次数的时间,每次刷新采样时钟芯片的时间
- #define WRITE_SECOND 0x80 //DS1302内部的相关地址
- #define WRITE_MINUTE 0x82
- #define WRITE_HOUR 0x84
- #define WRITE_DATE 0x86
- #define WRITE_MONTH 0x88
- #define WRITE_YEAR 0x8C
- #define WRITE_CHECK 0xC2 //用来检查芯片的备用电池是否用完了的地址
- #define READ_CHECK 0xC3 //用来检查芯片的备用电池是否用完了的地址
- #define READ_SECOND 0x81
- #define READ_MINUTE 0x83
- #define READ_HOUR 0x85
- #define READ_DATE 0x87
- #define READ_MONTH 0x89
- #define READ_YEAR 0x8D
- #define WRITE_PROTECT 0x8E
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(void); //定时中断函数
- void key_service(void); //按键服务的应用程序
- void key_scan(void);//按键扫描函数 放在定时中断里
- void ds1302_alarm_service(void); //ds1302出错报警
- void ds1302_sampling(void); //ds1302采样程序,内部每秒钟采集更新一次
- void Write1302 ( unsigned char addr, unsigned char dat );//修改时间的驱动
- unsigned char Read1302 ( unsigned char addr );//读取时间的驱动
- unsigned char bcd_to_number(unsigned char ucBcdTemp); //BCD转原始数值
- unsigned char number_to_bcd(unsigned char ucNumberTemp); //原始数值转BCD
- //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
- unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp); //日调整
- sbit SCLK_dr =P1^3;
- sbit DIO_dr_sr =P1^4;
- sbit DS1302_CE_dr =P1^5;
- sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
- sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
- sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
- sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
- sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit eeprom_scl_dr=P3^7; //时钟线
- sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned int uiSampingCnt=0; //采集Ds1302的计时器,每秒钟更新采集一次
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
- unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
- unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器
- unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
- unsigned int uiKey4Cnt1=0; //在软件滤波中,用到的变量
- unsigned int uiKey4Cnt2=0;
- unsigned char ucKey4Sr=1; //实时反映按键的电平状态
- unsigned char ucKey4SrRecord=0; //记录上一次按键的电平状态
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucVoiceLock=0; //蜂鸣器鸣叫的原子锁
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd=2; //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。
- unsigned char ucWd1Update=0; //窗口1更新显示标志
- unsigned char ucWd2Update=1; //窗口2更新显示标志
- unsigned char ucWd1Part1Update=0; //在窗口1中,局部1的更新显示标志
- unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
- unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志
- unsigned char ucWd2Part1Update=0; //在窗口2中,局部1的更新显示标志
- unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志
- unsigned char ucWd2Part3Update=0; //在窗口2中,局部3的更新显示标志
- unsigned char ucYear=0; //原始数据
- unsigned char ucMonth=0;
- unsigned char ucDate=0;
- unsigned char ucHour=0;
- unsigned char ucMinute=0;
- unsigned char ucSecond=0;
- unsigned char ucYearBCD=0; //BCD码的数据
- unsigned char ucMonthBCD=0;
- unsigned char ucDateBCD=0;
- unsigned char ucHourBCD=0;
- unsigned char ucMinuteBCD=0;
- unsigned char ucSecondBCD=0;
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- unsigned char ucTemp5=0; //中间过渡变量
- unsigned char ucTemp7=0; //中间过渡变量
- unsigned char ucTemp8=0; //中间过渡变量
- unsigned char ucDelayTimerLock=0; //原子锁
- unsigned int uiDelayTimer=0;
- unsigned char ucCheckDs1302=0; //检查Ds1302芯片是否正常
- unsigned char ucDs1302Error=0; //Ds1302芯片的备用电池是否用完了的报警标志
- unsigned char ucDs1302Lock=0;//原子锁
- unsigned int uiDs1302Cnt=0; //间歇性蜂鸣器报警的计时器
- unsigned char ucDpyTimeLock=0; //原子锁
- unsigned int uiDpyTimeCnt=0; //数码管的闪烁计时器,放在定时中断里不断累加
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- ds1302_sampling(); //ds1302采样程序,内部每秒钟采集更新一次
- display_service(); //显示的窗口菜单服务程序
- ds1302_alarm_service(); //ds1302出错报警
- }
- }
- /* 注释一:
- * 系统不用时时刻刻采集ds1302的内部数据,每隔一段时间更新采集一次就可以了。
- * 这个间隔时间应该小于或者等于1秒钟的时间,否则在数码管上看不到每秒钟的时间变化。
- */
- void ds1302_sampling(void) //ds1302采样程序,内部每秒钟采集更新一次
- {
- if(ucPart==0) //当系统不是处于设置日期和时间的情况下
- {
- ++uiSampingCnt; //累计主循环次数的时间
- if(uiSampingCnt>const_ds1302_sampling_time) //每隔一段时间就更新采集一次Ds1302数据
- {
- uiSampingCnt=0;
-
- ucYearBCD=Read1302(READ_YEAR); //读取年
- ucMonthBCD=Read1302(READ_MONTH); //读取月
- ucDateBCD=Read1302(READ_DATE); //读取日
- ucHourBCD=Read1302(READ_HOUR); //读取时
- ucMinuteBCD=Read1302(READ_MINUTE); //读取分
- ucSecondBCD=Read1302(READ_SECOND); //读取秒
- ucYear=bcd_to_number(ucYearBCD); //BCD转原始数值
- ucMonth=bcd_to_number(ucMonthBCD); //BCD转原始数值
- ucDate=bcd_to_number(ucDateBCD); //BCD转原始数值
- ucHour=bcd_to_number(ucHourBCD); //BCD转原始数值
- ucMinute=bcd_to_number(ucMinuteBCD); //BCD转原始数值
- ucSecond=bcd_to_number(ucSecondBCD); //BCD转原始数值
- ucWd2Update=1; //窗口2更新显示时间
- }
- }
- }
- //修改ds1302时间的驱动 ,注意,此处写入的是BCD码,
- void Write1302 ( unsigned char addr, unsigned char dat )
- {
- unsigned char i,temp; //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
- DS1302_CE_dr=0; //CE引脚为低,数据传送中止
- delay_short(1);
- SCLK_dr=0; //清零时钟总线
- delay_short(1);
- DS1302_CE_dr = 1; //CE引脚为高,逻辑控制有效
- delay_short(1);
- //发送地址
- for ( i=0; i<8; i++ ) //循环8次移位
- {
- DIO_dr_sr = 0;
- temp = addr;
- if(temp&0x01)
- {
- DIO_dr_sr =1;
- }
- else
- {
- DIO_dr_sr =0;
- }
- delay_short(1);
- addr >>= 1; //右移一位
- SCLK_dr = 1;
- delay_short(1);
- SCLK_dr = 0;
- delay_short(1);
- }
- //发送数据
- for ( i=0; i<8; i++ ) //循环8次移位
- {
- DIO_dr_sr = 0;
- temp = dat;
- if(temp&0x01)
- {
- DIO_dr_sr =1;
- }
- else
- {
- DIO_dr_sr =0;
- }
- delay_short(1);
- dat >>= 1; //右移一位
- SCLK_dr = 1;
- delay_short(1);
- SCLK_dr = 0;
- delay_short(1);
- }
- DS1302_CE_dr = 0;
- delay_short(1);
- }
- //读取Ds1302时间的驱动 ,注意,此处读取的是BCD码,
- unsigned char Read1302 ( unsigned char addr )
- {
- unsigned char i,temp,dat1;
- DS1302_CE_dr=0; //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
- delay_short(1);
- SCLK_dr=0;
- delay_short(1);
- DS1302_CE_dr = 1;
- delay_short(1);
- //发送地址
- for ( i=0; i<8; i++ ) //循环8次移位
- {
- DIO_dr_sr = 0;
- temp = addr;
- if(temp&0x01)
- {
- DIO_dr_sr =1;
- }
- else
- {
- DIO_dr_sr =0;
- }
- delay_short(1);
- addr >>= 1; //右移一位
- SCLK_dr = 1;
- delay_short(1);
- SCLK_dr = 0;
- delay_short(1);
- }
-
- /* 注释二:
- * 51单片机IO口的特点,在读取数据之前必须先输出高电平,
- * 如果是PIC,AVR等单片机,这里应该把IO方向寄存器设置为输入
- */
- DIO_dr_sr =1; //51单片机IO口的特点,在读取数据之前必须先输出高电平,
- temp=0;
- for ( i=0; i<8; i++ )
- {
- temp>>=1;
- if(DIO_dr_sr==1)
- {
- temp=temp+0x80;
- }
- DIO_dr_sr =1; //51单片机IO口的特点,在读取数据之前必须先输出高电平
- delay_short(1);
- SCLK_dr = 1;
- delay_short(1);
- SCLK_dr = 0;
- delay_short(1);
- }
- DS1302_CE_dr=0;
- delay_short(1);
- dat1=temp;
- return (dat1);
- }
- unsigned char bcd_to_number(unsigned char ucBcdTemp) //BCD转原始数值
- {
- unsigned char ucNumberResult=0;
- unsigned char ucBcdTemp10;
- unsigned char ucBcdTemp1;
-
- ucBcdTemp10=ucBcdTemp;
- ucBcdTemp10=ucBcdTemp10>>4;
- ucBcdTemp1=ucBcdTemp;
- ucBcdTemp1=ucBcdTemp1&0x0f;
- ucNumberResult=ucBcdTemp10*10+ucBcdTemp1;
- return ucNumberResult;
- }
- unsigned char number_to_bcd(unsigned char ucNumberTemp) //原始数值转BCD
- {
- unsigned char ucBcdResult=0;
- unsigned char ucNumberTemp10;
- unsigned char ucNumberTemp1;
- ucNumberTemp10=ucNumberTemp;
- ucNumberTemp10=ucNumberTemp10/10;
- ucNumberTemp10=ucNumberTemp10<<4;
- ucNumberTemp10=ucNumberTemp10&0xf0;
- ucNumberTemp1=ucNumberTemp;
- ucNumberTemp1=ucNumberTemp1%10;
- ucBcdResult=ucNumberTemp10|ucNumberTemp1;
- return ucBcdResult;
- }
- //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
- unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp) //日调整
- {
- unsigned char ucDayResult;
- unsigned int uiYearTemp;
- unsigned int uiYearYu;
-
- ucDayResult=ucDateTemp;
- switch(ucMonthTemp) //根据不同的月份来修正不同的日最大值
- {
- case 2: //二月份要计算是否是闰年
- uiYearTemp=2000+ucYearTemp;
- uiYearYu=uiYearTemp%4;
- if(uiYearYu==0) //闰年
- {
- if(ucDayResult>29)
- {
- ucDayResult=29;
- }
- }
- else
- {
- if(ucDayResult>28)
- {
- ucDayResult=28;
- }
- }
- break;
- case 4:
- case 6:
- case 9:
- case 11:
- if(ucDayResult>30)
- {
- ucDayResult=30;
- }
- break;
- }
- return ucDayResult;
- }
- void ds1302_alarm_service(void) //ds1302出错报警
- {
- if(ucDs1302Error==1) //备用电池的电量用完了报警提示
- {
- if(uiDs1302Cnt>const_ds1302_0_5s) //大概0.5秒钟蜂鸣器响一次
- {
- ucDs1302Lock=1; //原子锁加锁
- uiDs1302Cnt=0; //计时器清零
- ucDs1302Lock=0; //原子锁解锁
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
-
- }
- }
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
- {
- case 1: //显示日期窗口的数据 数据格式 NN-YY-RR 年-月-日
- if(ucWd1Update==1) //窗口1要全部更新显示
- {
- ucWd1Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow6=11; //显示一杠"-"
- ucDigShow3=11; //显示一杠"-"
- ucWd1Part1Update=1; //局部年更新显示
- ucWd1Part2Update=1; //局部月更新显示
- ucWd1Part3Update=1; //局部日更新显示
- }
- if(ucWd1Part1Update==1)//局部年更新显示
- {
- ucWd1Part1Update=0;
- ucTemp8=ucYear/10; //年
- ucTemp7=ucYear%10;
- ucDigShow8=ucTemp8; //数码管显示实际内容
- ucDigShow7=ucTemp7;
- }
- if(ucWd1Part2Update==1)//局部月更新显示
- {
- ucWd1Part2Update=0;
- ucTemp5=ucMonth/10; //月
- ucTemp4=ucMonth%10;
- ucDigShow5=ucTemp5; //数码管显示实际内容
- ucDigShow4=ucTemp4;
- }
- if(ucWd1Part3Update==1) //局部日更新显示
- {
- ucWd1Part3Update=0;
- ucTemp2=ucDate/10; //日
- ucTemp1=ucDate%10;
-
- ucDigShow2=ucTemp2; //数码管显示实际内容
- ucDigShow1=ucTemp1;
- }
- //数码管闪烁
- switch(ucPart) //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
- {
- case 0: //都不闪烁
- break;
- case 1: //年参数闪烁
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow8=ucTemp8; //数码管显示实际内容
- ucDigShow7=ucTemp7;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子锁加锁
- uiDpyTimeCnt=0; //及时把闪烁记时器清零
- ucDpyTimeLock=0; //原子锁解锁
- ucDigShow8=10; //数码管显示空,什么都不显示
- ucDigShow7=10;
- }
- break;
- case 2: //月参数闪烁
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow5=ucTemp5; //数码管显示实际内容
- ucDigShow4=ucTemp4;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子锁加锁
- uiDpyTimeCnt=0; //及时把闪烁记时器清零
- ucDpyTimeLock=0; //原子锁解锁
- ucDigShow5=10; //数码管显示空,什么都不显示
- ucDigShow4=10;
- }
- break;
- case 3: //日参数闪烁
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow2=ucTemp2; //数码管显示实际内容
- ucDigShow1=ucTemp1;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子锁加锁
- uiDpyTimeCnt=0; //及时把闪烁记时器清零
- ucDpyTimeLock=0; //原子锁解锁
- ucDigShow2=10; //数码管显示空,什么都不显示
- ucDigShow1=10;
- }
- break;
- }
- break;
- case 2: //显示时间窗口的数据 数据格式 SS FF MM 时 分 秒
- if(ucWd2Update==1) //窗口2要全部更新显示
- {
- ucWd2Update=0; //及时清零标志,避免一直进来扫描
- ucDigShow6=10; //显示空
- ucDigShow3=10; //显示空
- ucWd2Part3Update=1; //局部时更新显示
- ucWd2Part2Update=1; //局部分更新显示
- ucWd2Part1Update=1; //局部秒更新显示
- }
- if(ucWd2Part1Update==1)//局部时更新显示
- {
- ucWd2Part1Update=0;
- ucTemp8=ucHour/10; //时
- ucTemp7=ucHour%10;
- ucDigShow8=ucTemp8; //数码管显示实际内容
- ucDigShow7=ucTemp7;
- }
- if(ucWd2Part2Update==1)//局部分更新显示
- {
- ucWd2Part2Update=0;
- ucTemp5=ucMinute/10; //分
- ucTemp4=ucMinute%10;
- ucDigShow5=ucTemp5; //数码管显示实际内容
- ucDigShow4=ucTemp4;
- }
- if(ucWd2Part3Update==1) //局部秒更新显示
- {
- ucWd2Part3Update=0;
- ucTemp2=ucSecond/10; //秒
- ucTemp1=ucSecond%10;
-
- ucDigShow2=ucTemp2; //数码管显示实际内容
- ucDigShow1=ucTemp1;
- }
- //数码管闪烁
- switch(ucPart) //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
- {
- case 0: //都不闪烁
- break;
- case 1: //时参数闪烁
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow8=ucTemp8; //数码管显示实际内容
- ucDigShow7=ucTemp7;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子锁加锁
- uiDpyTimeCnt=0; //及时把闪烁记时器清零
- ucDpyTimeLock=0; //原子锁解锁
- ucDigShow8=10; //数码管显示空,什么都不显示
- ucDigShow7=10;
- }
- break;
- case 2: //分参数闪烁
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow5=ucTemp5; //数码管显示实际内容
- ucDigShow4=ucTemp4;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子锁加锁
- uiDpyTimeCnt=0; //及时把闪烁记时器清零
- ucDpyTimeLock=0; //原子锁解锁
- ucDigShow5=10; //数码管显示空,什么都不显示
- ucDigShow4=10;
- }
- break;
- case 3: //秒参数闪烁
- if(uiDpyTimeCnt==const_dpy_time_half)
- {
- ucDigShow2=ucTemp2; //数码管显示实际内容
- ucDigShow1=ucTemp1;
- }
- else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
- {
- ucDpyTimeLock=1; //原子锁加锁
- uiDpyTimeCnt=0; //及时把闪烁记时器清零
- ucDpyTimeLock=0; //原子锁解锁
- ucDigShow2=10; //数码管显示空,什么都不显示
- ucDigShow1=10;
- }
- break;
- }
- break;
- }
-
- }
- void key_scan(void)//按键扫描函数 放在定时中断里
- {
- if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock1=0; //按键自锁标志清零
- uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定时中断次数
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1; //自锁按键置位,避免一直触发
- ucKeySec=1; //触发1号键
- }
- }
- if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock2=0; //按键自锁标志清零
- uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定时中断次数
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1; //自锁按键置位,避免一直触发
- ucKeySec=2; //触发2号键
- }
- }
- /* 注释三:
- * 注意,此处把一个按键的短按和长按的功能都实现了。
- */
- if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock3=0; //按键自锁标志清零
- uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt3++; //累加定时中断次数
- if(uiKeyTimeCnt3>const_key_time3)
- {
- uiKeyTimeCnt3=0;
- ucKeyLock3=1; //自锁按键置位,避免一直触发
- ucKeySec=3; //短按触发3号键
- }
- }
- else if(uiKeyTimeCnt3<const_key_time17) //长按3秒
- {
- uiKeyTimeCnt3++; //累加定时中断次数
- if(uiKeyTimeCnt3==const_key_time17) //等于3秒钟,触发17号长按按键
- {
- ucKeySec=17; //长按3秒触发17号键
- }
- }
- /* 注释四:
- * 注意,此处是电平按键的滤波抗干扰处理
- */
- if(key_sr4==1) //对应朱兆祺学习板的S13键
- {
- uiKey4Cnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
- uiKey4Cnt2++; //类似独立按键去抖动的软件抗干扰处理
- if(uiKey4Cnt2>const_key_time4)
- {
- uiKey4Cnt2=0;
- ucKey4Sr=1; //实时反映按键松手时的电平状态
- }
- }
- else
- {
- uiKey4Cnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
- uiKey4Cnt1++;
- if(uiKey4Cnt1>const_key_time4)
- {
- uiKey4Cnt1=0;
- ucKey4Sr=0; //实时反映按键按下时的电平状态
- }
- }
- }
- void key_service(void) //按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 加按键 对应朱兆祺学习板的S1键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- switch(ucPart) //在不同的局部变量下,相当于二级菜单
- {
- case 1: //年
- ucYear++;
- if(ucYear>99)
- {
- ucYear=99;
- }
- ucWd1Part1Update=1; //更新显示
- break;
- case 2: //月
- ucMonth++;
- if(ucMonth>12)
- {
- ucMonth=12;
- }
- ucWd1Part2Update=1; //更新显示
- break;
- case 3: //日
- ucDate++;
- if(ucDate>31)
- {
- ucDate=31;
- }
- ucWd1Part3Update=1; //更新显示
- break;
- }
- break;
- case 2:
- switch(ucPart) //在不同的局部变量下,相当于二级菜单
- {
- case 1: //时
- ucHour++;
- if(ucHour>23)
- {
- ucHour=23;
- }
- ucWd2Part1Update=1; //更新显示
- break;
- case 2: //分
- ucMinute++;
- if(ucMinute>59)
- {
- ucMinute=59;
- }
- ucWd2Part2Update=1; //更新显示
- break;
- case 3: //秒
- ucSecond++;
- if(ucSecond>59)
- {
- ucSecond=59;
- }
- ucWd2Part3Update=1; //更新显示
- break;
- }
- break;
-
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 2:// 减按键 对应朱兆祺学习板的S5键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- switch(ucPart) //在不同的局部变量下,相当于二级菜单
- {
- case 1: //年
- ucYear--;
- if(ucYear>99)
- {
- ucYear=0;
- }
- ucWd1Part1Update=1; //更新显示
- break;
- case 2: //月
- ucMonth--;
- if(ucMonth<1)
- {
- ucMonth=1;
- }
- ucWd1Part2Update=1; //更新显示
- break;
- case 3: //日
- ucDate--;
- if(ucDate<1)
- {
- ucDate=1;
- }
- ucWd1Part3Update=1; //更新显示
- break;
- }
- break;
- case 2:
- switch(ucPart) //在不同的局部变量下,相当于二级菜单
- {
- case 1: //时
- ucHour--;
- if(ucHour>23)
- {
- ucHour=0;
- }
- ucWd2Part1Update=1; //更新显示
- break;
- case 2: //分
- ucMinute--;
- if(ucMinute>59)
- {
- ucMinute=0;
- }
- ucWd2Part2Update=1; //更新显示
- break;
- case 3: //秒
- ucSecond--;
- if(ucSecond>59)
- {
- ucSecond=0;
- }
- ucWd2Part3Update=1; //更新显示
- break;
- }
- break;
-
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 3://短按设置按键 对应朱兆祺学习板的S9键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 1:
- ucPart++;
- if(ucPart>3)
- {
- ucPart=1;
- ucWd=2; //切换到第二个窗口,设置时分秒
- ucWd2Update=1; //窗口2更新显示
- }
- ucWd1Update=1; //窗口1更新显示
- break;
- case 2:
- if(ucPart>0) //在窗口2的时候,要第一次激活设置时间,必须是长按3秒才可以,这里短按激活不了第一次
- {
- ucPart++;
- if(ucPart>3) //设置时间结束
- {
- ucPart=0;
- /* 注释五:
- * 每个月份的天数最大值是不一样的,在写入ds1302时钟芯片内部数据前,应该做一次调整。
- * 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
- */
- ucDate=date_adjust(ucYear,ucMonth,ucDate); //日调整 避免日的数值在某个月份超范围
- ucYearBCD=number_to_bcd(ucYear); //原始数值转BCD
- ucMonthBCD=number_to_bcd(ucMonth); //原始数值转BCD
- ucDateBCD=number_to_bcd(ucDate); //原始数值转BCD
- ucHourBCD=number_to_bcd(ucHour); //原始数值转BCD
- ucMinuteBCD=number_to_bcd(ucMinute); //原始数值转BCD
- ucSecondBCD=number_to_bcd(ucSecond); //原始数值转BCD
- Write1302 (WRITE_PROTECT,0X00); //禁止写保护
- Write1302 (WRITE_YEAR,ucYearBCD); //年修改
- Write1302 (WRITE_MONTH,ucMonthBCD); //月修改
- Write1302 (WRITE_DATE,ucDateBCD); //日修改
- Write1302 (WRITE_HOUR,ucHourBCD); //小时修改
- Write1302 (WRITE_MINUTE,ucMinuteBCD); //分钟修改
- Write1302 (WRITE_SECOND,ucSecondBCD); //秒位修改
- Write1302 (WRITE_PROTECT,0x80); //允许写保护
- }
- ucWd2Update=1; //窗口2更新显示
- }
- break;
-
- }
-
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
- case 17://长按3秒设置按键 对应朱兆祺学习板的S9键
- switch(ucWd) //在不同的窗口下,设置不同的参数
- {
- case 2:
- if(ucPart==0) //处于非设置时间的状态下,要第一次激活设置时间,必须是长按3秒才可以
- {
- ucWd=1;
- ucPart=1; //进入到设置日期的状态下
- ucWd1Update=1; //窗口1更新显示
- }
- break;
-
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- }
-
-
- /* 注释六:
- * 注意,此处就是第一次出现的电平按键程序,跟以往的下降沿按键不一样。
- * ucKey4Sr是经过软件滤波处理后,直接反应IO口电平状态的变量.当电平发生
- * 变化时,就会切换到不同的显示界面,这里多用了一个ucKey4SrRecord变量
- * 记录上一次的电平状态,是为了避免一直刷新显示。
- */
- if(ucKey4Sr!=ucKey4SrRecord) //说明S13的切换按键电平状态发生变化
- {
- ucKey4SrRecord=ucKey4Sr;//及时记录当前最新的按键电平状态 避免一直进来触发
- if(ucKey4Sr==1) //松手后切换到显示时间的窗口
- {
- ucWd=2; //显示时分秒的窗口
- ucPart=0; //进入到非设置时间的状态下
- ucWd2Update=1; //窗口2更新显示
- }
- else //按下去切换到显示日期的窗口
- {
- ucWd=1; //显示年月日的窗口
- ucPart=0; //进入到非设置时间的状态下
- ucWd1Update=1; //窗口1更新显示
- }
-
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(ucVoiceLock==0) //原子锁判断
- {
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
-
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
-
- }
- }
- if(ucDs1302Error>0) //EEPROM出错
- {
- if(ucDs1302Lock==0)//原子锁判断
- {
- uiDs1302Cnt++; //间歇性蜂鸣器报警的计时器
- }
- }
- if(ucDpyTimeLock==0) //原子锁判断
- {
- uiDpyTimeCnt++; //数码管的闪烁计时器
- }
- key_scan(); //按键扫描函数
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- /* 注释七:
- * 检查ds1302芯片的备用电池电量是否用完了。
- * 在上电的时候,在一个特定的地址里把数据读出来,如果发现不等于0x5a,
- * 则是因为备用电池电量用完了,导致保存在ds1302内部RAM数据区的数据被更改,
- * 与此同时,应该重新写入一次0x5a,为下一次通电判断做准备
- */
- ucCheckDs1302=Read1302(READ_CHECK); //判断ds1302内部的数据是否被更改
- if(ucCheckDs1302!=0x5a)
- {
- Write1302 (WRITE_PROTECT,0X00); //禁止写保护
- Write1302 (WRITE_CHECK,0x5a); //重新写入标志数据,方便下一次更换新电池后的判断
- Write1302 (WRITE_PROTECT,0x80); //允许写保护
- ucDs1302Error=1; //表示ds1302备用电池没电了,报警提示更换新电池
- }
- }
复制代码
总结陈词:
下一节开始讲单片机驱动温度传感器芯片的内容,欲知详情,请听下回分解-----利用DS18B20做一个温控器 。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-5-28 12:31
第四十九节:利用DS18B20做一个温控器 。
开场白:
DS18B20是一款常用的温度传感器芯片,它只占用单片机一根IO口,使用起来也特别方便。需要特别注意的是,正因为它只用一根IO口跟单片机通讯,因此读取一次温度值的通讯时间比较长,而且时序要求严格,在通讯期间不允许被单片机其它的中断干扰,因此在实际项目中,系统一旦选用了这款传感器芯片,就千万不要选用动态扫描数码管的显示方式。否则在关闭中断读取温度的时候,数码管的显示会有略微的“闪烁”现象。
DS18B20的测温范围是-55度至125度。在-10度至85度的温度范围内误差是+-0.5度,能满足大部分常用的测温要求。
这一节要教会大家三个知识点:
第一个:大概了解一下DS18B20的驱动程序。
第二个:做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个缓冲温差。本程序的缓冲温差是2度。
第三个:继续加深了解按键,显示,传感器它们三者是如何紧密关联起来的程序框架。
具体内容,请看源代码讲解。
(1) 硬件平台.
基于坚鸿51单片机学习板。
(2)实现功能:
本程序只有1个窗口。这个窗口有2个局部显示。
第1个局部是第7,6,5位数码管,显示设定的温度。
第2个局部是第4,3,2,1位数码管,显示实际环境温度。其中第4位数码管显示正负符号位。
S1按键是加键,S5按键是减键。通过它们可以直接设置“设定温度”。
一个LED灯用来模拟工控的继电器。
当实际温度低于或者等于设定温度2度以下时,模拟继电器的LED灯亮。
当实际温度等于或者大于设定温度时,模拟继电器的LED灯灭。
当实际温度处于设定温度和设定温度减去2度的范围内,模拟继电器的LED维持现状,这个2度范围用来做缓冲温差,避免继电器在临界温度附近频繁跳动切换。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_key_time1 20 //按键去抖动延时的时间
- #define const_key_time2 20 //按键去抖动延时的时间
- #define const_ds18b20_sampling_time 180 //累计主循环次数的时间,每次刷新采样时钟芯片的时间
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(void); //定时中断函数
- void key_service(void); //按键服务的应用程序
- void key_scan(void);//按键扫描函数 放在定时中断里
- void temper_control_service(void); //温控程序
- void ds18b20_sampling(void); //ds18b20采样程序
- void ds18b20_reset(); //复位ds18b20的时序
- unsigned char ds_read_byte(void ); //读一字节
- void ds_write_byte(unsigned char dat); //写一个字节
- unsigned int get_temper(); //读取一次没有经过换算的温度数值
- sbit dq_dr_sr=P2^6; //ds18b20的数据驱动线
- sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
- sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
- sbit led_dr=P3^5; //LED灯,模拟工控中的继电器
- sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- unsigned int uiSampingCnt=0; //采集Ds1302的计时器,每秒钟更新采集一次
- unsigned char ucSignFlag=0; //正负符号。0代表正数,1代表负数,表示零下多少度。
- unsigned long ulCurrentTemper=33; //实际温度
- unsigned long ulSetTemper=26; //设定温度
- unsigned int uiTemperTemp=0; //中间变量
- unsigned char ucKeySec=0; //被触发的按键编号
- unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器
- unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
- unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器
- unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucVoiceLock=0; //蜂鸣器鸣叫的原子锁
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd=1; //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
- unsigned char ucWd1Part1Update=1; //在窗口1中,局部1的更新显示标志
- unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- unsigned char ucTemp5=0; //中间过渡变量
- unsigned char ucTemp6=0; //中间过渡变量
- unsigned char ucTemp7=0; //中间过渡变量
- unsigned char ucTemp8=0; //中间过渡变量
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- ds18b20_sampling(); //ds18b20采样程序
- temper_control_service(); //温控程序
- display_service(); //显示的窗口菜单服务程序
- }
- }
- /* 注释一:
- * 做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个
- * 缓冲温差。本程序的缓冲温差是2度。
- */
- void temper_control_service(void) //温控程序
- {
- if(ucSignFlag==0) //是正数的前提下
- {
- if(ulCurrentTemper>=ulSetTemper) //当实际温度大于等于设定温度时
- {
- led_dr=0; //模拟继电器的LED灯熄灭
- }
- else if(ulCurrentTemper<=(ulSetTemper-2)) //当实际温度小于等于设定温度2读以下时,这里的2是缓冲温差2度
- {
- led_dr=1; //模拟继电器的LED灯点亮
- }
- }
- else //是负数,说明是零下多少度的情况下
- {
- led_dr=1; //模拟继电器的LED灯点亮
- }
- }
- void ds18b20_sampling(void) //ds18b20采样程序
- {
- ++uiSampingCnt; //累计主循环次数的时间
- if(uiSampingCnt>const_ds18b20_sampling_time) //每隔一段时间就更新采集一次Ds18b20数据
- {
- uiSampingCnt=0;
- ET0=0; //禁止定时中断
- uiTemperTemp=get_temper(); //读取一次没有经过换算的温度数值
- ET0=1; //开启定时中断
- if((uiTemperTemp&0xf800)==0xf800) //是负号
- {
- ucSignFlag=1;
- uiTemperTemp=~uiTemperTemp; //求补码
- uiTemperTemp=uiTemperTemp+1;
- }
- else //是正号
- {
- ucSignFlag=0;
- }
- ulCurrentTemper=0; //把int数据类型赋给long类型之前要先清零
- ulCurrentTemper=uiTemperTemp;
- ulCurrentTemper=ulCurrentTemper*10; //为了先保留一位小数点,所以放大10倍,
- ulCurrentTemper=ulCurrentTemper>>4; //往右边移动4位,相当于乘以0.0625. 此时保留了1位小数点,
- ulCurrentTemper=ulCurrentTemper+5; //四舍五入
- ulCurrentTemper=ulCurrentTemper/10; //四舍五入后,去掉小数点
- ucWd1Part2Update=1; //局部2更新显示实时温度
- }
- }
- //ds18b20驱动程序
- unsigned int get_temper() //读取一次没有经过换算的温度数值
- {
- unsigned char temper_H;
- unsigned char temper_L;
- unsigned int ds18b20_data=0;
- ds18b20_reset(); //复位ds18b20的时序
- ds_write_byte(0xCC);
- ds_write_byte(0x44);
- ds18b20_reset(); //复位ds18b20的时序
- ds_write_byte(0xCC);
- ds_write_byte(0xBE);
- temper_L=ds_read_byte();
- temper_H=ds_read_byte();
- ds18b20_data=temper_H; //把两个字节合并成一个int数据类型
- ds18b20_data=ds18b20_data<<8;
- ds18b20_data=ds18b20_data|temper_L;
- return ds18b20_data;
- }
- void ds18b20_reset() //复位ds18b20的时序
- {
- unsigned char x;
- dq_dr_sr=1;
- delay_short(8);
- dq_dr_sr=0;
- delay_short(80);
- dq_dr_sr=1;
- delay_short(14);
- x=dq_dr_sr;
- delay_short(20);
- }
- void ds_write_byte(unsigned char date) //写一个字节
- {
- unsigned char i;
- for(i=0;i<8;i++)
- {
- dq_dr_sr=0;
- dq_dr_sr=date&0x01;
- delay_short(5);
- dq_dr_sr=1;
- date=date>>1;
- }
- }
- unsigned char ds_read_byte(void ) //读一字节
- {
- unsigned char i;
- unsigned char date=0;
- for(i=0;i<8;i++)
- {
- dq_dr_sr=0;
- date=date>>1;
- dq_dr_sr=1;
- if(dq_dr_sr)
- {
- date=date|0x80;
- }
- delay_short(5);
- }
- return (date);
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
- {
- case 1:
- if(ucWd1Part1Update==1)//局部设定温度更新显示
- {
- ucWd1Part1Update=0;
- ucTemp8=10; //显示空
- if(ulSetTemper>=100)
- {
- ucTemp7=ulSetTemper%1000/100; //显示设定温度的百位
- }
- else
- {
- ucTemp7=10; //显示空
- }
- if(ulSetTemper>=10)
- {
- ucTemp6=ulSetTemper%100/10; //显示设定温度的十位
- }
- else
- {
- ucTemp6=10; //显示空
- }
- ucTemp5=ulSetTemper%10; //显示设定温度的个位
- ucDigShow8=ucTemp8; //数码管显示实际内容
- ucDigShow7=ucTemp7;
- ucDigShow6=ucTemp6;
- ucDigShow5=ucTemp5;
- }
- if(ucWd1Part2Update==1)//局部实际温度更新显示
- {
- if(ucSignFlag==0) //正数
- {
- ucTemp4=10; //显示空
- }
- else //负数,说明是零下多少度的情况下
- {
- ucTemp4=11; //显示负号-
- }
- if(ulCurrentTemper>=100)
- {
- ucTemp3=ulCurrentTemper%100/100; //显示实际温度的百位
- }
- else
- {
- ucTemp3=10; //显示空
- }
- if(ulCurrentTemper>=10)
- {
- ucTemp2=ulCurrentTemper%100/10; //显示实际温度的十位
- }
- else
- {
- ucTemp2=10; //显示空
- }
- ucTemp1=ulCurrentTemper%10; //显示实际温度的个数位
- ucDigShow4=ucTemp4; //数码管显示实际内容
- ucDigShow3=ucTemp3;
- ucDigShow2=ucTemp2;
- ucDigShow1=ucTemp1;
- }
- break;
- }
-
- }
- void key_scan(void)//按键扫描函数 放在定时中断里
- {
- if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock1=0; //按键自锁标志清零
- uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt1++; //累加定时中断次数
- if(uiKeyTimeCnt1>const_key_time1)
- {
- uiKeyTimeCnt1=0;
- ucKeyLock1=1; //自锁按键置位,避免一直触发
- ucKeySec=1; //触发1号键
- }
- }
- if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
- {
- ucKeyLock2=0; //按键自锁标志清零
- uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
- }
- else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
- {
- uiKeyTimeCnt2++; //累加定时中断次数
- if(uiKeyTimeCnt2>const_key_time2)
- {
- uiKeyTimeCnt2=0;
- ucKeyLock2=1; //自锁按键置位,避免一直触发
- ucKeySec=2; //触发2号键
- }
- }
- }
- void key_service(void) //按键服务的应用程序
- {
- switch(ucKeySec) //按键服务状态切换
- {
- case 1:// 加按键 对应朱兆祺学习板的S1键
- switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
- {
- case 1: //在窗口1下设置设定温度
- ulSetTemper++;
- if(ulSetTemper>125)
- {
- ulSetTemper=125;
- }
- ucWd1Part1Update=1; //更新显示设定温度
- break;
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
- case 2:// 减按键 对应朱兆祺学习板的S5键
- switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
- {
- case 1: //在窗口1下设置设定温度
- if(ulSetTemper>2) //由于缓冲温差是2度,所以我人为规定最小允许设定的温度不能低于2度
- {
- ulSetTemper--;
- }
- ucWd1Part1Update=1; //更新显示设定温度
- break;
-
- }
- ucVoiceLock=1; //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
- uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
- ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
- break;
-
-
- }
-
-
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(ucVoiceLock==0) //原子锁判断
- {
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
-
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
-
- }
- }
- key_scan(); //按键扫描函数
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- led_dr=0;//此处的LED灯模拟工控中的继电器
- key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=0; //小数点全部不显示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
下一节开始讲单片机采集模拟信号的内容,欲知详情,请听下回分解-----利用ADC0832采集电压的模拟信号。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-6-5 15:56
第五十节:利用ADC0832采集电压信号,用平均法和区间法进行软件滤波处理。
开场白:
ADC0832是一款常用的8位AD采样芯片,通过它可以把外部的模拟电压信号转换成数字信号,然后给单片机进行换算,显示等处理。
这一节要教会大家五个知识点:
第一个:分辨率的算法。有些书上说8位AD最高分辩可达到256级(0xff+1),当输入电压是0---5V时,电压精度为19.53mV(5000mV除以256),我认为这种说法是错误的。8位AD的最高分辨率应该是255级(0xff),当输入电压是0---5V时,电压精度为19.61mV(5000mV除以255)。
第二个:用求平均值的滤波法,可以使AD采样的数据更加圆滑,去除小毛刺。
第三个:用区间滤波法,在一些干扰很大的场合,可以避免末尾小数点的数据频繁跳动。
第四个:如何使系统可以采集到更高的电压。由于ADC0832直接采集的电压最大不能超过5V,如果要采集的最大电压是25V该怎么办?我们只要在外部多增加1个10K的电阻和1个40K的电阻组成分压电路,把25V分压成5V,然后再让ADC0832采样,这时采样到的数据只要乘以5的系数,就可以得到超过5V的实际电压。选择分压电阻时,阻值尽量不要太小,一般要10K级别以上,阻值大一点,对被采样的系统干扰影响就越小。
第五个:如何有效保护AD通道口。我在一些电压不稳定的工控场合,一般是在AD通道口对负极反接一个瞬变二极管SA5.0A。当电压超过5V时,瞬变二极管会导通吸收掉多余的能量,把电压降下来,避免AD通道口烧坏。
具体内容,请看源代码讲解。
(1) 硬件平台.
基于坚鸿51单片机学习板。
(2)实现功能:
本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过平均法,区间法滤波的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象
系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(void); //定时中断函数
- void ad_sampling_service(void); //AD采样与处理的服务程序
- sbit led_dr=P3^5; //LED灯
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- sbit adc0832_clk_dr = P1^2; // 定义adc0832的引脚
- sbit adc0832_cs_dr = P1^0;
- sbit adc0832_data_sr_dr = P1^1;
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Part1Update=1; //在窗口1中,局部1的更新显示标志
- unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- unsigned char ucTemp5=0; //中间过渡变量
- unsigned char ucTemp6=0; //中间过渡变量
- unsigned char ucTemp7=0; //中间过渡变量
- unsigned char ucTemp8=0; //中间过渡变量
- unsigned char ucAD=0; //AD值
- unsigned char ucCheckAD=0; //用来做校验对比的AD值
- unsigned long ulTemp=0; //参与换算的中间变量
- unsigned long ulTempFilterV=0; //参与换算的中间变量
- unsigned long ulBackupFilterV=5000; //备份最新采样数据的中间变量
- unsigned char ucSamplingCnt=0; //统计采样的次数 本程序采样8次后求平均值
- unsigned long ulV=0; //未经滤波处理的实时电压值
- unsigned long ulFilterV=0; //经过滤波后的实时电压值
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- ad_sampling_service(); //AD采样与处理的服务程序
- display_service(); //显示的窗口菜单服务程序
- }
- }
- void ad_sampling_service(void) //AD采样与处理的服务程序
- {
- unsigned char i;
- ucAD=0; //AD值
- ucCheckAD=0; //用来做校验对比的AD值
- /* 片选信号置为低电平 */
- adc0832_cs_dr = 0;
- /* 第一个脉冲,开始位 */
- adc0832_data_sr_dr = 1;
- adc0832_clk_dr = 0;
- delay_short(1);
- adc0832_clk_dr = 1;
- /* 第二个脉冲,选择通道 */
- adc0832_data_sr_dr = 1;
- adc0832_clk_dr = 0;
- adc0832_clk_dr = 1;
- /* 第三个脉冲,选择通道 */
- adc0832_data_sr_dr = 0;
- adc0832_clk_dr = 0;
- adc0832_clk_dr = 1;
- /* 数据线输出高电平 */
- adc0832_data_sr_dr = 1;
- delay_short(2);
- /* 第一个下降沿 */
- adc0832_clk_dr = 1;
- adc0832_clk_dr = 0;
- delay_short(1);
- /* AD值开始送出 */
- for (i = 0; i < 8; i++)
- {
- ucAD <<= 1;
- adc0832_clk_dr = 1;
- adc0832_clk_dr = 0;
- if (adc0832_data_sr_dr==1)
- {
- ucAD |= 0x01;
- }
- }
- /* 用于校验的AD值开始送出 */
- for (i = 0; i < 8; i++)
- {
- ucCheckAD >>= 1;
- if (adc0832_data_sr_dr==1)
- {
- ucCheckAD |= 0x80;
- }
- adc0832_clk_dr = 1;
- adc0832_clk_dr = 0;
- }
-
- /* 片选信号置为高电平 */
- adc0832_cs_dr = 1;
- if(ucCheckAD==ucAD) //检验相等
- {
-
- ulTemp=0; //把char类型数据赋值给long类型数据之前,必须先清零
- ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型
- /* 注释一:
- * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
- * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
- * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
- */
- ulTemp=5000*ulTemp/255; //进行电压换算
- ulV=ulTemp; //得到未经滤波处理的实时电压值
- ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压
- ulTempFilterV=ulTempFilterV+ulTemp; //累加8次后求平均值
- ucSamplingCnt++; //统计已经采样累计的次数
- if(ucSamplingCnt>=8)
- {
- /* 注释二:
- * 求平均值滤波法,为了得到的数据更加圆滑,去除小毛刺。
- * 向右边移动3位相当于除以8。
- */
- ulTempFilterV=ulTempFilterV>>3; //求平均值滤波法
- /* 注释三:
- * 以下区间滤波法,为了避免末尾小数点的数据频繁跳动。
- * 这里的20用于区间滤波法的正负偏差,这里的20代表0.020V。
- * 意思是只要最近采集到的数据在正负0.020V偏差范围内,就不更新。
- */
- if(ulBackupFilterV>=20) //最近备份的上一次数据大于等于0.02V的情况下
- {
- if(ulTempFilterV<(ulBackupFilterV-20)||ulTempFilterV>(ulBackupFilterV+20)) //在正负0.020V偏差范围外,更新
- {
- ulBackupFilterV=ulTempFilterV; //备份最新采样的数据,方便下一次对比判断
- ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
- ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
- }
- }
- else //最近备份的上一次数据小于0.02V的情况下
- {
- if(ulTempFilterV>(ulBackupFilterV+20)) //在正0.020V偏差范围外,更新
- {
- ulBackupFilterV=ulTempFilterV; //备份最新采样的数据,方便下一次对比判断
- ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
- ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
- }
-
- }
- ucSamplingCnt=0; //清零,为下一轮采样滤波作准备。
- ulTempFilterV=0;
- }
-
- }
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
- {
- ucWd1Part1Update=0;
- ucTemp8=ulV%10000/1000; //显示电压值个位
- ucTemp7=ulV%1000/100; //显示电压值小数点后第1位
- ucTemp6=ulV%100/10; //显示电压值小数点后第2位
- ucTemp5=ulV%10; //显示电压值小数点后第3位
- ucDigShow8=ucTemp8; //数码管显示实际内容
- ucDigShow7=ucTemp7;
- ucDigShow6=ucTemp6;
- ucDigShow5=ucTemp5;
- }
- if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
- {
- ucWd1Part2Update=0;
- ucTemp4=ulFilterV%10000/1000; //显示电压值个位
- ucTemp3=ulFilterV%1000/100; //显示电压值小数点后第1位
- ucTemp2=ulFilterV%100/10; //显示电压值小数点后第2位
- ucTemp1=ulFilterV%10; //显示电压值小数点后第3位
- ucDigShow4=ucTemp4; //数码管显示实际内容
- ucDigShow3=ucTemp3;
- ucDigShow2=ucTemp2;
- ucDigShow1=ucTemp1;
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- led_dr=0;//LED灯默认关闭
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=1; //显示未经过滤波电压的小数点
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=1; //显示经过滤波后电压的小数点
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
这节用区间滤波法虽然可以解决小数点后面的数据出现频繁跳动的现象,但是也存在一个小问题,就是精度受到了影响,比如我们设置的正负偏差是0.02V,那就意味着系统存在0.02V的误差。有没有更好的办法解决这个问题?如果系统的末尾数据一直不断处于频繁跳动中,那么只能牺牲一点精度,我认为用区间法已经是最好的解决办法了,但是经过本次实验,我观察到未经过滤波处理的数据只是偶尔跳动,并非频繁跳动,所以下一节我会给大家介绍一种不用牺牲精度,又可以很好滤波的方法。欲知详情,请听下回分解-----利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-6-15 12:21
第五十一节:利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。
开场白:
连续判断N次一致性的滤波法,是为了避免末尾小数点的数据偶尔跳动。这种滤波方法的原理跟我在按键扫描中去抖动的原理是一模一样的,被我频繁地应用在大量的工控项目中。
这一节要教会大家一个知识点:连续判断N次一致性的滤波法。
具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
具体内容,请看源代码讲解。
(1)硬件平台.
基于坚鸿51单片机学习板。
(2)实现功能:
本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过特定算法滤波后的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象。而且显示的电压值跟未经过滤波的电压值几乎是完全一致,不会出现上一节用区间滤波法所留下的0.02V误差问题。
系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_N 8 //连续判断N次一致性滤波方法中,N的取值
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驱动数码管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(void); //显示数码管字模的驱动函数
- void display_service(void); //显示的窗口菜单服务程序
- //驱动LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void T0_time(void); //定时中断函数
- void ad_sampling_service(void); //AD采样与处理的服务程序
- sbit led_dr=P3^5; //LED灯
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- sbit adc0832_clk_dr = P1^2; // 定义adc0832的引脚
- sbit adc0832_cs_dr = P1^0;
- sbit adc0832_data_sr_dr = P1^1;
- unsigned char ucDigShow8; //第8位数码管要显示的内容
- unsigned char ucDigShow7; //第7位数码管要显示的内容
- unsigned char ucDigShow6; //第6位数码管要显示的内容
- unsigned char ucDigShow5; //第5位数码管要显示的内容
- unsigned char ucDigShow4; //第4位数码管要显示的内容
- unsigned char ucDigShow3; //第3位数码管要显示的内容
- unsigned char ucDigShow2; //第2位数码管要显示的内容
- unsigned char ucDigShow1; //第1位数码管要显示的内容
- unsigned char ucDigDot8; //数码管8的小数点是否显示的标志
- unsigned char ucDigDot7; //数码管7的小数点是否显示的标志
- unsigned char ucDigDot6; //数码管6的小数点是否显示的标志
- unsigned char ucDigDot5; //数码管5的小数点是否显示的标志
- unsigned char ucDigDot4; //数码管4的小数点是否显示的标志
- unsigned char ucDigDot3; //数码管3的小数点是否显示的标志
- unsigned char ucDigDot2; //数码管2的小数点是否显示的标志
- unsigned char ucDigDot1; //数码管1的小数点是否显示的标志
- unsigned char ucDigShowTemp=0; //临时中间变量
- unsigned char ucDisplayDriveStep=1; //动态扫描数码管的步骤变量
- unsigned char ucWd1Part1Update=1; //在窗口1中,局部1的更新显示标志
- unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志
- unsigned char ucTemp1=0; //中间过渡变量
- unsigned char ucTemp2=0; //中间过渡变量
- unsigned char ucTemp3=0; //中间过渡变量
- unsigned char ucTemp4=0; //中间过渡变量
- unsigned char ucTemp5=0; //中间过渡变量
- unsigned char ucTemp6=0; //中间过渡变量
- unsigned char ucTemp7=0; //中间过渡变量
- unsigned char ucTemp8=0; //中间过渡变量
- unsigned char ucAD=0; //AD值
- unsigned char ucCheckAD=0; //用来做校验对比的AD值
- unsigned long ulTemp=0; //参与换算的中间变量
- unsigned long ulTempFilterV=0; //参与换算的中间变量
- unsigned long ulBackupFilterV=5000; //备份最新采样数据的中间变量
- unsigned char ucSamplingCnt=0; //记录连续N次采样的计数器
- unsigned long ulV=0; //未经滤波处理的实时电压值
- unsigned long ulFilterV=0; //经过滤波后的实时电压值
- //根据原理图得出的共阴数码管字模表
- code unsigned char dig_table[]=
- {
- 0x3f, //0 序号0
- 0x06, //1 序号1
- 0x5b, //2 序号2
- 0x4f, //3 序号3
- 0x66, //4 序号4
- 0x6d, //5 序号5
- 0x7d, //6 序号6
- 0x07, //7 序号7
- 0x7f, //8 序号8
- 0x6f, //9 序号9
- 0x00, //无 序号10
- 0x40, //- 序号11
- 0x73, //P 序号12
- };
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- ad_sampling_service(); //AD采样与处理的服务程序
- display_service(); //显示的窗口菜单服务程序
- }
- }
- void ad_sampling_service(void) //AD采样与处理的服务程序
- {
- unsigned char i;
- ucAD=0; //AD值
- ucCheckAD=0; //用来做校验对比的AD值
- /* 片选信号置为低电平 */
- adc0832_cs_dr = 0;
- /* 第一个脉冲,开始位 */
- adc0832_data_sr_dr = 1;
- adc0832_clk_dr = 0;
- delay_short(1);
- adc0832_clk_dr = 1;
- /* 第二个脉冲,选择通道 */
- adc0832_data_sr_dr = 1;
- adc0832_clk_dr = 0;
- adc0832_clk_dr = 1;
- /* 第三个脉冲,选择通道 */
- adc0832_data_sr_dr = 0;
- adc0832_clk_dr = 0;
- adc0832_clk_dr = 1;
- /* 数据线输出高电平 */
- adc0832_data_sr_dr = 1;
- delay_short(2);
- /* 第一个下降沿 */
- adc0832_clk_dr = 1;
- adc0832_clk_dr = 0;
- delay_short(1);
- /* AD值开始送出 */
- for (i = 0; i < 8; i++)
- {
- ucAD <<= 1;
- adc0832_clk_dr = 1;
- adc0832_clk_dr = 0;
- if (adc0832_data_sr_dr==1)
- {
- ucAD |= 0x01;
- }
- }
- /* 用于校验的AD值开始送出 */
- for (i = 0; i < 8; i++)
- {
- ucCheckAD >>= 1;
- if (adc0832_data_sr_dr==1)
- {
- ucCheckAD |= 0x80;
- }
- adc0832_clk_dr = 1;
- adc0832_clk_dr = 0;
- }
-
- /* 片选信号置为高电平 */
- adc0832_cs_dr = 1;
- if(ucCheckAD==ucAD) //检验相等
- {
-
- ulTemp=0; //把char类型数据赋值给long类型数据之前,必须先清零
- ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型
- /* 注释一:
- * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
- * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
- * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
- */
- ulTemp=5000*ulTemp/255; //进行电压换算
- ulV=ulTemp; //得到未经滤波处理的实时电压值
- ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压
- /* 注释二:
- * 以下连续判断N次一致性的滤波法,为了避免末尾小数点的数据偶尔跳动。
- * 这种滤波方法的原理跟我在按键扫描中的去抖动原理是一模一样的,被我频繁
- * 地应用在大量的工控项目中。
- * 具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。
- * 另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,
- * 我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间
- * 只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
- *
- */
- if(ulTempFilterV!=ulTemp) //发现变量有变化
- {
- ucSamplingCnt++; //计数器累加
- if(ucSamplingCnt>const_N) //如果连续N次都是一致的,则认为不是干扰。确实有数据需要更新显示。这里的const_N取值是8
- {
- ucSamplingCnt=0;
- ulTempFilterV=ulTemp; //及时保存更新了的数据,方便下一次有新数据对比做准备
- ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
- ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
- }
- }
- else
- {
- ucSamplingCnt=0; //只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
- }
-
- }
- }
- void display_service(void) //显示的窗口菜单服务程序
- {
- if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
- {
- ucWd1Part1Update=0;
- ucTemp8=ulV%10000/1000; //显示电压值个位
- ucTemp7=ulV%1000/100; //显示电压值小数点后第1位
- ucTemp6=ulV%100/10; //显示电压值小数点后第2位
- ucTemp5=ulV%10; //显示电压值小数点后第3位
- ucDigShow8=ucTemp8; //数码管显示实际内容
- ucDigShow7=ucTemp7;
- ucDigShow6=ucTemp6;
- ucDigShow5=ucTemp5;
- }
- if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
- {
- ucWd1Part2Update=0;
- ucTemp4=ulFilterV%10000/1000; //显示电压值个位
- ucTemp3=ulFilterV%1000/100; //显示电压值小数点后第1位
- ucTemp2=ulFilterV%100/10; //显示电压值小数点后第2位
- ucTemp1=ulFilterV%10; //显示电压值小数点后第3位
- ucDigShow4=ucTemp4; //数码管显示实际内容
- ucDigShow3=ucTemp3;
- ucDigShow2=ucTemp2;
- ucDigShow1=ucTemp1;
- }
- }
- void display_drive(void)
- {
- //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
- switch(ucDisplayDriveStep)
- {
- case 1: //显示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2: //显示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3: //显示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4: //显示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5: //显示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6: //显示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7: //显示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8: //显示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80; //显示小数点
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8) //扫描完8个数码管后,重新从第一个开始扫描
- {
- ucDisplayDriveStep=1;
- }
- }
- //数码管的74HC595驱动函数
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干扰就增强
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED灯的74HC595驱动函数
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09; //先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01; //再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干扰就增强
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- display_drive(); //数码管字模的驱动函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- led_dr=0;//LED灯默认关闭
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- hc595_drive(0x00,0x00); //关闭所有经过另外两个74HC595驱动的LED灯
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- ucDigDot8=1; //显示未经过滤波电压的小数点
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=1; //显示经过滤波后电压的小数点
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //开总中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
在单片机AD采样的系统中,我常用的滤波方法有求平均值法,区间法,连续判断N次一致性这三种方法。读者可以根据不同的系统特点选择对应的滤波方法,有一些要求高的系统还可以把三种滤波方法混合在一起用。关于AD采样的知识到本节已经讲完,下一节会讲什么新内容呢?欲知详情,请听下回分解-----return语句鲜为人知的用法。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-6-22 02:05
第五十二节:程序后续升级修改的利器,return语句鲜为人知的用法。
开场白:
return语句经常用在带参数返回的函数中,字面上理解就是返回的意思,因此很多单片机初学者很容易忽略了return语句还有中断强行退出的功能。利用这个强行退出的功能,在项目后续程序的升级修改上很方便,还可以有效减少if语句的嵌套层数,使程序阅读起来很简洁。这一节要教大家return语句三个鲜为人知的用法:
第一个鲜为人知的用法:在空函数里,可以插入很多个return语句,不仅仅是一个。
第二个鲜为人知的用法:return语句可以有效较少程序里条件判断语句的嵌套层数。
第三个鲜为人知的用法:return语句本身已经包含了类似break语句的功能,不管当前处于几层的内部循环嵌套,只要遇到return语句都可以强行退出全部循环,并且直接退出当前子程序,不执行当前子程序后面的任何语句,这个功能实在是太强大,太铁腕了。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
本程序实现的功能跟第三十九节是一摸一样的,唯一的差别就是在第三十九节的基础上,插入了几个return语句,用新的return语句替代原来的条件和循环判断语句。
波特率是:9600 。
通讯协议:EB 00 55 XX YY
加无效填充字节后,上位机实际上应该发送:00 EB 00 55 XX YY
其中第1位00是无效填充字节,防止由于硬件原因丢失第一个字节。
其中第2,3,4位EB 00 55就是数据头
后2位XX YY就是有效数据
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字后面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。
也就是说,当在 串口助手往单片机发送十六进制数据串: eb 00 55 01 02 时,会听到蜂鸣器”滴”的一声。
(3)源代码讲解如下:
总结陈词:
我在第一节就告诉读者了,搞单片机开发如果不会C语言的指针也没关系,不会影响做项目。我本人平时做项目时,也很少用指针,只有在三种场合下我才会用指针,因为在这三种场合下,用了指针感觉程序阅读起来更加清爽了。所以,指针还是有它独到的好处,有哪三种好处?欲知详情,请听下回分解-----指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-6-29 07:32
第五十三节:指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。
开场白:
当我们想把某种算法通过一个函数来实现的时候,如果不会指针,那么只有两种方法。
第1种:用不带参数返回的空函数。这是最原始的做法,也是我当年刚毕业就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口。
第2种:用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,如果要用在返回多个输出结果的函数中,就无能为力了,这时候该怎么办?就必须用指针了,也就是我下面讲到的第3种方法。
这一节要教大家一个知识点:通过指针,让函数可以返回多个变量。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
通过电脑串口调试助手,往单片机发送EB 00 55 XX YY 指令,其中EB 00 55是数据头, XX是被除数,YY是除数。单片机收到指令后就会返回6个数据,最前面两个数据是第1种运算方式的商和余数,中间两个数据是第2种运算方式的商和余数,最后两个数据是第3种运算方式的商和余数。
比如电脑发送:EB 00 55 08 02
单片机就返回:04 00 04 00 04 00 (04是商,00是余数)
串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。
波特率是:9600 。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_rc_size 10 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_long(unsigned int uiDelaylong);
- void delay_short(unsigned int uiDelayShort);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口服务程序,在main函数里
- void eusart_send(unsigned char ucSendData);
- void chu_fa_yun_suan_1(void);//第1种方法 求商和余数
- unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求商
- unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求余数
- void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp);//第3种方法 求商和余数
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器
- unsigned char ucBeiChuShu_1=0; //第1种方法中的被除数
- unsigned char ucChuShu_1=1; //第1种方法中的除数
- unsigned char ucShang_1=0; //第1种方法中的商
- unsigned char ucYu_1=0; //第1种方法中的余数
- unsigned char ucBeiChuShu_2=0; //第2种方法中的被除数
- unsigned char ucChuShu_2=1; //第2种方法中的除数
- unsigned char ucShang_2=0; //第2种方法中的商
- unsigned char ucYu_2=0; //第2种方法中的余数
- unsigned char ucBeiChuShu_3=0; //第3种方法中的被除数
- unsigned char ucChuShu_3=1; //第3种方法中的除数
- unsigned char ucShang_3=0; //第3种方法中的商
- unsigned char ucYu_3=0; //第3种方法中的余数
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- usart_service(); //串口服务程序
- }
- }
- /* 注释一:
- * 第1种方法,用不带参数返回的空函数,这是最原始的做法,也是我当年刚毕业
- * 就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。
- * 我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,
- * 调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。
- * 在本函数中,被除数ucBeiChuShu_1和除数ucChuShu_1就是输入全局变量,
- * 商ucShang_1和余数ucYu_1就是输出全局变量。这种方法的缺点是阅读不直观,
- * 封装性不强,没有面对用户的输入输出接口,
- */
- void chu_fa_yun_suan_1(void)//第1种方法 求商和余数
- {
- if(ucChuShu_1==0) //如果除数为0,则商和余数都为0
- {
- ucShang_1=0;
- ucYu_1=0;
- }
- else
- {
- ucShang_1=ucBeiChuShu_1/ucChuShu_1; //求商
- ucYu_1=ucBeiChuShu_1%ucChuShu_1; //求余数
- }
- }
- /* 注释二:
- * 第2种方法,用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,
- * 比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,
- * 如果要用在返回多个输出结果的函数中,就无能为力了。比如本程序,就不能同时输出
- * 商和余数,只能分两个函数来做。如果要在一个函数中同时输出商和余数,该怎么办?
- * 这个时候就必须用指针了,也就是我下面讲到的第3种方法。
- */
- unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求商
- {
- unsigned char ucShangTemp;
- if(ucChuShuTemp==0) //如果除数为0,则商为0
- {
- ucShangTemp=0;
- }
- else
- {
- ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp; //求商
- }
- return ucShangTemp; //返回运算后的结果 商
- }
- unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求余数
- {
- unsigned char ucYuTemp;
- if(ucChuShuTemp==0) //如果除数为0,则余数为0
- {
- ucYuTemp=0;
- }
- else
- {
- ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp; //求余数
- }
- return ucYuTemp; //返回运算后的结果 余数
- }
- /* 注释三:
- * 第3种方法,用带指针的函数,就可以顺心所欲,不受return的局限,想输出多少个
- * 运算结果都可以,赞一个!在本函数中,ucBeiChuShuTemp和ucChuShuTemp是输入变量,
- * 它们不是指针,所以不具备输出接口属性。*p_ucShangTemp和*p_ucYuTemp是输出变量,
- * 因为它们是指针,所以具备输出接口属性。
- */
- void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp)//第3种方法 求商和余数
- {
- if(ucChuShuTemp==0) //如果除数为0,则商和余数都为0
- {
- *p_ucShangTemp=0;
- *p_ucYuTemp=0;
- }
- else
- {
- *p_ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp; //求商
- *p_ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp; //求余数
- }
- }
- void usart_service(void) //串口服务程序,在main函数里
- {
-
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
- //第1种运算方法,依靠全局变量
- ucBeiChuShu_1=ucRcregBuf[uiRcMoveIndex+3]; //被除数
- ucChuShu_1=ucRcregBuf[uiRcMoveIndex+4]; //除数
- chu_fa_yun_suan_1(); //调用一次空函数就出结果了,结果保存在ucShang_1和ucYu_1全局变量中
- eusart_send(ucShang_1); //把运算结果返回给上位机观察
- eusart_send(ucYu_1);//把运算结果返回给上位机观察
- //第2种运算方法,依靠两个带return语句的返回函数
- ucBeiChuShu_2=ucRcregBuf[uiRcMoveIndex+3]; //被除数
- ucChuShu_2=ucRcregBuf[uiRcMoveIndex+4]; //除数
- ucShang_2=get_shang_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求商
- ucYu_2=get_yu_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求余数
- eusart_send(ucShang_2); //把运算结果返回给上位机观察
- eusart_send(ucYu_2);//把运算结果返回给上位机观察
- //第3种运算方法,依靠指针
- ucBeiChuShu_3=ucRcregBuf[uiRcMoveIndex+3]; //被除数
- ucChuShu_3=ucRcregBuf[uiRcMoveIndex+4]; //除数
- /* 注释四:
- * 注意,由于商和余数是指针形参,我们代入的变量必须带地址符号& 。比如&ucShang_3和&ucYu_3。
- * 因为我们是把变量的地址传递进去的。
- */
- chu_fa_yun_suan_3(ucBeiChuShu_3,ucChuShu_3,&ucShang_3,&ucYu_3);//第3种方法 求商和余数
- eusart_send(ucShang_3); //把运算结果返回给上位机观察
- eusart_send(ucYu_3);//把运算结果返回给上位机观察
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- if(uiVoiceCnt!=0)
- {
- uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
- beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
- }
- else
- {
- ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
- beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
- }
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- uiSendCnt=0; //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
-
- }
- else //发送中断,及时把发送中断标志位清零
- {
- TI = 0;
- }
-
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- //配置定时器
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- TH1=TL1=-(11059200L/12/32/9600); //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
这节讲了指针的第一大好处,它的第二大好处是什么?欲知详情,请听下回分解-----指针的第二大好处,指针作为数组在函数内部的化身。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-7-6 11:26
第五十四节:指针的第二大好处,指针作为数组在函数中的输入接口。
开场白:
如果不会指针,当我们想把一个数组的数据传递进某个函数内部的时候,只能通过全局变量的方式,这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入接口。
针对以上问题,这一节要教大家一个知识点:通过指针,为函数增加一个数组输入接口。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07 指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第1种方法的排序结果,中间3个数据EE EE EE是第1种和第2种的分割线,为了方便观察,没实际意义。最后5个数据是第2种方法的排序结果.
比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05
串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。
波特率是:9600 。
(3)源代码讲解如下:
总结陈词:
第2种方法通过指针,为函数增加了一个数组输入接口,已经比第1种纯粹用全局变量的方法直观多了,但是还有一个小小的遗憾,因为它的输出排序结果仍然要靠全局变量。为了让函数更加完美,我们能不能为函数再增加一个输出接口?当然可以。欲知详情,请听下回分解-----指针的第三大好处,指针作为数组在函数中的输出接口。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-7-10 15:42
第五十五节:指针的第三大好处,指针作为数组在函数中的输出接口。
开场白:
上一节介绍的第2种方法,由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了,但是由于只有输入接口,没有输出接口,输出接口仍然要靠全局变量数组,所以还是有一个小小的遗憾,这节介绍的第3种方法就是为了改变这个遗憾,为数组在函数中多增加一个输出接口,这样,函数既有输入接口,又有输出接口,这样的函数才算完美直观。这一节要教大家一个知识点:通过指针,为函数增加一个数组输出接口。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07 指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第2种方法的排序结果,中间3个数据EE EE EE是第2种和第3种的分割线,为了方便观察,没实际意义。最后5个数据是第3种方法的排序结果.
比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05
串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。
波特率是:9600 。
(3)源代码讲解如下:
总结陈词:
通过本节程序的讲解,一部分细心的读者可能会发现一个规律,其实所谓指针作为数组在函数中的输入接口和输出接口,输入接口的指针跟输出接口的指针在语法上没有任何区别,我没有用到C语言中专门的关键词去限定某个指针是输入,某个指针是输出,因此,这个告诉我们什么道理?指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,不像普通的函数变量形参只能做输入。发现了这个秘密,我们可不可以把本节程序中的输入接口和输出接口合并成一个输入输出接口呢?当然可以。欲知详情,请听下回分解-----指针的第四大好处,指针作为数组在函数中的输入输出接口。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-7-10 17:52
第五十六节:指针的第四大好处,指针作为数组在函数中的输入输出接口。
开场白:
通过前面几个章节的学习,我们知道指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,类似全局变量的特点。我们根据实际项目的情况,在必要的时候可以直接把输入接口和输出接口合并在一起,这种方法的缺点是没有把输入和输出分开,没有那么直观。但是优点也是很明显的,就是比较省程序ROM容量和数据RAM容量,而且运行效率也比较快。这一节要教大家一个知识点:指针作为数组在函数中输入输出接口的特点。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07 指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第3种方法的排序结果,中间3个数据EE EE EE是第3种和第4种的分割线,为了方便观察,没实际意义。最后5个数据是第4种方法的排序结果.
比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05
串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。
波特率是:9600 。
(3)源代码讲解如下:
总结陈词:
通过本章的学习,我们知道指针在函数接口中的双向性,这个双向性是一把双刃剑,既给我们带来便捷,也给我们在以下两个场合中带来隐患。
第一个场合:当需要把输入接口和输出接口分开时,我们希望输入接口的参数不要被意外改变,改变的仅仅只能是输出接口的数据。但是指针的双向性,就有可能导致我们在写函数内部代码的时候一不小心改变而没有发觉。
第二个场合:如果是一个现成封装好的函数直接给我们调用,当我们发现是指针作为接口的时候,我们就不敢确定这个接口是输入接口,还是输出接口,或者是输入输出接口,我们传递进去的参数可能会更改,除非用之前进行数据备份,否则是没有安全感可言的。
有没有办法巧妙的解决以上两个问题?当然有。欲知详情,请听下回分解-----为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-7-13 08:31
第五十七节:为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。
开场白:
通过上一节的学习,我们知道指针在函数接口中具有双向性,这个双向性是一把双刃剑,既给我们带来便捷,也给我们带来隐患。这一节要教大家以下知识点:
凡是做输入接口的指针,都应该加上const标签来标识,它可以让原来双向性的接口变成了单向性接口,它有两个好处:
第一个:如果你是用别人已经封装好的函数,你发现接口指针带了const标签,就足以说明这个指针只能做输入接口,你用了它,不用担心输入数据被修改。
第二个:如果是你自己写的函数,你在输入接口处的指针加了const标签,它可以预防你在写函数内部代码时不小心修改了输入接口的数据。比如,你试着在函数内部更改带const标签的输入接口数据,当你点击编译时,会编译不过,出现错误提示:error C183: unmodifiable lvalue。这就是一道防火墙啊!
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
我只是把第55节中凡是输入接口数据的指针都加了const关键字标签,其它代码内容没变。
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07 指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第3种方法的排序结果,中间3个数据EE EE EE是第3种和第4种的分割线,为了方便观察,没实际意义。最后5个数据是第4种方法的排序结果.
比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05
波特率是:9600 。
(3)源代码讲解如下:
总结陈词:
通过前面几节的学习,我们知道了指针在函数接口中的输入输出用途,以及const关键字的作用。下一节将要讲指针的第五大好处。欲知详情,请听下回分解-----指针的第五大好处,指针在众多数组中的中转站作用。
(未完待续,下节更精彩,不要走开哦)
作者: primer 时间: 2014-7-17 14:11
很不错。继续努力。
作者: primer 时间: 2014-7-17 14:19
本帖最后由 primer 于 2014-7-17 14:21 编辑
12345678910
作者: ceshi 时间: 2014-7-17 14:42
12123123123
作者: 黑夜之狼 时间: 2014-7-17 14:50
持续关注
作者: tiantan153 时间: 2014-7-17 16:12
鸿哥好样的!
作者: jianhong_wu 时间: 2014-7-17 16:21
感谢大家支持。
作者: Mr.Caiii 时间: 2014-7-19 10:53
特地过来顶下鸿哥,我坚信,坚持看下去,肯定会有很大的收获,谢谢鸿哥~!!!
作者: jianhong_wu 时间: 2014-7-19 14:17
非常感谢。我后续会继续更新。
作者: jianhong_wu 时间: 2014-7-20 06:45
本帖最后由 jianhong_wu 于 2014-7-20 12:53 编辑
第五十八节:指针的第五大好处,指针在众多数组中的中转站作用。
开场白:
单个变量数据之间可以通过一条指令任意自由赋值转移,但是数组之间不能通过一条指令直接赋值转移,必须用for等循环指令挨个把数组的数据一个一个来赋值转移,如果一个 函数中,有很多数组需要赋值转移,那就非常麻烦了,要用很多for语句,耗时。还好C语言里有个指针,它可以非常高效地来切换我们所需要的数组,起到很好的中转站作用。这一节要教大家一个知识点:指针在众多数组中的中转站作用。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:
在第57节的串口收发程序基础上修改。在串口接收函数中,以下代码有略微修改:
while(uiRcregTotal>=4&&uiRcMoveIndex<=(uiRcregTotal-4))//注意,这里是4,不是上一节的5,因为只有eb 00 55 xx这4个数据
通过上位机来调用下位机对应的数组数据。
通过电脑串口调试助手,往单片机发送EB 00 55 XX 指令,其中EB 00 55是数据头,XX的取值范围是0x01 至 0x05,每个不同的值代表调用下位机不同的数组数据。0x01调用第1组数据,0x02调用第2组数据,0x05调用第5组数据。
第1组:11 12 13 14 15
第2组:21 22 23 24 25
第3组:31 32 33 34 35
第4组:41 42 43 44 45
第5组:51 52 53 54 55
下位机返回21个数据,前面5个是第1种不带指针函数返回的数据。中间5个是第2种不带指针函数返回的数据。最后5个是第3种带指针函数返回的数据。期间2组EE EE EE是各函数返回的数据分割线,为了方便观察,没实际意义。
比如电脑发送:EB 0055 02
单片机就返回:21 2223 24 25 EE EE EE 21 22 23 24 25 EE EE EE 21 22 23 24 25
波特率是:9600 。
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_array_size 5 //参与排序的数组大小
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- #define const_rc_size 10 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_long(unsigned int uiDelaylong);
- void delay_short(unsigned int uiDelayShort);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口服务程序,在main函数里
- void send_array_1(unsigned char ucArraySec); //第1种函数,不带指针
- void send_array_2(unsigned char ucArraySec); //第2种函数,不带指针
- void send_array_3(unsigned char ucArraySec); //第3种函数,带指针
- void eusart_send(unsigned char ucSendData);
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- const unsigned char array_0x01[]={0x11,0x12,0x13,0x14,0x15}; //第1个常量数组
- const unsigned char array_0x02[]={0x21,0x22,0x23,0x24,0x25}; //第2个常量数组
- const unsigned char array_0x03[]={0x31,0x32,0x33,0x34,0x35}; //第3个常量数组
- const unsigned char array_0x04[]={0x41,0x42,0x43,0x44,0x45}; //第4个常量数组
- const unsigned char array_0x05[]={0x51,0x52,0x53,0x54,0x55}; //第5个常量数组
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- usart_service(); //串口服务程序
- }
- }
- /* 注释一:
- * 第1种函数,内部不带指针,根据上位机相关的指令,
- * 直接返回对应的数组。由于不带指针,因此多用了5个for循环来搬运数组。
- * 比较耗程序ROM容量,也不够简洁清晰。
- */
- void send_array_1(unsigned char ucArraySec)
- {
- unsigned int i;
- switch(ucArraySec)
- {
- case 1: //直接返回第1个常量数组
- for(i=0;i<5;i++)
- {
- eusart_send(array_0x01[i]);
- }
- break;
- case 2: //直接返回第2个常量数组
- for(i=0;i<5;i++)
- {
- eusart_send(array_0x02[i]);
- }
- break;
- case 3: //直接返回第3个常量数组
- for(i=0;i<5;i++)
- {
- eusart_send(array_0x03[i]);
- }
- break;
- case 4: //直接返回第4个常量数组
- for(i=0;i<5;i++)
- {
- eusart_send(array_0x04[i]);
- }
- break;
- case 5: //直接返回第5个常量数组
- for(i=0;i<5;i++)
- {
- eusart_send(array_0x05[i]);
- }
- break;
- }
-
- }
- /* 注释二:
- * 第2种函数,内部不带指针,根据上位机相关的指令,
- * 先转移对应的数组放到一个中间变量数组,然后发送数组。
- * 由于不带指针,因此多用了6个for循环来搬运数组。
- * 跟第1种函数一样,比较耗程序ROM容量,也不够简洁清晰。
- */
- void send_array_2(unsigned char ucArraySec) //第2种函数,不带指针
- {
- unsigned int i;
- unsigned char array_temp[5]; //临时中间数组
- switch(ucArraySec)
- {
- case 1: //直接返回第1个常量数组
- for(i=0;i<5;i++)
- {
- array_temp[i]=array_0x01[i]; //先挨个把对应的数组数据转移到中间数组里
- }
- break;
- case 2: //直接返回第2个常量数组
- for(i=0;i<5;i++)
- {
- array_temp[i]=array_0x02[i]; //先挨个把对应的数组数据转移到中间数组里
- }
- break;
- case 3: //直接返回第3个常量数组
- for(i=0;i<5;i++)
- {
- array_temp[i]=array_0x03[i]; //先挨个把对应的数组数据转移到中间数组里
- }
- break;
- case 4: //直接返回第4个常量数组
- for(i=0;i<5;i++)
- {
- array_temp[i]=array_0x04[i]; //先挨个把对应的数组数据转移到中间数组里
- }
- break;
- case 5: //直接返回第5个常量数组
- for(i=0;i<5;i++)
- {
- array_temp[i]=array_0x05[i]; //先挨个把对应的数组数据转移到中间数组里
- }
- break;
- }
- for(i=0;i<5;i++)
- {
- eusart_send(array_temp[i]); //把临时存放在中间数组的数据全部发送出去
- }
- }
- /* 注释三:
- * 第3种函数,内部带指针,根据上位机相关的指令,
- * 先把对应的数组首地址传递给一个中间指针,然后再通过
- * 指针把整个数组的数据发送出去,由于带指针,切换转移数组的数据非常快,
- * 只需传递一下首地址给指针就可以,非常高效,整个函数只用了1个for循环。
- * 跟前面第1,2种函数相比,更加节省程序容量,处理速度更加快,更加简洁。
- */
- void send_array_3(unsigned char ucArraySec) //第3种函数,带指针
- {
- unsigned int i;
- unsigned char *p_array; //临时中间指针,作为数组的中转站,非常高效
- switch(ucArraySec)
- {
- case 1: //直接返回第1个常量数组
- p_array=array_0x01; //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
- break;
- case 2: //直接返回第2个常量数组
- p_array=array_0x02; //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
- break;
- case 3: //直接返回第3个常量数组
- p_array=array_0x03; //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
- break;
- case 4: //直接返回第4个常量数组
- p_array=array_0x04; //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
- break;
- case 5: //直接返回第5个常量数组
- p_array=array_0x05; //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
- break;
- }
- for(i=0;i<5;i++)
- {
- eusart_send(p_array[i]); //通过指针把数组的数据全部发送出去
- }
- }
- void usart_service(void) //串口服务程序,在main函数里
- {
- unsigned char i=0;
- unsigned char ucWhichArray;
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcregTotal>=4&&uiRcMoveIndex<=(uiRcregTotal-4)) //注意,这里是4,不是上一节的5,因为只有eb 00 55 xx这4个数据
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
- ucWhichArray=ucRcregBuf[uiRcMoveIndex+3]; //上位机需要返回的某个数组
- send_array_1(ucWhichArray); //第1种函数返回数组的5个数据,不带指针
- eusart_send(0xee); //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
- eusart_send(0xee);
- eusart_send(0xee);
- send_array_2(ucWhichArray); //第2种函数返回数组的5个数据,不带指针
- eusart_send(0xee); //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
- eusart_send(0xee);
- eusart_send(0xee);
- send_array_3(ucWhichArray); //第3种函数返回数组的5个数据,带指针
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- uiSendCnt=0; //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
-
- }
- else //发送中断,及时把发送中断标志位清零
- {
- TI = 0;
- }
-
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- //配置定时器
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- TH1=TL1=-(11059200L/12/32/9600); //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
通过前面几节的学习,基本上讲完了我平时用指针的所有心得体会。
下一节开始讲新内容。在前面一些章节中,我提到为了防止中断函数把某些共享数据破坏,在主函数中更改某个数据变量时,应该先关闭中断,修改完后再打开中断;我也提到了网友“红金龙吸味”关于原子锁的建议。经过这段时间的思考和总结,我发现不管是关中断开中断,还是原子锁,其实本质上都是程序在多进程中临界点的数据处理,原子锁在程序员中有个专用名词叫互斥量,而我引以为豪的状态机程序框架,主函数的switch语句,外加一个定时中断,本质上就是2个独立进程在不断切换并行运行。我觉得这个临界点处理的知识很重要,也很容易忽略,所以我决定专门用两节内容来讲讲这方面的知识应用。欲知详情,请听下回分解-----关中断和开中断在多进程临界点的应用。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-7-21 00:40
第五十九节:串口程序第40,44,45节中存在一个bug,特此紧急公告。
经过网友“intech2008”的提醒,在我之前发表的第40,44,45节串口接收程序中,在计算检验和的地方,存在一个不容易发觉的bug。
原来的是:
for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
{
ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[i];
}
应该改成:
for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
{
ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
}
由于本连载技术文章在各大论坛发布和被转载,我没法做到处处提醒,不得不专门用一节内容来告知各位读者。
下节预告-----关中断和开中断在多进程临界点的应用。
(未完待续,下节更精彩,不要走开哦)
作者: ggg 时间: 2014-7-23 13:24
支持。非常好的文章。
作者: jianhong_wu 时间: 2014-7-26 10:26
第六十节:用关中断和互斥量来保护多线程共享的全局变量。
开场白:
在前面一些章节中,我提到为了防止中断函数把某些共享数据破坏,在主函数中更改某个数据变量时,应该先关闭中断,修改完后再打开中断;我也提到了网友“红金龙吸味”关于原子锁的建议。经过这段时间的思考和总结,我发现不管是关中断开中断,还是原子锁,其实本质上都是程序在多进程中临界点的数据处理,原子锁有个专用名词叫互斥量,而我引以为豪的状态机程序框架,主函数的switch语句,外加一个定时中断,本质上就是2个独立进程在不断切换并行运行。
为什么要保护多线程共享的全局变量?因为,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
这一节要教大家一个知识点:如何用关中断和互斥量来保护多线程共享的全局变量。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
在第5节的基础上略作修改,让蜂鸣器在前面3秒发生一次短叫报警,在后面6秒发生一次长叫报警,如此反复循环。
(3)源代码讲解如下:
总结陈词:
从下一节开始我准备用几章节的内容来讲常用的数学运算程序。这些程序经常要用在计算器,工控,以及高精度的仪器仪表等领域。C语言的语法中不是已经提供了+,-,*,/这些运算符号吗?为什么还要专门写算法程序?因为那些运算符只能进行简单的运算,一旦数据超过了unsigned long(4个字节)的范围就会出错。而这种大数据算法的程序是什么样的?欲知详情,请听下回分解----大数据的加法运算。
(未完待续,下节更精彩,不要走开哦)
作者: 又一个暑假 时间: 2014-7-28 21:16
同志们:
如果你们在看鸿哥的程序串口不懂的配置,可以参照我的,我刚开始没调出来是QQ群里的@小傅-惠州告诉我要在中间的 单字符串 发送区就可以的
使用说明书如下
-
说明书.jpg
(90.29 KB, 下载次数: 341)
作者: jianhong_wu 时间: 2014-7-28 21:24
很不错的分享精神。
作者: 又一个暑假 时间: 2014-7-28 21:40
很不幸的是我之前没用世界上最好用的串口调试助手
很幸运的是碰到这个问题后让我知道了世界上最好用的串口调试助手http://www.dumenmen.com/forum.ph ... &extra=page%3D2
作者: jianhong_wu 时间: 2014-7-28 23:30
{:soso_e113:}{:soso_e113:}现在发现这个软件还不迟。
作者: kaisadadi33 时间: 2014-7-30 22:57
很好很强大,找工作就指望这个帖子了
作者: xiaomage 时间: 2014-7-31 08:20
连载,这个讲的好,谢谢!
作者: jianhong_wu 时间: 2014-8-4 13:27
第六十一节:组合BCD码,非组合BCD码,以及数值三者之间的相互转换和关系。
开场白:
本来这一节打算讲大数据的加法运算的,但是考虑大数据运算的基础是非组合BCD码,所以多增加一节讲BCD码的内容。
计算机中的BCD码,经常使用的有两种格式,即组合BCD码,非组合BCD码。
组合BCD码,是将两位十进制数,存放在一个字节中,例如:十进制数51的存放格式是0101 0001。
非组合BCD码,是将一个字节的低四位编码表示十进制数的一位,而高4位都为0。例如:十进制数51的占用了两个字节的空间,存放格式为:00000101 00000001。
这一节要教大家两个知识点:
第一个:如何编写组合BCD码,非组合BCD码,以及数值三者之间的相互转换函数。
第二个:通过转换函数的编写,重温前面几节所讲到的指针用法。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送EB 00 55 XX YY YY … YY YY 指令,其中EB 00 55是数据头,XX 是指令类型。YY是具体的数据。
指令类型01代表发送的是数值,需要转成组合BCD码和非组合BCD码,并且返回上位机显示。
指令类型02代表发送的是组合BCD码,需要转成数值和非组合BCD码,并且返回上位机显示。
指令类型03代表发送的是非组合BCD码,需要转成数值和组合BCD码,并且返回上位机显示。
返回上位机的数据中,中间3个数据EE EE EE是分割线,为了方便观察,没实际意义。
例如:十进制的数据52013140,它的十六进制数据是03 19 A8 54。
(a)上位机发送数据:eb 00 55 01 03 19 a8 54
单片机返回:52 01 31 40 EE EE EE 05 02 00 01 03 01 04 00
(b)上位机发送组合BCD码:eb 00 55 02 52 01 31 40
单片机返回:03 19 A8 54 EE EE EE 05 02 00 01 03 01 04 00
(c)发送非组合BCD码:eb 00 55 03 05 02 00 01 03 01 04 00
单片机返回:03 19 A8 54 EE EE EE 52 01 31 40
(3)源代码讲解如下:
- #include "REG52.H"
- #define const_voice_short 40 //蜂鸣器短叫的持续时间
- /* 注释一:
- * 注意,此处的const_rc_size是20,比之前章节的缓冲区稍微改大了一点。
- */
- #define const_rc_size 20 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_long(unsigned int uiDelaylong);
- void delay_short(unsigned int uiDelayShort);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口服务程序,在main函数里
- void eusart_send(unsigned char ucSendData);
- void number_to_BCD4(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit4);//把数值转换成组合BCD码
- void number_to_BCD8(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit8);//把数值转换成非组合BCD码
- void BCD4_to_number(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucNumber); //组合BCD码转成数值
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD_bit8); //组合BCD码转成非组合BCD码
- void BCD8_to_number(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucNumber); //非组合BCD码转成数值
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD_bit4); //非组合BCD码转成组合BCD码
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- /* 注释二:
- * 注意,本程序规定数值的最大范围是0至99999999
- * 数组中的数据。高位在数组下标大的方向,低位在数组下标小的方向。
- */
- unsigned char ucBufferNumber[4]; //数值,用4个字节表示long类型的数值
- unsigned char ucBufferBCB_bit4[4]; //组合BCD码
- unsigned char ucBufferBCB_bit8[8]; //非组合BCD码
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- usart_service(); //串口服务程序
- }
- }
- void number_to_BCD4(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit4)//把数值转换成组合BCD码
- {
- unsigned long ulNumberTemp=0;
- unsigned char ucTemp=0;
- ulNumberTemp=p_ucNumber[3]; //把4个字节的数值合并成一个long类型数据
- ulNumberTemp=ulNumberTemp<<8;
- ulNumberTemp=ulNumberTemp+p_ucNumber[2];
- ulNumberTemp=ulNumberTemp<<8;
- ulNumberTemp=ulNumberTemp+p_ucNumber[1];
- ulNumberTemp=ulNumberTemp<<8;
- ulNumberTemp=ulNumberTemp+p_ucNumber[0];
- p_ucBCD_bit4[3]=ulNumberTemp%100000000/10000000;
- p_ucBCD_bit4[3]=p_ucBCD_bit4[3]<<4; //前半4位存第8位组合BCD码
- ucTemp=ulNumberTemp%10000000/1000000;
- p_ucBCD_bit4[3]=p_ucBCD_bit4[3]+ucTemp; //后半4位存第7位组合BCD码
- p_ucBCD_bit4[2]=ulNumberTemp%1000000/100000;
- p_ucBCD_bit4[2]=p_ucBCD_bit4[2]<<4; //前半4位存第6位组合BCD码
- ucTemp=ulNumberTemp%100000/10000;
- p_ucBCD_bit4[2]=p_ucBCD_bit4[2]+ucTemp;//后半4位存第5位组合BCD码
- p_ucBCD_bit4[1]=ulNumberTemp%10000/1000;
- p_ucBCD_bit4[1]=p_ucBCD_bit4[1]<<4; //前半4位存第4位组合BCD码
- ucTemp=ulNumberTemp%1000/100;
- p_ucBCD_bit4[1]=p_ucBCD_bit4[1]+ucTemp;//后半4位存第3位组合BCD码
- p_ucBCD_bit4[0]=ulNumberTemp%100/10;
- p_ucBCD_bit4[0]=p_ucBCD_bit4[0]<<4; //前半4位存第2位组合BCD码
- ucTemp=ulNumberTemp%10;
- p_ucBCD_bit4[0]=p_ucBCD_bit4[0]+ucTemp;//后半4位存第1位组合BCD码
- }
- void number_to_BCD8(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit8)//把数值转换成非组合BCD码
- {
- unsigned long ulNumberTemp=0;
- ulNumberTemp=p_ucNumber[3]; //把4个字节的数值合并成一个long类型数据
- ulNumberTemp=ulNumberTemp<<8;
- ulNumberTemp=ulNumberTemp+p_ucNumber[2];
- ulNumberTemp=ulNumberTemp<<8;
- ulNumberTemp=ulNumberTemp+p_ucNumber[1];
- ulNumberTemp=ulNumberTemp<<8;
- ulNumberTemp=ulNumberTemp+p_ucNumber[0];
- p_ucBCD_bit8[7]=ulNumberTemp%100000000/10000000;//一个字节8位存储第8位非组合BCD码
- p_ucBCD_bit8[6]=ulNumberTemp%10000000/1000000;//一个字节8位存储第7位非组合BCD码
- p_ucBCD_bit8[5]=ulNumberTemp%1000000/100000;//一个字节8位存储第6位非组合BCD码
- p_ucBCD_bit8[4]=ulNumberTemp%100000/10000;//一个字节8位存储第5位非组合BCD码
- p_ucBCD_bit8[3]=ulNumberTemp%10000/1000;//一个字节8位存储第4位非组合BCD码
- p_ucBCD_bit8[2]=ulNumberTemp%1000/100;//一个字节8位存储第3位非组合BCD码
- p_ucBCD_bit8[1]=ulNumberTemp%100/10;//一个字节8位存储第2位非组合BCD码
- p_ucBCD_bit8[0]=ulNumberTemp%10;//一个字节8位存储第1位非组合BCD码
- }
- void BCD4_to_number(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucNumber) //组合BCD码转成数值
- {
- unsigned long ulTmep;
- unsigned long ulSum;
- ulSum=0; //累加和数值清零
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[3];
- ulTmep=ulTmep>>4; //把组合BCD码第8位分解出来
- ulTmep=ulTmep*10000000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[3];
- ulTmep=ulTmep&0x0000000f; //把组合BCD码第7位分解出来
- ulTmep=ulTmep*1000000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[2];
- ulTmep=ulTmep>>4; //把组合BCD码第6位分解出来
- ulTmep=ulTmep*100000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[2];
- ulTmep=ulTmep&0x0000000f; //把组合BCD码第5位分解出来
- ulTmep=ulTmep*10000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[1];
- ulTmep=ulTmep>>4; //把组合BCD码第4位分解出来
- ulTmep=ulTmep*1000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[1];
- ulTmep=ulTmep&0x0000000f; //把组合BCD码第3位分解出来
- ulTmep=ulTmep*100;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[0];
- ulTmep=ulTmep>>4; //把组合BCD码第2位分解出来
- ulTmep=ulTmep*10;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit4[0];
- ulTmep=ulTmep&0x0000000f; //把组合BCD码第1位分解出来
- ulTmep=ulTmep*1;
- ulSum=ulSum+ulTmep; //累加各位数值
- //以上代码非常有规律,有兴趣的读者也可以自己想办法把它压缩成一个for循环的函数,可以极大节省容量。
- p_ucNumber[3]=ulSum>>24; //把long类型数据分解成4个字节
- p_ucNumber[2]=ulSum>>16;
- p_ucNumber[1]=ulSum>>8;
- p_ucNumber[0]=ulSum;
- }
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD_bit8) //组合BCD码转成非组合BCD码
- {
- unsigned char ucTmep;
- ucTmep=p_ucBCD_bit4[3];
- p_ucBCD_bit8[7]=ucTmep>>4; //把组合BCD码第8位分解出来
- p_ucBCD_bit8[6]=ucTmep&0x0f; //把组合BCD码第7位分解出来
- ucTmep=p_ucBCD_bit4[2];
- p_ucBCD_bit8[5]=ucTmep>>4; //把组合BCD码第6位分解出来
- p_ucBCD_bit8[4]=ucTmep&0x0f; //把组合BCD码第5位分解出来
- ucTmep=p_ucBCD_bit4[1];
- p_ucBCD_bit8[3]=ucTmep>>4; //把组合BCD码第4位分解出来
- p_ucBCD_bit8[2]=ucTmep&0x0f; //把组合BCD码第3位分解出来
- ucTmep=p_ucBCD_bit4[0];
- p_ucBCD_bit8[1]=ucTmep>>4; //把组合BCD码第2位分解出来
- p_ucBCD_bit8[0]=ucTmep&0x0f; //把组合BCD码第1位分解出来
- }
- void BCD8_to_number(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucNumber) //非组合BCD码转成数值
- {
- unsigned long ulTmep;
- unsigned long ulSum;
- ulSum=0; //累加和数值清零
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[7];
- ulTmep=ulTmep*10000000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[6];
- ulTmep=ulTmep*1000000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[5];
- ulTmep=ulTmep*100000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[4];
- ulTmep=ulTmep*10000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[3];
- ulTmep=ulTmep*1000;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[2];
- ulTmep=ulTmep*100;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[1];
- ulTmep=ulTmep*10;
- ulSum=ulSum+ulTmep; //累加各位数值
- ulTmep=0;
- ulTmep=p_ucBCD_bit8[0];
- ulTmep=ulTmep*1;
- ulSum=ulSum+ulTmep; //累加各位数值
- //以上代码非常有规律,有兴趣的读者也可以自己想办法把它压缩成一个for循环的函数,可以极大节省容量。
- p_ucNumber[3]=ulSum>>24; //把long类型数据分解成4个字节
- p_ucNumber[2]=ulSum>>16;
- p_ucNumber[1]=ulSum>>8;
- p_ucNumber[0]=ulSum;
- }
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD_bit4) //非组合BCD码转成组合BCD码
- {
- unsigned char ucTmep;
- ucTmep=p_ucBCD_bit8[7]; //把非组合BCD码第8位分解出来
- p_ucBCD_bit4[3]=ucTmep<<4;
- p_ucBCD_bit4[3]=p_ucBCD_bit4[3]+p_ucBCD_bit8[6]; //把非组合BCD码第7位分解出来
- ucTmep=p_ucBCD_bit8[5]; //把非组合BCD码第6位分解出来
- p_ucBCD_bit4[2]=ucTmep<<4;
- p_ucBCD_bit4[2]=p_ucBCD_bit4[2]+p_ucBCD_bit8[4]; //把非组合BCD码第5位分解出来
- ucTmep=p_ucBCD_bit8[3]; //把非组合BCD码第4位分解出来
- p_ucBCD_bit4[1]=ucTmep<<4;
- p_ucBCD_bit4[1]=p_ucBCD_bit4[1]+p_ucBCD_bit8[2]; //把非组合BCD码第3位分解出来
- ucTmep=p_ucBCD_bit8[1]; //把非组合BCD码第2位分解出来
- p_ucBCD_bit4[0]=ucTmep<<4;
- p_ucBCD_bit4[0]=p_ucBCD_bit4[0]+p_ucBCD_bit8[0]; //把非组合BCD码第1位分解出来
-
- }
- void usart_service(void) //串口服务程序,在main函数里
- {
- unsigned char i=0;
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
- switch(ucRcregBuf[uiRcMoveIndex+3]) //根据命令类型来进行不同的处理
- {
- case 1: //接收到的是数值,需要转成组合BCD码和非组合BCD码
- for(i=0;i<4;i++)
- {
- ucBufferNumber[3-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的数据,注意,高位在数组下标大的方向
- }
- number_to_BCD4(ucBufferNumber,ucBufferBCB_bit4);//把数值转换成组合BCD码
- number_to_BCD8(ucBufferNumber,ucBufferBCB_bit8);//把数值转换成非组合BCD码
- for(i=0;i<4;i++)
- {
- eusart_send(ucBufferBCB_bit4[3-i]); ////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
- }
- eusart_send(0xee); //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
- eusart_send(0xee);
- eusart_send(0xee);
- for(i=0;i<8;i++)
- {
- eusart_send(ucBufferBCB_bit8[7-i]); ////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
- }
- break;
- case 2: //接收到的是组合BCD码,需要转成数值和非组合BCD码
- for(i=0;i<4;i++)
- {
- ucBufferBCB_bit4[3-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的组合BCD码,注意,高位在数组下标大的方向
- }
- BCD4_to_number(ucBufferBCB_bit4,ucBufferNumber); //组合BCD码转成数值
- BCD4_to_BCD8(ucBufferBCB_bit4,ucBufferBCB_bit8); //组合BCD码转成非组合BCD码
- for(i=0;i<4;i++)
- {
- eusart_send(ucBufferNumber[3-i]); ////把数值返回给上位机观察,注意,高位在数组下标大的方向
- }
- eusart_send(0xee); //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
- eusart_send(0xee);
- eusart_send(0xee);
- for(i=0;i<8;i++)
- {
- eusart_send(ucBufferBCB_bit8[7-i]); ////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
- }
- break;
- case 3: //接收到的是非组合BCD码,需要转成数值和组合BCD码
- for(i=0;i<8;i++)
- {
- ucBufferBCB_bit8[7-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的非组合BCD码,注意,高位在数组下标大的方向
- }
- BCD8_to_number(ucBufferBCB_bit8,ucBufferNumber); //非组合BCD码转成数值
- BCD8_to_BCD4(ucBufferBCB_bit8,ucBufferBCB_bit4); //非组合BCD码转成组合BCD码
- for(i=0;i<4;i++)
- {
- eusart_send(ucBufferNumber[3-i]); ////把数值返回给上位机观察
- }
- eusart_send(0xee); //为了方便上位机观察,多发送3个字节ee ee ee作为分割线,注意,高位在数组下标大的方向
- eusart_send(0xee);
- eusart_send(0xee);
- for(i=0;i<4;i++)
- {
- eusart_send(ucBufferBCB_bit4[3-i]); ////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
- }
- break;
- }
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- uiSendCnt=0; //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
-
- }
- else //发送中断,及时把发送中断标志位清零
- {
- TI = 0;
- }
-
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- //配置定时器
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- TH1=TL1=-(11059200L/12/32/9600); //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
有了这一节非组合BCD的基础知识,下一节就开始讲大数据的算法程序。这些算法程序经常要用在计算器,工控,以及高精度的仪器仪表等领域。C语言的语法中不是已经提供了+,-,*,/这些运算符号吗?为什么还要专门写算法程序?因为那些运算符只能进行简单的运算,一旦数据超过了unsigned long(4个字节)的范围就会出错。而这种大数据算法的程序是什么样的?欲知详情,请听下回分解----大数据的加法运算。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-8-13 00:46
第六十二节:大数据的加法运算。
开场白:
直接用C语言的“+”运算符进行加法运算时,“被加数”,“加数”,“和”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据加法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是上一节讲到的BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
这一节要教大家两个知识点:
第一个:如何通过用for循环语句改写上一节的组合BCD码跟非组合BCD码的转换函数。
第二个:如何编写涉及到大数据加法运算的算法程序函数,同时也复习了指针的用途。
第三个:如何在串口程序中通过关键字来截取所需要的数据。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被加数和加数。单片机把组合BCD码的运算结果返回到上位机。最大范围4位,从0到9999,如果超范围则返回EE EE EE报错。往单片机发送的数据格式:EB 00 55 XX XX 0d 0a YY YY 0d 0a指令,其中EB 00 55是数据头,XX 是被加数,可以是1个字节,也可以是2个字节。YY是加数,可以是1个字节,也可以是2个字节。0d 0a是固定的结束标志。
例如:
(a)1234+5678=6912
上位机发送数据:eb 00 55 12 34 0d 0a 56 78 0d 0a
单片机返回:69 12
(b)9999+56=10055 超过4位的9999,所以报错
上位机发送数据:eb 00 55 99 99 0d 0a 56 0d 0a
单片机返回:EE EE EE 表示出错了
(3)源代码讲解如下:
- #include "REG52.H"
- /* 注释一:
- * 本系统中,规定最大运算位数是4位。
- * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
- * 不能超过256个,如果超过了这个极限,编译器就会报错。如果这个算法
- * 移植到stm32或者PIC等RAM比较大的单片机上,那么就可以把这个运算位数
- * 设置得更加大一点。
- */
- #define BCD4_MAX 2 //本系统中,规定的组合BCD码最大字节数,一个字节包含2位,因此4位有效运算数
- #define BCD8_MAX (BCD4_MAX*2) //本系统中,规定的非组合BCD码最大字节数,一个字节包含1位,因此4位有效运算数
- #define const_rc_size 30 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- #define uchar unsigned char //方便移植平台
- #define ulong unsigned long //方便移植平台
- //如果在VC的平台模拟此算法,则都定义成int类型,如下:
- //#define uchar int
- //#define ulong int
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_long(unsigned int uiDelaylong);
- void delay_short(unsigned int uiDelayShort);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口服务程序,在main函数里
- void eusart_send(unsigned char ucSendData);
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);
- void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
- uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
- uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- unsigned char ucDataBCD4_1[BCD4_MAX]; //接收到的第1个数组合BCD码数组形式 这里是指被加数
- unsigned char ucDataBCD4_cnt_1=0; //接收到的第1个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式 这里是指加数
- unsigned char ucDataBCD4_cnt_2=0; //接收到的第2个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式 这里是指和
- unsigned char ucDataBCD4_cnt_3=0; //接收到的第3个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_1[BCD8_MAX]; //接收到的第1个数非组合BCD码数组形式 这里是指被加数
- unsigned char ucDataBCD8_cnt_1=0; //接收到的第1个数非组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_2[BCD8_MAX]; //接收到的第2个数非组合BCD码数组形式 这里是指加数
- unsigned char ucDataBCD8_cnt_2=0; //接收到的第2个数非组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_3[BCD8_MAX]; //接收到的第3个数非组合BCD码数组形式 这里是指和
- unsigned char ucDataBCD8_cnt_3=0; //接收到的第3个数非组合BCD码数组的有效数据长度
- unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- usart_service(); //串口服务程序
- }
- }
- /* 注释二:
- * 组合BCD码转成非组合BCD码。
- * 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
- * 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
- * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
- * 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
- * 可以让我们根据数据的实际大小灵活运用。
- */
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
- {
- unsigned char ucTmep;
- unsigned char i;
- for(i=0;i<BCD8_MAX;i++) //先把即将保存转换结果的缓冲区清零
- {
- p_ucBCD_bit8[i]=0;
- }
- *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度
- for(i=0;i<ucBCD4_cnt;i++)
- {
- ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
- p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;
- p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;
- }
- }
- /* 注释三:
- * 非组合BCD码转成组合BCD码。
- * 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
- * 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
- * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
- * 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
- * 可以让我们根据数据的实际大小灵活运用。
- */
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
- {
- unsigned char ucTmep;
- unsigned char i;
- unsigned char ucBCD4_cnt;
- for(i=0;i<BCD4_MAX;i++) //先把即将保存转换结果的缓冲区清零
- {
- p_ucBCD_bit4[i]=0;
- }
- ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
- *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果
- for(i=0;i<ucBCD4_cnt;i++)
- {
- ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2]; //把非组合BCD码第8位分解出来
- p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
- p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2]; //把非组合BCD码第7位分解出来
- }
-
- }
- /* 注释四:
- *函数介绍:清零数组的全部数组数据
- *输入参数:ucARRAY_MAX代表数组定义的最大长度
- *输入输出参数:*destData--被清零的数组。
- */
- void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
- {
- uchar i;
- for(i=0;i<ucARRAY_MAX;i++)
- {
- destData[i]=0;
- }
- }
- /* 注释五:
- *函数介绍:获取数组的有效长度
- *输入参数:*destData--被获取的数组。
- *输入参数:ucARRAY_MAX代表数组定义的最大长度
- *返回值 :返回数组的有效长度。比如58786这个数据的有效长度是5
- *电子开发者作者:吴坚鸿
- */
- uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
- {
- uchar i;
- uchar DataLength=ucARRAY_MAX;
- for(i=0;i<ucARRAY_MAX;i++)
- {
- if(0!=destData[ucARRAY_MAX-1-i])
- {
- break;
- }
- else
- {
- DataLength--;
- }
- }
- return DataLength;
- }
- /* 注释六:
- *函数介绍:两个数相加
- *输入参数:
- *(1)*destData--被加数的数组。
- *(2)*sourceData--加数的数组。
- *(3)*resultData--和的数组。注意,调用本函数前,必须先把这个数组清零
- *返回值 :10代表计算结果超出范围出错,11代表正常。
- */
- uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData)
- {
- uchar addResult=11; //开始默认返回的运算结果是正常
- uchar destCnt=0;
- uchar sourceCnt=0;
- uchar i;
- uchar carryData=0; //进位
- uchar maxCnt=0; //最大位数
- uchar resultTemp=0; //存放临时运算结果的中间变量
- //为什么不在本函数内先把resultData数组清零?因为后面章节中的乘法运算中要用到此函数实现连加功能。
- //因此如果纯粹实现加法运算时,在调用本函数之前,必须先在外面把和的数组清零,否则会计算出错。
- destCnt=GetDataLength(destData,BCD8_MAX); //获取被加数的有效位数
- sourceCnt=GetDataLength(sourceData,BCD8_MAX); //获取加数的有效位数
- if(destCnt>=sourceCnt) //找出两个运算数据中最大的有效位数
- {
- maxCnt=destCnt;
- }
- else
- {
- maxCnt=sourceCnt;
- }
- for(i=0;i<maxCnt;i++)
- {
- resultTemp=destData[i]+sourceData[i]+carryData; //按位相加
- resultData[i]=resultTemp%10; //截取最低位存放进保存结果的数组
- carryData=resultTemp/10; //存放进位
- }
- resultData[i]=carryData;
- if((maxCnt==BCD8_MAX)&&(carryData==1)) //如果数组的有效位是最大值并且最后的进位是1,则计算溢出报错
- {
- ClearAllData(BCD8_MAX,resultData);
- addResult=10; //报错
- }
- return addResult;
- }
- void usart_service(void) //串口服务程序,在main函数里
- {
- unsigned char i=0;
- unsigned char k=0;
- unsigned char ucGetDataStep=0;
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcMoveIndex<uiRcregTotal) //说明还没有把缓冲区的数据读取完
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
-
- i=0;
- ucGetDataStep=0;
- ucDataBCD4_cnt_1=0; //第1个数组合BCD码数组的有效数据长度
- ucDataBCD4_cnt_2=0; //第2个数组合BCD码数组的有效数据长度
- ClearAllData(BCD4_MAX,ucDataBCD4_1); //清零第1个参与运算的数据
- ClearAllData(BCD4_MAX,ucDataBCD4_2); //清零第2个参与运算的数据
- //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
- while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
- {
- if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被加数
- {
- if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
- {
- for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
- {
- ucDataBCD4_1[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
- }
-
- i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
- ucGetDataStep=1; //切换到下一个关键字提取的步骤
- }
- else
- {
- i++;
- ucDataBCD4_cnt_1++; //统计第1个有效数据的长度
- }
-
- }
- else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
- {
- if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
- {
- for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
- {
- ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
- }
-
- break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环
- }
- else
- {
- i++;
- ucDataBCD4_cnt_2++; //统计第2个有效数据的长度
- }
- }
- }
- //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
- BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码 第1个数
- BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码 第2个数
- ClearAllData(BCD8_MAX,ucDataBCD8_3); //清零第3个参与运算的数据,用来接收运行的结果
- ucResultFlag=AddData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相加运算,结果放在ucDataBCD8_3数组里
- if(ucResultFlag==11) //表示运算结果没有超范围
- {
- ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX); //获取和的有效字节数
- BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
- for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
- {
- eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
- }
- }
- else //运算结果超范围,返回EE EE EE
- {
- eusart_send(0xee); //往上位机发送一个字节的函数
- eusart_send(0xee); //往上位机发送一个字节的函数
- eusart_send(0xee); //往上位机发送一个字节的函数
- }
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
- ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
- ucRcregBuf[1]=0;
- ucRcregBuf[2]=0;
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- uiSendCnt=0; //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
-
- }
- else //发送中断,及时把发送中断标志位清零
- {
- TI = 0;
- }
-
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- //配置定时器
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- TH1=TL1=-(11059200L/12/32/9600); //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
既然这节讲了加法程序,那么下一节接着讲常用的减法程序,这种大数据的减法程序是什么样的?欲知详情,请听下回分解----大数据的减法运算。
(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu 时间: 2014-8-19 16:19
第六十三节:大数据的减法运算。
开场白:
直接用C语言的“-”运算符进行加法运算时,“被减数”,“ 减数”,“差”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据减法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
这一节要教大家两个知识点:
第一个:如何编写比较两个非组合BCD码数据的大小。
第二个:如何编写涉及到大数据减法运算的算法程序函数,同时也复习了指针的用途。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被减数和减数。单片机把组合BCD码的运算结果返回到上位机。最大范围4位,从0到9999,如果被减数小于减数则返回EE EE EE报错。往单片机发送的数据格式:EB 00 55 XX XX 0d 0a YY YY 0d 0a指令,其中EB 00 55是数据头,XX 是被减数,可以是1个字节,也可以是2个字节。YY是减数,可以是1个字节,也可以是2个字节。0d 0a是固定的结束标志。
例如:
(a)8259 – 5267 = 2992
上位机发送数据:eb 00 55 82 59 0d 0a 52 67 0d 0a
单片机返回:29 92
(b)5267 - 8259=小于0 所以报错
上位机发送数据:eb 00 55 52 67 0d 0a 82 59 0d 0a
单片机返回:EE EE EE 表示出错了
(3)源代码讲解如下:
- #include "REG52.H"
- /* 注释一:
- * 本系统中,规定最大运算位数是4位。
- * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
- * 不能超过256个,如果超过了这个极限,编译器就会报错。如果这个算法
- * 移植到stm32或者PIC等RAM比较大的单片机上,那么就可以把这个运算位数
- * 设置得更加大一点。
- */
- #define BCD4_MAX 2 //本系统中,规定的组合BCD码最大字节数,一个字节包含2位,因此4位有效运算数
- #define BCD8_MAX (BCD4_MAX*2) //本系统中,规定的非组合BCD码最大字节数,一个字节包含1位,因此4位有效运算数
- #define const_rc_size 30 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- #define uchar unsigned char //方便移植平台
- #define ulong unsigned long //方便移植平台
- //如果在VC的平台模拟此算法,则都定义成int类型,如下:
- //#define uchar int
- //#define ulong int
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_long(unsigned int uiDelaylong);
- void delay_short(unsigned int uiDelayShort);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口服务程序,在main函数里
- void eusart_send(unsigned char ucSendData);
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);
- void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
- uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
- uchar CmpData(const uchar *destData,const uchar *sourceData); //比较两个数的大小
- uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData);//两个数相减
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- unsigned char ucDataBCD4_1[BCD4_MAX]; //接收到的第1个数组合BCD码数组形式 这里是指被减数
- unsigned char ucDataBCD4_cnt_1=0; //接收到的第1个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式 这里是指减数
- unsigned char ucDataBCD4_cnt_2=0; //接收到的第2个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式 这里是指差
- unsigned char ucDataBCD4_cnt_3=0; //接收到的第3个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_1[BCD8_MAX]; //接收到的第1个数非组合BCD码数组形式 这里是指被减数
- unsigned char ucDataBCD8_cnt_1=0; //接收到的第1个数非组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_2[BCD8_MAX]; //接收到的第2个数非组合BCD码数组形式 这里是指减数
- unsigned char ucDataBCD8_cnt_2=0; //接收到的第2个数非组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_3[BCD8_MAX]; //接收到的第3个数非组合BCD码数组形式 这里是指差
- unsigned char ucDataBCD8_cnt_3=0; //接收到的第3个数非组合BCD码数组的有效数据长度
- unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- usart_service(); //串口服务程序
- }
- }
- /* 注释二:
- * 组合BCD码转成非组合BCD码。
- * 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
- * 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
- * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
- * 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
- * 可以让我们根据数据的实际大小灵活运用。
- */
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
- {
- unsigned char ucTmep;
- unsigned char i;
- for(i=0;i<BCD8_MAX;i++) //先把即将保存转换结果的缓冲区清零
- {
- p_ucBCD_bit8[i]=0;
- }
- *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度
- for(i=0;i<ucBCD4_cnt;i++)
- {
- ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
- p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;
- p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;
- }
- }
- /* 注释三:
- * 非组合BCD码转成组合BCD码。
- * 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
- * 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
- * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
- * 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
- * 可以让我们根据数据的实际大小灵活运用。
- */
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
- {
- unsigned char ucTmep;
- unsigned char i;
- unsigned char ucBCD4_cnt;
- for(i=0;i<BCD4_MAX;i++) //先把即将保存转换结果的缓冲区清零
- {
- p_ucBCD_bit4[i]=0;
- }
- ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
- *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果
- for(i=0;i<ucBCD4_cnt;i++)
- {
- ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2]; //把非组合BCD码第8位分解出来
- p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
- p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2]; //把非组合BCD码第7位分解出来
- }
-
- }
- /* 注释四:
- *函数介绍:清零数组的全部数组数据
- *输入参数:ucARRAY_MAX代表数组定义的最大长度
- *输入输出参数:*destData--被清零的数组。
- */
- void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
- {
- uchar i;
- for(i=0;i<ucARRAY_MAX;i++)
- {
- destData[i]=0;
- }
- }
- /* 注释五:
- *函数介绍:获取数组的有效长度
- *输入参数:*destData--被获取的数组。
- *输入参数:ucARRAY_MAX代表数组定义的最大长度
- *返回值 :返回数组的有效长度。比如58786这个数据的有效长度是5
- *电子开发者作者:吴坚鸿
- */
- uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
- {
- uchar i;
- uchar DataLength=ucARRAY_MAX;
- for(i=0;i<ucARRAY_MAX;i++)
- {
- if(0!=destData[ucARRAY_MAX-1-i])
- {
- break;
- }
- else
- {
- DataLength--;
- }
- }
- return DataLength;
- }
- /* 注释六:
- *函数介绍:比较两个数的大小
- *输入参数:
- *(1)*destData--被比较数的数组。
- *(2)*sourceData--比较数的数组。
- *返回值 :9代表小于,10代表相等,11代表大于。
- */
- uchar CmpData(const uchar *destData,const uchar *sourceData)
- {
- uchar cmpResult=10; //开始默认相等
- uchar destCnt=0;
- uchar sourceCnt=0;
- uchar i;
- destCnt=GetDataLength(destData,BCD8_MAX);
- sourceCnt=GetDataLength(sourceData,BCD8_MAX);
- if(destCnt>sourceCnt) //大于
- {
- cmpResult=11;
- }
- else if(destCnt<sourceCnt) //小于
- {
- cmpResult=9;
- }
- else if((destCnt==0)&&(sourceCnt==0)) //如果都是等于0则等于
- {
- cmpResult=10;
- }
- else //否则就要继续判断
- {
- for(i=0;i<destCnt;i++)
- {
- if(destData[destCnt-1-i]>sourceData[destCnt-1-i]) //从最高位开始判断,如果最高位大于则大于
- {
- cmpResult=11;
- break;
- }
- else if(destData[destCnt-1-i]<sourceData[destCnt-1-i]) //从最高位开始判断,如果最高位小于则小于
- {
- cmpResult=9;
- break;
- }
- //否则继续判断下一位
- }
- }
- return cmpResult;
- }
- /* 注释七:
- *函数介绍:两个数相减
- *输入参数:
- *(1)*destData--被减数的数组。
- *(2)*sourceData--减数的数组。
- *(3)*resultData--差的数组。注意,调用本函数前,必须先把这个数组清零
- *返回值 :10代表计算结果是负数或者超出范围出错,11代表正常。
- */
- uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData)
- {
- uchar subResult=11; //开始默认正常
- uchar destCnt=0;
- uchar i;
- uchar carryData=0; //进位
- uchar maxCnt=0; //最大位数
- uchar resultTemp=0; //存放临时运算结果的中间变量
- //为什么不在本函数内先把resultData数组清零?因为后面章节中的除法运算中要用到此函数实现连减功能。
- //因此如果纯粹实现减法运算时,在调用本函数之前,必须先在外面把差的数组清零,否则会计算出错。
- if(CmpData(destData,sourceData)==9) //被减数小于减数,报错
- {
- subResult=10;
- return subResult; //返回判断结果,并且退出本程序,不往下执行本程序余下代码
- }
- destCnt=GetDataLength(destData,BCD8_MAX); //获取被减数的有效数据长度
- maxCnt=destCnt;
- for(i=0;i<maxCnt;i++)
- {
- resultTemp=sourceData[i]+carryData; //按位相加
- if(resultTemp>destData[i])
- {
- resultData[i]=destData[i]+10-sourceData[i]-carryData; //借位
- carryData=1;
- }
- else
- {
- resultData[i]=destData[i]-sourceData[i]-carryData; //不用借位
- carryData=0;
- }
- }
- return subResult;
- }
- void usart_service(void) //串口服务程序,在main函数里
- {
- unsigned char i=0;
- unsigned char k=0;
- unsigned char ucGetDataStep=0;
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcMoveIndex<uiRcregTotal) //说明还没有把缓冲区的数据读取完
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
-
- i=0;
- ucGetDataStep=0;
- ucDataBCD4_cnt_1=0; //第1个数组合BCD码数组的有效数据长度
- ucDataBCD4_cnt_2=0; //第2个数组合BCD码数组的有效数据长度
- ClearAllData(BCD4_MAX,ucDataBCD4_1); //清零第1个参与运算的数据
- ClearAllData(BCD4_MAX,ucDataBCD4_2); //清零第2个参与运算的数据
- //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
- while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
- {
- if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被加数
- {
- if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
- {
- for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
- {
- ucDataBCD4_1[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
- }
- i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
- ucGetDataStep=1; //切换到下一个关键字提取的步骤
- }
- else
- {
- i++;
- ucDataBCD4_cnt_1++; //统计第1个有效数据的长度
- }
-
- }
- else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
- {
- if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
- {
- for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
- {
- ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
- }
-
- break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环
- }
- else
- {
- i++;
- ucDataBCD4_cnt_2++; //统计第2个有效数据的长度
- }
- }
- }
- //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
- BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码 第1个数
- BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码 第2个数
- ClearAllData(BCD8_MAX,ucDataBCD8_3); //清零第3个参与运算的数据,用来接收运行的结果
- ucResultFlag=SubData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相减运算,结果放在ucDataBCD8_3数组里
- if(ucResultFlag==11) //表示运算结果没有超范围
- {
- ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX); //获取运算结果的有效字节数
- BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
- for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
- {
- eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
- }
- }
- else //运算结果超范围,返回EE EE EE
- {
- eusart_send(0xee); //往上位机发送一个字节的函数
- eusart_send(0xee); //往上位机发送一个字节的函数
- eusart_send(0xee); //往上位机发送一个字节的函数
- }
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
- ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
- ucRcregBuf[1]=0;
- ucRcregBuf[2]=0;
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- uiSendCnt=0; //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
-
- }
- else //发送中断,及时把发送中断标志位清零
- {
- TI = 0;
- }
-
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- //配置定时器
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- TH1=TL1=-(11059200L/12/32/9600); //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
既然这节讲了减法程序,那么下一节接着讲常用的乘法程序,这种大数据的乘法程序是什么样的?欲知详情,请听下回分解----大数据的乘法运算。
(未完待续,下节更精彩,不要走开哦)
作者: 又一个暑假 时间: 2014-8-21 10:29
本帖最后由 又一个暑假 于 2014-8-21 10:37 编辑
“其中第5,6位HH就是有效数据长度。高位在左,低位在右。“这句话没理解,鸿哥麻烦解释一下。“Led灯短亮发送:eb 00 55 02 00 02 00 28 6c”这里的“00 02”表示由“00 28”组成一个数据“0x0028”这样理解对吗
作者: jianhong_wu 时间: 2014-8-21 10:39
表示有两个字节合成的int型数据,左边的是高位,右边的是低位。比如十六进制的两个字节 A2 9C ,相当于十六进制的int型数据0xA29C,十进制的41628
作者: 又一个暑假 时间: 2014-8-21 10:59
鸿哥用了2个字节来表示数据长度,那么有效数据“XXXXX。。。。YYYYY”可以达到65535个字节对吧
作者: 又一个暑假 时间: 2014-8-21 11:55
本帖最后由 又一个暑假 于 2014-8-21 11:57 编辑
ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0; //清零最后一个字节的累加和变量 这里都清零了
for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
{
ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];//这里为什么ucRcregBuf[uiRcMoveIndex+6+uiRcSize]不加为什么不行
}
-
1.jpg
(59.1 KB, 下载次数: 330)
作者: jianhong_wu 时间: 2014-8-21 13:38
是的,理论上是可以达到65535个字节,实际上我们的接收缓冲区没有定时那么大的接收数组。
作者: jianhong_wu 时间: 2014-8-21 13:40
本帖最后由 jianhong_wu 于 2014-8-21 13:42 编辑
它是一个累加和变量,前面已经清零了一次,后面当然不用清零了。比如:
- sum=0; //清零一次
- for(i=0;i<50;i++)
- {
- sum=sum+a[i];
- }
复制代码
这里的sum就是累加和。
作者: 又一个暑假 时间: 2014-8-21 17:47
噢,看花眼了。问了如此低级的问题
作者: 看花开花落 时间: 2014-8-21 20:53
鸿哥什么时候发一次鸿哥版的模块化编程呀
作者: jianhong_wu 时间: 2014-8-21 21:39
再过五节左右就会讲到多文件编程的。
作者: waphaoyun 时间: 2014-8-22 19:26
鸿哥辛苦了,又更新了 我刚看到三十三节 不知啥时候才能赶上来!
作者: jianhong_wu 时间: 2014-8-26 00:44
第六十四节:大数据的乘法运算。
开场白:
直接用C语言的“*”运算符进行乘法运算时,“被乘数”,“ 乘数”,“积”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据乘法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
这一节要教大家一个知识点:
第一个:如何编写涉及到大数据乘法运算的算法程序函数,同时也复习了指针的用途。
具体内容,请看源代码讲解。
(1)硬件平台:
基于坚鸿51单片机学习板。
(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被乘数和乘数,单片机把组合BCD码的运算结果返回到上位机。被乘数与乘数的最大范围都是从0到99,如果运算的乘积超过允许保存的最大位数范围则返回EE EE EE报错。
往单片机发送的数据格式:EB 00 55 XX 0d 0a YY 0d 0a指令,其中EB 00 55是数据头,XX 是被乘数,是1个字节的组合BCD码。YY是乘数,可以是1个字节的组合BCD码。0d 0a是固定的结束标志。
例如:
(a)83 x 98 = 8134
上位机发送数据:eb 00 55 83 0d 0a 98 0d 0a
单片机返回:81 34
(3)源代码讲解如下:
- #include "REG52.H"
- /* 注释一:
- * 本系统中的乘法运算,规定两个乘数的最大范围是0至99.
- * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
- * 不能超过256个,如果超过了这个极限,编译器就会报错。由于51单片机RAM资源有限,
- * 因此规定乘数的最大范围不能超过99,如果这个算法移植到stm32或者PIC等RAM比较大
- * 的单片机上,那么就可以把这个运算位数设置得更加大一点。调整下面 BCD4_MAX的大小,
- * 可以调整运算的数据范围。
- */
- #define BCD4_MAX 3 //为了让乘法的结果不超过范围,因此把组合BCD码最大字节数从上一节的2改成3,一个字节包含2位,因此可以保存6位有效数
- #define BCD8_MAX (BCD4_MAX*2) //本系统中,规定的非组合BCD码能保存的最大字节数,一个字节包含1位,因此能保存6位有效运算数
- #define const_rc_size 30 //接收串口中断数据的缓冲区数组大小
- #define const_receive_time 5 //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
- #define uchar unsigned char //方便移植平台
- #define ulong unsigned long //方便移植平台
- //如果在VC的平台模拟此算法,则都定义成int类型,如下:
- //#define uchar int
- //#define ulong int
- void initial_myself(void);
- void initial_peripheral(void);
- void delay_long(unsigned int uiDelaylong);
- void delay_short(unsigned int uiDelayShort);
- void T0_time(void); //定时中断函数
- void usart_receive(void); //串口接收中断函数
- void usart_service(void); //串口服务程序,在main函数里
- void eusart_send(unsigned char ucSendData);
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);
- void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
- uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
- uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData); //两个数相加
- void EnlargeData(uchar *destData,uchar enlarge_cnt); //数组向大索引值移位,移一位相当于放大10倍
- uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData); //两个数相乘
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- unsigned int uiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
- unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
- unsigned int uiRcregTotal=0; //代表当前缓冲区已经接收了多少个数据
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
- unsigned int uiRcMoveIndex=0; //用来解析数据协议的中间变量
- unsigned char ucDataBCD4_1[BCD4_MAX]; //接收到的第1个数组合BCD码数组形式 这里是指被乘数
- unsigned char ucDataBCD4_cnt_1=0; //接收到的第1个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式 这里是指乘数
- unsigned char ucDataBCD4_cnt_2=0; //接收到的第2个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式 这里是指积
- unsigned char ucDataBCD4_cnt_3=0; //接收到的第3个数组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_1[BCD8_MAX]; //接收到的第1个数非组合BCD码数组形式 这里是指被乘数
- unsigned char ucDataBCD8_cnt_1=0; //接收到的第1个数非组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_2[BCD8_MAX]; //接收到的第2个数非组合BCD码数组形式 这里是指乘数
- unsigned char ucDataBCD8_cnt_2=0; //接收到的第2个数非组合BCD码数组的有效数据长度
- unsigned char ucDataBCD8_3[BCD8_MAX]; //接收到的第3个数非组合BCD码数组形式 这里是指积
- unsigned char ucDataBCD8_cnt_3=0; //接收到的第3个数非组合BCD码数组的有效数据长度
- unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- usart_service(); //串口服务程序
- }
- }
- /* 注释二:
- * 组合BCD码转成非组合BCD码。
- * 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
- * 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
- * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
- * 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
- * 可以让我们根据数据的实际大小灵活运用。
- */
- void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
- {
- unsigned char ucTmep;
- unsigned char i;
- for(i=0;i<BCD8_MAX;i++) //先把即将保存转换结果的缓冲区清零
- {
- p_ucBCD_bit8[i]=0;
- }
- *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度
- for(i=0;i<ucBCD4_cnt;i++)
- {
- ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
- p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;
- p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;
- }
- }
- /* 注释三:
- * 非组合BCD码转成组合BCD码。
- * 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
- * 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
- * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
- * 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
- * 可以让我们根据数据的实际大小灵活运用。
- */
- void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
- {
- unsigned char ucTmep;
- unsigned char i;
- unsigned char ucBCD4_cnt;
- for(i=0;i<BCD4_MAX;i++) //先把即将保存转换结果的缓冲区清零
- {
- p_ucBCD_bit4[i]=0;
- }
- ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
- *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果
- for(i=0;i<ucBCD4_cnt;i++)
- {
- ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2]; //把非组合BCD码第8位分解出来
- p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
- p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2]; //把非组合BCD码第7位分解出来
- }
-
- }
- /* 注释四:
- *函数介绍:清零数组的全部数组数据
- *输入参数:ucARRAY_MAX代表数组定义的最大长度
- *输入输出参数:*destData--被清零的数组。
- */
- void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
- {
- uchar i;
- for(i=0;i<ucARRAY_MAX;i++)
- {
- destData[i]=0;
- }
- }
- /* 注释五:
- *函数介绍:获取数组的有效长度
- *输入参数:*destData--被获取的数组。
- *输入参数:ucARRAY_MAX代表数组定义的最大长度
- *返回值 :返回数组的有效长度。比如58786这个数据的有效长度是5
- *电子开发者作者:吴坚鸿
- */
- uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
- {
- uchar i;
- uchar DataLength=ucARRAY_MAX;
- for(i=0;i<ucARRAY_MAX;i++)
- {
- if(0!=destData[ucARRAY_MAX-1-i])
- {
- break;
- }
- else
- {
- DataLength--;
- }
- }
- return DataLength;
- }
- /* 注释六:
- *函数介绍:两个数相加
- *输入参数:
- *(1)*destData--被加数的数组。
- *(2)*sourceData--加数的数组。
- *(3)*resultData--和的数组。注意,调用本函数前,必须先把这个数组清零
- *返回值 :10代表计算结果超出范围出错,11代表正常。
- */
- uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData)
- {
- uchar addResult=11; //开始默认返回的运算结果是正常
- uchar destCnt=0;
- uchar sourceCnt=0;
- uchar i;
- uchar carryData=0; //进位
- uchar maxCnt=0; //最大位数
- uchar resultTemp=0; //存放临时运算结果的中间变量
- //为什么不在本函数内先把resultData数组清零?因为后面章节中的乘法运算中要用到此函数实现连加功能。
- //因此如果纯粹实现加法运算时,在调用本函数之前,必须先在外面把和的数组清零,否则会计算出错。
- destCnt=GetDataLength(destData,BCD8_MAX); //获取被加数的有效位数
- sourceCnt=GetDataLength(sourceData,BCD8_MAX); //获取加数的有效位数
- if(destCnt>=sourceCnt) //找出两个运算数据中最大的有效位数
- {
- maxCnt=destCnt;
- }
- else
- {
- maxCnt=sourceCnt;
- }
- for(i=0;i<maxCnt;i++)
- {
- resultTemp=destData[i]+sourceData[i]+carryData; //按位相加
- resultData[i]=resultTemp%10; //截取最低位存放进保存结果的数组
- carryData=resultTemp/10; //存放进位
- }
- resultData[i]=carryData;
- if((maxCnt==BCD8_MAX)&&(carryData==1)) //如果数组的有效位是最大值并且最后的进位是1,则计算溢出报错
- {
- ClearAllData(BCD8_MAX,resultData);
- addResult=10; //报错
- }
- return addResult;
- }
- /* 注释七:
- *函数介绍:数组向大索引值移位,移一位相当于放大10倍
- *输入参数:*destData--被移位的数组。
- *输入参数:enlarge_cnt--被移位的个数。
- */
- void EnlargeData(uchar *destData,uchar enlarge_cnt)
- {
- uchar i;
- if(enlarge_cnt!=0)
- {
- for(i=0;i<(BCD8_MAX-enlarge_cnt);i++)
- {
- destData[BCD8_MAX-1-i]=destData[BCD8_MAX-1-enlarge_cnt-i];
- }
- for(i=0;i<enlarge_cnt;i++) //最低位被移空的补上0
- {
- destData[i]=0;
- }
- }
- }
- /* 注释八:
- *函数介绍:两个数相乘
- *输入参数:
- *(1)*destData--被乘数的数组。
- *(2)*sourceData--乘数的数组。
- *(3)*resultData--积的数组。
- *返回值 :10代表计算结果超出范围出错,11代表正常。
- */
- uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData)
- {
- uchar multResult=11; //开始默认正常
- uchar destCnt=0;
- uchar sourceCnt=0;
- uchar i;
- uchar j;
- uchar carryData=0; //进位
- uchar resultTemp=0; //存放临时运算结果的中间变量
- uchar nc_add_result; //接收相加的运算是否超出范围,这里不用判断,因为不会溢出
- uchar multArrayTemp[BCD8_MAX]; //存放临时运算结果的数组中间变量
- destCnt=GetDataLength(destData,BCD8_MAX); //获取被乘数的长度
- sourceCnt=GetDataLength(sourceData,BCD8_MAX); //获取乘数的长度
- ClearAllData(BCD8_MAX,resultData); //清零存储的结果
- if((0==destCnt)||(0==sourceCnt)) //被乘数或者乘数为0,则结果为0
- {
- return multResult;
- }
- if((destCnt+sourceCnt+2)>BCD8_MAX)
- {
- multResult=10; //运算结果有可能超范围报错
- return multResult;
- }
- for(i=0;i<sourceCnt;i++) //乘数
- {
- carryData=0; //清零进位
- ClearAllData(BCD8_MAX,multArrayTemp); //清零一位乘数相乘的结果中间变量数组
- for(j=0;j<destCnt;j++) //被乘数
- {
- resultTemp=destData[j]*sourceData[i]+carryData; //乘数的一位依次与被乘数各位相乘,并且加进位
- multArrayTemp[j]=resultTemp%10; //存储一位乘数相乘的结果
- carryData=resultTemp/10; //保存进位
- }
- multArrayTemp[j]=carryData; //存储最后的进位
- EnlargeData(multArrayTemp,i); //移位。移一次相当于放大10倍。
- nc_add_result=AddData(resultData,multArrayTemp,resultData); //把一位乘数相乘的结果存储进总结果
- }
- return multResult;
- }
- void usart_service(void) //串口服务程序,在main函数里
- {
- unsigned char i=0;
- unsigned char k=0;
- unsigned char ucGetDataStep=0;
- if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
- {
- ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
- //下面的代码进入数据协议解析和数据处理的阶段
- uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
- while(uiRcMoveIndex<uiRcregTotal) //说明还没有把缓冲区的数据读取完
- {
- if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55) //数据头eb 00 55的判断
- {
-
- i=0;
- ucGetDataStep=0;
- ucDataBCD4_cnt_1=0; //第1个数组合BCD码数组的有效数据长度
- ucDataBCD4_cnt_2=0; //第2个数组合BCD码数组的有效数据长度
- ClearAllData(BCD4_MAX,ucDataBCD4_1); //清零第1个参与运算的数据
- ClearAllData(BCD4_MAX,ucDataBCD4_2); //清零第2个参与运算的数据
- //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
- while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
- {
- if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被乘数
- {
- if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
- {
- for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
- {
- ucDataBCD4_1[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
- }
- i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
- ucGetDataStep=1; //切换到下一个关键字提取的步骤
- }
- else
- {
- i++;
- ucDataBCD4_cnt_1++; //统计第1个有效数据的长度
- }
-
- }
- else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是乘数
- {
- if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
- {
- for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
- {
- ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
- }
-
- break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环
- }
- else
- {
- i++;
- ucDataBCD4_cnt_2++; //统计第2个有效数据的长度
- }
- }
- }
- //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
- BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码 第1个数
- BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码 第2个数
- ClearAllData(BCD8_MAX,ucDataBCD8_3); //清零第3个参与运算的数据,用来接收运行的结果
- ucResultFlag=MultData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相乘运算,结果放在ucDataBCD8_3数组里
- if(ucResultFlag==11) //表示运算结果没有超范围
- {
- ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX); //获取运算结果的有效字节数
- if(ucDataBCD8_cnt_3==0) //如果1个有效位数都没有,表示数组所有的数据都是0,这个时候的有效位数应该人为的默认是1,表示一个0
- {
- ucDataBCD8_cnt_3=1;
- }
- BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
- for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
- {
- eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
- }
- }
- else //运算结果超范围,返回EE EE EE
- {
- eusart_send(0xee); //往上位机发送一个字节的函数
- eusart_send(0xee); //往上位机发送一个字节的函数
- eusart_send(0xee); //往上位机发送一个字节的函数
- }
- break; //退出循环
- }
- uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
- }
- ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
- ucRcregBuf[1]=0;
- ucRcregBuf[2]=0;
-
- uiRcregTotal=0; //清空缓冲的下标,方便下次重新从0下标开始接受新数据
-
- }
-
- }
- void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
- {
- ES = 0; //关串口中断
- TI = 0; //清零串口发送完成中断请求标志
- SBUF =ucSendData; //发送一个字节
- delay_short(400); //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
- TI = 0; //清零串口发送完成中断请求标志
- ES = 1; //允许串口中断
- }
- void T0_time(void) interrupt 1 //定时中断
- {
- TF0=0; //清除中断标志
- TR0=0; //关中断
- if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
- {
- uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
- ucSendLock=1; //开自锁标志
- }
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1; //开中断
- }
- void usart_receive(void) interrupt 4 //串口接收数据中断
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- if(uiRcregTotal>const_rc_size) //超过缓冲区
- {
- uiRcregTotal=const_rc_size;
- }
- ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
- uiSendCnt=0; //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
-
- }
- else //发送中断,及时把发送中断标志位清零
- {
- TI = 0;
- }
-
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i<uiDelayLong;i++)
- {
- for(j=0;j<500;j++) //内嵌循环的空指令数量
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i<uiDelayShort;i++)
- {
- ; //一个分号相当于执行一条空语句
- }
- }
- void initial_myself(void) //第一区 初始化单片机
- {
- beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
- //配置定时器
- TMOD=0x01; //设置定时器0为工作方式1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- TH1=TL1=-(11059200L/12/32/9600); //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- EA=1; //开总中断
- ES=1; //允许串口中断
- ET0=1; //允许定时中断
- TR0=1; //启动定时中断
- }
复制代码
总结陈词:
既然这节讲了乘法程序,那么下一节接着讲常用的除法程序,这种大数据的除法程序是什么样的?欲知详情,请听下回分解----大数据的除法运算。
(未完待续,下节更精彩,不要走开哦)
欢迎光临 独闷闷网 (http://www.dumenmen.com/) |
Powered by Discuz! X3.2 |