(一)按键行列扫描与蜂鸣器(1)技术体会:在行列式扫描结构的薄膜按键里,干扰很大,按键扫描程序非常讲究,尤其是去抖动的处理。(2)功能需求:每按一个按键,蜂鸣器就响一次。(3)硬件原理:(a)用4个IO来做2X2按键行列扫描,其中作为输入的2个IO口必须接上拉电阻20K左右。(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。而无源蜂鸣器是要靠断断续续的开关信号来驱动才能响,就是要频率来驱动。(4)源码适合的单片机:PIC18F4620,晶振为22.1184MHz(5)源代码讲解如下:#includepic18.h//包含芯片相关头文件//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr#definebeep_drLATA1//蜂鸣器输出#definekey_dr1LATB3//2X2按键行输出#definekey_dr2LATB4//2X2按键行输出#definekey_sr1RB6//2X2按键行输入#definekey_sr2RB7//2X2按键行输入//补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量//前缀都用cnt_表示。#definecnt_delay_cnt125//按键去抖动延时阀值#definecnt_delay_cnt25//按键行输出信号稳定的小延时阀值#definecnt_voice_time60//蜂鸣器响的声音长短的延时阀值voiddelay1(unsignedintde);//小延时程序,时间不宜太长,因为内部没有喂看门狗//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中//断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。voidkey_scan();//按键扫描函数,放在定时中断里voidkey_service();//按键服务函数,放在main函数循环里//补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名//后缀都用_step表示。unsignedcharkey_step=1;//按键扫描步骤变量,在switch()语句的括号里//补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名//后缀都用_lock表示。unsignedcharkey_lock1=0;//按键自锁标志//补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量//后缀都用_cnt表示。unsignedintdelay_cnt1=0;//延时计数器的变量unsignedintdelay_cnt2=0;//延时计数器的变量unsignedintvoice_time_cnt;//蜂鸣器响的声音长短的计数延时//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类//后缀都用_sec表示。Unsignedcharkey_sec=0;//哪个按键被触发//主程序main(){ADCON0=0x00;ADCON1=0x0f;//全部为数字信号ADCON2=0xa1;//右对齐RBPU=0;//上拉电阻SSPEN=0;//决定RA5不作为串口TRISB3=0;//配置按键行扫描IO为输出TRISB4=0;//配置按键行扫描IO为输出TRISB6=1;//配置按键列扫描IO为输入TRISB7=1;//配置按键列扫描IO为输入T1CON=0x24;//定时器中断配置TMR1H=0xF5;TMR1L=0x5F;TMR1IF=0;TMR1IE=1;TMR1ON=1;TMR1IE=1;//补充说明,以上的内容为寄存器配置,每种不同的单片机会有点差异,//大家不用过度关注以上寄存器的配置,只要知道有这么一回事即可beep_dr=0;//关蜂鸣器,上电初始化IOwhile(1){CLRWDT();//喂看门狗,大家不用过度关注此行key_service();//按键服务}}voidkey_scan()//按键扫描函数{//补充说明:如果中断一次就把所有的按键都扫描完,中断占用的时间片就会太多,势//必会影响main函数里其他子程序的运行,为了避免一口气把所//的按键都扫描完,此//处用switch语句把4个按键分成2等分,一次中断只扫描2个按键switch(key_step)//按键扫描步骤,{case1://扫描1号键,2号键key_dr1=0;//按键行扫描输出第一行低电平key_dr2=1;delay_cnt2=0;//延时计数器清零key_step++;//切换到下一个运行步骤break;case2:delay_cnt2++;if(delay_cnt2cnt_delay2)//小延时,但不是去抖动延时,替代一直受网友争议的delay1(40){delay_cnt2=0;key_step++;//切换到下一个运行步骤}break;case3:If(key_sr1==1&&key_sr2==1){//如果没有按键按下,则2个IO输入都是高电平key_step++;//如果没有按键按下,下一个中断扫描下2个按键key_lock1=0;//按键自锁标志清零delay_cnt1=0;//按键去抖动延时计数器清零,此行非常巧妙}Elseif(key_sr1==0&&key_sr2==1&&key_lock1==0){//key_lock1按键自锁,避免按键一直触发,下降沿有效++delay_cnt1;//延时计数器//补充说明:有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常//巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,//在延时去抖动的时候,还必须要监控延时这段时间里,按键IO输入口是否会由于受到某//种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零//我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上//给我加薪1000元。if(delay_cnt1cnt_delay_cnt1)//延时计数器超过一定的数值{delay_cnt1=0;key_lock1=1;//自锁按键置位,避免一直触发,只有松开按键,//此标志位才会被清零key_sec=1;//触发1号键}}elseif(key_sr1==1&&key_sr2==0&&key_lock1==0){++delay_cnt1;if(delay_cnt1cnt_delay_cnt1){delay_cnt1=0;key_lock1=1;//自锁按键置位,避免一直触发key_sec=2;//触发2号键}}break;case4://扫描//扫描3号键,4号键key_dr1=1;key_dr2=0;//按键行扫描输出第二行低电平delay_cnt2=0;//延时计数器清零key_step++;//切换到下一个运行步骤break;case5:delay_cnt2++;if(delay_cnt2cnt_delay2)//小延时,但不是去抖动延时,替代一直受网友争议的delay1(40){delay_cnt2=0;key_step++;//切换到下一个运行步骤}break;case6:if(key_sr1==1&&key_sr2==1){key_step++;key_lock1=0;delay_cnt1=0;}Elseif(key_sr1==0&&key_sr2==1&&key_lock1==0){++delay_cnt1;if(delay_cnt1cnt_delay_cnt1){delay_cnt1=0;key_lock1=1;key_sec=3;//触发3号键}}elseif(key_sr1==1&&key_sr2==0&&key_lock1==0){++delay_cnt1;if(delay_cnt1cnt_delay_cnt1){delay_cnt1=0;key_lock1=1;//自锁按键置位,避免一直触发key_sec=4;//触发4号键}}break;}if(key_step6)//第1组按键与第2组按键反复轮流扫描{key_step=1;}}voidkey_service()//按键服务函数{switch(key_sec)//按键服务状态切换{case1://1号键//补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0//时,会不断自减,一直到它为0时,自动把蜂鸣器关闭voice_time_cnt=cnt_voice_time;//蜂鸣器响“滴”一声就停key_sec=0;//相应完按键处理程序之后,把按键选择变量清零,//避免一直触发break;case2://2号键voice_time_cnt=cnt_voice_time;//蜂鸣器响“滴”一声就停key_sec=0;//相应完按键处理程序之后,把按键选择变量清零,//避免一直触发break;case3://3号键voice_time_cnt=cnt_voice_time;//蜂鸣器响“滴”一声就停key_sec=0;//相应完按键处理程序之后,把按键选择变量清零,//避免一直触发break;case4://4号键voice_time_cnt=cnt_voice_time;//蜂鸣器响“滴”一声就停key_sec=0;//相应完按键处理程序之后,把按键选择变量清零,//避免一直触发break;}}//中断voidinterrupttimer1rbint(void){if(TMR1IE==1&&TMR1IF==1)//定时中断{TMR1IF=0;//定时中断标志位关闭TMR1ON=0;//定时中断开关关闭key_scan();//按键扫描函数if(voice_time_cnt)//控制蜂鸣器声音的长短{beep_dr=1;//蜂鸣器响--voice_time_cnt;//蜂鸣器响的声音长短的计数延时}else{beep_dr=0;//蜂鸣器停止}TMR1H=0xF5;//重新设置定时时间间隔TMR1L=0x5F;TMR1ON=1;//定时中断开关打开}}voiddelay1(unsignedintde){unsignedintt;for(t=0;tde;t++);}(6)小结:以上是我常用的编程结构。后续我做的所有项目基本上是这样一种编程结构。这一节技术上要特别重视按键扫描。有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,在延时去抖动的时候,还必须要监控延时这段时间里,按键IO输入口是否会由于受到某种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零,我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上给我加薪1000元。(未完待续下一节)第二节:独立按键扫描与蜂鸣器(1)学习目标:利用上一节的程序框架,把按键行列扫描方式改成独立按键扫描方式。通过这节的练习,加深熟悉吴坚鸿的程序框架,以及独立按键扫描的编程方式。(2)功能需求:每按一个按键,蜂鸣器就响一次。(3)硬件原理:(a)用4个IO来做独立按键扫描,4个IO口都必须接上拉电阻20K左右。(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。(4)源码适合的单片机:PIC18F4620,晶振为22.1184MHz(5)源代码讲解如下:#includepic18.h//包含芯片相关头文件//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr#definebeep_drLATA1//蜂鸣器输出#definekey_sr1RB6//独立按键输入#definekey_sr2RB7//独立按键输入#definekey_sr3RB3//独立按键输入