时钟介绍

DS1302

涓流充电时钟芯片,内含有一个实时时钟/日历和 31 字节静态 RAM,通过简单的串行接口与单片机进行通信; 操作 DS1302 的大致过程,就是将各种数据写入 DS1302 的寄存器,以设置它当前的时间的格式。然后使 DS1302 开始运作,DS1302 时钟会按照设置情况运转,再用单片机将其寄存器内的数据读出

  • VCC2:主电源引脚
  • X1、X2:DS1302 外部晶振引脚,通常需外接 32.768K 晶振
  • GND:电源地
  • CE:使能引脚,也是复位引脚(新版本功能变)。
  • I/O:串行数据引脚,数据输出或者输入都从这个引脚
  • SCLK:串行时钟引脚
  • VCC1:备用电源

控制寄存器

控制寄存器用于存放 DS1302 的控制命令字,DS1302 的 RST 引脚回到 高电平 后写入的第一个字节就为控制命令。 它用于对 DS1302 读写过程进行控制,格式如下:

  • 第 7 位永远都是 1
  • 第 6 位,1 表示 RAM,寻址内部存储器地址;0 表示 CK,寻址内部寄存器
  • 第 5 到第 1 位,为 RAM 或者寄存器的地址;
  • 最低位,高电平表示 RD,即下一步操作将要“读”;低电平表示 W,即下一步操作将要“写”

日历/时钟寄存器

DS1302 共有 12 个寄存器,其中有 7 个与日历、时钟相关,存放的数据为 BCD 码形式

  • 秒寄存器

低四位 为秒的个位,高的次三位 为秒的十位。 最高位 CH 为 DS1302 的运行标志,当 CH=0 时,DS1302 内部时钟运行,反之 CH=1 时停止;

  • 小时寄存器

时寄存器。 最高位为 12/24 小时的格式选择位,该位为 1 时表示 12 小时格式。当设置为 12 小时显示格式时, 第 5 位 的高电平表示下午(PM);而当设置为 24 小时格式时,第 5 位位具体的时间数据。

  • 写保护寄存器

当该寄存器 最高位 WP 为 1 时,DS1302 只读不写,所以要在往 DS1302 写数据之前确保 WP 为 0

  • 慢充电寄存器(涓细电流充电)寄存器

我们知道,当 DS1302 掉电时,可以马上调用外部电源保护时间数据。该寄存器就是 配置备用电源的充电选项的。其中 高四位(4 个 TCS)只有在 1010 的情况下才能使用充电选项; 低四位 的情况与 DS1302 内部电路有关。

BCD 码是什么呢?

BCD 码是通过 4 位二进制码来表示 1 位十进制中的 0~9 这 10 个数码

DS1302 的读写时序

在控制指令字输入后的下一个 SCLK 时钟的上升沿时, 数据被写入 DS1302,数据输入从低位(位 0)开始。同样,在紧跟 8 位的控制指令字后的下一个 SCLK 脉冲的下降沿 读出 DS1302 的数据, 读出数据时从低位 0 位到高位 7

  • CE(RST)

复位时序,即在 RST 引脚产生一个正脉冲, 在整个读写器件,RST 要保持高电平,一次字节读写完毕之后,要注意把 RST 返回低电平准备下次读写周期;

  • 单字节读时序

    注意读之前还是要先对寄存器写命令, 从最低位开始写;可以看到,写数据是在 SCLK 的上升沿实现,而读数据在 SCLK 的下降沿实现。所以,在单字节读时序中,写命令的第八个上升沿结束后紧接着的第八个下降沿就将要读寄存器的第一位数据读到数据线上了!这个就是 DS1302 操作中最特别的地方。 当然读出来的数据也是最低位开始

读操作有两处需要特别注意的地方。第一,DS1302 的时序图上的箭头都是针对 DS1302来说的,因此读操作的时候,先写第一个字节指令,上升沿的时候 DS1302 来锁存数据,下降沿我们用单片机发送数据。到了第二个字数据,由于我们这个时序过程相当于CPOL=0/CPHA=0,前沿发送数据,后沿读取数据,第二个字节是 DS1302 下降沿输出数据,我们的单片机上升沿来读取,因此箭头从 DS1302 角度来说,出现在了下降沿

第二个需要注意的地方就是,我们的单片机没有标准的 SPI 接口,和 I2C 一样需要用 IO口来模拟通信过程。在读 DS1302 的时候,理论上 SPI 是上升沿读取,但是程序是用 IO 口模拟的,所以数据的读取和时钟沿的变化不可能同时了,必然就有一个先后顺序。通过实验发现,如果先读取 IO 线上的数据,再拉高 SCLK 产生上升沿,那么读到的数据一定是正确的,而颠倒顺序后数据就有可能出错
  • 单字节写时序

两个字节的数据配合 16 个上升沿将数据写入即可。

程序注意事项

  • 要记得在操作 DS1302 之前 关闭写保护
  • 注意用 延时 来降低单片机的速度以配合器件时序
  • DS1302 读出来的数据是 BCD 码形式,要转换成我们习惯的 10 进制
  • 读取字节之前,将 IO 设置为输入口,读取完之后,要将其改回输出口
  • 在写程序的时候,建议实现 开辟数组(内存空间)来集中放置 DS1302 的一系列数据

显示时分秒例1

main.c

# include "DS1302.h"
# include "smg.h"

void main()
{
	u8 time_buf[8];
	 ds1302_init();
	 while(1)
	 {
		 ds1302_read_time();
		 //时
		 time_buf[0]=gsmg_code[gDS1302_TIME[2]/16];
		 time_buf[1]=gsmg_code[gDS1302_TIME[2]%16];
		 time_buf[2]=0x40;
		 //分
		 time_buf[3]=gsmg_code[gDS1302_TIME[1]/16];
		 time_buf[4]=gsmg_code[gDS1302_TIME[1]%16];
		 time_buf[5]=0x40;
		 //秒
		 time_buf[6]=gsmg_code[gDS1302_TIME[0]/16];
		 time_buf[7]=gsmg_code[gDS1302_TIME[0]%16];
		  smg_display(time_buf,1);
	 }
	
}

DS1302.c

# include "DS1302.h"
# include "intrins.h"

//---DS1302写入和读取时分秒的地址命令---//
u8 gWRITE_RTC_ADDR[7]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};//秒分时日月周年
u8 gREAD_RTC_ADDR[7]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};//秒分时日月周年

//---DS1302时钟初始化2022年4月5日星期二13点15分20秒。---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
u8 gDS1302_TIME[7]={0x50,0x17,0x14,0x05,0x04,0x02,0x21};//秒分时日月周年

/*******************************************************************************
* 函 数 名       : ds1302_write_byte
* 函数功能		 : DS1302写单字节
* 输    入       : addr:地址/命令
				   dat:数据
* 输    出    	 : 无
*******************************************************************************/
void ds1302_write_byte(u8 addr,u8 dat)//addr:控制命令的地址 dat:数据
{
	u8 i=0;
	DS1302_RST=0;//首先CE输出低电平
	_nop_();//延时1ms
	DS1302_CLK=0;//SCLK也输出低电平
	_nop_();//延时1ms
	DS1302_RST=1;//CE输出高电平
	_nop_();//延时1ms
	for(i=0;i<8;i++)//循环 8 次,每次写 1 位,先写低位再写高位
	{
		DS1302_IO=addr&0x01;
		addr>>=1;
		DS1302_CLK=1;//上升沿写入
		_nop_();//延时1ms
		DS1302_CLK=0;//下降沿
		_nop_();//延时1ms
		
	}
	for(i=0;i<8;i++)
	{
		DS1302_IO=dat&0x01;
		dat>>=1;
		DS1302_CLK=1;//上升沿写入
		_nop_();//延时1ms
		DS1302_CLK=0;//下降沿
		_nop_();//延时1ms
	}
	DS1302_RST=0;//拉低CE
	_nop_();//延时1ms
}
/*******************************************************************************
* 函 数 名       : ds1302_read_byte
* 函数功能		 : DS1302读单字节
* 输    入       : addr:地址/命令
* 输    出    	 : 读取的数据
*******************************************************************************/
u8 ds1302_read_byte(u8 addr)
{
	u8 i=0;
	u8 temp=0;
	u8 value=0;
	DS1302_RST=0;//首先CE低电平
	_nop_();//延时1ms
	DS1302_CLK=0;//SCLK也输出低电平
	_nop_();//延时1ms
	DS1302_RST=1;//CE输出高电平
	_nop_();//延时1ms
	for(i=0;i<8;i++)
	{
		DS1302_IO=addr&0x01;
		addr>>=1;
		DS1302_CLK=1;//上升沿写入
		_nop_();//延时1ms
		DS1302_CLK=0;//下降沿
		_nop_();//延时1ms
	}
	for(i=0;i<8;i++)
	{
		temp=DS1302_IO;
		value=(temp<<7)|(value>>1);//先将 value 右移 1 位,然后 temp 左移 7 位,最后或运算

		DS1302_CLK=1;
		_nop_();//延时1ms
		DS1302_CLK=0;//下降沿
		_nop_();//延时1ms
	}
	DS1302_RST=0;//CE低电平
	_nop_();//延时1ms
	DS1302_CLK=1;//对于实物中,P3.4 口没有外接上拉电阻的,此处代码需要添加,使数据口有一个上升沿脉冲。
	_nop_();//延时1ms
	DS1302_IO=0;
	_nop_();//延时1ms
	DS1302_IO=1;
	_nop_();//延时1ms
	
	return value;
}
/*******************************************************************************
* 函 数 名       : ds1302_init
* 函数功能		 : DS1302初始化时间
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void ds1302_init()
{
	u8 i=0;
	ds1302_write_byte(0x8e,0x00);//关闭写保护
	for(i=0;i<7;i++)
	{
		ds1302_write_byte(gWRITE_RTC_ADDR[i],gDS1302_TIME[i]);
	}
	ds1302_write_byte(0x8e,0x80);//打开写保护
}
/*******************************************************************************
* 函 数 名       : ds1302_read_time
* 函数功能		 : DS1302读取时间
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void ds1302_read_time()
{
	u8 i=0;
	for(i=0;i<7;i++)
	{
		gDS1302_TIME[i]=ds1302_read_byte(gREAD_RTC_ADDR[i]);
	}
}

DS1302.h

# ifndef _DS1302_H
# define _DS1302_H

# include "reg52.h"
typedef unsigned char u8;
typedef unsigned int u16;

sbit DS1302_RST=P3^5;
sbit DS1302_CLK=P3^6;
sbit DS1302_IO=P3^4;

extern u8 gDS1302_TIME[7];//声明(其他文件就可以用了)

void ds1302_write_byte(u8 addr,u8 dat);
u8 ds1302_read_byte(u8 addr);
void ds1302_init();
void ds1302_read_time();
# endif

smg.c

# include "smg.h"


u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
				0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
void delay_10us(u16 ten_us)//当传入 Ten_us=1时,大约延时10us
{
	while(ten_us--);
}
void smg_display(u8 dat[],u8 pos)
{
	u8 i=0;
	u8 pos_temp=pos-1;
	
	for(i=pos_temp;i<8;i++)
	{
		switch(i)
		{
				case 0:A0=1;A1=1;A2=1;break;//Y7//板子从左边数第一个数码管,下面以此类推
				case 1:A0=0;A1=1;A2=1;break;//Y6
				case 2:A0=1;A1=0;A2=1;break;//Y5
				case 3:A0=0;A1=0;A2=1;break;//Y4
				case 4:A0=1;A1=1;A2=0;break;//Y3
				case 5:A0=0;A1=1;A2=0;break;//Y2
				case 6:A0=1;A1=0;A2=0;break;//Y1
				case 7:A0=0;A1=0;A2=0;break;//Y0
		}
			SMG_A0_F_PORT=dat[i-pos_temp];//传送段选数据
			delay_10us(100);//延时1毫秒左右
			SMG_A0_F_PORT=0x00;//消影
	}
}

smg.h

# ifndef _smg_H
# define _smg_H

# include "DS1302.h"

sbit A0=P2^2;
sbit A1=P2^3;
sbit A2=P2^4;
# define SMG_A0_F_PORT P0//宏定义数码管P0端口
extern u8 gsmg_code[17];  //注意要加extern 
void delay_10us(u16 ten_us);
void smg_display(u8 dat[],u8 pos);
# endif

显示时分秒例2

main.c

//使用定时器设计一个时钟
//数码管:Datapros(),DigDisplay()
//定时器初始化
//中断函数,主函数
//临时变量:a,秒:sec,分:min,时:hour

# include<reg52.h>

typedef unsigned char uchar;
typedef unsigned int uint;

uchar a=0,sec=0,min=0,hour=0;
uchar DataTime[8];


sbit A0=P2^2;
sbit A1=P2^3;
sbit A2=P2^4;

uchar code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
                    0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~F的值

 //延时函数
void delay(uchar i)
{
    while(i--);
}

//数码管显示函数
void DigDisplay()
{
    uchar i;
    for(i=0;i<8;i++)       //动态扫描显示8个数码管
    {
        switch(i)
        {
            case(0):
                A0=0;A1=0;A2=0;break;
            case(1):
                A0=1;A1=0;A2=0;break;
            case(2):
                A0=0;A1=1;A2=0;break;
            case(3):
                A0=1;A1=1;A2=0;break;
            case(4):
                A0=0;A1=0;A2=1;break;
            case(5):
                A0=1;A1=0;A2=1;break;
            case(6):
                A0=0;A1=1;A2=1;break;
            case(7):
                A0=1;A1=1;A2=1;break;            
        }
        P0=DataTime[i];          //给P0口赋值,时对应的数码管输出对应的值
        delay(100);
        P0=0x00;           //消隐
    }
}

//对得到的时间进行处理,供P0口使用
void Datapros()
{
    DataTime[0]=smgduan[sec%10];         //取余得到秒的个位    ,通过smgduan[]得到对应的十六进制数
    DataTime[1]=smgduan[sec/10];         //取余得到秒的十位
    DataTime[2]=0x40;                      //显示一条横线
    DataTime[3]=smgduan[min%10];
    DataTime[4]=smgduan[min/10];
    DataTime[5]=0x40;
    DataTime[6]=smgduan[hour%10];
    DataTime[7]=smgduan[hour/10];        
    
}
 //定时器初始化
void TimerInit()
{
    TMOD=0X01;       //选择定时器0,方式1
    TH0=0Xd8;          //对TH0和TL0赋初值为10ms,
    TL0=0Xf0;            
    EA=1;
    ET0=1;
    TR0=1;
}

void main()
{
    TimerInit();
    while(1)
    {
        Datapros();    
        DigDisplay();    
    }    
}


void Timer() interrupt 1
{
    TH0=0Xd8;
    TL0=0Xf0;
    a++;
    if(a==99)           // TH0和TL0溢出一次为10ms,溢出100次为1s.
    {
        sec++;             //+1s
        a=0;
        if(sec==60)
        {
            min++;          //+1分钟
            a=0;
            sec=0;
            if(min==60)
            {
                hour++;
                a=0;
                sec=0;
                min=0;
                if(hour==24)
                {
                    a=0;
                    hour=0;
                    min=0;
                    sec=0;

                    
                    }    
            }
            
        }    
    }    
}

显示毫秒秒分例3

main.c

# include "reg52.h"			
typedef unsigned int u16;	
typedef unsigned char u8;

sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
					0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~F的值

u8 ssec,sec,min; //毫秒,秒,分
u8 DisplayData[8];
/*******************************************************************************
* 函 数 名         : delay
* 函数功能		   : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(u16 i)
{
	while(i--);	
}

/*******************************************************************************
* 函 数 名         : Timer0Init
* 函数功能		   : 定时器0初始化
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void Timer0Init()
{
	TMOD|=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。

	TH0=0Xd8;	//给定时器赋初值,定时10ms
	TL0=0Xf0;	
	TR0=1;//打开定时器			
}


/*******************************************************************************
* 函 数 名         : DigDisplay
* 函数功能		   : 数码管动态扫描函数,循环扫描8个数码管显示
*******************************************************************************/
void DigDisplay()
{
	u8 i;
	for(i=0;i<8;i++)
	{
		switch(i)	 //位选,选择点亮的数码管,
		{
			case(0):
				LSA=1;LSB=1;LSC=1; break;//显示第7位
			case(1):
				LSA=0;LSB=1;LSC=1; break;//显示第6位
			case(2):
				LSA=1;LSB=0;LSC=1; break;//显示第5位
			case(3):
				LSA=0;LSB=0;LSC=1; break;//显示第4位
			case(4):
				LSA=1;LSB=1;LSC=0; break;//显示第3位 
			case(5):
				LSA=0;LSB=1;LSC=0; break;//显示第2位 
			case(6):
				LSA=1;LSB=0;LSC=0; break;//显示第1位 
			case(7):
				LSA=0;LSB=0;LSC=0; break;//显示第0位	
		}
		P0=DisplayData[i];//发送段码
		delay(100); //间隔一段时间扫描	
		P0=0x00;//消隐
	}
}

void datapros()
{
	DisplayData[0]=smgduan[min/10];
	DisplayData[1]=smgduan[min%10];
	DisplayData[2]=0x40;	
	DisplayData[3]=smgduan[sec/10];
	DisplayData[4]=smgduan[sec%10];
	DisplayData[5]=0x40;
	DisplayData[6]=smgduan[ssec/10];
	DisplayData[7]=smgduan[ssec%10];
}
/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{	
	Timer0Init();  //定时器0初始化
	while(1)
	{
		if(TF0==1)	  //定时器T0溢出标志
		{
			TF0=0;
			TH0=0Xd8;	//给定时器赋初值,定时10ms
			TL0=0Xf0;
			ssec++;
			if(ssec>=100)  //1s
			{
				ssec=0;
				sec++;
				if(sec>=60)
				{
					sec=0;
					min++;
					if(min>=60)
					{
						min=0;
					}
				}	
			}	
		}	
		datapros();
		DigDisplay();	
	}		
}

计时器(数码管显示)

main.c

/**************************************************************************************
实验名称:计时器(数码管显示)
接线说明:	
实验现象:下载程序后,按下K1键开始计时,再次按下K1键停止计时,按下K2键清零																				  
***************************************************************************************/
# include "public.h"
# include "key.h"
# include "beep.h"
# include "smg.h"
# include "time.h"

/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{	
	u8 key=0;
	u8 time_buf[8];
	u8 time_flag=0;

	time0_init();
	while(1)
	{
		key=key_scan(0);
		if(key==KEY1_PRESS)//开始和停止计时
		{
			time_flag=!time_flag;
			beep_alarm(1000,10);//短暂提示音
		}	
		else if(key==KEY2_PRESS)//清除计时
		{
			time0_stop();
			time_flag=0;
			gmin=0;
			gsec=0;
			gmsec=0;
			beep_alarm(1000,10);//短暂提示音		
		}
		if(time_flag)
			time0_start();//开始计时
		else
			time0_stop();//停止计时
	
		time_buf[0]=gsmg_code[gmin/10];
		time_buf[1]=gsmg_code[gmin%10];//分
		time_buf[2]=0x40;//-
		time_buf[3]=gsmg_code[gsec/10];
		time_buf[4]=gsmg_code[gsec%10];//秒
		time_buf[5]=0x40;//-
		time_buf[6]=gsmg_code[gmsec/10];
		time_buf[7]=gsmg_code[gmsec%10];//毫秒
		smg_display(time_buf,1);//显示	
	}
}

public.c(通用)

# include "public.h"

/*******************************************************************************
* 函 数 名       : delay_10us
* 函数功能		 : 延时函数,ten_us=1时,大约延时10us
* 输    入       : ten_us
* 输    出    	 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
	while(ten_us--);	
}

/*******************************************************************************
* 函 数 名       : delay_ms
* 函数功能		 : ms延时函数,ms=1时,大约延时1ms
* 输    入       : ms:ms延时时间
* 输    出    	 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
	u16 i,j;
	for(i=ms;i>0;i--)
		for(j=110;j>0;j--);
}

public.h

# ifndef _public_H
# define _public_H

# include "reg52.h"

typedef unsigned int u16;	//对系统默认数据类型进行重定义
typedef unsigned char u8;


void delay_10us(u16 ten_us);
void delay_ms(u16 ms);

# endif

beep.c(蜂鸣器)

# include "beep.h"

/*******************************************************************************
* 函 数 名       : beep_alarm
* 函数功能		 : 蜂鸣器报警函数
* 输    入       : time:报警持续时间
				   fre:报警频率
* 输    出    	 : 无
*******************************************************************************/
void beep_alarm(u16 time,u16 fre)
{
	while(time--)
	{
		BEEP=!BEEP;
		delay_10us(fre);	
	}		
}

beep.h

# ifndef _beep_H
# define _beep_H

# include "public.h"

//管脚定义
sbit BEEP=P2^5;

//函数声明
void beep_alarm(u16 time,u16 fre);
# endif

key.c(按键)

# include "key.h"

/*******************************************************************************
* 函 数 名       : key_scan
* 函数功能		 : 检测独立按键是否按下,按下则返回对应键值
* 输    入       : mode=0:单次扫描按键
				   mode=1:连续扫描按键
* 输    出    	 : KEY1_PRESS:K1按下
				   KEY2_PRESS:K2按下
				   KEY3_PRESS:K3按下
				   KEY4_PRESS:K4按下
				   KEY_UNPRESS:未有按键按下
*******************************************************************************/
u8 key_scan(u8 mode)
{
	static u8 key=1;

	if(mode)key=1;//连续扫描按键
	if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
	{
		delay_10us(1000);//消抖
		key=0;
		if(KEY1==0)
			return KEY1_PRESS;
		else if(KEY2==0)
			return KEY2_PRESS;
		else if(KEY3==0)
			return KEY3_PRESS;
		else if(KEY4==0)
			return KEY4_PRESS;	
	}
	else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1)	//无按键按下
	{
		key=1;			
	}
	return KEY_UNPRESS;		
}

key.h

# ifndef _key_H
# define _key_H

# include "public.h"

//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;


//使用宏定义独立按键按下的键值
# define KEY1_PRESS	1
# define KEY2_PRESS	2
# define KEY3_PRESS	3
# define KEY4_PRESS	4
# define KEY_UNPRESS	0


u8 key_scan(u8 mode);

# endif

smg.c(数码管)

# include "smg.h"

//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
				0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

/*******************************************************************************
* 函 数 名       : smg_display
* 函数功能		 : 动态数码管显示
* 输    入       : dat:要显示的数据
				   pos:从左开始第几个位置开始显示,范围1-8
* 输    出    	 : 无
*******************************************************************************/
void smg_display(u8 dat[],u8 pos)
{
	u8 i=0;
	u8 pos_temp=pos-1;

	for(i=pos_temp;i<8;i++)
	{
	   	switch(i)//位选
		{
			case 0: LSC=1;LSB=1;LSA=1;break;
			case 1: LSC=1;LSB=1;LSA=0;break;
			case 2: LSC=1;LSB=0;LSA=1;break;
			case 3: LSC=1;LSB=0;LSA=0;break;
			case 4: LSC=0;LSB=1;LSA=1;break;
			case 5: LSC=0;LSB=1;LSA=0;break;
			case 6: LSC=0;LSB=0;LSA=1;break;
			case 7: LSC=0;LSB=0;LSA=0;break;
		}
		SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
		delay_10us(100);//延时一段时间,等待显示稳定
		SMG_A_DP_PORT=0x00;//消音
	}
}

smg.h

# ifndef _smg_H
# define _smg_H

# include "public.h"


# define SMG_A_DP_PORT	P0	//使用宏定义数码管段码口

//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

extern u8 gsmg_code[17];

void smg_display(u8 dat[],u8 pos);

# endif

time.c(定时器)

# include "time.h"

//变量定义
u8 gmsec=0;//ms级定义
u8 gsec=0;//秒定义
u8 gmin=0;//分定义

/*******************************************************************************
* 函 数 名       : time0_init
* 函数功能		 : 定时器0中断配置函数,通过设置TH和TL即可确定定时时间
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void time0_init(void)
{
	TMOD|=0X01;//选择为定时器0模式,工作方式1
	TH0=0XDC;	//给定时器赋初值,定时10ms
	TL0=0X00;	
	ET0=1;//打开定时器0中断允许
	EA=1;//打开总中断
	TR0=0;//关闭定时器		
}

/*******************************************************************************
* 函 数 名       : time0_start
* 函数功能		 : 定时器0打开
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void time0_start(void)
{
	TR0=1;//打开定时器	
}

/*******************************************************************************
* 函 数 名       : time0_stop
* 函数功能		 : 定时器0关闭
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void time0_stop(void)
{
	TR0=0;//关闭定时器
	TH0=0X00;
	TL0=0X00;	
}

void time0() interrupt 1 //定时器0中断函数
{
	TH0=0XDC;	//给定时器赋初值,定时10ms
	TL0=0X00;
	gmsec++;//10ms加1次
	if(gmsec==100)//定时1秒
	{
		gmsec=0;
		gsec++;//对秒计数
		if(gsec==60)//到达60秒,即1分钟
		{
			gsec=0;
			gmin++;//对分计数
			if(gmin==60)gmin=0;
		}	
	}					
}

time.h

# ifndef _time_H
# define _time_H

# include "public.h"


//变量声明
extern u8 gmsec;//ms级定义
extern u8 gsec;//秒定义
extern u8 gmin;//分定义


//函数声明
void time0_init(void);
void time0_start(void);
void time0_stop(void);
# endif

实验现象

计时器(LCD显示)

public.c,public.h---beep.c,beep.h---key.c,key.h---time.c,time.h 和上一个例子一模一样;只需把数码管的替换成 LCD 和改一下 main.c即可。

main.c

/**************************************************************************************
实验名称:计时器(LCD1602显示)
接线说明:	
实验现象:下载程序后,按下K1键开始计时,再次按下K1键停止计时,按下K2键清零																				  
***************************************************************************************/
# include "public.h"
# include "key.h"
# include "beep.h"
# include "lcd1602.h"
# include "time.h"

/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{	
	u8 key=0;
	u8 time_buf[9];
	u8 time_flag=0;

	lcd1602_init();
	time0_init();
	while(1)
	{
		key=key_scan(0);
		if(key==KEY1_PRESS)//开始和停止计时
		{
			time_flag=!time_flag;
			beep_alarm(1000,10);//短暂提示音
		}	
		else if(key==KEY2_PRESS)//清除计时
		{
			time0_stop();
			time_flag=0;
			gmin=0;
			gsec=0;
			gmsec=0;
			beep_alarm(1000,10);//短暂提示音		
		}
		if(time_flag)
			time0_start();//开始计时
		else
			time0_stop();//停止计时
	
		time_buf[0]=gmin/10+0x30;
		time_buf[1]=gmin%10+0x30;//分
		time_buf[2]='-';//-
		time_buf[3]=gsec/10+0x30;
		time_buf[4]=gsec%10+0x30;//秒
		time_buf[5]='-';//-
		time_buf[6]=gmsec/10+0x30;
		time_buf[7]=gmsec%10+0x30;//毫秒
		time_buf[8]='\0';
		lcd1602_show_string(0,0,time_buf);//显示	
	}
}

lcd1602.c

# include "lcd1602.h"


/*******************************************************************************
* 函 数 名       : lcd1602_write_cmd
* 函数功能		 : LCD1602写命令
* 输    入       : cmd:指令
* 输    出    	 : 无
*******************************************************************************/
# if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
void lcd1602_write_cmd(u8 cmd)
{
	LCD1602_RS=0;//选择命令
	LCD1602_RW=0;//选择写
	LCD1602_E=0;
	LCD1602_DATAPORT=cmd;//准备命令
	delay_ms(1);
	LCD1602_E=1;//使能脚E先上升沿写入
	delay_ms(1);
	LCD1602_E=0;//使能脚E后负跳变完成写入	
}
# else	//4位LCD
void lcd1602_write_cmd(u8 cmd)
{
	LCD1602_RS=0;//选择命令
	LCD1602_RW=0;//选择写
	LCD1602_E=0;
	LCD1602_DATAPORT=cmd;//准备命令
	delay_ms(1);
	LCD1602_E=1;//使能脚E先上升沿写入
	delay_ms(1);
	LCD1602_E=0;//使能脚E后负跳变完成写入
	
	LCD1602_DATAPORT=cmd<<4;//准备命令
	delay_ms(1);
	LCD1602_E=1;//使能脚E先上升沿写入
	delay_ms(1);
	LCD1602_E=0;//使能脚E后负跳变完成写入	
}
# endif

/*******************************************************************************
* 函 数 名       : lcd1602_write_data
* 函数功能		 : LCD1602写数据
* 输    入       : dat:数据
* 输    出    	 : 无
*******************************************************************************/
# if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
void lcd1602_write_data(u8 dat) 
{
	LCD1602_RS=1;//选择数据
	LCD1602_RW=0;//选择写
	LCD1602_E=0;
	LCD1602_DATAPORT=dat;//准备数据
	delay_ms(1);
	LCD1602_E=1;//使能脚E先上升沿写入
	delay_ms(1);
	LCD1602_E=0;//使能脚E后负跳变完成写入		
}
# else
void lcd1602_write_data(u8 dat) 
{
	LCD1602_RS=1;//选择数据
	LCD1602_RW=0;//选择写
	LCD1602_E=0;
	LCD1602_DATAPORT=dat;//准备数据
	delay_ms(1);
	LCD1602_E=1;//使能脚E先上升沿写入
	delay_ms(1);
	LCD1602_E=0;//使能脚E后负跳变完成写入
	
	LCD1602_DATAPORT=dat<<4;//准备数据
	delay_ms(1);
	LCD1602_E=1;//使能脚E先上升沿写入
	delay_ms(1);
	LCD1602_E=0;//使能脚E后负跳变完成写入		
}
# endif

/*******************************************************************************
* 函 数 名       : lcd1602_init
* 函数功能		 : LCD1602初始化
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
# if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
void lcd1602_init(void)
{
	lcd1602_write_cmd(0x38);//数据总线8位,显示2行,5*7点阵/字符
	lcd1602_write_cmd(0x0c);//显示功能开,无光标,光标闪烁
	lcd1602_write_cmd(0x06);//写入新数据后光标右移,显示屏不移动
	lcd1602_write_cmd(0x01);//清屏	
}
# else
void lcd1602_init(void)
{
	lcd1602_write_cmd(0x28);//数据总线4位,显示2行,5*7点阵/字符
	lcd1602_write_cmd(0x0c);//显示功能开,无光标,光标闪烁
	lcd1602_write_cmd(0x06);//写入新数据后光标右移,显示屏不移动
	lcd1602_write_cmd(0x01);//清屏	
}
# endif

/*******************************************************************************
* 函 数 名       : lcd1602_clear
* 函数功能		 : LCD1602清屏
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void lcd1602_clear(void)
{
	lcd1602_write_cmd(0x01);	
}

/*******************************************************************************
* 函 数 名       : lcd1602_show_string
* 函数功能		 : LCD1602显示字符
* 输    入       : x,y:显示坐标,x=0~15,y=0~1;
				   str:显示字符串
* 输    出    	 : 无
*******************************************************************************/
void lcd1602_show_string(u8 x,u8 y,u8 *str)
{
	u8 i=0;

	if(y>1||x>15)return;//行列参数不对则强制退出

	if(y<1)	//第1行显示
	{	
		while(*str!='\0')//字符串是以'\0'结尾,只要前面有内容就显示
		{
			if(i<16-x)//如果字符长度超过第一行显示范围,则在第二行继续显示
			{
				lcd1602_write_cmd(0x80+i+x);//第一行显示地址设置	
			}
			else
			{
				lcd1602_write_cmd(0x40+0x80+i+x-16);//第二行显示地址设置	
			}
			lcd1602_write_data(*str);//显示内容
			str++;//指针递增
			i++;	
		}	
	}
	else	//第2行显示
	{
		while(*str!='\0')
		{
			if(i<16-x) //如果字符长度超过第二行显示范围,则在第一行继续显示
			{
				lcd1602_write_cmd(0x80+0x40+i+x);	
			}
			else
			{
				lcd1602_write_cmd(0x80+i+x-16);	
			}
			lcd1602_write_data(*str);
			str++;
			i++;	
		}	
	}				
}

lcd1602.h

# ifndef _lcd1602_H
# define _lcd1602_H

# include "public.h"

//LCD1602数据口4位和8位定义,若为1,则为LCD1602四位数据口驱动,反之为8位
# define LCD1602_4OR8_DATA_INTERFACE	0	//默认使用8位数据口LCD1602

//管脚定义
sbit LCD1602_RS=P2^6;//数据命令选择
sbit LCD1602_RW=P2^5;//读写选择
sbit LCD1602_E=P2^7; //使能信号
# define LCD1602_DATAPORT P0	//宏定义LCD1602数据端口


//函数声明
void lcd1602_init(void);
void lcd1602_clear(void);
void lcd1602_show_string(u8 x,u8 y,u8 *str);

# endif