Chinaunix首页 | 论坛 | 博客
  • 博客访问: 57133
  • 博文数量: 20
  • 博客积分: 1415
  • 博客等级: 上尉
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-29 01:11
文章分类
文章存档

2011年(1)

2010年(19)

我的朋友

分类: C/C++

2010-11-02 14:04:44


4.4.1  程序流程图

首先来看看整个程序的流程图。对整个程序的流程、功能有了全面掌握之后,我们才能全盘考虑,才能写出好的代码,减少BUG的出现。

main主函数主循环如图4-5所示。

由于本实例中涉及了定时器中断,所以,在此也要设计有关定时器中断的流程图,如图4-6所示。

当进入中断后,首先是保护现场,即保存变量等,并屏蔽中断,防止中断处理过程中,进入另一个中断,从而打乱程序的顺序。之后,将时间累计值加1,表 示又一次时间中断。之后,对定时器参数进行重新设置,包括TMR0溢出值的设置,才能保证下一次中断的准确产生。之后,恢复现场、使能中断,最后退出中 断。

 
图4-5   全局循环流程
 
图4-6   中断处理函数流程

4.4.2  预定义及全局变量

前面说到了多用变量来衔接各个函数,从而分工明确,也易于代码维护。本小节,我们来编写一下预定义,后面的代码就能根据这些预定义来编写不同的分支,调用不同的变量。

我们采用枚举类型来定义菜单,代码如下:

  1. enum my_menu  
  2. {  
  3.     normol_run,  
  4.     clock_hour,  
  5.     clock_min,  
  6.     clock_sec,  
  7.     alarm_hour,  
  8.     alarm_min,  
  9.     alarm_sec  
  10. }; 

上述代码定义了正常运行状态normol_run,用来表示正处于正常的运行状态,没有用户输入;调整时钟小时状态clock_hour用来表示现 在处于用户输入状态,正在更改时钟的小时数值;类似地,还有clock_min和clock_sec。闹钟部分,则有变量alarm_hour、 alarm_min和alarm_sec。

使用同样的方法,我们也可以用枚举类型来定义运行过程中用到的不同变量,代码如下:

  1. enum my_param  
  2. {  
  3.     curr_cl_hour,  
  4.     curr_cl_min,  
  5.     curr_cl_sec,  
  6.     curr_al_hour,  
  7.     curr_al_min,  
  8.     curr_al_sec,  
  9.     temp_cl_hour,  
  10.     temp _cl_min,  
  11.     temp _cl_sec,  
  12.     temp _al_hour,  
  13.     temp _al_min,  
  14.     temp _al_sec  

上述代码,我们定义了当前正常运行状态下的时钟小时curr_cl_hour、时钟分钟curr_cl_min、时钟秒 数 curr_cl_sec、闹钟小时curr_al_hour、闹钟分钟curr_al_min、闹钟秒数curr_al_sec,以及临时状态下,即 用户调整时间/闹钟状态下的时钟小时temp_cl_hour、时钟分钟temp _cl_min、时钟秒数temp _cl_sec、闹钟小时temp_al_hour、闹钟分钟temp _al_min和闹钟秒数temp _al_sec。

下列代码是对键盘的定义,分别表示模式切换、增加、减少和确定:

  1. enum my_keyborad  
  2. {  
  3.     mode,  
  4.     add,  
  5.     subtract,  
  6.     ok  
  7. }

4.4.3  main主函数及初始化

本小节,我们来讲述main主函数如何编写,代码如下:

  1. void main()  
  2. {  
  3.     init();  
  4.     while(1)  
  5.     {  
  6.         count_time();  
  7.         keyboard();  
  8.         key_cmd();  
  9.         lcd_refresh();  
  10.     }  

上面的main函数,调理很清晰,程序开始后,进行初始化,之后就进入一个while大循环,依次进行时间计算、键盘扫描、键盘响应和液晶显示刷新函数。各个函数之间通过变量来衔接。 

接下来,我们来看看初始化函数的编写。我们设计定时器是200次时钟周期溢出。在“硬件电路设计”一节中,我们采用4MHz的外部晶振,而PIC16F877内部的定时器采用晶振频率的1/4。故而定时器中断周期是

  1. void init()  
  2. {  
  3.     OPTION=0b00000111 ;   
  4.     TMR0=56;                        //设置200次时钟周期溢出  
  5.     INTCON =0b10100000 ;        //使能定时器中断  
  6.     TRISD=0;      
  7.     lcd_init();  
  8.     time_count = 0;  
  9.     curr_menu = normol_run;  
  10.     curr_cl_hour = 0;  
  11.     curr_cl_min = 0;  
  12.     curr_cl_sec = 0;  
  13.     curr_al_hour = 0;  
  14.     curr_al_min = 0;  
  15.     curr_al_sec = 0;  
  16.     temp_cl_hour = 0;  
  17.     temp_cl_min = 0;  
  18.     temp_cl_sec = 0;  
  19.     temp_al_hour = 0;  
  20.     temp_al_min = 0;  
  21.     temp_al_sec = 0;  

其中,液晶初始化函数的代码如下:

  1. void lcd_init()  
  2. {  
  3.     delayMs(300);               //最好延时300ms以上  
  4.     lcd_write(0x429,12);            //专用初始化命令  
  5.     lcd_write(0x418,12);            //内部RC振荡器  
  6.     lcd_write(0x401,12);            //开启振荡器  
  7.     lcd_write(0x403,12);            //开启显示器  
  8. }
.4  定时器中断函数

为了保证定时准确,定时中断函数中只累计最小的时间单位,更高的时间单位放在while循环中累计,即count_time()函数(参见4.4.5节),以防妨碍定时中断的准确。

  1. void interrupt ISR(void)  
  2. {  
  3.     ...         //保护现场,屏蔽中断(根据实际工程修改)  
  4.     if(time_out>0)  
  5.         time_out--;  
  6.     time_count++;  
  7.     if((INTCON&0b00000100)!=0)  
  8.     {  
  9.         INTCONINTCON=INTCON&0b11111011;   
  10.         TMR0=0;  
  11.         PORTD++; }  
  12.     }  
  13.     ...         //恢复现场,使能中断(根据实际工程修改)  
  14. }

设计这个函数是为了保证定时的准确,不能由于定时中断函数中运行过多的程序,而耽误了中断时间,导致中断时间不准确。所以,时间的运算、进位及超时的判断都在while循环中进行。我们用count_time函数来表示。

  1. void count_time()  
  2. {  
  3.     if(time_count == 5000)  
  4.     {  
  5.         time_count = 0;  
  6.         curr_cl_sec++;  
  7.     }  
  8.     if(curr_cl_sec == 60)  
  9.     {  
  10.         curr_cl_sec = 0;  
  11.         curr_cl_min++;  
  12.     }  
  13.     if(curr_cl_min == 60)  
  14.     {  
  15.         curr_cl_min = 0;  
  16.         curr_cl_hour++;  
  17.     }  
  18.     if(curr_cl_hour == 24)  
  19.     {  
  20.         curr_cl_sec = 0;  
  21.     }  
  22.     if( (curr_cl_hour == curr_al_hour)  
  23.     && (curr_cl_min == curr_al_min)  
  24.     &&( curr_cl_sec == curr_al_sec)  
  25.     {  
  26.         bell();                         //蜂鸣器响起  
  27.     }  
  28.     if(time_out<=0)  
  29.     {  
  30.         curr_menu = normal_run;  
  31.         time_out = 0;  
  32.     }  
  33.     else if(time_out%800==0)  
  34.     {  
  35.         flag_Flash = ~flag_Flash;       //每0.8秒闪烁一次  
  36.     }  
  37. }
4.6  液晶底层驱动

本小节,我们来看看段式液晶底层驱动的编写,有关LCM0825的指令,请参考数据手册。下列是写数据/命令给LCM0825的函数:

  1. void lcd_write(int dat,int length)  
  2. {  
  3.     int temp,i;  
  4.     nCS;  
  5.     for(i=0;i=length;i++)  
  6.     {  
  7.         temp = dat<<i;  
  8.         nWR;  
  9.         if( length = 12)  
  10.         {  
  11.             if( temp & 0x800 ==0x800)  
  12.                 DATA = 1;  
  13.             else      
  14.                 DATA =0;  
  15.         }  
  16.         if( length = 13)  
  17.         {  
  18.             if( temp & 0x1000 ==0x1000)  
  19.                 DATA = 1;  
  20.             else      
  21.                 DATA =0;  
  22.         }  
  23.         WR;  
  24.         delayMs(100);  
  25.     }  
  26.     CS;  
  27.     WR  
  28.     DATA;  
  29. }
.4.7  液晶显示程序

液晶刷新函数,顾名思义,就是根据状态用来刷新屏幕上的显示。我们可以将其分为4个部分,即刷新前置字符、刷新小时、刷新分钟和刷新秒数。代码如下所示:

  1. void lcd_refresh()  
  2. {  
  3.     display_front();  
  4.     display_hour();  
  5.     display_min();  
  6.     display_sec();  

1. 显示前端提示子函数

前端显示子函数,也就是显示段式液晶上第1位和第2位的函数,用来表示当前是正常运行状态,或是调节时钟状态,或是调节闹钟状态,效果图如图4-7 所示。图的左侧是显示“CL”,表示CLOCK,调节时钟状态;中间是“AL”,表示ALARM,调节闹钟状态;右侧无显示,表示正常运行状态。

 
(点击查看大图)图4-7   前端显示效果图
前端显示的代码如下所示:
  1. void display_front()  
  2. {  
  3.     switch( curr_menu )  
  4.     {  
  5.         case normal_run :  
  6.             display_number(13,1,0);         //第1个数码管不显示任何数字  
  7.             display_number(13,2,0);         //第2个数码管不显示任何数字  
  8.             break;  
  9.         case clock_hour :  
  10.         case clock_min :  
  11.         case clock_sec :  
  12.             display_number(10,1,0);         //第1个数码管显示“A”  
  13.             display_number(12,2,0);         //第2个数码管显示“L”  
  14.             break;  
  15.         case alarm_hour :  
  16.         case alarm_min :  
  17.         case alarm_sec :  
  18.             display_number(11,1,0);         //第1个数码管显示“C”  
  19.             display_number(12,2,0);         //第2个数码管显示“L”  
  20.             break;  
  21.         default: break;  
  22.     }  

2. 显示小时子函数

显示小时子函数,在段式液晶的第3位和第4位上将小时显示出来,显示的效果如图4-8所示。注意,小时显示的第四位要加个小点DOT,以便和分钟区分。

 
图4-8  显示小时效果图
显示小时的代码如下所示:
  1. void display_hour()  
  2. {  
  3.     if(curr_menu == clock_hour)  
  4.     {  
  5.         display_number(curr_cl_hour/10,3,0);          
  6.         display_number(curr_cl_hour%10,4,1);      
  7.     }  
  8.     if(curr_menu == alarm_hour)  
  9.     {  
  10.         display_number(curr_al_hour/10,3,0);      
  11.         display_number(curr_al_hour%10,4,1);      
  12.     }  

3. 显示分钟子函数

相同的道理,分钟的显示是在段式液晶的第5位和第6位上显示的。效果图和小时显示一样,代码如下所示:

  1. void display_min()  
  2. {  
  3.     if(curr_menu == clock_min)  
  4.     {  
  5.         display_number(curr_cl_min/10,5,0);  
  6.         display_number(curr_cl_min%10,6,1);   
  7.     }  
  8.     if(curr_menu == alarm_min)  
  9.     {  
  10.         display_number(curr_al_min/10,5,0);   
  11.         display_number(curr_al_min%10,6,1);   
  12.     }  

4. 显示秒数子函数

相同的道理,秒数的显示是在段式液晶的第7位和第8位上显示的。效果图和小时显示一样,代码如下所示:

  1. void display_sec()  
  2. {  
  3.     if(curr_menu == clock_sec)  
  4.     {  
  5.         display_number(curr_cl_sec/10,7,0);       
  6.         display_number(curr_cl_sec%10,8,1);   
  7.     }  
  8.     if(curr_menu == alarm_sec)  
  9.     {  
  10.         display_number(curr_al_sec/10,7,0);   
  11.         display_number(curr_al_sec%10,8,1);   
  12.     }  

5. 显示数字子函数

我们来讲述前几小节经常调用的显示数字子函数display_number(),代码如下所示:

  1. void display_number(int number, int section, int dot)  
  2. {  
  3.     int temp_dat;  
  4.     if(dot ==1)  
  5.         show_arry[0]=array[2*number]&0x1;  
  6.     temp_dat = 0x1400 + (2*section)<<4 + show_array[0];  
  7.     lcd_write(temp_dat, 13);  
  8.     temp_dat = 0x1400 + (2*section+1)<<4 + show_array[1];  
  9.     lcd_write(temp_dat, 13);  
  10. }

4.4.8  键值读入程序

我们来讲述键盘扫描程序。根据“硬件电路设计”一节的设计,4个按键是连接在PORTB上面的。所以,键值读入程序的代码如下所示:

  1. int keyboard(void)  
  2. {  
  3.         int keyvalue,key;  
  4.         keyvalue = PORTB;  
  5.     keyvaluekeyvalue = keyvalue & 0x0f  
  6.     switch(keyvalue)  
  7.     {  
  8.         case 0x1:  
  9.             key = OK;  
  10.             break;  
  11.         case 0x2:  
  12.             key = subtract;  
  13.             break;  
  14.         case 0x4:  
  15.             key = add;  
  16.             break;  
  17.         case 0x8:  
  18.             key = mode;  
  19.             break;  
  20.         default: break;  
  21.     }  
  22.     return keyvalue;  

4.4.9  键盘响应程序

上一小节,我们讲述了键盘扫描程序,已获取了当前输入的键值,之后,程序就进入键盘响应函数。

1. 键盘响应总分支

我们用switch来分支表示,这样,层次更加分明。代码如下所示:

  1. void key_cmd()  
  2. {  
  3.     switch key:  
  4.     {  
  5.         case mode:  
  6.             mode_fun();  
  7.             time_out = 50000;  
  8.             break;  
  9.         case add:  
  10.             add_fun();  
  11.             time_out = 50000;  
  12.             break;  
  13.             case subtract:  
  14.             subtract_fun();  
  15.             time_out = 50000;  
  16.             break;  
  17.         case ok:  
  18.             ok_fun();  
  19.             time_out = 50000;  
  20.             break;  
  21.         default:break;  
  22.     }  

2. “模式切换”按键响应函数

“模式切换”按键,表示切换模式,即“调节时钟”、“调节闹钟”和“正常运行”互相切换。代码如下所示:

  1. void mode_fun()  
  2. {  
  3.     if(curr_menu<alarm_sec)  
  4.         curr_menu++;  
  5.     if(curr_menu == alarm_sec)  
  6.         curr_menu = normol_run;  

3. “增加”按键响应函数

“增加”按键,表示增加输入值。代码如下所示:

  1. void add_fun()  
  2. {  
  3.     switch( curr_menu )  
  4.     {  
  5.         case clok_hour:  
  6.             temp_cl_hour++;  
  7.             break;  
  8.         case clok_min:  
  9.             temp_cl_min++;  
  10.             break;  
  11.         case clok_sec:  
  12.             temp_cl_sec++;  
  13.             break;  
  14.         case alarm_hour:  
  15.             temp_al_hour++;  
  16.             break;  
  17.         case alarm_min:  
  18.             temp_al_min++;  
  19.             break;  
  20.         case alarm_sec:  
  21.             temp_al_sec++;  
  22.             break;  
  23.     }  

4. “减少”按键响应函数

“减少”按键,表示减少输入值。代码如下所示:

  1. void subtract_fun()  
  2. {  
  3.     switch( curr_menu )  
  4.     {  
  5.         case clok_hour:  
  6.             temp_cl_hour--;  
  7.             break;  
  8.         case clok_min:  
  9.             temp_cl_min--;  
  10.             break;  
  11.         case clok_sec:  
  12.             temp_cl_sec--;  
  13.             break;  
  14.         case alarm_hour:  
  15.             temp_al_hour--;  
  16.             break;  
  17.         case alarm_min:  
  18.             temp_al_min--;  
  19.             break;  
  20.         case alarm_sec:  
  21.             temp_al_sec--;  
  22.             break;  
  23.     }  

5. “确认”按键响应函数

“确认”按键,表示确认当前输入的值,并保存到最终变量上。代码如下所示:

  1. void enter_fun()  
  2. {  
  3.     if(time_out >= 0)  
  4.     {  
  5.         curr_cl_hour = temp_cl_hour;  
  6.         curr_cl_min = temp_cl_min;  
  7.         curr_cl_sec = temp_cl_sec;  
  8.         curr_al_hour = temp_al_hour;  
  9.         curr_al_min = temp_al_min;  
  10.         curr_al_sec = temp_al_sec;  
  11.     }  
  12. }
.5  本章小结

本章讲述了如何设计一个基于PIC单片机的数字电子闹钟。实现了时钟、闹钟功能,并具备调整时钟/闹钟的功能。读者学习之后,可以根据实际应用的需 要,更换器件,添加更多的软件设置,以达到符合自己需要的功能。比如:采用点阵式液晶,可显示图片;添加多个定时日程管理;采用矩阵键盘,增加输入功能按 键(reset等);软件上,更加细化定时,可以做成码表,精确到百分之一秒;支持二十四进制和十二进制并可切换。



阅读(3748) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-11-02 17:26:59

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com