自制电子时钟—总结

使用到的模块及其功能:

  • 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*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,一个是写操作,另一个是读操作嘛!
  2. 读写操作
    读写
    • 写:
      在进行操作之前先得将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置为低电平,操作结束。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      /*写入数据到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。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//判断液晶是否忙,如果忙就等待
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;
}

蜂鸣器

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
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);
}

定时器获取延时时间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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); //延时一下,不延时特别难听
}
}

歌曲谱子数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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; //指针指向要播放的曲目