C51单片机实现数字秒表计时(分段计时,LCD1602显示,总计时),误差精度在1s之内

一、设计题目:数字秒表设计

二、课程设计内容及要求

        基本要求:

        1.按键2个,一个用于计时开始/停止,一个用于数字清零

        2.数码管显示,数码管初始显示00-00-00(分-秒-毫秒)

        3.基本功能,按计时开始/停止键,数码管从当前计时数字累加,精度为10ms,计时10分钟误差补超过2秒,再按计时开始/停止键,数码管停止计时,按数字清零键,数码管恢复初始显示,重  新计时

        扩展功能:

        1.显示更换为1602液晶屏显示

        2.实现分段计时和总计时两个模式

此次设计实现了基本功能和拓展功能。

三、明确用户需求,根据设计需求绘制原理图(K4键可删掉):

原理图

四、代码解析

第一步:根据原理图定义引脚信息,及相关控制位、标志位、以及需要用到的延时函数

//定义引脚
sbit LCD_RS = P1^0;   
sbit LCD_RW = P1^1;
sbit LCD_E = P2^5;sbit K1 = P3^6;    //定时开始、暂停键
sbit K2 = P3^7;    //定时清零键
sbit K3 = P3^4;    //显示分段计时键
uint keyCount = 0;    //按键次数标志
uint flag1 = 0;       //计时时的时间是否显示
uint time[] = {0x00,0x00,0x00,0x00,0x00,0x00};    //用来存放计时所产生的时间
uint temp[] = {0x00,0x00,0x00,0x00,0x00,0x00};    //用于计算、并time数组时间的时、分、秒
uchar msg[] = {"     round"};    //提示界面
uchar wname[] = {"welcome"};     //友好界面uchar all_time[4][6];            //存放分段计时所产生的时间uint flag = -1;        //作为向all_time数组保存时间的数组下标
//延时函数delay_nops()
void delay_nops(){_nop_();_nop_();_nop_();_nop_();
}
//延时函数Delay(uint num)
void Delay(uint num)
{while( --num );
}
//延时函数delay1(int ms)
void delay1(int ms)
{unsigned char n;while(ms--){for(n = 0; n<250; n++){_nop_();_nop_();_nop_();_nop_();}}
}

第二步:LCD1602显示部分

//在对LCD1602进行写操作(包括写命令和写数据)的时候(读操作不需要),需要检查“忙”标志位,不“忙”即可写入
int LCD_BUSY(){bit isBusy;LCD_RS = 0;    //设置RS为命令寄存器LCD_RW = 1;    //设置RW为读操作,读busy位LCD_E = 1;     //设置E(使能端)为1,进行读操作delay_nops();    //延时isBusy = (bit)(P0&0X80);    //根据原理图引脚信息及LCD1602文档,busy位对应P1^7口,从P1^7读出端口的状态LCD_E = 0;     //恢复使能标志return  isBusy;    //返回busy位状态信息
}
//LCD1602 写命令操作
void LCD_WCMD(uchar cmd){while(LCD_BUSY());    //对LCD1602进行写操作前,要检查“忙”标志位,不“忙”即可继续写操作LCD_RS = 0;           //设置RS为命令寄存器LCD_RW = 0;           //设置RW为写操作LCD_E = 0;            //设置使能E为低电平,后续给予高电平,即形成一个正脉冲,完成写操作_nop_();              //延时一个空操作时间,保证引脚初始状态正确赋值_nop_();              //延时一个空操作时间,保证引脚初始状态正确赋值P0 = cmd;             //向P0口写入命令,16进制delay_nops();         //延时若干个空操作LCD_E = 1;            //给予E高电平,形成正脉冲,完成写操作delay_nops();         //延时若干个空操作,保证命令正确写入   LCD_E = 0;            //恢复使能E为低电平Delay(10);            //延时
}
//LCD1602 写数据操作,与LCD1602写命令操作类似,但又有不同之处,不同之处在下面代码中标出
void LCD_WDATA(uchar dat){while(LCD_BUSY());    LCD_RS = 1;    //将RS设置为数据寄存器LCD_RW = 0;LCD_E = 0;P0 = dat;      //向P0口写入数据delay_nops();LCD_E = 1;delay_nops();LCD_E = 0;Delay(5);
}
//设置LCD1602 的显示位置,以标准 0x80+RAM 地址方式写入命令,具体LCD1602RAM地址映射图如下
void LCD_POS(uchar pos){LCD_WCMD(pos | 0x80);    //根据下面RAM地址映射图,举例,例如:LCD_WCMD(0x00 | 0x80);即显示在LCD1602的第一行,从第一个单元开始显示、LCD_WCMD(0x40 | 0x80);即显示在LCD1602的第二行,从第一个单元开始显示
}

        LCD1602 RAM地址映射图:

 

//配置完LCD1602引脚和端口信息,以及LCD1602相关函数后,根据LCD1602使用手册进行LCD1602的初始化
void LCD_INIT(){LCD_RW = 0;         //设置LCD1602中RS为命令寄存器delay1(15);         //延时LCD_WCMD(0x01);     //写入命令,清屏,清除LCD的显示内容LCD_WCMD(0x38);     //16*2显示,5*7点阵,8位数据delay1(5);          //延时,保证正确写入,下方延时函数功能皆是如此LCD_WCMD(0x38);     //重复写入,保证正确写入delay1(5);LCD_WCMD(0x38);    delay1(5);LCD_WCMD(0x38);delay1(5);LCD_WCMD(0x0c);     //开显示,不显示光标     delay1(5);LCD_WCMD(0x01);     //清除LCD的显示内容delay1(5);
}
//LCD1602 显示部分
void display(){uchar i;for(i = 0;i<6;i++){temp[i] = time[i] + 0x30;    //这里是把time数组(定时器T0计时产生的时间),进行赋值给temp数组,便于后期进行时、分、秒拆分显示}LCD_POS(0x45);    //设置LCD1602上显示为第二行第6个单元开始,具体请看上面提到的RAM地址映射图LCD_WDATA(temp[5]);    //显示定时器T0产生的 分(十位)LCD_WDATA(temp[4]);    //显示定时器T0产生的 分(个位)LCD_WDATA(0x3a);       //显示“:”LCD_WDATA(temp[3]);    //显示定时器T0产生的 秒(十位)LCD_WDATA(temp[2]);    //显示定时器T0产生的 秒(个位)LCD_WDATA(0x3a);       //显示“:”LCD_WDATA(temp[1]);    //显示定时器T0产生的 0.1sLCD_WDATA(temp[0]);    //显示定时器T0产生的 0.01s}

第三步:主函数部分

int main(){uchar i;TMOD = 0X01;    //选择T0工作方式1TH0 = 0XDC;    //10ms触发一次中断TL0 = 0X00;    //10ms触发一次中断EA = 1;        //开启中断总开关ET0 = 1;        //开启定时中断T0LCD_INIT();    //LCD1602初始化LCD_POS(0X00);    //设置LCD1602 显示位置为第一行第一个单元for(i = 0;i<7;i++){LCD_WDATA(wname[i]);    //友好界面}delay1(100);while(1){if(K1 == 0){    //判断按键K1是否按下,按下则进行计时,或者暂停(根据keyCount判断)keyCount++;    delay1(100);switch(keyCount){case 1:TR0 = 1;    //打开定时器T0,开始LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X31);    //显示这是第几轮计时break;case 2:TR0 = 0;    //关闭定时器T0,暂停flag++;saveTime();    //在暂停的时候进行时间保存,即达到分段计时的效果break;case 3:TR0 = 1;    //打开定时器T0,开始LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X32);break;case 4:TR0 = 0;    //关闭定时器T0,暂停flag++; saveTime();break;case 5:TR0 = 1;    //打开定时器T0,开始LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X33);break;case 6:TR0 = 0;    //关闭定时器T0,暂停flag++;saveTime();break;case 7:TR0 = 1;LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X34);break;case 8:TR0 = 0;flag++;saveTime();break;default:TR0 = 0;flag = -1;    break;}delay1(10);}if(K2==0){    //定时时间清零键TR0=0;keyCount = 0;flag=3;delay1(10);flag=3;delay1(10);flag=3;delay1(10);for(i=0;i<6;i++){    //对在中断处理函数中保存在time中的时间进行清零time[i] = 0;}}if(K3==0){    //显示分段计时,所存储的时间段TR0 = 0;flag1 = 1;display1(flag);    //显示分段时间段flag1 = 0;    //显示完自动隐式退出delay1(10);     }  if(flag1==0){      // 当flag1==0,即自动退出“显示分段计时”后,显示计时页面display();    //显示计时页面} }
}

        定时中断处理函数:

//定时器T0的中断处理函数,time数组用来保存定时器T0计时所产生的时间void timer0() interrupt 1{TH0 = 0XDC;    //重新装入初值TL0 = 0X00;time[0]++;   //保存0.01sif(time[0]==10){  time[1]++;  //保存0.1stime[0] = 0;}if(time[1]==10){  time[2]++;   //保存1stime[1] = 0;}if(time[2]==10){  time[3]++;  //保存10stime[2] = 0;}if(time[3]==6){   time[4]++;  //保存1mintime[3] = 0;}if(time[4]==10){  time[5]++;   //保存10mintime[4] = 0;}if(time[5]==6){time[5] = 0;}
}

      LCD1602 显示时间函数(循环显示):

//LCD1602 显示函数 时分秒函数
void display1(uint m){uchar i;uint t = 0x31;for(i = 0;i<=m;i++){LCD_WCMD(0X01);LCD_POS(0X00);LCD_WDATA(0X3C);LCD_WDATA(t++);LCD_WDATA(0X3E);LCD_POS(0x45);LCD_WDATA(all_time[i][5] + 0x30);LCD_WDATA(all_time[i][4] + 0x30);LCD_WDATA(0x3a);LCD_WDATA(all_time[i][3] + 0x30);LCD_WDATA(all_time[i][2] + 0x30);LCD_WDATA(0x3a);LCD_WDATA(all_time[i][1] + 0x30);LCD_WDATA(all_time[i][0] + 0x30);delay1(500);}}

      在main函数中调用的保存分段时间函数:

//保存时间段
void saveTime(){uchar j = 0;if(flag != -1){for(j = 0;j<6;j++){all_time[flag][j] = time[j];    //保存每次暂停所产生的时间段,即分段计时功能}}
}

        在main函数中调用的显示分段时间的函数:

//显示分段计时存储的时间信息,类似前面的display()函数
void display1(uint m){uchar i;uint t = 0x31;for(i = 0;i<=m;i++){LCD_WCMD(0X01);    LCD_POS(0X00);LCD_WDATA(0X3C);LCD_WDATA(t++);LCD_WDATA(0X3E);LCD_POS(0x45);LCD_WDATA(all_time[i][5] + 0x30);    //这里加上0x30是根据单片机系统自带字符库编码方式LCD_WDATA(all_time[i][4] + 0x30);LCD_WDATA(0x3a);LCD_WDATA(all_time[i][3] + 0x30);LCD_WDATA(all_time[i][2] + 0x30);LCD_WDATA(0x3a);LCD_WDATA(all_time[i][1] + 0x30);LCD_WDATA(all_time[i][0] + 0x30);delay1(500);}}

第四步:实物连接

 

完整代码:

#include<reg51.h>
#include<intrins.h>#define uchar unsigned char
#define uint  unsigned intsbit LCD_RS = P1^0;
sbit LCD_RW = P1^1;
sbit LCD_E = P2^5;sbit K1 = P3^6;
sbit K2 = P3^7;
sbit K3 = P3^4;
uint keyCount = 0;
uint flag1 = 0;
uint time[] = {0x00,0x00,0x00,0x00,0x00,0x00};
uint temp[] = {0x00,0x00,0x00,0x00,0x00,0x00};
uchar msg[] = {"     "};
uchar wname[] = {"welcome"};uchar all_time[4][6];uint flag = -1;
void delay_nops(){_nop_();_nop_();_nop_();_nop_();
}
void Delay(uint num)
{while( --num );
}
void delay1(int ms)
{unsigned char n;while(ms--){for(n = 0; n<250; n++){_nop_();_nop_();_nop_();_nop_();}}
}
int LCD_BUSY(){bit isBusy;LCD_RS = 0;LCD_RW = 1;LCD_E = 1;delay_nops();isBusy = (bit)(P0&0X80);LCD_E = 0;return  isBusy;
}
void LCD_WCMD(uchar cmd){while(LCD_BUSY());LCD_RS = 0;LCD_RW = 0;LCD_E = 0;_nop_();_nop_();P0 = cmd;delay_nops();LCD_E = 1;delay_nops();LCD_E = 0;Delay(10);
}
void LCD_WDATA(uchar dat){while(LCD_BUSY());LCD_RS = 1;LCD_RW = 0;LCD_E = 0;P0 = dat;delay_nops();LCD_E = 1;delay_nops();LCD_E = 0;Delay(5);
}void LCD_INIT(){LCD_RW = 0;delay1(15); LCD_WCMD(0x01);LCD_WCMD(0x38);delay1(5);LCD_WCMD(0x38);delay1(5);LCD_WCMD(0x38);delay1(5);LCD_WCMD(0x38);delay1(5);LCD_WCMD(0x0c);delay1(5);LCD_WCMD(0x01);delay1(5);
}
void LCD_POS(uchar pos){LCD_WCMD(pos | 0x80);
}
void display(){uchar i;for(i = 0;i<6;i++){temp[i] = time[i] + 0x30;}LCD_POS(0x45);LCD_WDATA(temp[5]);LCD_WDATA(temp[4]);LCD_WDATA(0x3a);LCD_WDATA(temp[3]);LCD_WDATA(temp[2]);LCD_WDATA(0x3a);LCD_WDATA(temp[1]);LCD_WDATA(temp[0]);}
void saveTime(){uchar j = 0;if(flag != -1){for(j = 0;j<6;j++){all_time[flag][j] = time[j];}}
}
void display1(uint m){uchar i;uint t = 0x31;for(i = 0;i<=m;i++){LCD_WCMD(0X01);LCD_POS(0X00);LCD_WDATA(0X3C);LCD_WDATA(t++);LCD_WDATA(0X3E);LCD_POS(0x45);LCD_WDATA(all_time[i][5] + 0x30);LCD_WDATA(all_time[i][4] + 0x30);LCD_WDATA(0x3a);LCD_WDATA(all_time[i][3] + 0x30);LCD_WDATA(all_time[i][2] + 0x30);LCD_WDATA(0x3a);LCD_WDATA(all_time[i][1] + 0x30);LCD_WDATA(all_time[i][0] + 0x30);delay1(500);}}
int main(){uchar i;TMOD = 0X01;TH0 = 0XDC;TL0 = 0X00;EA = 1;ET0 = 1;LCD_INIT();LCD_POS(0X00);P3 = 0XC0 | P3;LCD_POS(0X00);for(i = 0;i<7;i++){LCD_WDATA(wname[i]);}delay1(100);while(1){if(K1 == 0){keyCount++;delay1(100);switch(keyCount){case 1:TR0 = 1;LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X31);break;case 2:TR0 = 0;flag++;saveTime();break;case 3:TR0 = 1;LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X32);break;case 4:TR0 = 0;flag++; saveTime();break;case 5:TR0 = 1;LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X33);break;case 6:TR0 = 0;flag++;saveTime();break;case 7:TR0 = 1;LCD_POS(0x00);for(i = 0;i<10;i++){LCD_WDATA(msg[i]);}LCD_WDATA(0X34);break;case 8:TR0 = 0;flag++;saveTime();break;default:TR0 = 0;flag = -1;break;}delay1(10);}if(K2==0){TR0=0;keyCount = 0;flag=3;delay1(10);flag=3;delay1(10);flag=3;delay1(10);for(i=0;i<6;i++){time[i] = 0;}}if(K3==0){TR0 = 0;flag1 = 1;display1(flag);flag1 = 0;delay1(10);     }  if(flag1==0){display();} }
}
void timer0() interrupt 1{TH0 = 0XDC;TL0 = 0X00;time[0]++;   //0.01sif(time[0]==10){  time[1]++;  //0.1stime[0] = 0;}if(time[1]==10){  time[2]++;   //1stime[1] = 0;}if(time[2]==10){  time[3]++;  //10stime[2] = 0;}if(time[3]==6){   time[4]++;  //1mintime[3] = 0;}if(time[4]==10){  time[5]++;   //10mintime[4] = 0;}if(time[5]==6){time[5] = 0;}
}

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注