2015-11-19 16:11:42 cjdsn 阅读数 1550
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3414 人正在学习 去看看 朱有鹏
/* 
write by cjdsn
 时间计算
1/22.1184=0.0000005425
1/0.5425=1843317
1843317/40=46083
//我的是阳极显示,0开1关
*/


#include<reg52.h>  
unsigned char code Tab[12]=


{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x7f,0xff}; 
 //数码管显示0~9的段码表
unsigned char int_time;        //记录中断次数
unsigned char second;        //储存秒
unsigned char t,h;
sbit pd=P2^0;
sbit pc=P2^1;
sbit pb=P2^2;
sbit pa=P2^3;
sbit p27=P2^7;
/******************************************************************


*****
函数功能:快速动态扫描延时,延时约0.6毫秒
*******************************************************************


*****/
void delay(void)
{
  unsigned char i;
  for(i=0;i<200;i++)
        ;
}
/******************************************************************


*****
函数功能:显示秒
入口参数:k
出口参数:无
*******************************************************************


*****/
 void DisplaySecond(unsigned char k)
{
   pb=0;        //P2.1引脚输出低电平
P0=Tab[k/10];          //显示十位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
   pb=1;
   pa=0;      //P2.0引脚输出低电平
P0=Tab[k%10];         //显示个位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
pa=1;
pd=0;
P0=Tab[t/10];  //分 十位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
pd=1;
pc=0;
P0=Tab[t%10];  //分 个位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
pc=1;
///////////////////////////////////
pc=0;
P0=Tab[10];
delay();
delay();
delay();
delay();
delay();
delay();
pc=1;
//====================================//
/*
P2=0xff;     //关闭所有数码管
P0=0xff;        //显示个位
*/
//====================================//
}


///////////////////////////////////////////////////////////////////


/////////////////


 void Displayh()
{
   pb=0;        //P2.1引脚输出低电平
P0=Tab[t/10];          //显示十位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
   pb=1;
   pa=0;      //P2.0引脚输出低电平
P0=Tab[t%10];         //显示个位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
pa=1;
pd=0;
P0=Tab[h/10];  //分 十位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
pd=1;
pc=0;
P0=Tab[h%10];  //分 个位
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
pc=1;
///////////////////////////////////
pc=0;
P0=Tab[10];
delay();
delay();
delay();
delay();
delay();
delay();
pc=1;
}


////////////////////////////////  显示小数点
displayms(void)
 {
P2=1;
p27=1;
pa=0;
pb=1;
P0=Tab[10];
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
pa=1;
 }


//==========================================================//


 
void main(void)     
   {
   P2=1;
   p27=1;
     TMOD=0x01;                 //使用定时器T0
    TH0=(-46083)/256;    //将定时器计时时间设定为


46083×1.085微秒
//=50000微秒=50毫秒
TL0=(-46083)%256;
EA=1;                 
      ET0=1;                 
          TR0=1;                   
  int_time=0;     //中断次数初始化
second=0;   //秒初始化
t=59;

while(1)
{       if(h<=0){
      DisplaySecond(second);//调用秒的显示子程序
  }
else Displayh();
}
 }  
//********************************************************
//函数功能:定时器T0的中断服务程序
//*******************************************************
  void interserve(void ) interrupt 1 using 1  
  {
    TR0=0;    //关闭定时器T0
    int_time ++;        //每来一次中断,中断次数int_time自加1


断次数清0  
if(int_time==40)   //够20次中断,即1秒钟进行一次检测结果采样
 {
        int_time=0;    //
second++;    //秒加1
if(h>0){displayms();}
if(second==60)
   {  second =0; //秒等于60就返回0
 t++;}
if(t==60){t=0;h++;
if(h==24)h=0;}
 }      
  TH0=(-46083)/256;   //重新给计数器T0赋初值
TL0=(-46083)%256;
TR0=1;     //启动定时器T0
 }  
2019-05-18 00:18:34 jianfeng_520 阅读数 1573
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3414 人正在学习 去看看 朱有鹏

说明

今天学校布置的课后作业,利用51单片机做一个秒表,本来是做了个8位数码管的秒表,但后来想了下秒表计时一般都不会用到以小时为单位,就把小时的部分以及毫秒的部分去掉了,就留下了分钟跟秒。

每个开发板的电路连接不一定是一样的,我所使用这块开发板是普中科技的,连接数码管的位选是接了一个138译码器的,如果是购买的普中科技的开发板可以直接复制程序就能使用,如果开发板的数码管的位选是接了锁存器的,那么就要相应地更改。

如果不理解138芯片使用方法的,我也要把138芯片的真值表附在下面了,顺便说下它的用法:
74HC138译码器可接受3位二进制加权地址输入(A0, A1和A2),并当使能时,提供8个互斥的低有效输出(Y0至Y7)。74HC138特有3个使能输入端:两个低有效(E1和E2)和一个高有效(E3)。除非E1和E2置低且E3置高,否则74HC138将保持所有输出为高。74HC138正常工作的话,一次只能输出一个低电平,其他全为高电平

要求:

  1. 按下按钮S1,秒表开始计时;
  2. 按下按钮S2,秒表暂停计时(停留在计时的数);
  3. 按下按钮S3,秒表清零;
  4. 计时精确到0.5s

电路图

**单片机****8个数码管****4个独立按键****138芯片**138芯片真值表

程序

程序已经过测试,可以正常使用,注释非常多,比较容易理解。

/****************************************************************************************
								秒表实验
实验现象:按下按钮S1开始计时,最大为59.59。按下按钮S2定时器暂停,按下按钮S3清零

*****************************************************************************************/
#include <reg52.h>
#define uint unsigned int						  //对数据类型进行声明定义
#define uchar unsigned char

sbit LS138_A = P2^2;							  //138芯片A2引脚
sbit LS138_B = P2^3;							  //138芯片A1引脚
sbit LS138_C = P2^4;							  //138芯片A0引脚

uchar DT_s = 0;								  //秒计时
uchar DT_min = 0;									  //分计时

uchar code duan[] = {0x3F,0x06,0x5B,0x4F,		  //共阴数码管无小数点 显示0~9
					 0x66,0x6D,0x7D,0x07,
					 0x7F,0x6F	
					};

uchar code duanpoint[] = {0xbf,0x86,0xdb,0xcf,	   //共阴数码管有小数点 显示0~9
						  0xe6,0xed,0xfd,0x87,
						  0xff,0xef,0xf7,0xfc,
						  0xb9,0xde,0xf9,0xf1
						 };

/************************************************
函数名				:delay1ms
函数功能            :t=1,大约延时1ms
************************************************/
void delay1ms(uint t)
{
	uint i,j;
	for(i=0;i<t;i++)
	{
		for(j=0;j<120;j++);	
	}
}

/************************************************
函数名				:Timer0Init
函数功能			:定时/计数器0中断初始化
*************************************************/
void Timer0Init()
{
	TMOD = 0x01; 		//选择T0定时/计数器,工作在方式1,16位计数器		
	TH0 = 0xFC;			//计数初始值,计数从64536开始,计1000个数,完成一次计数,时间为1ms
	TL0 = 0x18;		
	ET0 = 1;			//定时/计数器0中断允许位
	EA = 1;				//总中断
}

/************************************************
函数名				:S3
函数功能			:按下按钮S3时,秒表归零
*************************************************/
void S3()
{
	DT_s = 0;			//秒归零
	DT_min = 0;			//分归零
	TR0 = 0;			//运行控制位清0,关闭定时器
}

/**********************************************************
函数名				:ScanKey
函数功能			:按键扫描函数,循环扫描哪个键被按下
**********************************************************/
void ScanKey()
{
	uchar Key;		   //临时变量
	Key = P3&0x07;	   //只需要三个按键,分别接的P3^1,P3^2,P3^3,故把P3高五位被屏蔽
	if(Key!=0x07)	   //判断哪个键被按下
	{
		delay1ms(10);	 //消抖10ms
		Key = P3&0x07;	 //再次赋值,避免此时按键状态改变
		if(Key!=0x07)	 //确认按键被按下
		{
			switch(Key)
			{
				case 0x05:				  //S1(P3^2)被按下
							TR0 = 1;	  //定时器0运行控制位为1,启动定时器0
							break;
				case 0x06:				  //S2(P3^1)被按下
							TR0 = 0;	  //定时器0运行控制位为0,关闭定时器0
							break;
				case 0x03:				  //S3(P3^3)被按下
							S3();
							break;
			}
		}
		while(Key!=0x07)		//松手检测
		{
			Key = P3&0x07;
		}
	}
}

/*******************************************************************
函数名				:DigDisplay
函数功能			:数码管动态扫描函数,循环扫描8个数码管显示
********************************************************************/
void DigDisplay(uchar s,uchar min)
{
	LS138_A = 0;			  //秒个位位选
	LS138_B = 0;
	LS138_C = 0;
	P0 = duan[s%10];		  //发送段码,显示秒个位
	delay1ms(5);			  //间隔一段时间扫描
	LS138_A = 1;			  //秒十位位选
	LS138_B = 0;
	LS138_C = 0;
	P0 = duan[s/10];		  //发送段码显示秒十位
	delay1ms(5);			  //间隔一段时间扫描

	LS138_A = 0;			  //分的个位位选
	LS138_B = 1;
	LS138_C = 0;
	P0 = duanpoint[min%10];		  //发送段码,显示分的个位
	delay1ms(5);			  //间隔一段时间扫描
	LS138_A = 1;			  //分的十位位选
	LS138_B = 1;			  //间隔一段时间扫描
	LS138_C = 0;
	P0 = duan[min/10];		  //发送段码,显示分的十位
	delay1ms(5);			  //间隔一段时间扫描
	P0 = 0x00;				  //消隐
}

/**********************************************************
函数名				:主函数
函数功能			:无
**********************************************************/
void main(void)
{
	P0 = 0x00;		  //读端口前写1
	P3 = 0xFF;		  //读端口前写1
	Timer0Init();	  //定时器中断初始化函数
	while(1)
	{
		DigDisplay(DT_s,DT_min);	   //数码管显示函数
		ScanKey();							   //按键扫描函数
	}
}

/**********************************************************
函数名				:Timer0
函数功能			:定时器计数
**********************************************************/
void Timer0() interrupt 1
{
	static uint count_s;	
	static uint count_min;		
	TH0 = 0xFC;				//计数值初始化,从64536开始计数,计满时为65536,溢出时即为 1ms
	TL0 = 0x18;
	count_s++;				 //秒计数
	count_min++;				 //分计数
	if(count_s==1000)			 //计数到1s时,秒计数器开始工作
	{
		count_s = 0;		 //秒计数清零
		DT_s++;			 //显示秒计数值自增
		if(DT_s>59)		 //秒数最大为59
		{
			DT_s = 0;
		}
	}
	if(count_min==60000)		  //计数到60000ms时,秒计数器开始工作
	{
		count_min = 0;		  //分计数清零
		DT_min++;				  //显示分计数值自增
		if(DT_min>59)			  //分数最大为59
		{
			DT_min = 0;
		}
	}
}
2018-01-11 09:00:12 qq_27332285 阅读数 7464
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3414 人正在学习 去看看 朱有鹏

设计要求:

最多可记录十组数据

可删除其中多组数据

可上下查阅所记录的数据

数据通过lcd1602显示

计时精度到0.01s

由于并没有使用24c02,导致程序去数据较大,仿真可能不行。实际验证通过

程序界面如图:


仿真界面如图:


   
  如果编译出错,将keil按如图参数设置:



以下为程序代码:

包括两个文件,主程序里面是:

#include<1602.h>
#include<reg52.h>




#ifndef uchar
#define uchar unsigned char
#endif


#ifndef uint 
#define uint unsigned int
#endif


/*按键定义*/
/*开始键,计数键,停止计数键或者清零键,上下查阅键,删除数据键*/


sbit key_start=P1^0;
sbit key_count=P1^1;
sbit key_stop=P1^2;
sbit key_up=P1^3;
sbit key_down=P1^4;




/*变量定义*/
int count=0;//计数器


uint i,j,state,k=0,countnum,m,delete;


/*设置标志位*/


char flag;
char maxflag;
char clearflag;
char turnflag; //
char upflag;  //为1时可以继续上翻
char minflag=0; //为0时可以继续下翻
char downflag; 


/*定义数组 */
 unsigned char str[] = "time:";
 unsigned char str2[]= "num:";
 unsigned char str3[]="...press start..";
 unsigned char str4[]="....counting....";
 unsigned char str5[]=".. press count..";
 unsigned char str6[]="press up or down";
 unsigned char str7[]="..overcounting..";




 int number[10]=0;
 int num2[5]=0;


void keyscan();
void init();




void main()
{
   
    InitLcd1602();
    init();
/*以下为调试程序段*/
   LcdShowStr(0,0,str3);
  //  LcdShowStr(0,1,str2);
//  LcdWriteCom(0x91);
//  LcdWriteData('0'+1);
//  LcdWriteCom(0x90);
//   LcdWriteData('0'+1);
/*以上为调试程序段*/




    while(1)
{ //LcdShowStr(0,1,str4); //测试
    keyscan();
Lcd1602_Delay1ms(50);
}


}
void init()
{
IT0=0;
EX0=0;
TMOD=0x01;
TH0=(65536-10000)/256; 
TL0=(65536-10000)%256;
EA=0; //开总中断


// key_count=0;
// key_stop=0;
// key_up=0;
// key_down=0;
// key_delete=0;


flag=0; //标志位为0时代表未计时状态,为1时代表为计时状态
i=0;
maxflag=0;//定时标志位,为1时代表定时已满
clearflag=0;//清除标志位
turnflag=0;//为1时可以上下翻页显示
downflag=0;
upflag=1;
}
void keyscan()
{
if((key_start==0)&&(flag==0)) //防抖处理
{
Lcd1602_Delay1ms(10); //防抖处理
if((key_start==0)&&(flag==0))//防抖处理
{
while((!(key_start==0)&&(flag==0)));//防抖处理
InitLcd1602();
EA=1;//检测到开始按键按下后,启动定时器
ET0=1; //计时器0开启
 TR0=1; //计时器打开
flag=1;//设置标志位为计时状态
LcdShowStr(0,0,str4);
LcdShowStr(0,1,str5);
}
}
if((key_count==0)&&(flag==1)&&(maxflag==0))
{
       Lcd1602_Delay1ms(50);
if((key_count==0)&&(flag==1)&&(maxflag==0))
{

while(!((key_count==0)&&(flag==1)&&(maxflag==0)));

    InitLcd1602();
LcdShowStr(0,0,str);
    LcdShowStr(0,1,str2);
number[i]=count;
LcdWriteCom(0xC7);
        LcdWriteData('0'+i+1); 
LcdWriteCom(0xC6);
        LcdWriteData('0');
if(i==9)
{
LcdWriteCom(0xC7);
LcdWriteData('0'); 
LcdWriteCom(0xC6);
LcdWriteData('0'+1);
}




num2[0]=number[i]/10000;
num2[1]=number[i]/1000%10;
num2[2]=number[i]/100%10;
num2[3]=number[i]/10%10;
num2[4]=number[i]%10;

LcdWriteCom(0x86);
    LcdWriteData('0'+num2[0]);


LcdWriteCom(0x87);
    LcdWriteData('0'+num2[1]);


LcdWriteCom(0x88);
    LcdWriteData('0'+num2[2]);


LcdWriteCom(0x89);
    LcdWriteData('.');


 LcdWriteCom(0x8A);
    LcdWriteData('0'+num2[3]);


LcdWriteCom(0x8B);
    LcdWriteData('0'+num2[4]);


LcdWriteCom(0x8D);
    LcdWriteData('S');


i++;
state=i;


if(i==10)
{
maxflag=1;

}
}
}


if(key_stop==0)
{
Lcd1602_Delay1ms(50);

 if(key_stop==0)
{
while(!(key_stop==0));
if(clearflag==0)
{
TR0=0;  //关定时器,查阅时定时就停止
EX0=1;  //开外部中断,保证只有在上下翻阅时才能触发外部中断
count=0;//计数清零
clearflag=1;//清除标志位打开,也就是说再按一下这个按键所有制清零
turnflag=1;
minflag=0;

}
else if(clearflag==1)//此块执行清除任务
{

InitLcd1602();
number[10]=0;
clearflag=0;
LcdShowStr(0,0,str3);
turnflag=0;
maxflag=0;
flag=0;
i=0;//将数组计数给清零
}
}
}





if(key_up==0)
{
Lcd1602_Delay1ms(50);
     if(key_up==0)
{
while(!(key_up==0));
if(upflag==0)
{
InitLcd1602();
LcdShowStr(0,0,str);
LcdShowStr(0,1,str2);
 minflag=0;//只要上翻过后都可以下翻
 state++;
if(state==10)
{
LcdWriteCom(0xC7);
LcdWriteData('0'); 
LcdWriteCom(0xC6);
LcdWriteData('0'+1);
}
else
{
LcdWriteCom(0xC7);
LcdWriteData('0'+state); 
LcdWriteCom(0xC6);
LcdWriteData('0');
}
m=state-1;
delete=m;
 
 /*显示时间*/
num2[0]=number[m]/10000;
num2[1]=number[m]/1000%10;
num2[2]=number[m]/100%10;
num2[3]=number[m]/10%10;
num2[4]=number[m]%10;

LcdWriteCom(0x86);
LcdWriteData('0'+num2[0]);

LcdWriteCom(0x87);
LcdWriteData('0'+num2[1]);

LcdWriteCom(0x88);
LcdWriteData('0'+num2[2]);

LcdWriteCom(0x89);
LcdWriteData('.');

LcdWriteCom(0x8A);
LcdWriteData('0'+num2[3]);

LcdWriteCom(0x8B);
LcdWriteData('0'+num2[4]);

LcdWriteCom(0x8D);
LcdWriteData('S');
if(state==10)
{
upflag=1;
}
}
 
 
}

}


if(key_down==0)
{
 Lcd1602_Delay1ms(50);
 if(key_down==0)
{
while(!(key_down==0));
if((turnflag==1)&&(minflag==0))
{
InitLcd1602();
LcdShowStr(0,0,str);
LcdShowStr(0,1,str2);
/*显示组号*/
state--;
LcdWriteCom(0xC7);
LcdWriteData('0'+state); 
LcdWriteCom(0xC6);
LcdWriteData('0');
k=state-1;
delete=k;


/*显示时间*/
num2[0]=number[k]/10000;
num2[1]=number[k]/1000%10;
num2[2]=number[k]/100%10;
num2[3]=number[k]/10%10;
num2[4]=number[k]%10;

LcdWriteCom(0x86);
LcdWriteData('0'+num2[0]);

LcdWriteCom(0x87);
LcdWriteData('0'+num2[1]);

LcdWriteCom(0x88);
LcdWriteData('0'+num2[2]);

LcdWriteCom(0x89);
LcdWriteData('.');

LcdWriteCom(0x8A);
LcdWriteData('0'+num2[3]);

LcdWriteCom(0x8B);
LcdWriteData('0'+num2[4]);

LcdWriteCom(0x8D);
LcdWriteData('S');

if(state==1)
{
minflag=1;

}
upflag=0;

}
}
 }




}


void timer0() interrupt 1
{
TH0=(65536-10000)/256; 
TL0=(65536-10000)%256;
count++;
if(count==6000)
{
count=0; 
ET0=0; //计时器0关闭
TR0=0; //计时器关闭
turnflag=1;
EX0=1;
InitLcd1602();
LcdShowStr(0,0,str6);
LcdShowStr(0,1,str7);
}
}


void  counter0(void) interrupt 0  using 1
{
  number[delete]=0;
LcdWriteCom(0x86);
LcdWriteData('0');

LcdWriteCom(0x87);
LcdWriteData('0');

LcdWriteCom(0x88);
LcdWriteData('0');


LcdWriteCom(0x89);
LcdWriteData('.');


LcdWriteCom(0x8A);
LcdWriteData('0');


LcdWriteCom(0x8B);
LcdWriteData('0');


LcdWriteCom(0x8D);
LcdWriteData('S');

}


然后是lcd的驱动程序:



#include<1602.h>








void Read_Busy()           //忙检测函数,判断bit7是0,允许执行;1禁止
{
    unsigned char sta;      //
    LCD1602_DB = 0xff;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do
    {
        LCD1602_EN = 1;
        sta = LCD1602_DB;
        LCD1602_EN = 0;    //使能,用完就拉低,释放总线
    }while(sta & 0x80);
}


void Lcd1602_Write_Cmd(unsigned char cmd)     //写命令
{
    Read_Busy();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_EN = 1;
    LCD1602_EN = 0;    
}


void Lcd1602_Write_Data(unsigned char dat)   //写数据
{
      Read_Busy();
      LCD1602_RS = 1;
      LCD1602_RW = 0;
      LCD1602_DB = dat;
      LCD1602_EN = 1;
      LCD1602_EN = 0;
}


void LcdSetCursor(unsigned char x,unsigned char y)  //坐标显示
{
    unsigned char addr;
    if(y == 0)
        addr = 0x00 + x;
    else
        addr = 0x40 + x;
    
    Lcd1602_Write_Cmd(addr|0x80);
}




void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str)     //显示字符串
{
    LcdSetCursor(x,y);      //当前字符的坐标
    while(*str != '\0')
    {
        Lcd1602_Write_Data(*str++);
    }
}


void InitLcd1602()              //1602初始化
{
    Lcd1602_Write_Cmd(0x38);    //打开,5*8,8位数据
    Lcd1602_Write_Cmd(0x0c);
    Lcd1602_Write_Cmd(0x06);
    Lcd1602_Write_Cmd(0x01);    //清屏   
}


  
 void Lcd1602_Delay1ms(uint c)   //误差 0us
{
    uchar a,b;
for (; c>0; c--)
{
for (b=199;b>0;b--)
{
  for(a=1;a>0;a--);
}      
}
   
}
/*******************************************************************************
* 函 数 名         : LcdWriteCom
* 函数功能   : 向LCD写入一个字节的命令
* 输    入         : com
* 输    出         : 无
*******************************************************************************/
#ifndef LCD1602_4PINS //当没有定义这个LCD1602_4PINS时
void LcdWriteCom(uchar com)  //写入命令
{
LCD1602_EN = 0;     //使能
LCD1602_RS = 0;   //选择发送命令
LCD1602_RW = 0;   //选择写入

LCD1602_DB = com;     //放入命令
Lcd1602_Delay1ms(1); //等待数据稳定


LCD1602_EN = 1;          //写入时序
Lcd1602_Delay1ms(5);  //保持时间
LCD1602_EN = 0;
}
#else 
void LcdWriteCom(uchar com)  //写入命令
{
LCD1602_EN = 0; //使能清零
LCD1602_RS = 0; //选择写入命令
LCD1602_RW = 0; //选择写入


LCD1602_DB = com; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改
Lcd1602_Delay1ms(1);


LCD1602_EN = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_EN = 0;


// Lcd1602_Delay1ms(1);
LCD1602_DB = com << 4; //发送低四位
Lcd1602_Delay1ms(1);


LCD1602_EN = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_EN = 0;
}
#endif


/*******************************************************************************
* 函 数 名         : LcdWriteData
* 函数功能   : 向LCD写入一个字节的数据
* 输    入         : dat
* 输    出         : 无
*******************************************************************************/   
#ifndef LCD1602_4PINS   
void LcdWriteData(uchar dat) //写入数据
{
LCD1602_EN = 0; //使能清零
LCD1602_RS = 1; //选择输入数据
LCD1602_RW = 0; //选择写入


LCD1602_DB = dat; //写入数据
Lcd1602_Delay1ms(1);


LCD1602_EN = 1;   //写入时序
Lcd1602_Delay1ms(5);   //保持时间
LCD1602_EN = 0;
}
#else
void LcdWriteData(uchar dat) //写入数据
{
LCD1602_EN = 0;  //使能清零
LCD1602_RS = 1;  //选择写入数据
LCD1602_RW = 0;  //选择写入


LCD1602_DB = dat; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改
Lcd1602_Delay1ms(1);


LCD1602_EN = 1;  //写入时序
Lcd1602_Delay1ms(5);
LCD1602_EN = 0;


LCD1602_DB = dat << 4; //写入低四位
Lcd1602_Delay1ms(1);


LCD1602_EN = 1;  //写入时序
Lcd1602_Delay1ms(5);
LCD1602_EN = 0;
}
#endif

还有一个1602.h文件:

#include<reg52.h>


#ifndef uchar
#define uchar unsigned char
#endif


#ifndef uint 
#define uint unsigned int
#endif


#define LCD1602_DB  P0    




sbit LCD1602_RS = P2^6;
sbit LCD1602_RW = P2^5;
sbit LCD1602_EN = P2^7;




void InitLcd1602(); //初始化lcd1602


void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str);//写入一个字符串


void LcdWriteData(uchar dat1);//写入一个单个字
void LcdWriteCom(uchar com);//写入地址
void Lcd1602_Delay1ms(uint c);
void InitLcd1602();



实际效果如图:

2011-11-27 18:41:42 jinmmd 阅读数 6075
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3414 人正在学习 去看看 朱有鹏

下午自学了一下单片机的前几章的知识,理解了数码管的显示原理以及静态显示和动态扫描的编程方法。其中,比较重要的几个概念有:数码管公共端类型(共阴极、共阳极)、位选(决定多位数码管中哪一位显示)、段选(决定数码管显示的数字),另外还有锁存器(教材P36,可通过控制锁存端来改变数据输出的状态)。

上周单片机实验课的内容是编一个秒表,在4位数码管中分别显示分和秒还有分秒,即最多可显示“9:59.9”。这里我用两个键实现了秒表的启动/暂停和清零功能,代码如下:

#include <reg51.h>
#define uint unsigned int
#define uchar unsigned char
sbit S1 = P1^3;
sbit S2 = P1^1;
sbit S3 = P1^2;
sbit S4 = P1^0;
sbit beep = P2^7;
sbit a = P0^0;
sbit b = P0^1;
sbit c = P0^2;
sbit d = P0^3;
sbit e = P0^4;
sbit f = P0^5;
sbit g = P0^6;
sbit p = P0^7;
sbit key1 = P1^4;
sbit key2 = P1^5;
sbit key3 = P3^6;
sbit key4 = P3^7;
uchar num,kms,sec,min;
uchar code N[10] = {0xc0, 0xf9 ,0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; //0, 1, 2, 3, ...8, 9
uchar code Z[10] = {0x40, 0x79 ,0x24, 0x30, 0x19, 0x12, 0x02, 0x78, 0x00, 0x10}; //0., 1., 2., ... 8., 9.

void delayms(uint xms)
{
	uint i,j;
	for(i = xms; i > 0; i--)
		for(j = 110; j > 0; j--);
}

void init()
{
	TMOD = 0x01;
	TH0 = 0x3c;
	TL0 = 0xb0;
	S3 = 1;
	S1 = S2 = S4 = 0;
	P0 = N[0];
	delayms(5);

	S3 = 0;
	S1 = S2 = S4 = 1;
	P0 = Z[0];
	delayms(5);

	EA = 1;
	TR0 = 0;
	ET0 = 1;	
	kms = sec = min = 0;
}

void display()
{
	uchar shi, ge;
	if(kms >= 0)
	{
		S4 = 0;
		S1 = S2 = S3 = 1;
		P0 = N[kms];
	}
	delayms(5);
	if(sec >= 0)
	{
		shi = sec/10;
		ge = sec%10;

		S3 = 0;
		S1 = S2 = S4 = 1;
		P0 = Z[ge];
		delayms(5);

		S2 = 0;
		S1 = S3 = S4 = 1;
		P0 = N[shi];
	}
	delayms(5);
	if(min >= 0)
	{
		S1 = 0;
		S2 = S3 = S4 = 1;
		P0 = N[min];
	}
	delayms(5);
}

void keyscan()
{
	if(key1 == 0)
	{
		delayms(10);
		if(key1 == 0)
		{
			while(!key1);
			TR0 = ~TR0;
		}
	}
	if(key2 == 0)
	{
		delayms(10);
		if(key2 == 0)
		{
			min = sec = kms = 0;
			while(!key1);
		}
	}
}

main()
{
	init();
	while(1)
	{
		keyscan();
		display();		
	}
}

void T0_time() interrupt 1
{
	TH0 = 0x3c;
	TL0 = 0xb0;
		num++;
	if(num == 2)
	{
		num = 0;
		kms++;
		if(kms == 10)
		{
			kms = 0;
			sec++;
			if(sec == 60)
			{
				sec = 0;
				min++;
				if(min == 10)
				{
					TR0 = 0;
					min = 9;
					sec = 59;
					kms = 9;
				}
			}
		}
	}
} 

程序通过“Keil uVision4”调试,大家也可以在板子上运行一下试试。

注意:需要根据板子的端口定义相关变量,不要不分青红皂白就直接使用我的程序哈。

最后上个图,第一次做单片机,希望以后还有时间可以做更深入的更好玩的东西。


2019-02-21 14:24:32 weixin_44488826 阅读数 129
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3414 人正在学习 去看看 朱有鹏

不同数据类型间的相互转换

在 C 语言中,不同数据类型之间是可以混合运算的。当表达式中的数据类型不一致时,首先转换为同一种类型,然后再进行计算。C 语言有两种方法实现类型转换,一是自动类型转换,另外一种是强制类型转换。这块内容是比较繁杂的,因此我们根据常用的编程应用来讲部分相关内容。

当不同数据类型之间混合运算的时候,不同类型的数据首先会转换为同一类型,转换的主要原则是:短字节的数据向长字节数据转换。比如:

unsigned char a;
unsigned int b;
unsigned int c;
c = a *b;

在运算的过程中,程序会自动全部按照 unsigned int 型来计算。比如 a=10,b=200,c 的结果就是 2000。那当 a=100,b=700,那 c 是 70000 吗?新手最容易犯这种错误,大家要注意每个变量类型的取值范围,c 的数据类型是 unsigned int 型,取值范围是 0~65535,而 70000超过 65535 了,其结果会溢出,最终 c 的结果是(70000 - 65536) = 4464。

那要想让 c 正常获得 70000 这个结果,需要把 c 定义成一个 unsigned long 型。我们如果写成:

unsigned char a=100;
unsigned int b=700;
unsigned long c=0;
c = a*b;

有做过实验的同学,会发现这个 c 的结果还是 4464,这个是个什么情况呢?

大家注意,C 语言不同类型运算的时候数值会转换同一类型运算,但是每一步运算都会进行识别判断,不会进行一个总的分析判断。比如我们这段代码中 a 和 b 相乘的时候,是按照 unsigned int 类型运算的,运算的结果也是 unsigned int 类型的 4464,只是最终把 unsigned int类型 4464 赋值给了一个 unsigned long 型的变量而已。我们在运算的时候如何避免这类问题的产生呢?可以采用强制类型转换的方法。

在一个变量前边加上一个数据类型名,并且这个类型名用小括号括起来,就表示把这个变量强制转换成括号里的类型。如 c = (unsigned long)a * b;由于强制类型转换运算符优先级高于*,所以这个地方的运算是先把 a 转换成一个 unsigned long 型的变量,而后与 b 相乘,根据 C 语言的规则 b 会自动转换成一个 unsigned long 型的变量,而后运算完毕结果也是一个unsigned long 型的,最终赋值给了 c。

不同类型变量之间的相互赋值,短字节类型变量向长字节类型变量赋值时,其值保持不变,比如:

unsigned char a=100;
unsigned int b=700;
b=a;

那么最终 b 的值就是 100 了。但是如果我们的程序是

unsigned char a=100;
unsigned int b=700;
a=b;

那么 a 的值仅仅是取了 b的低 8 位,我们首先要把 700 变成一个 16 位的二进制数据,然后取它的低 8 位出来,也就是 188,这就是长字节类型给短字节类型赋值的结果,会从长字节类型的低位开始截取刚好等于短字节类型长度的位,然后赋给短字节类型。

在 51 单片机里边,有一种特殊情况,就是 bit 类型的变量,这个 bit 类型的强制类型转换,是不符合上边讲的这个原则的,比如:

bit a=0;
unsigned char b;
a=(bit)b;

这个地方要特别注意,使用 bit 做强制类型转换,不是取 b 的最低位,而是它会判断 b 这个变量是 0 还是非 0的值,如果 b 是 0,那么 a 的结果就是 0,如果 b 是任意非 0 的其它值,那么 a 的结果都是 1。

定时时间精准性调整

在 6.5.2 章节有一个数码管秒表显示程序,那个程序是 1 秒数码管加 1,但是细心的同学做了实验后,经过长时间运行会发现,和我们实际的时间有了较大误差了,那如何去调整这种误差呢?要解决问题,先找到问题是什么原因造成的。

先对我们前面讲过的中断内容做一个较深层次的补充。还是讲解中断的那个场景,当我们在看电视的时候,突然发生了水开的中断,我们必须去提水的时候,第一,我们从电视跟前跑到厨房需要一定的时间,第二,因为我们看的电视是智能数字电视,因此在去提水之前我们可以使用遥控器将我们的电视进行暂停操作,方便回来后继续从刚才的剧情往下进行。

那么暂停电视,跑到厨房提水,这一点点时间是很短的,在实际生活中可以忽略不计,但是在单片机秒表程序中,误差是会累计的,每 1 秒钟都差了几个微妙,时间一久,造成的累计误差就不可小觑了。

单片机系统里,硬件进入中断需要一定的时间,大概是几个机器周期,还要进行原始数据保护,就是把进中断之前程序运行的一些变量先保存起来,专业术语叫做中断压栈,进入中断后,重新给定时器 TH 和 TL 赋值,也需要几个机器周期,这样下来就会消耗一定的时间,我们得把这些时间补偿回来。

方法一,使用软件 debug 进行补偿。
我们在前边讲过使用 debug 来观察程序运行时间,那我们可以把我们 2 次进入中断的时间间隔观察出来,看看和我们实际定时的时间相差了几个机器周期,然后在进行定时器初值赋值的时候,进行一个调整。我们用的是 11.0592M 的晶振,发现差了几个机器周期,就把定时器初值加上几个机器周期,这样就相当于进行了一个补偿。

方法二,使用累计误差计算出来。
有的时候,除了程序本身存在的误差外,硬件精度也可能会影响到时钟的精度,比如晶振,会随着温度变化出现温漂现象,就是实际值和标称值要差一点。那么我们还可以采取累计误差的方法来提高精度。比如我们可以让时钟运行半个小时或者一个小时,看看最终时间差了几秒,然后算算一共进了多少次定时器中断,把这差的几秒平均分配到每次的定时器中断中,就可以实现时钟的调整。

大家要明白,这个世界上本就没有绝对的精确,我们只能在一定程度上提高精确度,但是永远都不会使误差为零,如果在这个基础上还感觉精度不够的话,不要着急,后边我们会专门讲时钟芯片的,通常时钟芯片计时的精度比单片机的精度要高一些。

字节操作修改位的技巧

这里再介绍个编程小技巧,在编程时,有的情况下需要改变一个字节中的某一位或者几位,但是又不想改变其它位原有的值,该如何操作呢?

比如我们学定时器的时候遇到一个寄存器 TCON,这个寄存器是可以进行位操作的,可以直接写 TR0=1;TR0 是 TCON 的一个位,因为这个寄存器是允许位操作,这样写是没有任何问题的。还有一个寄存器 TMOD,这个寄存器是不支持位操作的,那如果我们要使用 T0的模式 1,我们希望达到的效果是 TMOD 的低 4 位是 0b0001,但如果我们直接写成 TMOD =0x01 的话,实际上已经同时操作到了高 4 位,即属于 T1 的部分,设置成了 0b0000,如果T1 定时器没有用到的话,那我们随便怎么样都行,但是如果程序中既用到了 T0,又用到了T1,那我们设置 T0 的同时已经干扰到了 T1 的模式配置,这是我们不希望看到的结果。

在这种情况下,就可以用我们前边学过的“&”和“|”运算了。对于二进制位操作来说,不管该位原来的值是 0 还是 1,它跟 0 进行&运算,得到的结果都是 0,而跟 1 进行&运算,将保持原来的值不变;不管该位原来的值是 0 还是 1,它跟 1 进行|运算,得到的结果都是 1,而跟 0 进行|运算,将保持原来的值不变。

利用上述这个规律,我们就可以着手解决刚才的问题了。如果我们现在要设置 TMOD 使定时器 0 工作在模式 1 下,又不干扰定时器 1 的配置,我们可以进行这样的操作:TMOD =TMOD & 0xF0; TMOD = TMOD | 0x01;第一步与 0xF0 做&运算后,TMOD 的高 4 位不变,低4 位清零,变成了 0bxxxx0000;然后再进行第二步与 0x01 进行|运算,那么高 7 位均不变,最低位变成 1 了,这样就完成了只将低 4 位的值修改位 0b0001,而高 4 位保持原值不变的任务,即只设置了 T0 而不影响 T1。熟练掌握并灵活运用这个方法,会给你以后的编程带来便利。

另外,在 C 语言中,a &= b;等价于 a = a&b;同理,a |= b;等价于 a = a|b;那么刚才的一段代码就可以写成 TMOD &= 0xF0;TMOD |= 0x01 这样的简写形式。这种写法可以一定程度上简化代码,是 C 语言常用的一种编程风格。

数码管扫描函数算法改进

在学习数码管动态扫描的时候,为了方便大家理解,我们程序写的细致一些,给大家引入了 switch 的用法,随着编程能力与领悟能力的增强,对于 74HC138 这种非常有规律的数字器件,我们在编程上也可以改进一下逻辑算法,让程序变的更简洁。这种逻辑算法,通常不是靠学一下可以全部掌握的,而是通过不断的编写程序以及研究他人程序的过程中一点点积累起来的,从今天开始,大家就要开始积累吧。

前边动态扫描刷新函数我们是这么写的:

P0 = 0xFF;
switch (i){
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
default: break;
}

我们来分析每一个 case 分支,它们的结构是相同的,即改变 ADDR2~0、改变索引 i、取数据写入 P0,只要把 case 后的常量与 ADDR2~0 和 LedBuff 的下标对比,就可以发现它们其实是相等的,那么我们可以直接把常量值(实际上就是 i 在改变前的值)赋值给它们即可,而不必写上 6 遍。还剩下一个 i 的操作,它进行了 5 次相同的++与一次归 0 操作,那么很明显用++和 if 判断就可以替代这些操作。下面就是我们据此改进后的代码:

P0 = 0xFF;
P1 = (P1 & 0xF8) | i;
P0 = LedBuff[i];
if (i < 5){
i++;
}else{
i = 0;
}

大家看一下,P1 = (P1 & 0xF8) | i;这行代码就利用了上面讲到的&和|运算来将 i 的低 3 位直接赋值到 P1 口的低 3 位上,而 P0 的赋值也只需要一行代码,i 的处理也很简单。这样写成的代码是不是要简洁的多,也巧妙的多,而功能与前面的 switch 是一样的,同样可以完美实现动态显示刷新的功能。

秒表程序

做了一个秒表程序给同学们做参考,程序中涉及到的知识点我们都讲过了,包括了定时器、数码管、中断、按键等多个知识点。多知识点同时应用到一个程序中的小综合,因此需要大家完全消化掉。此程序是一个“真正的”并且“实用的”秒表程序,第一它有足够的分辨率,保留到小数点后两位,即每 10ms 计一次数,第二它也足够精确,因为我们补偿了定时器中断延时造成的误差,如果你愿意,它完全可以为用来测量你的百米成绩。这种小综合也是将来做大项目程序的基础,因此还是老规矩,大家边抄边理解,理解透彻后独立写出来就算此关通过。

#include <reg52.h>
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;
unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //数码管显示缓冲区
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char KeySta[4] = { //按键当前状态
1, 1, 1, 1
};
bit StopwatchRunning = 0; //秒表运行标志
bit StopwatchRefresh = 1; //秒表计数刷新标志
unsigned char DecimalPart = 0; //秒表的小数部分
unsigned int IntegerPart = 0; //秒表的整数部分
unsigned char T0RH = 0; //T0 重载值的高字节
unsigned char T0RL = 0; //T0 重载值的低字节
void ConfigTimer0(unsigned int ms);
void StopwatchDisplay();
void KeyDriver();
void main(){
EA = 1; //开总中断
ENLED = 0; //使能选择数码管
ADDR3 = 1;
P2 = 0xFE; //P2.0 置 0,选择第 4 行按键作为独立按键
ConfigTimer0(2); //配置 T0 定时 2ms
while (1){
if (StopwatchRefresh){ //需要刷新秒表示数时调用显示函数
StopwatchRefresh = 0;
StopwatchDisplay();
}
KeyDriver(); //调用按键驱动函数
}
}
/* 配置并启动 T0,ms-T0 定时时间 */
void ConfigTimer0(unsigned int ms){
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 18; //补偿中断响应延时造成的误差
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 为模式 1
TH0 = T0RH; //加载 T0 重载值
TL0 = T0RL;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
}
/* 秒表计数显示函数 */
void StopwatchDisplay(){
signed char i;
unsigned char buf[4]; //数据转换的缓冲区
//小数部分转换到低 2 位
LedBuff[0] = LedChar[DecimalPart%10];
LedBuff[1] = LedChar[DecimalPart/10];
//整数部分转换到高 4 位
buf[0] = IntegerPart%10;
buf[1] = (IntegerPart/10)%10;
buf[2] = (IntegerPart/100)%10;
buf[3] = (IntegerPart/1000)%10;
for (i=3; i>=1; i--){ //整数部分高位的 0 转换为空字符
if (buf[i] == 0){
LedBuff[i+2] = 0xFF;
}else{
break;
}
}
for ( ; i>=0; i--){ //有效数字位转换为显示字符
LedBuff[i+2] = LedChar[buf[i]];
}
LedBuff[2] &= 0x7F; //点亮小数点
}
/* 秒表启停函数 */
void StopwatchAction(){
if (StopwatchRunning){ //已启动则停止
StopwatchRunning = 0;
}else{ //未启动则启动
StopwatchRunning = 1;
}
}
/* 秒表复位函数 */
void StopwatchReset(){
StopwatchRunning = 0; //停止秒表
DecimalPart = 0; //清零计数值
IntegerPart = 0;
StopwatchRefresh = 1; //置刷新标志
}
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver(){
unsigned char i;
static unsigned char backup[4] = {1,1,1,1};
for (i=0; i<4; i++){ //循环检测 4 个按键
if (backup[i] != KeySta[i]){ //检测按键动作
if (backup[i] != 0){ //按键按下时执行动作
if (i == 1){ //Esc 键复位秒表
StopwatchReset();
}else if (i == 2){//回车键启停秒表
StopwatchAction();
}
}
backup[i] = KeySta[i]; //刷新前一次的备份值
}
}
}
/* 按键扫描函数,需在定时中断中调用 */
void KeyScan(){
unsigned char i;
static unsigned char keybuf[4] = { //按键扫描缓冲区
0xFF, 0xFF, 0xFF, 0xFF
};
//按键值移入缓冲区
keybuf[0] = (keybuf[0] << 1) | KEY1;
keybuf[1] = (keybuf[1] << 1) | KEY2;
keybuf[2] = (keybuf[2] << 1) | KEY3;
keybuf[3] = (keybuf[3] << 1) | KEY4;
//消抖后更新按键状态
for (i=0; i<4; i++){
if (keybuf[i] == 0x00){
//连续 8 次扫描值为 0,即 16ms 内都是按下状态时,可认为按键已稳定的按下
KeySta[i] = 0;
}else if (keybuf[i] == 0xFF){
//连续 8 次扫描值为 1,即 16ms 内都是弹起状态时,可认为按键已稳定的弹起
KeySta[i] = 1;
}
}
}
/* 数码管动态扫描刷新函数,需在定时中断中调用 */
void LedScan(){
static unsigned char i = 0; //动态扫描索引
P0 = 0xFF; //关闭所有段选位,显示消隐
P1 = (P1 & 0xF8) | i; //位选索引值赋值到 P1 口低 3 位
P0 = LedBuff[i]; //缓冲区中索引位置的数据送到 P0 口
if (i < 5){ //索引递增循环,遍历整个缓冲区
i++;
}else{
i = 0;
}
}
/* 秒表计数函数,每隔 10ms 调用一次进行秒表计数累加 */
void StopwatchCount(){
if (StopwatchRunning){ //当处于运行状态时递增计数值
DecimalPart++; //小数部分+1
if (DecimalPart >= 100){ //小数部分计到 100 时进位到整数部分
DecimalPart = 0;
IntegerPart++; //整数部分+1
if (IntegerPart >= 10000){ //整数部分计到 10000 时归零
IntegerPart = 0;
}
}
StopwatchRefresh = 1; //设置秒表计数刷新标志
}
}
/* T0 中断服务函数,完成数码管、按键扫描与秒表计数 */
void InterruptTimer0() interrupt 1{
static unsigned char tmr10ms = 0;
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
LedScan(); //数码管扫描显示
KeyScan(); //按键扫描
//定时 10ms 进行一次秒表计数
tmr10ms++;
if (tmr10ms >= 5){
tmr10ms = 0;
StopwatchCount(); //调用秒表计数函数
}
}

关于这个程序有两点值得提一下:首先是定时器配置函数,虽然这样在程序里通过计算得出初值(重载值)增加了些许代码,但它换来的是便利性和编程效率,因为只要你完成这个函数,之后所有需要用定时器定时 x 毫秒的场合,你都可以直接把函数拿过去,用所需要的毫秒数作为实参调用它即可,不需要在用计算器埋头算一通了,是不是很值呢。其次是我们没有使用矩阵按键的程序,而是只用矩阵按键的第 4 行作为独立按键来使用,因为秒表只需要 2 个键就够了,这里是想告诉大家,处理问题要灵活,千万不能墨守成规,能用简单方法解决的问题,就不要选择复杂的方案。


作者:seven-soft
来源:CSDN
原文:https://blog.csdn.net/softn/article/details/51847974
版权声明:本文为博主原创文章,转载请附上博文链接!

51单片机秒表程序

阅读数 247

没有更多推荐了,返回首页