51单片机实战之电子时钟

自制电子时钟–总结

源代码https://gitee.com/kiloGrand/electronic-clock

使用到的模块及其功能:

  • DS1302:低功耗实时时钟芯片,它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。
  • LCD1602:用来显示字母、数字、符号等的点阵型液晶模块,能够同时显示16x02即32个字符。
  • 按键k1,k2,k3,k4:
    k1:切换模式,比如工作模式、计时模式、设置模式。
    k2:设置模式: plus;计时模式: 打开计时,暂停计时
    k3:设置模式: shift;计时模式:归零
    k4:闹钟响时,关闭闹钟
  • 蜂鸣器:闹钟铃声,天空之城

主要原理:

    工作模式下,51单片机从DS1302中获取数据,再把数据传递到LCD来显示时间和日期;设置模式下,通过k2和k3来改变数据;计时模式下,通过k2来打开或关闭定时器1,来实现开始/暂停计时,通过k3来计时初始化;闹钟响时,打开定时器1,通过天空之城乐谱来控制蜂鸣器的音调。

DS1302

  • 编码是BCD8421编码,用4位二进制数来表示1位十进制数中的0~9这10个数字

十进制数 8421码
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001

/*BCD8421编码,十进制数据转换成BCD码 */
unsigned char encode_BCD(unsigned char dat)		   
{
	unsigned char dat1,dat2;
	dat1 = dat/10;
	dat2 = dat%10;
	dat = dat2 + dat1*16;
	return dat; 
}

/*BCD8421解码,BCD码转换成十进制数据   */
unsigned char decode_BCD(unsigned char dat)
{
	unsigned char dat1,dat2;
	dat1 = dat/16;
	dat2 = dat%16;
	dat = dat2 + dat1*10;
	return dat;
} 

  • 读写数据到DS1302
    DS1302的数据读写是通过I/O串行进行的。当进行一次读写操作时最少得发送两个字节,第一个字节是控制字节,就是一个命令,告诉DS1302是读还是写操作,是对RAM还是对CLOK寄存器操作,以及操作的地址。第二个字节就是要读或写的数据了。

    1. 控制字节
    • 位0就是读写位,当位0为1时,就是告诉DS1302,下面是进行读出操作,而当位0为0时就是写入操作。
    • 位0-位5是要进行操作的DS1302寄存器地址。
      位6就是告诉DS1302,是要对RAM进行操作还是对时间寄存器进行操作,0就是对时间寄存器操作,一般我们都是对时间寄存器进行操作。
    • 位7就是固定的1。
    • 现在就知道为什么控制字80H是写秒寄存器,而81H是读秒寄存器了吧。80H换成二进制就是10000000。而81H的二进制就是10000001,一个是写操作,另一个是读操作嘛!
    1. 读写操作
      51单片机实战之电子时钟_第1张图片
    • 写:
      在进行操作之前先得将CE(也可说是RST)置高电平,然后单片机将控制字的位0放到I/O上,当I/O的数据稳定后,将SCLK置高电平,DS1302检测到SCLK的上升沿后就将I/O上的数据读取,然后单片机将SCLK置为低电平,再将控制字的位1放到I/O上,如此反复,将一个字节控制字的8个位传给DS1302。
      接下来就是传一个字节的数据给DS1302,在SCLK低电平时单片机将数据放到IO上,当SCLK上升沿时,DS1302读取。当传完数据后,单片机将CE置为低电平,操作结束。
    • 读:
      在进行操作之前先得将CE(也可说是RST)置高电平,然后单片机将控制字的位0放到I/O上,当I/O的数据稳定后,将SCLK置高电平,DS1302检测到SCLK的上升沿后就将I/O上的数据读取,然后单片机将SCLK置为低电平,再将控制字的位1放到I/O上,如此反复,将一个字节控制字的8个位传给DS1302。
      接下来就是从DS1302读数据,在SCLK高电平时DS1302放数据到IO上,将SCLK置为低电平后,产生下降沿电压,单片机就可从IO上读取数据。当传完数据后,单片机将CE置为低电平,操作结束。

/*写入数据到DS1302*/
void write_DS1302_dat(unsigned char addr,unsigned char dat)
{
	unsigned char i;
	//初始化
	TRST = 0;
	_nop_();	 
	TSCLK = 0;
	_nop_();
	
	TRST = 1; //拉高CE,开始读写数据
	_nop_();
	for(i=0;i<8;i++)		//写入地址
	{
	    
		TIO = addr & 0x01;	//发送地址的最低位 
		addr >>= 1;
		TSCLK = 1;			//产生上升沿电压,写入命令
		_nop_();
		TSCLK = 0;
		_nop_();
	}
	for(i=0;i<8;i++)		//写入数据
	{
		
		TIO = dat & 0x01;	//发送数据的最低位
		dat >>= 1;
		TSCLK = 1;			//产生上升沿电压,写入数据
		_nop_();
		TSCLK = 0;
		_nop_();
	}
	TRST = 0;		 //数据传输结束
	_nop_();
}

/*从DS1302读取数据*/
unsigned char read_DS1302_dat(unsigned char addr)
{
	unsigned char i,dat=0,dat1=0;
	//初始化
	TRST = 0;
	_nop_();	 
	TSCLK = 0;
	_nop_();

	TRST = 1; //拉高CE,开始读写数据
	for(i=0;i<8;i++)		//开始传送八位地址命令
	{	    
		TIO = addr & 0x01;	//发送地址的最低位 
		addr >>= 1;
		TSCLK = 1;			//产生上升沿电压,写入命令
		_nop_();
		TSCLK = 0;			//DS1302下降沿时,放置数据
		_nop_();		
	}
	for(i=0;i<8;i++)		//读取8位数据
	{				
		dat1 = TIO;			//从最低位开始接收
		dat = (dat>>1) | (dat1<<7);

		TSCLK = 1;
		_nop_();
		TSCLK = 0;			//产生下降沿电压,读取数据
		_nop_();					
	}

	TRST = 0;
	_nop_();	//以下为DS1302复位的稳定时间,必须的。
	TSCLK = 1;
	_nop_();
	TIO = 0;
	_nop_();
	TIO = 1;
	_nop_();
	return dat;
}

LCD1602

  • LCD操作模式
输入 输出 输出
读状态 RS=0,RW=H,EN为高变低脉冲 D0~D7个状态值
读数据 RS=1,RW=1,EN为高变低脉冲
写指令 RS=0,RW=0,D0–D7=数据,EN由高脉冲变为低脉冲 D0–D7状态值
写数据 RS=1, RW=0, D0–D7=数据,EN由高脉冲变为低脉冲 D0–D7状态值
  • 详细说明:
    (1) RS和RW都为0时表示对LCD写指令操作,包括写入LCD的显示模式和设定LCD地址的指令。.显示模式包括清屏、地址归为、显示状态、进入点设定、功能设定、游标显示模式操作;关于地址的操作包括设定CGRAM地址、设定DDRAM地址。
    (2) 当RS=0,RW=1时,表示读LCD状态,此时可以读取LCD忙信号,同时可以读取地址计数器的值。忙信号的状态用来确定LCD内部动作是否完成,若在LCD内部出于忙状态时对LCD进行读写操作将会失败。
    (3) 当RS=1时,若RW=0表示写数据操作,若RW=1表示读数据操作

  • 地址:
    LCD供两行,第一行可立即显示字符的地址为00H—0FH,第二行可立即显示字符的地址为40H—67H。


//判断液晶是否忙,如果忙就等待
void read_busy()
{
	unsigned char busy;
	P0 = 0xff;
	RS = 0;
	RW = 1;
	do
	{
		EN = 1;
		busy = P0;	   			//读状态,RS = 0;RW = 1;EN = 1;
		EN = 0;
	}while(busy & 0x80);	 	//判断状态码最高位,STA7读写使能,1:禁止,0:允许
}

//写1字节指令
void write_cmd(unsigned char cmd)	   //RS=L,RW=L,E=下降沿脉冲
{
	read_busy();
	RS = 0;
	RW = 0;
	P0 = cmd;
	EN = 1;
	EN = 0;	
}

//写1字节数据
void write_dat(unsigned char dat)	   //RS=H,RW=L,E=下降沿脉冲
{
	read_busy();
	RS = 1;
	RW = 0;
	P0 = dat;
	EN = 1;
	EN = 0;
}

蜂鸣器

核心代码:

void delay_us(unsigned int t)		 //us延时 12MHz下
{
	t/=10;
	for(t;t;t--);
}

void play_tone(unsigned int tone)	 //播音调函数,就是方波发生器
{

		Buzzer=!Buzzer;				 
		delay_us(tone);		
}

定时器获取延时时间:

void delay_ms(unsigned int t)		 //毫秒延时12MHz下
{
	unsigned int i=0;
	while(t--)
		for(i=0;i<75;i++);
}

void time_init( void )
{
	TMOD|=0x10;						 //使用定时器1
	TH1=(65536-65000)/256;			 //装初值
	TL1=(65536-65000)%256;
	EA =  1;						 //开中断,打开定时器开关
	ET1 = 1;
	TR1 = 1;
}

void timer1_interrupt(unsigned char *song) 		 		//定时器1 中断
{
	TH1=(65536-50000)/256;			 //装初值
	TL1=(65536-50000)%256;
	music_s++;
	
	if(music_s>=4*t_tone)					 //一个音节播放的时间,这里可以通过调t_tone前的系数可以改变时长
	{
		music_s=0;						 //讲计时器清零
		if((*tone_p)!=0)			 //如果音不是0
			tone = tones[*tone_p+7*(*(tone_p+1))-1];  //赋值音调
		else 
			tone = 0;			     //关了蜂鸣器
		t_tone = *(tone_p+2);		 //取时间啊

		tone_p+=3;					 //移动指针

		if(*tone_p == 8) tone_p = song;	   //结束标志

		delay_ms(30);				 //延时一下,不延时特别难听
	}
}

歌曲谱子数组:

unsigned int tones[]=   			 //C调音调
{
3816,3401,3030,2865,2551,2272,2024,	 //低音
1912,1703,1517,1432,1275,1136,1012,	 //中音
965, 851, 758, 715, 605, 538, 466	 //高音
};

unsigned char code sky[]={			 //谱子,天空之城
//格式: 音调, 音度, 拍数
//例: 4,1,1 //音调fa,中音,时长半拍
//0代表空音
0,0,2,
0,0,2,
0,0,2,
6,1,1,
7,1,1,

1,2,3,
7,1,1,
1,2,2,
3,2,2,
8		//结束标志
};

sbit Buzzer=P1^5;					 //定义buzzer引脚
unsigned char music_s=0, t_tone=0;	     //music_s用作定时器计时, t_tone保存音调时长
unsigned int  tone=0;			     //tone保存音调,
char *tone_p=sky;					 //指针指向要播放的曲目

你可能感兴趣的:(单片机,单片机,嵌入式)