共用体除非必要,否则我们不推荐使用,枚举的用法比较简单,在本书 19 章的项目实践中有很好的示例,这节课我们先来练习一下结构体的使用。下边这个程序的功能是一个带日期的电子钟,相当于一个简易万年历了,并且加入了按键调时功能。学有余力的同学看到这里,不妨先不看我们提供的代码,自己写写试试。如果能够独立写一个按键可调的万年历程序,单片机可以说基本入门了。如果自己还不能够独立完成这个程序,那么还是老规矩,先抄并且理解,而后自己独立默写出来,并且要边默写边理解。
本例直接忽略了星期这项内容,通过上、下、左、右、回车、ESC 这 6 个按键可以调整时间。这也是一个具有综合练习性质的实例,虽然在功能实现上没有多少难度,但要进行的操作却比较多而且烦琐,同学们可以从中体会到把繁杂的功能实现分解为一步步函数操作的必要性以及方便灵活性。简单说一下这个程序的几个要点,方便大家阅读理解程序。
1. 把 DS1302 的底层操作封装为一个 DS1302.c 文件,对上层应用提供基本的实时时间的操作接口,这个文件也是我们的又一个功能模块了,我们的积累也越来越多了。
2. 定义一个结构体类型 sTime 用来封装日期时间的各个元素,又用该结构体定义了一个时间缓冲区变量 bufTime 来暂存从 DS1302 读出的时间和设置时间时的设定值。需要注意的是在其它文件中要使用这个结构体变量时,必须首先再声明一次 sTime 类型;
3. 定义一个变量 setIndex 来控制当前是否处于设置时间的状态,以及设置时间的哪一位,该值为 0 就表示正常运行,1~12 分别代表可以修改日期时间的 12 个位;
4. 由于这节课的程序功能要进行时间调整,用到了 1602 液晶的光标功能,添加了设置光标的函数,我们要改变哪一位的数字,就在 1602 对应位置上进行光标闪烁,所以 Lcd1602.c在之前文件的基础上添加了两个控制光标的函数;
5. 时间的显示、增减、设置移位等上层功能函数都放在 main.c 中来实现,当按键需要这些函数时则在按键文件中做外部声明,这样做是为了避免一组功能函数分散在不同的文件内而使程序显得凌乱。
DS1302.c 最终向外提供出与具体时钟芯片寄存器位置无关的、由时间结构类型 sTime 作为接口的实时时间的读取和设置函数,如此处理体现了我们前面提到过的层次化编程的思想。应用层可以不关心底层实现细节,底层实现的改变也不会对应用层造成影响,比如说日后你可能需要换一款时钟芯片,而它与 DS1302 的操作和时间寄存器顺序是不同的,那么你需要做的也仅是针对这款新的时钟芯片设计出底层操作函数,最终提供出同样的以 sTime 为接口的操作函数即可,应用层无需做任何的改动。
-
- #include <reg52.h>
- #define LCD1602_DB P0
-
- sbit LCD1602_RS = P1^0;
- sbit LCD1602_RW = P1^1;
- sbit LCD1602_E = P1^5;
-
-
- void LcdWaitReady(){
- unsigned char sta;
-
- LCD1602_DB = 0xFF;
- LCD1602_RS = 0;
- LCD1602_RW = 1;
- do {
- LCD1602_E = 1;
- sta = LCD1602_DB;
- LCD1602_E = 0;
- } while (sta & 0x80);
- }
-
- void LcdWriteCmd(unsigned char cmd){
- LcdWaitReady();
- LCD1602_RS = 0;
- LCD1602_RW = 0;
- LCD1602_DB = cmd;
- LCD1602_E = 1;
- LCD1602_E = 0;
- }
-
- void LcdWriteDat(unsigned char dat){
- LcdWaitReady();
- LCD1602_RS = 1;
- LCD1602_RW = 0;
- LCD1602_DB = dat;
- LCD1602_E = 1;
- LCD1602_E = 0;
- }
-
- void LcdSetCursor(unsigned char x, unsigned char y){
- unsigned char addr;
- if (y == 0){
- addr = 0x00 + x;
- }else{
- addr = 0x40 + x;
- }
- LcdWriteCmd(addr | 0x80);
- }
-
- void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
- LcdSetCursor(x, y);
- while (*str != '\0'){
- LcdWriteDat(*str++);
- }
- }
-
- void LcdOpenCursor(){
- LcdWriteCmd(0x0F);
- }
-
- void LcdCloseCursor(){
- LcdWriteCmd(0x0C);
- }
-
- void InitLcd1602(){
- LcdWriteCmd(0x38);
- LcdWriteCmd(0x0C);
- LcdWriteCmd(0x06);
- LcdWriteCmd(0x01);
- }
为了本例的具体需求,在之前文件的基础上添加两个控制光标效果打开和关闭的函数,虽然函数都很简单,但为了保持程序整体上良好的模块化和层次化,还是应该在液晶驱动文件内以函数的形式提供,而不是由应用层代码直接来调用具体的液晶写命令操作。
/***************************keyboard.c 文件程序源代码****************************/
(此处省略,可参考之前章节的代码)
-
- #include <reg52.h>
-
- struct sTime {
- unsigned int year;
- unsigned char mon;
- unsigned char day;
- unsigned char hour;
- unsigned char min;
- unsigned char sec;
- unsigned char week;
- };
-
- bit flag200ms = 1;
- struct sTime bufTime;
- unsigned char setIndex = 0;
- unsigned char T0RH = 0;
- unsigned char T0RL = 0;
-
- void ConfigTimer0(unsigned int ms);
- void RefreshTimeShow();
- extern void InitDS1302();
- extern void GetRealTime(struct sTime *time);
- extern void SetRealTime(struct sTime *time);
- extern void KeyScan();
- extern void KeyDriver();
- extern void InitLcd1602();
- extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
- extern void LcdSetCursor(unsigned char x, unsigned char y);
- extern void LcdOpenCursor();
- extern void LcdCloseCursor();
-
- void main(){
- unsigned char psec=0xAA;
- EA = 1;
- ConfigTimer0(1);
- InitDS1302();
- InitLcd1602();
-
-
- LcdShowStr(3, 0, "20 - - ");
- LcdShowStr(4, 1, " : : ");
-
- while (1){
- KeyDriver();
- if (flag200ms && (setIndex == 0)){
- flag200ms = 0;
- GetRealTime(&bufTime);
- if (psec != bufTime.sec){
- RefreshTimeShow();
- psec = bufTime.sec;
- }
- }
- }
- }
-
- void ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd){
- unsigned char str[4];
-
- str[0] = (bcd >> 4) + '0';
- str[1] = (bcd&0x0F) + '0';
- str[2] = '\0';
- LcdShowStr(x, y, str);
- }
-
- void RefreshTimeShow(){
- ShowBcdByte(5, 0, bufTime.year);
- ShowBcdByte(8, 0, bufTime.mon);
- ShowBcdByte(11, 0, bufTime.day);
- ShowBcdByte(4, 1, bufTime.hour);
- ShowBcdByte(7, 1, bufTime.min);
- ShowBcdByte(10, 1, bufTime.sec);
- }
-
- void RefreshSetShow(){
- switch (setIndex){
- case 1: LcdSetCursor(5, 0); break;
- case 2: LcdSetCursor(6, 0); break;
- case 3: LcdSetCursor(8, 0); break;
- case 4: LcdSetCursor(9, 0); break;
- case 5: LcdSetCursor(11, 0); break;
- case 6: LcdSetCursor(12, 0); break;
- case 7: LcdSetCursor(4, 1); break;
- case 8: LcdSetCursor(5, 1); break;
- case 9: LcdSetCursor(7, 1); break;
- case 10: LcdSetCursor(8, 1); break;
- case 11: LcdSetCursor(10, 1); break;
- case 12: LcdSetCursor(11, 1); break;
- default: break;
- }
- }
-
- unsigned char IncBcdHigh(unsigned char bcd){
- if ((bcd&0xF0) < 0x90){
- bcd += 0x10;
- }else{
- bcd &= 0x0F;
- }
- return bcd;
- }
-
- unsigned char IncBcdLow(unsigned char bcd){
- if ((bcd&0x0F) < 0x09){
- bcd += 0x01;
- }else{
- bcd &= 0xF0;
- }
- return bcd;
- }
-
- unsigned char DecBcdHigh(unsigned char bcd){
- if ((bcd&0xF0) > 0x00){
- bcd -= 0x10;
- }else{
- bcd |= 0x90;
- }
- return bcd;
- }
-
- unsigned char DecBcdLow(unsigned char bcd){
- if ((bcd&0x0F) > 0x00){
- bcd -= 0x01;
- }else{
- bcd |= 0x09;
- }
- return bcd;
- }
-
- void IncSetTime(){
- switch (setIndex){
- case 1: bufTime.year = IncBcdHigh(bufTime.year); break;
- case 2: bufTime.year = IncBcdLow(bufTime.year); break;
- case 3: bufTime.mon = IncBcdHigh(bufTime.mon); break;
- case 4: bufTime.mon = IncBcdLow(bufTime.mon)
永不止步步
发表于01-30 09:54 浏览65535次