• 回复
  • 收藏
  • 点赞
  • 分享
  • 发新帖

吴坚鸿单片机程序技巧与模式连载

吴坚鸿

拥有多年电子开发经验,功力深厚,目前经营独立工作室。

主攻PIC,瑞萨,STC,51系列等各类单片机的开发,擅长使用VHDL语言开发CPLD,精通使用VC,VB开发电脑上位机软件。

本文作者认为传授单片机技术最好要有一个学习板作为硬件平台,即使没有硬件平台,至少每个程序也要有相对应的硬件原理图。但在本文当中作者将所有的硬件原理都用中文来描述,尽可能确保读者看文字就知道硬件电路大概的原理。

文章中所有程序都是用C语言并且基于PIC单片机,因为对于读者来说,所有单片机的C语言都大同小异,只要学会一种,其他的都懂了。而相对的,程序侧重点转移到了教大家编程的模式与框架上。

本文文章每一节的程序里主要包含两方面的内容。一方面是程序框架,另一方面是项目中所遇到的某个具体技术难题的解决方法。

全部回复(5)
正序查看
倒序查看
一木01
LV.3
2
2014-02-11 13:41

一)按键行列扫描与蜂鸣器

 

(1)技术体会:在行列式扫描结构的薄膜按键里,干扰很大,按键扫描程序非常讲究,尤其是去抖动的处理。

(2)功能需求:每按一个按键,蜂鸣器就响一次。

(3)硬件原理:

(a)用4个IO来做2X2按键行列扫描,其中作为输入的2个IO口必须接上拉电阻20K左右。

(b)用1个IO经过8050三极管来驱动有源蜂鸣器,有源蜂鸣器通电就一直响,断电就停止。而无源蜂鸣器是要靠断断续续的开关信号来驱动才能响,就是要频率来驱动。

(4)源码适合的单片机:PIC18F4620,晶振为22.1184MHz

(5)源代码讲解如下:

#include //包含芯片相关头文件

//补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的//IO后缀都//是_sr

#define beep_dr LATA1 //蜂鸣器输出

#define key_dr1 LATB3 //2X2按键行输出

#define key_dr2 LATB4 //2X2按键行输出

#define key_sr1 RB6 //2X2按键行输入

#define key_sr2 RB7 //2X2按键行输入

//补充说明:吴坚鸿程序风格是这样的,凡是做延时计数阀值的常量

//前缀都用cnt_表示。

#define cnt_delay_cnt1 25 //按键去抖动延时阀值

#define cnt_delay_cnt2 5 //按键行输出信号稳定的小延时阀值

#define cnt_voice_time 60 //蜂鸣器响的声音长短的延时阀值

void delay1(unsigned int de) ;//小延时程序,时间不宜太长,因为内部没有喂看门狗

//补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中

//断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序//放在中断里,别听他们,信鸿哥无坎坷。

void key_scan(); //按键扫描函数,放在定时中断里

void key_service(); //按键服务函数,放在main函数循环里

//补充说明:吴坚鸿程序风格是这样的,凡是switch()语句括号里面的变量名

//后缀都用_step表示。

unsigned char key_step=1; //按键扫描步骤变量,在switch()语句的括号里

//补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名

//后缀都用_lock表示。

unsigned char key_lock1=0; //按键自锁标志

//补充说明:吴坚鸿程序风格是这样的,凡是计数器延时的变量

//后缀都用_cnt表示。

unsigned int delay_cnt1=0; //延时计数器的变量

unsigned int delay_cnt2=0; //延时计数器的变量

unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时

//补充说明:吴坚鸿程序风格是这样的,凡是做类型的变量的分类

//后缀都用_sec表示。

Unsigned char key_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; //关蜂鸣器,上电初始化IO

while(1)

{

CLRWDT(); //喂看门狗,大家不用过度关注此行

key_service(); //按键服务

}

}

void key_scan() //按键扫描函数

{

//补充说明:如果中断一次就把所有的按键都扫描完,中断占用的时间片就会太多,势//必会影响main函数里其他子程序的运行,为了避免一口气把所//的按键都扫描完,此

//处用switch语句把4个按键分成2等分,一次中断只扫描2个按键

switch(key_step) //按键扫描步骤,

{

case 1: //扫描1号键,2号键

key_dr1=0; //按键行扫描输出第一行低电平

key_dr2=1;

delay_cnt2=0; //延时计数器清零

key_step++; //切换到下一个运行步骤

break;

case 2:

delay_cnt2++;

if(delay_cnt2>cnt_delay_cnt2) //小延时,但不是去抖动延时,替代一直受网友争议的delay1(40)

{

delay_cnt2=0;

key_step++; //切换到下一个运行步骤

}

break;

case 3:

if(key_sr1==1&&key_sr2==1)

{ //如果没有按键按下,则2个IO输入都是高电平

key_step++; //如果没有按键按下,下一个中断扫描下2个

0
回复
一木01
LV.3
3
2014-02-11 13:43

//按键

key_lock1=0; //按键自锁标志清零

delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙

}

Else if(key_sr1==0&&key_sr2==1&&key_lock1==0)

{ // key_lock1按键自锁,避免按键一直触发,下降沿有效

++delay_cnt1; //延时计数器

//补充说明:有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常

//巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,

//在延时去抖动的时候,还必须要监控延时这段时间里,按键IO输入口是否会由于受到某//种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零

//我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上

//给我加薪1000元。

if(delay_cnt1>cnt_delay_cnt1) //延时计数器超过一定的数值

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发,只有松开按键,

//此标志位才会被清零

key_sec=1; //触发1号键

}

}

else if(key_sr1==1&&key_sr2==0&&key_lock1==0)

{

++delay_cnt1;

if(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=2; //触发2号键

}

}

break;

case 4: //扫描//扫描3号键,4号键

key_dr1=1;

key_dr2=0; //按键行扫描输出第二行低电平

delay_cnt2=0; //延时计数器清零

key_step++; //切换到下一个运行步骤

break;

case 5:

delay_cnt2++;

if(delay_cnt2>cnt_delay_cnt2) //小延时,但不是去抖动延时,替代一直受网友争议的delay1(40)

{

delay_cnt2=0;

key_step++; //切换到下一个运行步骤

}

break;

case 6:

if(key_sr1==1&&key_sr2==1)

{

key_step++;

key_lock1=0;

delay_cnt1=0;

}

Else if(key_sr1==0&&key_sr2==1&&key_lock1==0)

{

++delay_cnt1;

if(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1;

key_sec=3; //触发3号键

}

}

else if(key_sr1==1&&key_sr2==0&&key_lock1==0)

{

++delay_cnt1;

if(delay_cnt1>cnt_delay_cnt1)

{

delay_cnt1=0;

key_lock1=1; //自锁按键置位,避免一直触发

key_sec=4; //触发4号键

}

}

break;

}

0
回复
一木01
LV.3
4
2014-02-11 13:44

if(key_step>6) //第1组按键与第2组按键反复轮流扫描

{

key_step=1;

}

}

void key_service() //按键服务函数

{

switch(key_sec) //按键服务状态切换

{

case 1:// 1号键

// 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0

//时,会不断自减,一直到它为0时,自动把蜂鸣器关闭

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 2:// 2号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 3://3号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

case 4://4号键

voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停

key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,

//避免一直触发

break;

}

}

//中断

void interrupt timer1rbint(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; //定时中断开关打开

}

}

void delay1(unsigned int de)

{

unsigned int t;

for(t=0;t

}

(6)小结:

以上是我常用的编程结构。后续我做的所有项目基本上是这样一种编程结构。这一节技术上要特别重视按键扫描。有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,在延时去抖动的时候,还必须要监控延时这段时间里,按键IO输入口是否会由于受到某种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零,我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上给我加薪1000元。

 

 

待续

0
回复
tanik
LV.5
5
2017-03-07 22:44
@一木01
if(key_step>6)//第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;t}(6)小结:以上是我常用的编程结构。后续我做的所有项目基本上是这样一种编程结构。这一节技术上要特别重视按键扫描。有按键触发之后,不要马上响应,要延时一段时间去抖动,此处本人设计非常巧妙,很多人仅仅知道按键延时的时候要保证还能去处理别的程序,这样是还不够的,在延时去抖动的时候,还必须要监控延时这段时间里,按键IO输入口是否会由于受到某种干扰突然由低变成高,如果一旦变成高,那么延时计数器delay_cnt1必须重新清零,我当年就是因为这样处理,把卖给富士康100台受干扰死机的设备修好了,老板马上给我加薪1000元。  待续
这么好的文章,居然没有人赞?等待后续。
0
回复
ruohan
LV.9
6
2017-03-23 13:17
@tanik
这么好的文章,居然没有人赞?等待后续。

推荐本书看看了

0
回复