文章节选自:《ARM Cortex-M0从这里开始》 作者:zhaojun_xf https://bbs.eeworld.com.cn/thread-324656-1-1.html
数字时钟芯片 数字时钟芯片的种类很多,市场上比较流行的有三线的DS1302、I2C总线的PCF8563等。其中,DS1302价格比较低廉、容易购买,非常适合DIY。下面将用数字时钟芯片DS1302设计电子时钟。 1 DS1302概述 DS1302 是DALLAS公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和31字节静态RAM,通过简单的串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、月、年和星期等信息。DS1302与单片机之间能简单地采用同步串行的方式进行通信,仅需用到三个口线:(1)RES(复位),(2)I/O(数据线),(3)SCLK(串行时钟)。 1. DS1302的特点: 实时时钟具有能计算2100 年之前的秒、分、时、日、星期、月、年的能力、还有闰年调整的能力; 31×8位暂存数据存储RAM; 串行I/O口方式使得管脚数量最少; 宽范围工作电压2.0 ~5.5V; 工作电流2.0V时,小于300nA; 读/写时钟或RAM数据时有两种传送方式单字节传送和多字节传送字符组方式; 8脚DIP封装或可选的8脚SOIC封装根据表面装配; 与TTL兼容Vcc = 5V; 2. DS1302管脚描述 DS1302有DIP和SOIC两种封装,引脚排列如图8-5所示。
表8-2是DS1302的管脚描述。
3. DS1302指令与寄存器说明 DS1302有多条控制指令,其中比较常用的指令如表8-3所示。对于DS1302的数据格式均是采用BCD编码,高位没有使用的用“0”填充。
寄存器说明: CH:时钟停止位 CH = 0 振荡器工作允许; CH = 1 振荡器停止;
WP:写保护位 WP = 0 寄存器数据能够写入; WP = 1 寄存器数据不能写入;
TCS:涓流充电选择 TCS = 1010 使能涓流充电; TCS = 其他 禁止涓流充电;
DS:二极管选择位 DS = 01 选择一个二极管; DS = 10 选择两个二极管; DS = 00或11,即使TCS = 1010,充电功能也被禁止。 2 硬件电路的设计 对于DS1302的连接非常简单,只需要把它的3条控制线与LPC1114的3条GPIO相连就可以了,具体的硬件连接如图8-6所示。这里使用了一个3V纽扣电池作为备用电池,保证时钟在系统断电下也能够正常工作。
3 软件的实现 DS1302的控制并不难,只需要按照其时序和控制格式编写程序就能实现时钟控制。由于DS1302是专一的时钟芯片,并不需要开发者对时间进行控制。在应用中只要设置好正确的时间后,就可以直接从芯片中读取时间值来。 1. 指令和数据定义 为了方便控制,首先在头文件中定义DS1302的控制命令、数据格式,再定义个全面变量,用于存放时钟值,如程序清单8-1所示。 /****************************** 程序清单8-1 **********************************/ /******************************************************************************** * 【宏定义】 macro definition ********************************************************************************/
/*************** 指令格式 ******************************/
#define DS1302_READ_SEC (0x81) // 读秒 #define DS1302_READ_MIN (0x83) // 读分 #define DS1302_READ_HOUR (0x85) // 读时 #define DS1302_READ_DATE (0x87) // 读日 #define DS1302_READ_MON (0x89) // 读月 #define DS1302_READ_WEEK (0x8B) // 读周 #define DS1302_READ_YEAR (0x8D) // 读年
#define DS1302_WRITE_SEC (0x80) // 写秒 #define DS1302_WRITE_MIN (0x82) // 写分 #define DS1302_WRITE_HOUR (0x84) // 写时 #define DS1302_WRITE_DATE (0x86) // 写日 #define DS1302_WRITE_MON (0x88) // 写月 #define DS1302_WRITE_WEEK (0x8A) // 写周 #define DS1302_WRITE_YEAR (0x8C) // 写年
#define DS1302_READ_CALENDAR (0xBF) // 读多字节 #define DS1302_WRITE_CALENDAR (0xBE) // 写多字节
#define DS1302_READ_PROTECT (0x8F) // 读保护 #define DS1302_WRITE_PROTECT (0x8E) // 写保护
#define DS1302_READ_CHARGE (0x91) // 读充电指令 #define DS1302_WRITE_CHARGE (0x90) // 写充电指令
/*************** 数据格式 ******************************/
#define DS1302_PROTECT_DIS (0x00) // 00000000 寄存器数据能够写入 #define DS1302_PROTECT_EN (0x80) // 10000000 寄存器数据不能写入
#define DS1302_CHARGE_DIS0 (0xA4) // 101001xx 充电开一个二极管 #define DS1302_CHARGE_DIS1 (0xA8) // 101010xx 充电开两个二极管 #define DS1302_CHARGE_EN (0x00) // 0000xxxx 充电关
#define DS1302_OSC_DIS (0x00) // 0xxxxxxx 振荡器工作允许 #define DS1302_OSC_EN (0x80) // 1xxxxxxx 振荡器停止
#define DS1302_MODE_12 (0x80) // 1xxxxxxx 12小时制 #define DS1302_MODE_24 (0x00) // 0xxxxxxx 24小时制
#define DS1302_MODE_AM (0x00) // xxx0xxxx 上午 #define DS1302_MODE_PA (0x10) // xxx1xxxx 下午
/******************************************************************************** * 【数据结构】 Data Structures ********************************************************************************/ typedef struct { uint8 Sec; // 秒 uint8 Min; // 分 uint8 Hour; // 时 uint8 Date; // 日 uint8 Month; // 月 uint8 Week; // 周 uint8 Year; // 年 } DS1302Clock; // 时钟定义 2. 写操作 写操作完成从数据端口写入一个字节数据到DS1302,数据的写入是通过8次,按顺序写入8个位。 /****************************** 程序清单8-2 **********************************/ /******************************************************************************** * FunctionName : DS1302_WriteByte() * Description : 往DS1302写入1Byte数据 * EntryParameter : ucDa 写入的数据 * ReturnValue : None ********************************************************************************/ void DS1302_WriteByte(uint8 ucDa) { uint8 i; for(i=8; i>0; i--) { DS1302_CLK_CLR(); // 时钟拉低 if (ucDa & 0x01) { DS1302_IO_SET(); // 写入1 } else { DS1302_IO_CLR(); // 写入0 } DS1302_CLK_SET(); // 时钟拉高 ucDa >>= 1; // 写入一位数据 } } 3. 读操作 读操作是实现从DS1302的数据端口读取一个字节数据,在读取数据之前先要设置数据端口为输入模式;然后8次读取数据端口数据组成一字节数据;读取完数据后需要把数据端口设置成为输出端口,方便其他函数实现写入功能。 /****************************** 程序清单8-3 **********************************/ /******************************************************************************** * FunctionName : DS1302_ReadByte() * Description : 从DS1302读取1Byte数据 * EntryParameter : None * ReturnValue : 读取的数据 ********************************************************************************/ uint8 DS1302_ReadByte(void) { uint8 i,tmp,value; DS1302_IO_DIR_IN(); // 端口设置为输入 for(i=8; i>0; i--) { tmp >>= 1; DS1302_CLK_SET(); // 时钟信号 DS1302_Delay(1);
DS1302_CLK_CLR(); // 时钟信号 DS1302_Delay(1); value = DS1302_IO_PIN(); if (value == 1) // 位判断 { tmp |= 0x80; // 写入"1" } } DS1302_IO_DIR_OUT(); // 设置端口为输出 return(tmp); } 4.写时钟数据 图8-7所示为写一字节时钟数据的时序图,写入数据必须把CE拉高,然后先写入地址或指令字节,之后写入一字节的相应数据即可。
/****************************** 程序清单8-4 **********************************/ /******************************************************************************** * FunctionName : DS1302_WriteOne() * Description : 往DS1302地址写入数据。先写地址,后写命令/数据 * EntryParameter : ord - DS1302地址, dat - 要写的数据 * ReturnValue : None ********************************************************************************/ void DS1302_WriteOne(uint8 ord, uint8 dat) { DS1302_CE_CLR(); // DS1302_CE = 0 DS1302_CLK_CLR(); // DS1302_CLK = 0 DS1302_CE_SET(); // DS1302_CE = 1
DS1302_WriteByte(ord); // 命令 DS1302_WriteByte(dat); // 写1Byte数据 DS1302_CE_CLR(); // DS1302_CE = 0 DS1302_CLK_SET(); // DS1302_CLK = 1 } 5. 读时钟数据 如图8-8所示为从DS1302读取一字节时钟数据的时序图,读取数据的整个过程中也必须把CE拉高,然后写写入一字节地址或指令,之后就可以从数据端口读取一字节的数据了。
/****************************** 程序清单8-5 **********************************/ /******************************************************************************** * FunctionName : DS1302_ReadOne() * Description : 读取DS1302某地址的数据,先写地址,后读命令/数据 * EntryParameter : ord - DS1302命令 * ReturnValue : 返回读取数据 ********************************************************************************/ uint8 DS1302_ReadOne(uint8 ord) { uint8 ucDa; DS1302_CE_CLR(); // DS1302_CE = 0 DS1302_CLK_CLR(); // DS1302_CLK = 0 DS1302_CE_SET(); // DS1302_CE = 1
DS1302_WriteByte(ord); // 地址,命令 ucDa = DS1302_ReadByte(); // 读1Byte数据 DS1302_CE_CLR(); // DS1302_CE = 0 DS1302_CLK_SET(); // DS1302_CLK = 1 return(ucDa); // 返回读取数据 } 6. 初始化DS1302 在进行DS1302的操作之前必须初始化DS1302。初始化DS1302包括设置端口为GPIO、设置端口的方向以及写保护操作和关闭涓流充电。 在设置端口为GPIO时需要特别注意,对于有AD功能的GPIO端口,必须设置成为数字输入模式而不是模拟输入模式,否则无法读取端口数据。 /****************************** 程序清单8-6 **********************************/ /******************************************************************************** * FunctionName : DS1302Init() * Description : 初始化DS18B20 * EntryParameter : None * ReturnValue : None ********************************************************************************/ void DS1302Init(void) { // 设置为GPIO DS1302_CLK_PIN_MOD(); DS1302_IO_PIN_MOD(); DS1302_CE_PIN_MOD();
// 设置为输出端口 DS1302_CLK_DIR_OUT(); // CLK DS1302_IO_DIR_OUT(); // IO DS1302_CE_DIR_OUT(); // CE
DS1302_WriteOne(DS1302_WRITE_PROTECT,DS1302_PROTECT_EN); // 写保护操作 DS1302_WriteOne(DS1302_WRITE_CHARGE,DS1302_CHARGE_EN); // 禁止充电 } 7. 设置时钟 对于时钟的设置,项目中一共提供了两个函数,这两个函数的操作是一样的,不同的只是数据存放的形式不同。一个是把时钟数据放入数组中,一个是把时钟数据放入结构体中。本项目中使用的是第二个函数,代码如程序清单8-7所示。 写入时钟值之前必须先开写入保护功能,再按照顺序写入时钟值,完成后需要恢复保护功能,预防干扰等更改时钟值。 /****************************** 程序清单8-7 **********************************/ /******************************************************************************** * FunctionName : DS1302SetClock1() * Description : 初始时间格式为: 秒 分 时 日 月 星期 年 * EntryParameter : pSecDa - 初始时间地址。 * ReturnValue : None ********************************************************************************/ void DS1302SetClock1(DS1302Clock *clock) { DS1302_WriteOne(DS1302_WRITE_PROTECT,DS1302_PROTECT_DIS); // 控制命令,写操作 DS1302_WriteOne(DS1302_WRITE_SEC, clock->Sec); // 写秒 DS1302_WriteOne(DS1302_WRITE_MIN, clock->Min); // 写分 DS1302_WriteOne(DS1302_WRITE_HOUR,clock->Hour); // 写小时 DS1302_WriteOne(DS1302_WRITE_DATE,clock->Date); // 写日期 DS1302_WriteOne(DS1302_WRITE_MON, clock->Month); // 写月 DS1302_WriteOne(DS1302_WRITE_WEEK,clock->Week); // 写星期 DS1302_WriteOne(DS1302_WRITE_YEAR,clock->Year); // 写年
DS1302_WriteOne(DS1302_WRITE_PROTECT,DS1302_PROTECT_EN); // 控制命令,写保护 } 8. 获取时钟 同样读取时钟值也有两个函数,区别和设置时钟函数是一样的,代码如程序清单8-8所示,这里就不在叙述。 /****************************** 程序清单8-8 **********************************/ /******************************************************************************** * FunctionName : DS1302GetClock1() * Description : 读取DS1302当前时间,当前时间格式为: 秒 分 时 日 月 星期 年 * EntryParameter : clock - 保存当前时间地址。 * ReturnValue : None ********************************************************************************/ void DS1302GetClock1(DS1302Clock *clock) { clock->Sec = DS1302_ReadOne(DS1302_READ_SEC); // 读秒 clock->Min = DS1302_ReadOne(DS1302_READ_MIN); // 读分 clock->Hour = DS1302_ReadOne(DS1302_READ_HOUR); // 读小时 clock->Date = DS1302_ReadOne(DS1302_READ_DATE); // 读日期 clock->Month = DS1302_ReadOne(DS1302_READ_MON); // 读月 clock->Week = DS1302_ReadOne(DS1302_READ_WEEK); // 读星期 clock->Year = DS1302_ReadOne(DS1302_READ_YEAR); // 读年 }
|