欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

51单片机基本刷屏测试实验_51单片机开发基础8——实时时钟实验

发布时间:2025/3/8 27 豆豆
生活随笔 收集整理的这篇文章主要介绍了 51单片机基本刷屏测试实验_51单片机开发基础8——实时时钟实验 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

8.1 实时时钟简介

       RTC(Real Time Clock),是实时时钟的缩写,实时时钟是日常生活中应用最为广泛的功能。它为人们提供精确的实时时间,或者为电子系统提供精确的时间基准,目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源。有些时钟芯片为了在主电源掉电时,还可以工作,需要外加电池供电。

       现在的高端处理器大都内置了RTC模块,但是由于51单片机速度较慢,主要用于低端的控制系统中,所以没有内置RTC模块,需要采用时钟芯片来完成这个功能,现在常用的时钟芯片有很多,现在以DS1302为例说明时钟芯片的使用方法。

8.2 DS1302简介

8.2.1 DS1302概述

       DS1302是美国DALLAS公司推出的一种高性能、低功耗的实时时钟芯片,附加31字节静态RAM,采用SPI三线接口与CPU进行同步通信,并可采用突发方式一次传送多个字节的时钟信号和RAM数据。实时时钟可提供秒、分、时、日、星期、月和年,一个月小于31天时可以自动调整,且具有闰年补偿功能。工作电压宽达2.5~5.5V。采用双电源供电(主电源和备用电源),可设置备用电源充电方式,提供了对备用电源进行涓细电流充电的能力。

8.2.2通信协议

       在之前的章节中,除了USART那一部分,都是采用了并行通信作为数据传输的方式,并行通信虽然速度很快,但是对硬件有着很高的要求,比如如果传输8位的数据,就需要8根通信线,如果是16位的数据就需要16根通信线,并且随着通信线长度不一样,可能会存在数据错误或者丢失的情况。串行通信虽然速度没有并行通信那么高,但是一根数据线可以传送任意字节的数据,降低了设计中布线的难度。

       DS1302就是串行通信方式,芯片的引脚分布如下图所示。

引脚编号

英文缩写

引脚功能

1

VCC2

主电源

2

X1

32.768KHz晶振

3

X2

32.768KHz晶振

4

GND

数字地

5

RST

复位

6

I/O

数据输入/输出

7

CLK

时钟输入

8

VCC1

备用电源(接电池)

    串行通信中,用到了两个端口,时钟信号CLK和数据信号I/O,时钟信号用于提供数据发送的脉冲,数据信号I/O用于将数据拆成0101的形式发送过去,DS1302的时序包括读和写两种时序,时序图如下图所示。

(1)写时序

(2)读时序

8.2.3 RTC内部寄存器

(1)秒寄存器

读地址:0x81

写地址:0x80

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

CH

Second  1

Second  2

0~59

Bit 7:时钟开关

       0:关闭

       1:开启

Bit 6~Bit 4:秒数据十位

Bit 3~Bit 0:秒数据个位

(2)分钟寄存器

读地址:0x83

写地址:0x82

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

-

Minute  1

Minute  2

0~59

Bit 6~Bit 4:分钟数据十位

Bit 3~Bit 0:分钟数据个位

(3)小时寄存器

读地址:0x85

写地址:0x84

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

12/24

0

Hour  1

Hour  2

1~12

0~23

AM/PM

Hour  1

Bit 7:小时制选择

       0:24小时制

       1:12小时制

Bit 5~Bit 4:小时数据十位(24小时制)

              当Bit 7设置为12小时制的时候Bit5代表上下午,Bit 4代表小时数据的十位

Bit 3~Bit 0:小时数据个位

(4)日期寄存器

读地址:0x87

写地址:0x86

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

0

0

Data  1

Data  2

1~31

Bit 5~Bit 4:日期数据十位

Bit 3~Bit 0:日期数据个位

(5)月份寄存器

读地址:0x89

写地址:0x88

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

0

0

0

Month  1

Month  2

1~12

Bit 5~Bit 4:月份数据十位

Bit 3~Bit 0:月份数据个位

(6)星期寄存器

读地址:0x8B

写地址:0x8A

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

0

0

0

0

0

Day

1~7

Bit 2~Bit 0:星期数据个位

(7)年份寄存器

读地址:0x8D

写地址:0x8C

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

Year  1

Year  2

0~99

Bit 7~Bit 4:年份数据十位

Bit 3~Bit 0:年份数据个位

(8)写保护寄存器

读地址:0x8F

写地址:0x8E

Bit  7

Bit  6

Bit  5

Bit  4

Bit  3

Bit  2

Bit  1

Bit  0

数据范围

WP

0

0

0

0

0

0

0

Bit 7:写保护控制

       0:关闭写保护

       1:开启写保护

8.2.4 原理图

8.3 例程分析

(1)由于程度很长,只做几个重点位置的讲解。先来看显示部分

在之前1602显示的实验上增加了一个函数LCD_Show_String,这个函数用于在屏幕任意位置显示字符串,C语言中的字符串其实是一个一维数组,这个一维数组中存放的是ASCII码,假设定义一个字符串Hello World,那么实际在单片机里面存储的数据如下表所示

00  H

01  H

02  H

03  H

04  H

05  H

06  H

07  H

08  H

09  H

0A  H

H

e

l

l

o

W

o

r

l

d

换算到16进制里面就是

00  H

01  H

02  H

03  H

04  H

05  H

06  H

07  H

08  H

09  H

0A  H

0x48

0x65

0x6C

0x6C

0x6F

0x20

0x57

0x6F

0x72

0x6C

0x64

现在来分析这个子函数

第112行:使用switch语句来进行坐标转换,因为LCD1602第1行第1个位置的地址是0x80,第2行第1个位置的地址则是0xC0,所以需要用分支语句来控制最后的地址

第115行,如果是第1行(第1行用0表示的),那么地址就是行地址加列地址,1602内部规定了列地址从0~15,如果是第1行第2个位置,那么具体的地址就应该是0x80+1=0x81,如果是第2行第5个位置就应该是0xC0+4=0xC4

第124行:地址设置属于输入命令,所以应该调用LCD命令写入函数,将之前的地址数据写入LCD1602中

第125行:由于LCD1602设置了地址自动加一,所以写入连续的数据的时候不需要频繁设置地址,这就可以采用循环的方式把字符串写进去,ASCII虽然有128个数据,但是能够显示的数据并不多,仔细观察ASCII码表可以发现,只有空格之后的数据是可以显示的,之前的都是控制字符,而空格的ASCII码值是0x20,程序中的\0的ASCII码值是0x00,也就是说当检测到要写入的数据是0x00的时候就说明字符串写完了,此时结束循环即可

第127行:利用LCD数据写入函数把指针指向的地址里面的数据写入LCD1602

第128行:指针自增,为了让指针指向的下一个字符的地址,因为数组里面的数据在地址中都是连续存放的,如果第一个字符的地址是0x00,那么下一个字符的地址就一定是0x01

(2)然后我们来看DS1302的驱动函数,重点分析如何将一个字节拆分成0101的二进制位发出去,并分析如何将0101的二进制位变成一个完整的字节。

假设存在1个字节0x23,现在我想把这个字节从最低位到最高位一位一位的将数据传送出去,应该怎么办呢?

       首先23 H=0010 0011B,最低位是1,最高位是0,现在将0x23&0x01进行运算,结果当然是0x01,这时,我们就应该将数据线变成1,然后0x23往右移动一个二进制位,得出的结果是11 H=0001 0001 B(这里有一个重点,数据右移的时候,最高位是补0的,数据左移的时候,最低位补0)。

       假设上面的数据右移了2次后,最初的23 H变成了08 H=0000 1000 B,现在继续对0x08&0x01做运算得出的结果是0,这时,将数据线变为0,如此循环8次,就可以将1个字节分成串行数据一位一位的传送出去了。

上图所示的代码就是串行数据的发送与接收,下面开始考虑接收,如何将串行数据拼接成并行数据呢?

       假设串行数据先发送最低位,首先将一个数据00 H右移一个二进制位,得出的数据当然还是00 H,然后如果数据总线上的电平是1,那么此时就把00 H和80 H做或运算,得出的结果就是80 H,然后下一个电平的时候80 H右移一个二进制位,得出的结果是40 H,如果此时数据线的电平还是1,那就继续和80 H做或运算,得C0 H,最终通过8次运算,就可以将1个字节全部接收完毕。

       根据上面的分析和DS1302的时序图,就可以写出DS1302读取数据的函数,如下图所示。

(3)下面我们来分析下如何将DS1302计算得出的数据显示在屏幕上,主函数的程序如下图所示。

    在while循环里面,由于数据不连续,所以需要先写显示的地址,然后写入数据以显示年为例,由于年份后面2位(个位和十位)的坐标是第1行的第4列和第5列,所以只需要将地址设置成第一行的第4列就行了,由于1602内部地址从0开始,所以第1行的第4列地址应该是0x80+3。

       第229行和第230行里面,数据除以10取整数部分和除以10取余数部分都比较容易理解,那么为什么要加上0x30呢,这是因为ASCII码表里面,0~9的ASCII值是0x30~0x39,所以如果不加0x30,那么写入的0~9实际是控制字符,刚才说过了ASCII码表里面0x20之前的都是控制字符,直接写入0x00~0x09是不显示的,所以加上0x30之后,9就变成了0x39。

8.4 完整代码

/********************************************************************************************************* 头 文 件 引 用*********************************************************************************************************/#include //导入51单片机头文件/********************************************************************************************************* 数 据 类 型 定 义*********************************************************************************************************/#define u8 unsigned char //定义无符号字符型数据(0~255)#define u16 unsigned int //定义无符号整型数据(0~65535)/********************************************************************************************************* 硬 件 端 口 定 义*********************************************************************************************************///LCD1602控制端口#define LCD_DB P0 //LCD数据口sbit LCD_RS = P2^0 ; //数据命令选择sbit LCD_RW = P2^1 ; //读写控制sbit LCD_EN = P2^2 ; //使能控制//DS1302控制端口sbit DS_CLK = P2^6 ; //串行时钟sbit DS_RST = P2^5 ; //复位sbit DS_IO = P2^7 ; //串行数据/********************************************************************************************************* 数 据 结 构 定 义*********************************************************************************************************/typedef struct{ u8 Second; //秒 u8 Minute; //分 u8 Hour; //时 u8 Date; //日 u8 Month; //月 u8 Year; //年}DS1302_Data;DS1302_Data Time;/********************************************************Name :delay_msFunction :毫秒延时函数Paramater : ms:延时的时间Return :None********************************************************/void delay_ms( u16 ms ){ u8 i ; while( --ms ) for( i=0; i<110; i++ ) ;}/********************************************************************************************************* LCD1602 显 示 程 序*********************************************************************************************************//********************************************************Name :LCD_Write_CommandFunction :LCD写入命令Paramater : Command:命令代码Return :None********************************************************/void LCD_Write_Command( u8 Command ){ LCD_RS = 0 ; //命令模式 LCD_RW = 0 ; //写模式 LCD_EN = 0 ; //使能复位 LCD_DB = Command ; //发送数据到P0总线 delay_ms( 5 ) ; LCD_EN = 1 ; //使能拉高 delay_ms( 1 ) ; LCD_EN = 0 ; //下降沿数据写入 delay_ms( 1 ) ;}/********************************************************Name :LCD_Write_DataFunction :LCD写入数据Paramater : Data:数据Return :None********************************************************/void LCD_Write_Data( u8 Data ){ LCD_RS = 1 ; //数据模式 LCD_RW = 0 ; //写模式 LCD_EN = 0 ; //使能复位 LCD_DB = Data ; //发送数据到P0总线 delay_ms( 5 ) ; LCD_EN = 1 ; //使能拉高 delay_ms( 1 ) ; LCD_EN = 0 ; //下降沿数据写入 delay_ms( 1 ) ;}/********************************************************Name :LCD_InitFunction :LCD初始化Paramater :NoneReturn :None********************************************************/void LCD_Init(){ LCD_Write_Command( 0x38 ) ; //8位总线宽度+显示2行+每个字符占用5×10的点阵 LCD_Write_Command( 0x0C ) ; //开启显示+关闭光标+关闭光标显示 LCD_Write_Command( 0x06 ) ; //光标右移+写入数据后显示屏不移动 LCD_Write_Command( 0x01 ) ; //清屏}/********************************************************Name :LCD_Show_StringFunction :LCD显示字符串Paramater :NoneReturn :None********************************************************/void LCD_Show_String( u8 x, u8 y, u8 *str ){ u8 Address ; //计算坐标 switch( y ) { case 0: Address=0x80+x ; //第一行数据地址 break; case 1: Address=0xC0+x ; //第二行数据地址 break; default: break; } //写入数据 LCD_Write_Command( Address ) ; //设置写入地址 while( *str!='\0' ) { LCD_Write_Data( *str ) ; //写入数据 str ++ ; //指针地址累加 }}/********************************************************************************************************* DS1302 时 钟 程 序*********************************************************************************************************//********************************************************Name :DS1302_Write_ByteFunction :DS1302写入字节Paramater : Byte:写入的字节Return :None********************************************************/void DS1302_Write_Byte( u8 Byte ){ u8 i ; for( i=0; i<8; i++ ) { if( ( Byte&0x01 )==0x01 ) //判断最低位是1 DS_IO = 1 ; //数据线拉高发送1 else DS_IO = 0 ; //数据线拉低发送0 Byte >>= 1 ; //数据右移一个位 DS_CLK = 0 ; //时钟线复位 DS_CLK = 1 ; //时钟线拉高产生上升沿 }}/********************************************************Name :DS1302_Read_ByteFunction :DS1302读取字节Paramater :NoneReturn :读取的字节********************************************************/u8 DS1302_Read_Byte(){ u8 i, Byte ; DS_CLK = 1 ; //时钟线拉高 Byte = 0 ; for( i=0; i<8; i++ ) { Byte >>= 1 ; //数据右移一个位 DS_CLK = 0 ; //时钟线拉低产生下降沿 if( DS_IO==1 ) //判断数据线上的值为1 Byte |= 0x80 ; //字节写入1 DS_CLK = 1 ; //时钟线拉高 } return Byte ;}/********************************************************Name :DS1302_Read_TimeFunction :DS1302读取时间Paramater :NoneReturn :None********************************************************/void DS1302_Read_Time(){ u8 i, Byte ; u8 Read_Address[] = { 0x81, 0x83, 0x85, 0x87, 0x89, 0x8D } ; //寄存器地址 for( i=0; i<6; i++ ) { DS_RST = 0 ; //复位 DS_CLK = 0 ; //时钟线复位 DS_RST = 1 ; //停止复位 DS1302_Write_Byte( Read_Address[ i ] ) ; //发送地址 Byte = DS1302_Read_Byte() ; //读取数据 switch( i ) { case 0: Time.Second = ( ( Byte&0xF0 )>>4 )*10+( Byte&0x0F ) ; //计算秒 break ; case 1: Time.Minute = ( ( Byte&0xF0 )>>4 )*10+( Byte&0x0F ) ; //计算分 break ; case 2: Time.Hour = ( ( Byte&0xF0 )>>4 )*10+( Byte&0x0F ) ; //计算时 break ; case 3: Time.Date = ( ( Byte&0xF0 )>>4 )*10+( Byte&0x0F ) ; //计算日 break ; case 4: Time.Month = ( ( Byte&0xF0 )>>4 )*10+( Byte&0x0F ) ; //计算月 break ; case 5: Time.Year = ( ( Byte&0xF0 )>>4 )*10+( Byte&0x0F ) ; //计算年 break ; } }}/********************************************************************************************************* 主 函 数*********************************************************************************************************/void main(){ LCD_Init() ; LCD_Show_String( 0, 0, " 2000 - 00 - 00 " ) ; LCD_Show_String( 0, 1, " 00 : 00 : 00 " ) ; while( 1 ) { DS1302_Read_Time() ; //DS1302读取时间 //显示年 LCD_Write_Command( 0x80+3 ) ; //写入显示地址 LCD_Write_Data( 0x30+Time.Year/10 ) ; //写入十位 LCD_Write_Data( 0x30+Time.Year%10 ) ; //写入个位 //显示月 LCD_Write_Command( 0x80+8 ) ; //写入显示地址 LCD_Write_Data( 0x30+Time.Month/10 ) ; //写入十位 LCD_Write_Data( 0x30+Time.Month%10 ) ; //写入个位 //显示日 LCD_Write_Command( 0x80+13 ) ; //写入显示地址 LCD_Write_Data( 0x30+Time.Date/10 ) ; //写入十位 LCD_Write_Data( 0x30+Time.Date%10 ) ; //写入个位 //显示时 LCD_Write_Command( 0xC0+2 ) ; //写入显示地址 LCD_Write_Data( 0x30+Time.Hour/10 ) ; //写入十位 LCD_Write_Data( 0x30+Time.Hour%10 ) ; //写入个位 //显示分 LCD_Write_Command( 0xC0+7 ) ; //写入显示地址 LCD_Write_Data( 0x30+Time.Minute/10 ) ; //写入十位 LCD_Write_Data( 0x30+Time.Minute%10 ) ; //写入个位 //显示秒 LCD_Write_Command( 0xC0+12 ) ; //写入显示地址 LCD_Write_Data( 0x30+Time.Second/10 ) ; //写入十位 LCD_Write_Data( 0x30+Time.Second%10 ) ; //写入个位 }}

总结

以上是生活随笔为你收集整理的51单片机基本刷屏测试实验_51单片机开发基础8——实时时钟实验的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。