jianhong_wu
发表于 2014-1-8 01:54:50
本帖最后由 jianhong_wu 于 2014-1-8 03:03 编辑
第十三节:按住一个独立按键不松手的加速匀速触发。
开场白:
上一节讲了按住一个独立按键不松手的连续步进触发功能,这节要教会大家如何在上一节的基础上,略作修改,就可以实现按键的加速匀速触发。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。
(2)实现功能:两个独立按键S1和S5,S1键作为加键。S5键做为减键。每按一次S1键则被设置参数uiSetNumber自加1。如果按住S1键不松手超过1秒钟,被设置参数uiSetNumber以不断变快的时间间隔往上自加1,这个称为加速触发的功能,直到到达极限值,则以固定的速度加1,这个过程叫匀速。S5作为减法按键,每触发一次,uiSetNumber就减1,其加速和匀速触发功能跟S1按键一样。当被设置参数uiSetNumber小于500的时候,LED灯灭;当大于或者等于500的时候,LED灯亮。需要注意的是:
第一步:每次按下去触发一次单击按键,如果按下去到松手的时间不超过1秒,则不会进入连续加速触发模式。
第二步:如果按下去不松手的时间超过1秒,则进入连续加速触发模式。按键触发节奏不断加快,蜂鸣器鸣叫的节奏也不断加快。直到它们都到达一个极限值,然后以此极限值间隔匀速触发。在刚开始加速的时候,按键触发与蜂鸣器触发的步骤是一致的,等它们任意一个达到极限值的时候,急促的声音跟按键的触发不一致,并不是蜂鸣器每叫一次,按键就触发一次。实际上加速到最后,按键触发的速度远远比蜂鸣器的触发速度快。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_time_1s 444 //1秒钟的时间需要的定时中断次数
#define const_initial_set 160//连续触发模式的时候,按键刚开始的间隔触发时间
#define const_min_level30 //连续触发模式的时候,按键经过加速后,如果一旦发现小于这个值,则直接变到最后的间隔触发时间
#define const_sub_dt10 //按键的"加速度",相当于按键间隔时间每次的变化量
#define const_last_min_set 5 //连续触发模式的时候,按键经过加速后,最后的间隔触发时间
#define const_syn_min_level45 //产生同步声音的最小阀值 这个时间必须要比蜂鸣器的时间略长一点。
void initial_myself();
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();//定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
void led_run();//led灯的应用程序
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;//LED的驱动IO口
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned intuiKeyCtntyCnt1=0;//按键连续触发的间隔延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiSynCtntyCnt1=0; //产生按键同步声音的计数器
unsigned intuiCtntyTimeSet1=const_initial_set; //按键每次触发的时间间隔,这数值不断变小,导致速度不断加快
unsigned intuiCtntySynSet1=const_initial_set;//同步声音的时间间隔,这数值不断变小,导致速度不断加快
unsigned char ucCtntyFlag1=0;//是否处于连续加速触发模式的标志位
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned intuiKeyCtntyCnt2=0;//按键连续触发的间隔延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiSynCtntyCnt2=0; //产生按键同步声音的计数器
unsigned intuiCtntyTimeSet2=const_initial_set; //按键每次触发的时间间隔,这数值不断变小,导致速度不断加快
unsigned intuiCtntySynSet2=const_initial_set; //同步声音的时间间隔,这数值不断变小,导致速度不断加快
unsigned char ucCtntyFlag2=0; //是否处于连续加速触发模式的标志位
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
unsigned intuiSetNumber=0; //设置的数据
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
led_run();//led灯的应用程序
}
}
void led_run()//led灯的应用程序
{
if(uiSetNumber<500)//如果被设置的参数uiSetNumber小于500,LED灯则灭。否则亮。
{
led_dr=0;//灭
}
else
{
led_dr=1;//亮
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
/* 注释一:
* 独立按键连续加速扫描的过程:
* 第一步:每次按下去触发一次单击按键,如果按下去到松手的时间不超过1秒,则不会进入连续加速触发模式。
* 第二步:如果按下去不松手的时间超过1秒,则进入连续加速触发模式。按键触发节奏不断加快,蜂鸣器鸣叫的节奏
* 也不断加快。直到它们都到达一个极限值,然后以此极限值间隔匀速触发。在刚开始加速的时候,按键触发与
* 蜂鸣器触发的步骤是一致的,等它们任意一个达到极限值的时候,急促的声音跟按键的触发不一致,并不是
* 蜂鸣器每叫一次,按键就触发一次。实际上加速到最后,按键触发的速度远远比蜂鸣器的触发速度快。
*/
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
uiKeyCtntyCnt1=0; //按键连续加速的时间间隔延时计数器清零
uiSynCtntyCnt1=0;//蜂鸣器连续加速的时间间隔延时计数器清零
uiCtntyTimeSet1=const_initial_set; //按键每次触发的时间间隔初始值,这数值不断变小,导致速度不断加快
uiCtntySynSet1=const_initial_set; //同步声音的时间间隔初始值,这数值不断变小,导致鸣叫的节奏不断加快
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1;//自锁按键置位,避免一直触发
ucCtntyFlag1=0; //连续加速触发模式标志位 0代表单击1代表连续加速触发
ucKeySec=1; //触发1号键
}
}
else if(uiKeyTimeCnt1<const_time_1s) //按住累加到1秒
{
uiKeyTimeCnt1++;
}
else//按住累加到1秒后仍然不放手,这个时候进入有节奏的连续加速触发
{
uiKeyCtntyCnt1++; //按键连续触发延时计数器累加
//按住没松手,每隔一段uiCtntyTimeSet1时间按键就触发一次,而且uiCtntyTimeSet1不断减小,速度就越来越快
if(uiKeyCtntyCnt1>uiCtntyTimeSet1)
{
if(uiCtntyTimeSet1>const_min_level)
{
uiCtntyTimeSet1=uiCtntyTimeSet1-const_sub_dt; //uiCtntyTimeSet1不断减小,速度就越来越快
}
else
{
uiCtntyTimeSet1=const_last_min_set; //uiCtntyTimeSet1不断减小,到达一个极限值
}
uiKeyCtntyCnt1=0;
ucCtntyFlag1=1;//进入连续加速触发模式
ucKeySec=1; //触发1号键
}
uiSynCtntyCnt1++; //蜂鸣器连续触发延时计数器累加
//按住没松手,每隔一段uiCtntySynSet1时间蜂鸣器就触发一次,而且uiCtntySynSet1不断减小,鸣叫的节奏就越来越快
if(uiSynCtntyCnt1>uiCtntySynSet1)
{
uiCtntySynSet1=uiCtntySynSet1-const_sub_dt; //uiCtntySynSet1不断减小,鸣叫的节奏就越来越快
if(uiCtntySynSet1<const_syn_min_level)
{
uiCtntySynSet1=const_syn_min_level; //uiCtntySynSet1不断减小,达到一个极限值
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
uiSynCtntyCnt1=0;
}
}
if(key_sr2==1)
{
ucKeyLock2=0;
uiKeyTimeCnt2=0;
uiKeyCtntyCnt2=0;
uiSynCtntyCnt2=0;
uiCtntyTimeSet2=const_initial_set;
uiCtntySynSet2=const_initial_set;
}
else if(ucKeyLock2==0)
{
uiKeyTimeCnt2++;
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1;
ucCtntyFlag2=0;
ucKeySec=2;
}
}
else if(uiKeyTimeCnt2<const_time_1s)
{
uiKeyTimeCnt2++;
}
else
{
uiKeyCtntyCnt2++;
if(uiKeyCtntyCnt2>uiCtntyTimeSet2)
{
if(uiCtntyTimeSet2>const_min_level)
{
uiCtntyTimeSet2=uiCtntyTimeSet2-const_sub_dt;
}
else
{
uiCtntyTimeSet2=const_last_min_set;
}
uiKeyCtntyCnt2=0;
ucCtntyFlag2=1;
ucKeySec=2;
}
uiSynCtntyCnt2++;
if(uiSynCtntyCnt2>uiCtntySynSet2)
{
uiCtntySynSet2=uiCtntySynSet2-const_sub_dt;
if(uiCtntySynSet2<const_syn_min_level)
{
uiCtntySynSet2=const_syn_min_level;
}
uiVoiceCnt=const_voice_short;
uiSynCtntyCnt2=0;
}
}
}
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 1号键 连续加键对应朱兆祺学习板的S1键
uiSetNumber++; //被设置的参数连续往上加
if(uiSetNumber>1000) //最大是1000
{
uiSetNumber=1000;
}
if(ucCtntyFlag1==0) //如果是在单击按键的情况下,则蜂鸣器鸣叫,否则蜂鸣器在按键扫描key_scan里鸣叫
{
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
}
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 2号键 连续减键对应朱兆祺学习板的S5键
/* 注释二:
* 在单片机的C语言编译器中,当无符号数据0减去1时,就会溢出,变成这个类型数据的最大值。
* 比如是unsigned int的0减去1就等于65535(0xffff),unsigned char的0减去1就等于255(0xff)
*/
uiSetNumber--; //被设置的参数连续往下减
if(uiSetNumber>1000) //最小是0.为什么这里用1000?因为0减去1就是溢出变成了65535(0xffff)
{
uiSetNumber=0;
}
if(ucCtntyFlag2==0)//如果是在单击按键的情况下,则蜂鸣器鸣叫,否则蜂鸣器在按键扫描key_scan里鸣叫
{
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
}
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
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三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}
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和S5两个按键就是本程序中用到的两个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
led_dr=0;//LED灯灭
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
到目前为止,前面一共花了8节内容仔细讲解了独立按键的扫描程序,如果是矩阵键盘,我们该怎么写程序?欲知详情,请听下回分解-----矩阵键盘的单个触发。
(未完待续,下节更精彩,不要走开哦)
翼翔天开
发表于 2014-1-9 15:20:19
鸿哥,V5!我继续跟进学习哦!{:soso_e113:}
jianhong_wu
发表于 2014-1-10 12:55:02
翼翔天开 发表于 2014-1-9 15:20
鸿哥,V5!我继续跟进学习哦!
欢迎欢迎。学习中有什么问题欢迎提出来。
jianhong_wu
发表于 2014-1-12 08:52:31
第十四节:矩阵键盘的单个触发。
开场白:
上一节讲了按键的加速匀速触发。这节开始讲矩阵键盘的单个触发。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。。
(2)实现功能:16个按键中,每按一个按键都能触发一次蜂鸣器发出“滴”的一声。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time20 //按键去抖动延时的时间
void initial_myself();
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();//定时中断函数
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口
unsigned char ucKeyStep=1;//按键扫描步骤变量
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
/* 注释一:
*矩阵按键扫描的详细过程:
*先输出某一列低电平,其它三列输出高电平,这个时候再分别判断输入的四行,
*如果发现哪一行是低电平,就说明对应的某个按键被触发。依次分别输出另外三列
*中的某一列为低电平,再分别判断输入的四行,就可以检测完16个按键。内部详细的
*去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的。
*/
switch(ucKeyStep)
{
case 1: //按键扫描输出第一列低电平
key_dr1=0;
key_dr2=1;
key_dr3=1;
key_dr4=1;
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++;//如果没有按键按下,切换到下一个运行步骤
ucKeyLock=0;//按键自锁标志清零
uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
}
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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=1;//触发1号键 对应朱兆祺学习板的S1键
}
}
else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=5;//触发5号键 对应朱兆祺学习板的S5键
}
}
else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=9;//触发9号键 对应朱兆祺学习板的S9键
}
}
else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=13;//触发13号键 对应朱兆祺学习板的S13键
}
}
}
break;
case 4: //按键扫描输出第二列低电平
key_dr1=1;
key_dr2=0;
key_dr3=1;
key_dr4=1;
uiKeyTimeCnt=0;//延时计数器清零
ucKeyStep++; //切换到下一个运行步骤
break;
case 5: //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
uiKeyTimeCnt++;
if(uiKeyTimeCnt>1)
{
uiKeyTimeCnt=0;
ucKeyStep++; //切换到下一个运行步骤
}
break;
case 6:
if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
{
ucKeyStep++;//如果没有按键按下,切换到下一个运行步骤
ucKeyLock=0;//按键自锁标志清零
uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
}
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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=2;//触发2号键 对应朱兆祺学习板的S2键
}
}
else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=6;//触发6号键 对应朱兆祺学习板的S6键
}
}
else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=10;//触发10号键 对应朱兆祺学习板的S9键
}
}
else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=14;//触发14号键 对应朱兆祺学习板的S13键
}
}
}
break;
case 7: //按键扫描输出第三列低电平
key_dr1=1;
key_dr2=1;
key_dr3=0;
key_dr4=1;
uiKeyTimeCnt=0;//延时计数器清零
ucKeyStep++; //切换到下一个运行步骤
break;
case 8: //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
uiKeyTimeCnt++;
if(uiKeyTimeCnt>1)
{
uiKeyTimeCnt=0;
ucKeyStep++; //切换到下一个运行步骤
}
break;
case 9:
if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
{
ucKeyStep++;//如果没有按键按下,切换到下一个运行步骤
ucKeyLock=0;//按键自锁标志清零
uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
}
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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=3;//触发3号键 对应朱兆祺学习板的S3键
}
}
else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=7;//触发7号键 对应朱兆祺学习板的S7键
}
}
else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=11;//触发11号键 对应朱兆祺学习板的S11键
}
}
else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
{
uiKeyTimeCnt++;//去抖动延时计数器
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=15;//触发15号键 对应朱兆祺学习板的S15键
}
}
}
break;
case 10: //按键扫描输出第四列低电平
key_dr1=1;
key_dr2=1;
key_dr3=1;
key_dr4=0;
uiKeyTimeCnt=0;//延时计数器清零
ucKeyStep++; //切换到下一个运行步骤
break;
case 11: //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
uiKeyTimeCnt++;
if(uiKeyTimeCnt>1)
{
uiKeyTimeCnt=0;
ucKeyStep++; //切换到下一个运行步骤
}
break;
case 12:
if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
{
ucKeyStep=1;//如果没有按键按下,返回到第一步,重新开始扫描
ucKeyLock=0;//按键自锁标志清零
uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙
}
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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
ucKeySec=16;//触发16号键 对应朱兆祺学习板的S16键
}
}
}
break;
}
}
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 1号键 对应朱兆祺学习板的S1键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 2号键 对应朱兆祺学习板的S2键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 3号键 对应朱兆祺学习板的S3键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 4:// 4号键 对应朱兆祺学习板的S4键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 5:// 5号键 对应朱兆祺学习板的S5键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 6:// 6号键 对应朱兆祺学习板的S6键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 7:// 7号键 对应朱兆祺学习板的S7键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 8:// 8号键 对应朱兆祺学习板的S8键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 9:// 9号键 对应朱兆祺学习板的S9键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 10:// 10号键 对应朱兆祺学习板的S10键
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 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三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}
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()//第一区 初始化单片机
{
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
在这一节中,有的人咋看我的按键扫描代码,会觉得代码太多了。我一直认为,只要单片机容量够,代码多一点少一点并不重要,只要不影响运行效率就行。而且有时候,代码写多一点,可读性非常强,修改起来也非常方便。如果一味的追求压缩代码,就会刻意用很多循环,数组等元素,代码虽然紧凑了,但是可分离性,可改性,可读性就没那么强。我说那么多并不是因为我技术有限而不懂压缩,就找个借口敷衍大家,不信?我下一节把这节的代码压缩一下分享给大家。凡是相似度高的那部分代码都可以压缩,具体怎么压缩?欲知详情,请听下回分解-----矩阵键盘单个触发的压缩代码编程。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-1-12 08:53:24
第十五节:矩阵键盘单个触发的压缩代码编程。
开场白:
上一节讲了矩阵键盘的单个触发。这节要教会大家在不改变其它任何性能的情况下,把上一节的按键扫描程序压缩一下容量。经过压缩后,把原来1558个字节压缩到860个字节的程序容量。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。。
(2)实现功能:16个按键中,每按一个按键都能触发一次蜂鸣器发出“滴”的一声。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time20 //按键去抖动延时的时间
void initial_myself();
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();//定时中断函数
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口
unsigned char ucKeyStep=1;//按键扫描步骤变量
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志
unsigned char ucRowRecord=1; //记录当前扫描到第几列了
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
/* 注释一:
*矩阵按键扫描的详细过程:
*先输出某一列低电平,其它三列输出高电平,这个时候再分别判断输入的四行,
*如果发现哪一行是低电平,就说明对应的某个按键被触发。依次分别输出另外三列
*中的某一列为低电平,再分别判断输入的四行,就可以检测完16个按键。内部详细的
*去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的。
*/
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键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 2号键 对应朱兆祺学习板的S2键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 3号键 对应朱兆祺学习板的S3键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 4:// 4号键 对应朱兆祺学习板的S4键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 5:// 5号键 对应朱兆祺学习板的S5键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 6:// 6号键 对应朱兆祺学习板的S6键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 7:// 7号键 对应朱兆祺学习板的S7键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 8:// 8号键 对应朱兆祺学习板的S8键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 9:// 9号键 对应朱兆祺学习板的S9键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 10:// 10号键 对应朱兆祺学习板的S10键
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 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三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}
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()//第一区 初始化单片机
{
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
已经花了两节讲矩阵键盘的单个触发程序。那么,矩阵键盘可不可以实现类似独立按键的组合按键功能?当然可以,但是也有一些附加限制条件。欲知详情,请听下回分解-----矩阵键盘的组合按键触发。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-1-16 11:39:02
第十六节:矩阵键盘的组合按键触发。
开场白:
上一节讲了矩阵键盘单个触发的压缩代码编程。这节讲矩阵键盘的组合按键触发。要教会大家三个知识点:
第一点:如何把矩阵键盘翻译成独立按盘的处理方式。然后按独立按键的方式来实现组合按键的功能。
第二点:要提醒大家在设计矩阵键盘时,很容易犯的一个错误。任意两个组合按键不能处于同一行,否则触发性能大打折扣。在做产品的时候,硬件电路设计中,除了四路行输入的要加上拉电阻,四路列输出也应该串入一个470欧左右的限流电阻,否则当同一行的两个按键同时按下时,很容易烧坏单片机IO口。为什么?大家仔细想想原因。因为如果没有限流电阻,同一行的两个按键同时按下时,在某一瞬间,输出的两路高低电平将会直接短接在一起,引起短路。在朱兆祺的学习板中,S1至S4是同一行,S5至S8是同一行,S9至S12是同一行,S13至S16是同一行。
第三点:在鸿哥矩阵键盘的组合按键处理程序中,组合按键的去抖动延时const_key_time_comb千万不能等于单击按键的去抖动延时const_key_time,否则组合按键会覆盖单击按键的触发。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。
(2)实现功能:16个按键中,每按一个按键都能触发一次蜂鸣器发出“滴”的一声。在同时按下S1和S16按键时,将会点亮一个LED灯。在同时按下S4和S13按键时,将会熄灭一个LED灯。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
/* 注释一:
*注意:组合按键的去抖动延时const_key_time_comb千万不能等于单击按键
*的去抖动延时const_key_time,否则组合按键会覆盖单击按键的触发。
*/
#define const_key_time12 //按键去抖动延时的时间
#define const_key_time_comb14 //组合按键去抖动延时的时间
void initial_myself();
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();//定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
/* 注释二:
*注意:任意两个组合按键不能处于同一行,否则触发性能大打折扣。
*在做产品的时候,硬件电路设计中,除了四路行输入的要加上拉电阻,
*四路列输出也应该串入一个470欧左右的限流电阻,否则当同一行的两个
*按键同时按下时,很容易烧坏单片机IO口。为什么?大家仔细想想原因。
*因为如果没有限流电阻,同一行的两个按键同时按下时,在某一瞬间,
*输出的两路高低电平将会直接短接在一起,引起短路。
*在朱兆祺的学习板中,S1至S4是同一行,S5至S8是同一行,S9至S12是同一行,S13至S16是同一行。
*/
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 led_dr=P3^5;//LED灯的输出
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
unsigned char ucKeyStep=1;//按键扫描步骤变量
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //16个按键去抖动延时计数器
unsigned char ucKeyLock=0; //16个按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt_01_16=0; //S1和S16组合按键去抖动延时计数器
unsigned char ucKeyLock_01_16=0; //S1和S16组合按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt_04_13=0; //S4和S13组合按键去抖动延时计数器
unsigned char ucKeyLock_04_13=0; //S4和S13组合按键触发后自锁的变量标志
unsigned char ucRowRecord=1; //记录当前扫描到第几列了
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
unsigned intuiKeyStatus=0xffff;//此变量每一位代表一个按键的状态,共16个按键。1代表没有被按下,0代表被按下。
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
}
}
void key_scan()//按键扫描函数 放在定时中断里
{
/* 注释三:
*第一步:先把16个按键翻译成独立按键。
*第二步: 再按独立按键的去抖动方式进行按键识别。
*第三步: 本程序把矩阵键盘翻译成独立按键的处理方式后,大家可以按独立按键的方式
* 来实现组合按键,双击,长按和短按,按住连续触发等功能。
* 我本人不再详细介绍这方面的内容。有兴趣的朋友,可以参考一下我前面章节讲的独立按键。
*/
switch(ucKeyStep)
{
case 1: //把16个按键的状态快速记录在uiKeyStatus变量的每一位中,相当于把矩阵键盘翻译成独立按键。
for(ucRowRecord=1;ucRowRecord<5;ucRowRecord++)
{
if(ucRowRecord==1)//第一列输出低电平
{
key_dr1=0;
key_dr2=1;
key_dr3=1;
key_dr4=1;
//如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
if(key_sr1==0)
{
uiKeyStatus=uiKeyStatus&0xfffe; //对应朱兆祺学习板的S1键被按下
}
if(key_sr2==0)
{
uiKeyStatus=uiKeyStatus&0xffef; //对应朱兆祺学习板的S5键被按下
}
if(key_sr3==0)
{
uiKeyStatus=uiKeyStatus&0xfeff; //对应朱兆祺学习板的S9键被按下
}
if(key_sr4==0)
{
uiKeyStatus=uiKeyStatus&0xefff; //对应朱兆祺学习板的S13键被按下
}
}
else if(ucRowRecord==2)//第二列输出低电平
{
key_dr1=1;
key_dr2=0;
key_dr3=1;
key_dr4=1;
//如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
if(key_sr1==0)
{
uiKeyStatus=uiKeyStatus&0xfffd; //对应朱兆祺学习板的S2键被按下
}
if(key_sr2==0)
{
uiKeyStatus=uiKeyStatus&0xffdf; //对应朱兆祺学习板的S6键被按下
}
if(key_sr3==0)
{
uiKeyStatus=uiKeyStatus&0xfdff; //对应朱兆祺学习板的S10键被按下
}
if(key_sr4==0)
{
uiKeyStatus=uiKeyStatus&0xdfff; //对应朱兆祺学习板的S14键被按下
}
}
else if(ucRowRecord==3)//第三列输出低电平
{
key_dr1=1;
key_dr2=1;
key_dr3=0;
key_dr4=1;
//如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
if(key_sr1==0)
{
uiKeyStatus=uiKeyStatus&0xfffb; //对应朱兆祺学习板的S3键被按下
}
if(key_sr2==0)
{
uiKeyStatus=uiKeyStatus&0xffbf; //对应朱兆祺学习板的S7键被按下
}
if(key_sr3==0)
{
uiKeyStatus=uiKeyStatus&0xfbff; //对应朱兆祺学习板的S11键被按下
}
if(key_sr4==0)
{
uiKeyStatus=uiKeyStatus&0xbfff; //对应朱兆祺学习板的S15键被按下
}
}
else //第四列输出低电平
{
key_dr1=1;
key_dr2=1;
key_dr3=1;
key_dr4=0;
//如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
if(key_sr1==0)
{
uiKeyStatus=uiKeyStatus&0xfff7; //对应朱兆祺学习板的S4键被按下
}
if(key_sr2==0)
{
uiKeyStatus=uiKeyStatus&0xff7f; //对应朱兆祺学习板的S8键被按下
}
if(key_sr3==0)
{
uiKeyStatus=uiKeyStatus&0xf7ff; //对应朱兆祺学习板的S12键被按下
}
if(key_sr4==0)
{
uiKeyStatus=uiKeyStatus&0x7fff; //对应朱兆祺学习板的S16键被按下
}
}
}
ucKeyStep=2; //切换到下一个运行步骤
break;
case 2://像独立按键一样进行去抖动和翻译。以下代码相似度很高,大家有兴趣的话还可以加for循环来压缩代码
if((uiKeyStatus&0x0001)==0x0001)//说明1号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=1; //被触发1号键
}
}
if((uiKeyStatus&0x0002)==0x0002)//说明2号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=2; //被触发2号键
}
}
if((uiKeyStatus&0x0004)==0x0004)//说明3号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=3; //被触发3号键
}
}
if((uiKeyStatus&0x0008)==0x0008)//说明4号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=4; //被触发4号键
}
}
if((uiKeyStatus&0x0010)==0x0010)//说明5号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=5; //被触发5号键
}
}
if((uiKeyStatus&0x0020)==0x0020)//说明6号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=6; //被触发6号键
}
}
if((uiKeyStatus&0x0040)==0x0040)//说明7号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=7; //被触发7号键
}
}
if((uiKeyStatus&0x0080)==0x0080)//说明8号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=8; //被触发8号键
}
}
if((uiKeyStatus&0x0100)==0x0100)//说明9号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=9; //被触发9号键
}
}
if((uiKeyStatus&0x0200)==0x0200)//说明10号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=10; //被触发10号键
}
}
if((uiKeyStatus&0x0400)==0x0400)//说明11号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=11; //被触发11号键
}
}
if((uiKeyStatus&0x0800)==0x0800)//说明12号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=12; //被触发12号键
}
}
if((uiKeyStatus&0x0800)==0x0800)//说明12号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=12; //被触发12号键
}
}
if((uiKeyStatus&0x1000)==0x1000)//说明13号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=13; //被触发13号键
}
}
if((uiKeyStatus&0x2000)==0x2000)//说明14号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=14; //被触发14号键
}
}
if((uiKeyStatus&0x4000)==0x4000)//说明15号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=15; //被触发15号键
}
}
if((uiKeyStatus&0x8000)==0x8000)//说明16号键没有被按下来
{
uiKeyTimeCnt=0;
ucKeyLock=0;
}
else if(ucKeyLock==0)
{
uiKeyTimeCnt++;
if(uiKeyTimeCnt>const_key_time)
{
uiKeyTimeCnt=0;
ucKeyLock=1; //自锁按键,防止不断触发
ucKeySec=16; //被触发16号键
}
}
if((uiKeyStatus&0x8001)==0x0000)//S1和S16的组合键盘被按下。
{
if(ucKeyLock_01_16==0)
{
uiKeyTimeCnt_01_16++;
if(uiKeyTimeCnt_01_16>const_key_time_comb)
{
uiKeyTimeCnt_01_16=0;
ucKeyLock_01_16=1;
ucKeySec=17; //被触发17号组合键
}
}
}
else
{
uiKeyTimeCnt_01_16=0; //S1和S16组合按键去抖动延时计数器
ucKeyLock_01_16=0; //S1和S16组合按键触发后自锁的变量标志
}
if((uiKeyStatus&0x1008)==0x0000)//S4和S13的组合键盘被按下。
{
if(ucKeyLock_04_13==0)
{
uiKeyTimeCnt_04_13++;
if(uiKeyTimeCnt_04_13>const_key_time_comb)
{
uiKeyTimeCnt_04_13=0;
ucKeyLock_04_13=1;
ucKeySec=18; //被触发18号组合键
}
}
}
else
{
uiKeyTimeCnt_04_13=0; //S4和S13组合按键去抖动延时计数器
ucKeyLock_04_13=0; //S4和S13组合按键触发后自锁的变量标志
}
uiKeyStatus=0xffff; //及时恢复状态,方便下一次扫描
ucKeyStep=1;//返回到第一个运行步骤重新开始扫描
break;
}
}
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 1号键 对应朱兆祺学习板的S1键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 2号键 对应朱兆祺学习板的S2键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 3号键 对应朱兆祺学习板的S3键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 4:// 4号键 对应朱兆祺学习板的S4键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 5:// 5号键 对应朱兆祺学习板的S5键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 6:// 6号键 对应朱兆祺学习板的S6键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 7:// 7号键 对应朱兆祺学习板的S7键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 8:// 8号键 对应朱兆祺学习板的S8键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 9:// 9号键 对应朱兆祺学习板的S9键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 10:// 10号键 对应朱兆祺学习板的S10键
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;
case 17:// 17号组合键 对应朱兆祺学习板的S1和S16键的组合按键
led_dr=1; //LED灯亮
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 18:// 18号组合键 对应朱兆祺学习板的S4和S13键的组合按键
led_dr=0; //LED灯灭
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
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三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}
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三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了如何把矩阵键盘翻译成独立按键的处理方式,然后像独立按键一样实现组合按键的功能,关于矩阵按键的双击,长按和短按,按键连续触发等功能我不再详细介绍,有兴趣的朋友可以参考我前面章节讲的独立按键。在实际的项目中,按键可以控制很多外设。为了以后进一步讲按键控制外设等功能,接下来我会讲哪些新内容呢?欲知详情,请听下回分解-----两片联级74HC595驱动16个LED灯的基本驱动程序。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-1-29 12:27:28
第十七节:两片联级74HC595驱动16个LED灯的基本驱动程序。
开场白:
上一节讲了如何把矩阵键盘翻译成独立按键的处理方式。这节讲74HC595的驱动程序。要教会大家两个知识点:
第一点:朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引脚接地。如果在工控中,用来控制继电器,那么此芯片的片选脚OE不要为了省一个IO口而直接接地,否则会引起上电瞬间继电器莫名其妙地动作。为了解决这个问题,OE脚应该用一个IO口单独驱动,并且千万要记住,此IO必须接一个15K左右的上拉电阻,然后在程序刚上电运行时,先把OE置高,并且尽快把所有的74HC595输出口置低,然后再把OE置低.当然还有另外一种解决办法,就是用一个10uF的电解电容跟一个100K的下拉电阻,组成跟51单片机外围复位电路原理一样的电路,连接到OE口,这样确保上电瞬间OE口有一小段时间是处于高电平状态,在此期间,尽快通过软件把74hc595的所有输出口置低。
第二点:两个联级74HC595的工作过程:每个74HC595内部都有一个8位的寄存器,两个联级起来就有两个寄存器。ST引脚就相当于一个刷新信号引脚,当ST引脚产生一个上升沿信号时,就会把寄存器的数值输出到74HC595的输出引脚并且锁存起来,DS是数据引脚,SH是把新数据送入寄存器的时钟信号。也就是说,SH引脚负责把数据送入到寄存器里,ST引脚负责把寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。
(2)实现功能:两片联级的74HC595驱动的16个LED灯交叉闪烁。比如,先是第1,3,5,7,9,11,13,15八个灯亮,其它的灯都灭。然后再反过来,原来亮的就灭,原来灭的就亮。交替闪烁。
(3)源代码讲解如下:
#include "REG52.H"
#define const_time_level 200
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time();//定时中断函数
/* 注释一:
* 朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引脚接地。如果在工控中,用来控制继电器,
* 那么此芯片的片选脚OE不要为了省一个IO口而直接接地,否则会引起上电瞬间继电器莫名其妙地动作。
* 为了解决这个问题,OE脚应该用一个IO口单独驱动,并且千万要记住,此IO必须接一个15K左右的
* 上拉电阻,然后在程序刚上电运行时,先把OE置高,并且尽快把所有的74HC595输出口置低,然后再把OE置低.
* 当然还有另外一种解决办法,就是用一个10uF的电解电容跟一个100K的下拉电阻,组成跟51单片机外围复位电路原理
* 一样的电路,连接到OE口,这样确保上电瞬间OE口有一小段时间是处于高电平状态,在此 期间,
* 尽快通过软件把74hc595的所有输出口置低。
*/
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucLedStep=0; //步骤变量
unsigned intuiTimeCnt=0; //统计定时中断次数的延时计数器
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker();
}
}
/* 注释二:
* 两个联级74HC595的工作过程:
* 每个74HC595内部都有一个8位的寄存器,两个联级起来就有两个寄存器。ST引脚就相当于一个刷新
* 信号引脚,当ST引脚产生一个上升沿信号时,就会把寄存器的数值输出到74HC595的输出引脚并且锁存起来,
* DS是数据引脚,SH是把新数据送入寄存器的时钟信号。也就是说,SH引脚负责把数据送入到寄存器里,ST引脚
* 负责把寄存器的数据更新输出到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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void led_flicker() ////第三区 LED闪烁应用程序
{
switch(ucLedStep)
{
case 0:
if(uiTimeCnt>=const_time_level) //时间到
{
uiTimeCnt=0; //时间计数器清零
hc595_drive(0x55,0x55);
ucLedStep=1; //切换到下一个步骤
}
break;
case 1:
if(uiTimeCnt>=const_time_level) //时间到
{
uiTimeCnt=0; //时间计数器清零
hc595_drive(0xaa,0xaa);
ucLedStep=0; //返回到上一个步骤
}
break;
}
}
void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
if(uiTimeCnt<0xffff)//设定这个条件,防止uiTimeCnt超范围。
{
uiTimeCnt++;//累加定时中断的次数,
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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()//第一区 初始化单片机
{
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了74HC595的驱动程序,它是一次控制16个LED同时亮灭的,在实际中应用不太方便,如果我们想要像单片机IO口直接控制LED那样方便,我们该怎么编写程序呢?欲知详情,请听下回分解-----把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-1-29 13:21:04
第十八节:把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。
开场白:
上一节讲了74HC595的驱动程序。为了更加方便操作74HC595输出的每个IO状态,这节讲如何把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。要教会大家两个知识点:
第一点:如何灵活运用与和非的运算符来实现位的操作。
第二点:如何灵活运用一个更新变量来实现静态刷新输出或者静态刷新显示的功能。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。
(2)实现功能:两片联级的74HC595驱动的16个LED灯交叉闪烁。比如,先是第1,3,5,7,9,11,13,15八个灯亮,其它的灯都灭。然后再反过来,原来亮的就灭,原来灭的就亮。交替闪烁。
(3)源代码讲解如下:
#include "REG52.H"
#define const_time_level 200
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
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=0; //步骤变量
unsigned intuiTimeCnt=0; //统计定时中断次数的延时计数器
unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker();
led_update();//LED更新函数
}
}
/* 注释一:
* 把74HC595驱动程序翻译成类似单片机IO口直接驱动方式的过程。
* 每次更新LED输出,记得都要把ucLed_update置1表示更新。
*/
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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void led_flicker() ////第三区 LED闪烁应用程序
{
switch(ucLedStep)
{
case 0:
if(uiTimeCnt>=const_time_level) //时间到
{
uiTimeCnt=0; //时间计数器清零
ucLed_dr1=1;//每个变量都代表一个LED灯的状态
ucLed_dr2=0;
ucLed_dr3=1;
ucLed_dr4=0;
ucLed_dr5=1;
ucLed_dr6=0;
ucLed_dr7=1;
ucLed_dr8=0;
ucLed_dr9=1;
ucLed_dr10=0;
ucLed_dr11=1;
ucLed_dr12=0;
ucLed_dr13=1;
ucLed_dr14=0;
ucLed_dr15=1;
ucLed_dr16=0;
ucLed_update=1;//更新显示
ucLedStep=1; //切换到下一个步骤
}
break;
case 1:
if(uiTimeCnt>=const_time_level) //时间到
{
uiTimeCnt=0; //时间计数器清零
ucLed_dr1=0;//每个变量都代表一个LED灯的状态
ucLed_dr2=1;
ucLed_dr3=0;
ucLed_dr4=1;
ucLed_dr5=0;
ucLed_dr6=1;
ucLed_dr7=0;
ucLed_dr8=1;
ucLed_dr9=0;
ucLed_dr10=1;
ucLed_dr11=0;
ucLed_dr12=1;
ucLed_dr13=0;
ucLed_dr14=1;
ucLed_dr15=0;
ucLed_dr16=1;
ucLed_update=1;//更新显示
ucLedStep=0; //返回到上一个步骤
}
break;
}
}
void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
if(uiTimeCnt<0xffff)//设定这个条件,防止uiTimeCnt超范围。
{
uiTimeCnt++;//累加定时中断的次数,
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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()//第一区 初始化单片机
{
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式,接下来,我们该如何来运用这种驱动方式实现跑马灯的程序?欲知详情,请听下回分解-----依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-1 05:32:34
第十九节:依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。
开场白:
上一节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。这节在上一节的驱动程序基础上,开始讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,以后任何所谓复杂的工程项目,都不再复杂。要教会大家一个知识点:通过本跑马灯程序,加深理解鸿哥所有实战项目中switch状态机的思想精髓。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。
(2)实现功能:第1个至第8个LED灯,先依次逐个亮,再依次逐个灭。第9至第16个LED灯一直灭。
(3)源代码讲解如下:
#include "REG52.H"
#define const_time_level_01_08200//第1个至第8个LED跑马灯的速度延时时间
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_01_08(); // 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
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_01_08=0; //第1个至第8个LED跑马灯的步骤变量
unsigned intuiTimeCnt_01_08=0; //第1个至第8个LED跑马灯的统计定时中断次数的延时计数器
unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker_01_08(); // 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
led_update();//LED更新函数
}
}
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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/
void led_flicker_01_08() //第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
{
switch(ucLedStep_01_08)
{
case 0:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr1=1;//第1个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=1; //切换到下一个步骤
}
break;
case 1:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr2=1;//第2个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=2; //切换到下一个步骤
}
break;
case 2:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr3=1;//第3个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=3; //切换到下一个步骤
}
break;
case 3:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr4=1;//第4个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=4; //切换到下一个步骤
}
break;
case 4:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr5=1;//第5个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=5; //切换到下一个步骤
}
break;
case 5:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr6=1;//第6个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=6; //切换到下一个步骤
}
break;
case 6:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr7=1;//第7个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=7; //切换到下一个步骤
}
break;
case 7:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr8=1;//第8个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=8; //切换到下一个步骤
}
break;
case 8:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr8=0;//第8个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=9; //切换到下一个步骤
}
break;
case 9:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr7=0;//第7个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=10; //切换到下一个步骤
}
break;
case 10:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr6=0;//第6个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=11; //切换到下一个步骤
}
break;
case 11:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr5=0;//第5个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=12; //切换到下一个步骤
}
break;
case 12:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr4=0;//第4个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=13; //切换到下一个步骤
}
break;
case 13:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr3=0;//第3个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=14; //切换到下一个步骤
}
break;
case 14:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr2=0;//第2个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=15; //切换到下一个步骤
}
break;
case 15:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr1=0;//第1个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=0; //返回到最开始处,重新开始新的一次循环。
}
break;
}
}
void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
if(uiTimeCnt_01_08<0xffff)//设定这个条件,防止uiTimeCnt超范围。
{
uiTimeCnt_01_08++;//累加定时中断的次数,
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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()//第一区 初始化单片机
{
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节讲了在第1个至第8个LED灯中,先依次逐个亮再依次逐个灭的跑马灯程序。下一节我们略作修改,继续做跑马灯的程序,要求在第9个至第16个LED灯中,依次逐个亮灯并且每次只能亮一个灯(其它的都灭),依次循环,我们该如何编写程序?欲知详情,请听下回分解-----依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-1 05:33:35
本帖最后由 jianhong_wu 于 2014-2-1 05:38 编辑
第二十节:依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。
开场白:
上一节讲了先依次逐个亮再依次逐个灭的跑马灯程序。这一节在上一节的基础上,略作修改,继续讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,以后任何所谓复杂的工程项目,都不再复杂。要教会大家一个知识点:通过本跑马灯程序,加深理解鸿哥所有实战项目中switch状态机的思想精髓。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。
(2)实现功能:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。第1至第8个LED灯一直灭。
(3)源代码讲解如下:
#include "REG52.H"
#define const_time_level_09_16300//第9个至第16个LED跑马灯的速度延时时间
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
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 intuiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器
unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
led_update();//LED更新函数
}
}
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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/
void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
switch(ucLedStep_09_16)
{
case 0:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr16=0;//第16个灭
ucLed_dr9=1;//第9个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=1; //切换到下一个步骤
}
break;
case 1:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr9=0;//第9个灭
ucLed_dr10=1;//第10个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=2; //切换到下一个步骤
}
break;
case 2:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr10=0;//第10个灭
ucLed_dr11=1;//第11个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=3; //切换到下一个步骤
}
break;
case 3:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr11=0;//第11个灭
ucLed_dr12=1;//第12个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=4; //切换到下一个步骤
}
break;
case 4:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr12=0;//第12个灭
ucLed_dr13=1;//第13个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=5; //切换到下一个步骤
}
break;
case 5:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr13=0;//第13个灭
ucLed_dr14=1;//第14个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=6; //切换到下一个步骤
}
break;
case 6:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr14=0;//第14个灭
ucLed_dr15=1;//第15个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=7; //切换到下一个步骤
}
break;
case 7:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr15=0;//第15个灭
ucLed_dr16=1;//第16个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
}
break;
}
}
void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
if(uiTimeCnt_09_16<0xffff)//设定这个条件,防止uiTimeCnt超范围。
{
uiTimeCnt_09_16++;//累加定时中断的次数,
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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()//第一区 初始化单片机
{
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
上一节和这一节讲了两种不同的跑马灯程序,如果要让这两种不同的跑马灯程序都能各自独立运行,就涉及到多任务并行处理的程序框架。没错,下一节就讲多任务并行处理这方面的知识,欲知详情,请听下回分解-----多任务并行处理两路跑马灯。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-1 05:34:51
第二十一节:多任务并行处理两路跑马灯。
开场白:
上一节讲了依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。这一节要结合前面两节的内容,实现多任务并行处理两路跑马灯。要教会大家一个知识点:利用鸿哥的switch状态机思想,实现多任务并行处理的程序。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。
(2)实现功能:
第一路独立运行的任务是:第1个至第8个LED灯,先依次逐个亮,再依次逐个灭。
第二路独立运行的任务是:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。
(3)源代码讲解如下:
#include "REG52.H"
#define const_time_level_01_08200//第1个至第8个LED跑马灯的速度延时时间
#define const_time_level_09_16300//第9个至第16个LED跑马灯的速度延时时间
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
void led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
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_01_08=0; //第1个至第8个LED跑马灯的步骤变量
unsigned intuiTimeCnt_01_08=0; //第1个至第8个LED跑马灯的统计定时中断次数的延时计数器
unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned intuiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器
unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
led_update();//LED更新函数
}
}
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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/
void led_flicker_01_08() //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
{
switch(ucLedStep_01_08)
{
case 0:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr1=1;//第1个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=1; //切换到下一个步骤
}
break;
case 1:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr2=1;//第2个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=2; //切换到下一个步骤
}
break;
case 2:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr3=1;//第3个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=3; //切换到下一个步骤
}
break;
case 3:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr4=1;//第4个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=4; //切换到下一个步骤
}
break;
case 4:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr5=1;//第5个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=5; //切换到下一个步骤
}
break;
case 5:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr6=1;//第6个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=6; //切换到下一个步骤
}
break;
case 6:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr7=1;//第7个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=7; //切换到下一个步骤
}
break;
case 7:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr8=1;//第8个亮
ucLed_update=1;//更新显示
ucLedStep_01_08=8; //切换到下一个步骤
}
break;
case 8:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr8=0;//第8个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=9; //切换到下一个步骤
}
break;
case 9:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr7=0;//第7个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=10; //切换到下一个步骤
}
break;
case 10:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr6=0;//第6个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=11; //切换到下一个步骤
}
break;
case 11:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr5=0;//第5个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=12; //切换到下一个步骤
}
break;
case 12:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr4=0;//第4个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=13; //切换到下一个步骤
}
break;
case 13:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr3=0;//第3个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=14; //切换到下一个步骤
}
break;
case 14:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr2=0;//第2个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=15; //切换到下一个步骤
}
break;
case 15:
if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
{
uiTimeCnt_01_08=0; //时间计数器清零
ucLed_dr1=0;//第1个灭
ucLed_update=1;//更新显示
ucLedStep_01_08=0; //返回到最开始处,重新开始新的一次循环。
}
break;
}
}
void led_flicker_09_16() //第二路独立运行的任务第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
switch(ucLedStep_09_16)
{
case 0:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr16=0;//第16个灭
ucLed_dr9=1;//第9个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=1; //切换到下一个步骤
}
break;
case 1:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr9=0;//第9个灭
ucLed_dr10=1;//第10个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=2; //切换到下一个步骤
}
break;
case 2:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr10=0;//第10个灭
ucLed_dr11=1;//第11个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=3; //切换到下一个步骤
}
break;
case 3:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr11=0;//第11个灭
ucLed_dr12=1;//第12个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=4; //切换到下一个步骤
}
break;
case 4:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr12=0;//第12个灭
ucLed_dr13=1;//第13个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=5; //切换到下一个步骤
}
break;
case 5:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr13=0;//第13个灭
ucLed_dr14=1;//第14个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=6; //切换到下一个步骤
}
break;
case 6:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr14=0;//第14个灭
ucLed_dr15=1;//第15个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=7; //切换到下一个步骤
}
break;
case 7:
if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
{
uiTimeCnt_09_16=0; //时间计数器清零
ucLed_dr15=0;//第15个灭
ucLed_dr16=1;//第16个亮
ucLed_update=1;//更新显示
ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
}
break;
}
}
void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
if(uiTimeCnt_01_08<0xffff)//设定这个条件,防止uiTimeCnt超范围。
{
uiTimeCnt_01_08++;//累加定时中断的次数,
}
if(uiTimeCnt_09_16<0xffff)//设定这个条件,防止uiTimeCnt超范围。
{
uiTimeCnt_09_16++;//累加定时中断的次数,
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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()//第一区 初始化单片机
{
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这一节讲了多任务并行处理两路跑马灯的程序,从下一节开始,将会在跑马灯的基础上,新加入按键这个元素。如何把按键跟跑马灯的任务有效的关联起来,欲知详情,请听下回分解-----独立按键控制跑马灯的方向。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-1 05:35:35
第二十二节:独立按键控制跑马灯的方向。
开场白:
上一节讲了多任务并行处理两路跑马灯的程序。这一节要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的任务有效的关联起来。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1键作为改变方向的独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。
(2)实现功能:
第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。按一次独立按键S1,将会更改跑马灯的运动方向。
(3)源代码讲解如下:
#include "REG52.H"
#define const_time_level_09_16300//第9个至第16个LED跑马灯的速度延时时间
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=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 intuiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器
unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量
unsigned char ucLedDirFlag=0; //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
led_update();//LED更新函数
key_service(); //按键服务的应用程序
}
}
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号键
}
}
}
void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键
if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
{
ucLedDirFlag=1;
}
else
{
ucLedDirFlag=0;
}
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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/
void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
switch(ucLedStep_09_16)
{
case 0:
if(uiTimeCnt_09_16>=const_time_level_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>=const_time_level_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>=const_time_level_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>=const_time_level_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>=const_time_level_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>=const_time_level_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>=const_time_level_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>=const_time_level_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超范围。
{
uiTimeCnt_09_16++;//累加定时中断的次数,
}
key_scan(); //按键扫描函数
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这一节讲了独立按键控制跑马灯的方向。如果按键要控制跑马灯的速度,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的速度。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-1 05:36:23
第二十三节:独立按键控制跑马灯的速度。
开场白:
上一节讲了独立按键控制跑马灯的方向。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的速度有效关联起来。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。在上一节的基础上,增加一个加速按键和一个减速按键,用矩阵键盘中的S5键作为加速独立按键,用矩阵键盘中的S9键作为减速独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。
(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S5,速度都会加快。每按一次独立按键S9,速度都会减慢。跟上一节一样,用S1来改变方向。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
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_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=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 intuiTimeCnt_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 intuiSetTimeLevel_09_16=300;//速度变量,此数值越大速度越慢,此数值越小速度越快。
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
led_update();//LED更新函数
key_service(); //按键服务的应用程序
}
}
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键
if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
{
ucLedDirFlag=1;
}
else
{
ucLedDirFlag=0;
}
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;
}
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;
}
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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/
void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
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超范围。
{
uiTimeCnt_09_16++;//累加定时中断的次数,
}
key_scan(); //按键扫描函数
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这一节讲了独立按键控制跑马灯的速度。如果按键要控制跑马灯的启动和暂停,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的启动和暂停。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-1 05:37:35
第二十四节:独立按键控制跑马灯的启动和暂停。
开场白:
上一节讲了独立按键控制跑马灯的速度。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的启动和暂停有效关联起来。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。在上一节的基础上,增加一个启动和暂停按键,用矩阵键盘中的S13键作为启动和暂停独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。
(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。其它跟上一节一样,用S1来改变方向,用S5和S9来改变速度。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间
#define const_key_time420 //按键去抖动延时的时间
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
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,因此必须一直输出低电平
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt4=0; //按键去抖动延时计数器
unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=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 intuiTimeCnt_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 intuiSetTimeLevel_09_16=300;//速度变量,此数值越大速度越慢,此数值越小速度越快。
unsigned char ucLedStartFlag=1; //启动和暂停的变量,0代表暂停,1代表启动
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
led_update();//LED更新函数
key_service(); //按键服务的应用程序
}
}
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;
}
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;
}
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;
}
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键ucLedStartFlag为0时代表暂停,为1时代表启动
if(ucLedStartFlag==1)//启动和暂停两种状态循环切换
{
ucLedStartFlag=0;
}
else //启动和暂停两种状态循环切换
{
ucLedStartFlag=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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
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三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这几节循序渐进地讲了独立按键控制跑马灯各种状态的程序。在很多实际工控项目中,经常会涉及到运动的自动控制,运动的自动控制就必然会涉及到感应器。下一节我将会讲感应器和运动控制的程序框架,欲知详情,请听下回分解-----用LED灯和按键来模拟工业自动化设备的运动控制。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-10 02:18:57
第二十五节:用LED灯和按键来模拟工业自动化设备的运动控制。
开场白:
前面三节讲了独立按键控制跑马灯的各种状态,这一节我们要做一个机械手控制程序,这个机械手可以左右移动,最左边有一个开关感应器,最右边也有一个开关感应器。它也可以上下移动,最下面有一个开关感应器。左右移动是通过一个气缸控制,上下移动也是通过一个气缸控制。而单片机控制气缸,本质上是通过三极管把信号放大,然后控制气缸上的电磁阀。这个系统机械手驱动部分的输出和输入信号如下:
2个输出IO口,分别控制2个气缸。对于左右移动的气缸,当IO口为0时往左边跑,当IO口为1时往右边跑。对于上下移动的气缸,当IO口为0时往上边跑,当IO口为1时往下边跑。
3个输入IO口,分别检测3个开关感应器。感应器没有被触发时,IO口检测为高电平1。被触发时,IO口检测为低电平0。
这一节继续要教会大家两个知识点:
第一点:如何用软件进行开关感应器的抗干扰处理。
第二点:如何用Switch语句搭建工业自动控制的程序框架。还是那句话,我们只要以Switch语句为支点,再复杂再繁琐的程序都可以轻松地编写出来。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1键作为启动独立按键,用S5按键模拟左边的开关感应器,用S9按键模拟右边的开关感应器,用S13按键模拟下边的开关感应器。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。
(2)实现功能:
开机默认机械手在左上方的原点位置。按下启动按键后,机械手从左边开始往右边移动,当机械手移动到最右边时,机械手马上开始往下移动,最后机械手移动到最右下角的位置时,延时1秒,然后原路返回,一直返回到左上角的原点位置。注意:启动按键必须等机械手处于左上角原点位置时,启动按键的触发才有效。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_sensor20 //开关感应器去抖动延时的时间
#define const_1s500//1秒钟大概的定时中断次数
void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void left_to_right();//从左边移动到右边
void right_to_left(); //从右边返回到左边
void up_to_dowm(); //从上边移动到下边
void down_to_up(); //从下边返回到上边
void run(); //设备自动控制程序
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();//LED更新函数
void T0_time();//定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
void sensor_scan(); //开关感应器软件抗干扰处理函数,放在定时中断里。
sbit hc595_sh_dr=P2^3;
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit left_sr=P0^1; //左边的开关感应器 对应朱兆祺学习板的S5键
sbit right_sr=P0^2; //右边的开关感应器 有对应朱兆祺学习板的S9键
sbit down_sr=P0^3; //下边的开关感应器 对应朱兆祺学习板的S13键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned char ucLeftSr=0;//左边感应器经过软件抗干扰处理后的状态标志
unsigned char ucRightSr=0;//右边感应器经过软件抗干扰处理后的状态标志
unsigned char ucDownSr=0;//下边感应器经过软件抗干扰处理后的状态标志
unsigned intuiLeftCnt1=0;//左边感应器软件抗干扰所需的计数器变量
unsigned intuiLeftCnt2=0;
unsigned intuiRightCnt1=0;//右边感应器软件抗干扰所需的计数器变量
unsigned intuiRightCnt2=0;
unsigned intuiDownCnt1=0; //下边软件抗干扰所需的计数器变量
unsigned intuiDownCnt2=0;
unsigned intuiVoiceCnt=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=1;//刷新变量。每次更改LED灯的状态都要更新一次。
unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量
unsigned intuiRunTimeCnt=0;//运动中的时间延时计数器变量
unsigned char ucRunStep=0;//运动控制的步骤变量
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
run(); //设备自动控制程序
led_update();//LED更新函数
key_service(); //按键服务的应用程序
}
}
/* 注释一:
* 开关感应器的抗干扰处理,本质上类似按键的去抖动处理。唯一的区别是:
* 按键去抖动关注的是IO口的一种状态,而开关感应器关注的是IO口的两种状态。
* 当开关感应器从原来的1状态切换到0状态之前,要进行软件滤波处理过程,一旦成功地
* 切换到0状态了,再想从0状态切换到1状态的时候,又要经过软件滤波处理过程,符合
* 条件后才能切换到1的状态。通俗的话来说,按键的去抖动从1变成0难,从0变成1容易。
* 开关感应器从1变成0难,从0变成1也难。这里所说的"难"是指要经过去抖处理。
*/
void sensor_scan() //开关感应器软件抗干扰处理函数,放在定时中断里。
{
if(left_sr==1)//左边感应器是高电平,说明有可能没有被接触 对应朱兆祺学习板的S5键
{
uiLeftCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiLeftCnt2++; //类似独立按键去抖动的软件抗干扰处理
if(uiLeftCnt2>const_sensor)
{
uiLeftCnt2=0;
ucLeftSr=1; //说明感应器确实没有被接触
}
}
else //左边感应器是低电平,说明有可能被接触到了
{
uiLeftCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiLeftCnt1++;
if(uiLeftCnt1>const_sensor)
{
uiLeftCnt1=0;
ucLeftSr=0; //说明感应器确实被接触到了
}
}
if(right_sr==1)//右边感应器是高电平,说明有可能没有被接触 对应朱兆祺学习板的S9键
{
uiRightCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiRightCnt2++; //类似独立按键去抖动的软件抗干扰处理
if(uiRightCnt2>const_sensor)
{
uiRightCnt2=0;
ucRightSr=1; //说明感应器确实没有被接触
}
}
else //右边感应器是低电平,说明有可能被接触到了
{
uiRightCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiRightCnt1++;
if(uiRightCnt1>const_sensor)
{
uiRightCnt1=0;
ucRightSr=0; //说明感应器确实被接触到了
}
}
if(down_sr==1)//下边感应器是高电平,说明有可能没有被接触 对应朱兆祺学习板的S13键
{
uiDownCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiDownCnt2++; //类似独立按键去抖动的软件抗干扰处理
if(uiDownCnt2>const_sensor)
{
uiDownCnt2=0;
ucDownSr=1; //说明感应器确实没有被接触
}
}
else //下边感应器是低电平,说明有可能被接触到了
{
uiDownCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiDownCnt1++;
if(uiDownCnt1>const_sensor)
{
uiDownCnt1=0;
ucDownSr=0; //说明感应器确实被接触到了
}
}
}
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号键
}
}
}
void key_service() //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 启动按键 对应朱兆祺学习板的S1键
if(ucLeftSr==0)//处于左上角原点位置
{
ucRunStep=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 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(15);
hc595_sh_dr=1;
delay_short(15);
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(15);
hc595_sh_dr=1;
delay_short(15);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(15);
hc595_st_dr=1;
delay_short(15);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void left_to_right()//从左边移动到右边
{
ucLed_dr1=1; // 1代表左右气缸从左边移动到右边
ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}
void right_to_left() //从右边返回到左边
{
ucLed_dr1=0; // 0代表左右气缸从右边返回到左边
ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}
void up_to_down() //从上边移动到下边
{
ucLed_dr2=1; // 1代表上下气缸从上边移动到下边
ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}
void down_to_up() //从下边返回到上边
{
ucLed_dr2=0; // 0代表上下气缸从下边返回到上边
ucLed_update=1;//刷新变量。每次更改LED灯的状态都要更新一次。
}
void run() //设备自动控制程序
{
switch(ucRunStep)
{
case 0: //机械手处于左上角原点的位置,待命状态。此时触发启动按键ucRunStep=1,就触发后续一些列的连续动作。
break;
case 1: //机械手从左边往右边移动
left_to_right();
ucRunStep=2;//这就是鸿哥传说中的怎样灵活控制步骤变量
break;
case 2: //等待机械手移动到最右边,直到触发了最右边的开关感应器。
if(ucRightSr==0)//右边感应器被触发
{
ucRunStep=3;//这就是鸿哥传说中的怎样灵活控制步骤变量
}
break;
case 3: //机械手从右上边往右下边移动,从上往下。
up_to_down();
ucRunStep=4;//这就是鸿哥传说中的怎样灵活控制步骤变量
break;
case 4: //等待机械手从右上边移动到右下边,直到触发了右下边的开关感应器。
if(ucDownSr==0)//右下边感应器被触发
{
uiRunTimeCnt=0;//时间计数器清零,为接下来延时1秒钟做准备
ucRunStep=5;//这就是鸿哥传说中的怎样灵活控制步骤变量
}
break;
case 5: //机械手在右下边延时1秒
if(uiRunTimeCnt>const_1s)//延时1秒
{
ucRunStep=6;//这就是鸿哥传说中的怎样灵活控制步骤变量
}
break;
case 6: //原路返回,机械手从右下边往右上边移动。
down_to_up();
ucRunStep=7;//这就是鸿哥传说中的怎样灵活控制步骤变量
break;
case 7: //原路返回,等待机械手移动到最右边的感应开关
if(ucRightSr==0)
{
ucRunStep=8;//这就是鸿哥传说中的怎样灵活控制步骤变量
}
break;
case 8: //原路返回,等待机械手从右边往左边移动
right_to_left();
ucRunStep=9;//这就是鸿哥传说中的怎样灵活控制步骤变量
break;
case 9: //原路返回,等待机械手移动到最左边的感应开关,表示返回到了原点
if(ucLeftSr==0) //返回到左上角的原点位置
{
ucRunStep=0;//这就是鸿哥传说中的怎样灵活控制步骤变量
}
break;
}
}
void T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
sensor_scan(); //开关感应器软件抗干扰处理函数
key_scan(); //按键扫描函数
if(uiRunTimeCnt<0xffff) //不要超过最大int类型范围
{
uiRunTimeCnt++; //延时计数器
}
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
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,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
前面花了很多节内容在讲按键和跑马灯的关系,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的。人机界面的程序框架该怎么样写?欲知详情,请听下回分解-----在主函数while循环中驱动数码管的动态扫描程序。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-17 02:56:45
第二十六节:在主函数while循环中驱动数码管的动态扫描程序。
开场白:
上一节通过一个机械手自动控制程序展示了我在工控常用的编程框架,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的,这一节开始讲最常用的人机界面------动态数码管的驱动。
这一节要教会大家两个知识点:
第一点:数码管的动态驱动原理。
第二点:如何通过编程,让数码管显示的内容转移到几个变量接口上,方便以后编写更上一层的窗口程序。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。用两片74HC595动态驱动八位共阴数码管。
(2)实现功能:
开机后显示8765.4321的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
#include "REG52.H"
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(); //显示数码管字模的驱动函数
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
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 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 ucDisplayUpdate=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)
{
display_drive();//显示数码管字模的驱动函数
}
}
/* 注释一:
* 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
* 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
* 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
* 数码管公共位com的引脚。
*/
void display_drive()
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1://显示第1位
ucDigShowTemp=dig_table;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
/* 注释二:
* 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
* 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
* 位的数码管界面,这样可以增加显示的效果。但是,由于坚鸿51学习板是间接经过74HC595驱动数码管的,
* 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
* 因此这里不再需要加delay延时函数或者计数延时。
*/
}
//数码管的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 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灯
}
void initial_peripheral() //第二区 初始化外围
{
/* 注释四:
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
* 了解来龙去脉,就可以知道本驱动程序的框架原理了。
*/
ucDigShow8=8;//第8位数码管要显示的内容
ucDigShow7=7;//第7位数码管要显示的内容
ucDigShow6=6;//第6位数码管要显示的内容
ucDigShow5=5;//第5位数码管要显示的内容
ucDigShow4=4;//第4位数码管要显示的内容
ucDigShow3=3;//第3位数码管要显示的内容
ucDigShow2=2;//第2位数码管要显示的内容
ucDigShow1=1;//第1位数码管要显示的内容
ucDigDot8=0;
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=1;//显示第5位的小数点
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
}
总结陈词:
把本程序下载到坚鸿51学习板上,发现显示的效果还是挺不错的。但是,本程序也有一个弱点,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果,有没有办法改善它?当然有。欲知详情,请听下回分解-----在定时中断里动态扫描数码管的程序。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-17 02:57:50
本帖最后由 jianhong_wu 于 2014-2-22 22:48 编辑
第二十七节:在定时中断里动态扫描数码管的程序。
开场白:
上一节讲了在主函数循环中动态扫描数码管的程序,但是该程序有一个隐患,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果。这一节要教会大家两个知识点:
第一个:如何把动态扫描数码管的程序放在定时中断里,彻底解决上节的显示隐患。
第二个:在定时中断里的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的初始值2000改成了500。
具体内容,请看源代码讲解。
(1)硬件平台:基于坚鸿51单片机学习板。用两片74HC595动态驱动八位共阴数码管。
(2)实现功能:
开机后显示8765.4321的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
#include "REG52.H"
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(); //显示数码管字模的驱动函数
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time();//定时中断函数
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 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 ucDisplayUpdate=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)
{
;
}
}
/* 注释一:
* 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
* 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
* 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
* 数码管公共位com的引脚。
*/
void display_drive()
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1://显示第1位
ucDigShowTemp=dig_table;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
/* 注释二:
* 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
* 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
* 位的数码管界面,这样可以增加显示的效果。但是,由于坚鸿51学习板是间接经过74HC595驱动数码管的,
* 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
* 因此这里不再需要加delay延时函数或者计数延时。
*/
}
//数码管的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 T0_time() interrupt 1
{
TF0=0;//清除中断标志
TR0=0; //关中断
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;//关闭独立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() //第二区 初始化外围
{
/* 注释五:
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
* 了解来龙去脉,就可以知道本驱动程序的框架原理了。
*/
ucDigShow8=8;//第8位数码管要显示的内容
ucDigShow7=7;//第7位数码管要显示的内容
ucDigShow6=6;//第6位数码管要显示的内容
ucDigShow5=5;//第5位数码管要显示的内容
ucDigShow4=4;//第4位数码管要显示的内容
ucDigShow3=3;//第3位数码管要显示的内容
ucDigShow2=2;//第2位数码管要显示的内容
ucDigShow1=1;//第1位数码管要显示的内容
ucDigDot8=0;
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=1;//显示第5位的小数点
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
有的朋友会质疑,很多教科书上说,定时中断函数里面的内容应该越少越好,你把动态驱动数码管的函数放在中断里面,难道不会影响其它任务的执行吗?我的回答是,大部分的小项目都不会影响,只有少数实时性要求非常高的项目会影响,而对于这类项目,我的做法是从一开始设计硬件电路板的时候,就应该放弃用动态扫描数码管的方案,而是应该选数码管专用驱动芯片来实现静态驱动。因为动态扫描数码管本来就不适合应用在实时性非常高的项目。
前面这两节都讲了数码管的驱动程序,要在此基础上,做一些项目中经常遇到的界面应用,我们该怎么写程序?欲知详情,请听下回分解----数码管通过切换窗口来设置参数。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-23 01:28:49
本帖最后由 jianhong_wu 于 2014-2-28 14:42 编辑
第二十八节:数码管通过切换窗口来设置参数。
开场白:
上一节讲了数码管的驱动程序,这节在上节的基础上,通过按键切换不同的窗口来设置不同的参数。
这一节要教会大家三个知识点:第一个:鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
第二个:如何通过一个窗口变量来把按键,数码管,被设置的参数关联起来。
第三个:需要特别注意,在显示被设置参数时,应该先分解出每一位,然后再把分解出来的数据过渡到显示缓冲变量里。
具体内容,请看源代码讲解。
(1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9键
(2)实现功能:
通过按键设置4个不同的参数。
一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间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 intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志unsigned intuiVoiceCnt=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 intuiSetData1=0;//本程序中需要被设置的参数1
unsigned intuiSetData2=0;//本程序中需要被设置的参数2
unsigned intuiSetData3=0;//本程序中需要被设置的参数3
unsigned intuiSetData4=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位数码管显示无/* 注释二:
* 此处为什么要多加4个中间过渡变量ucTemp?是因为uiSetData1分解数据的时候
* 需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时
* 了一会。我们的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,
* 当uiSetData1还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致
* 显示的数据瞬间产生不完整,影响显示效果。因此,为了把需要显示的数据过渡最快,
* 所以采取了先分解,再过渡显示的方法。
*/ //先分解数据
ucTemp4=uiSetData1/1000;
ucTemp3=uiSetData1%1000/100;
ucTemp2=uiSetData1%100/10;
ucTemp1=uiSetData1%10;
//再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
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; ucDigShow4=ucTemp4;//第4位数码管要显示的内容
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
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; ucDigShow4=ucTemp4;//第4位数码管要显示的内容
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
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; ucDigShow4=ucTemp4;//第4位数码管要显示的内容
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
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--; /* 注释三:
* 单片机C编译有一个特点,当一个无符号类型的数据0减去1时,就会溢出反而变成这个类型数据的最大值
* 对于int类型的数据,最大值肯定比9999大,因此下面的临界点用if(uiSetData1>9999)来判断。
*/
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;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
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为工作方式1TH0=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; //启动定时中断}
总结陈词:
这节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。我们要把高位为0的去掉不显示,该怎么改程序呢?欲知详情,请听下回分解-----数码管通过切换窗口来设置参数,并且不显示为0的高位。
(未完待续,下节更精彩,不要走开哦)
jianhong_wu
发表于 2014-2-23 01:30:09
本帖最后由 jianhong_wu 于 2014-2-28 14:41 编辑
第二十九节:数码管通过切换窗口来设置参数,并且不显示为0的高位。
开场白:
上一节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。
这一节要教会大家两个知识点:第一个:在上一节display_service()函数里略作修改,把高位为0的去掉不显示。
第二个:加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
具体内容,请看源代码讲解。
(1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9键
(2)实现功能:
通过按键设置4个不同的参数。
一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。
并且要求被设置的数据不显示为0的高位。比如参数是12时,不能显示“0012”,只能第4,3位不显示,第2,1位显示“12”。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间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 intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志unsigned intuiVoiceCnt=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 intuiSetData1=0;//本程序中需要被设置的参数1
unsigned intuiSetData2=0;//本程序中需要被设置的参数2
unsigned intuiSetData3=0;//本程序中需要被设置的参数3
unsigned intuiSetData4=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;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
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为工作方式1TH0=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:33
本帖最后由 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_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间
#define const_dpy_time_half200//数码管闪烁时间的半值
#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 intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=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 intuiSetData1=0;//本程序中需要被设置的参数1
unsigned intuiSetData2=0;//本程序中需要被设置的参数2
unsigned intuiSetData3=0;//本程序中需要被设置的参数3
unsigned intuiSetData4=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 intuiDpyTimeCnt=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;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
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; //启动定时中断
}
总结陈词:
这节讲了数码管通过闪烁来设置数据的基本程序,但是该程序只有一个窗口。实际应用中,有些项目会有几个窗口,而且每个窗口都要设置几个参数,这样的程序该怎么写?欲知详情,请听下回分解-----数码管通过一二级菜单来设置数据的综合程序。
(未完待续,下节更精彩,不要走开哦)
页:
1
[2]
3
4
5
6
7
8
9
10
11