源代码https://gitee.com/kiloGrand/electronic-clock
工作模式下,51单片机从DS1302中获取数据,再把数据传递到LCD来显示时间和日期;设置模式下,通过k2和k3来改变数据;计时模式下,通过k2来打开或关闭定时器1,来实现开始/暂停计时,通过k3来计时初始化;闹钟响时,打开定时器1,通过天空之城乐谱来控制蜂鸣器的音调。
十进制数 | 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寄存器操作,以及操作的地址。第二个字节就是要读或写的数据了。
/*写入数据到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;
}
输入 | 输出 | 输出 |
---|---|---|
读状态 | 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; //指针指向要播放的曲目