实时时钟DS1302(SPI协议)
目录一,DS1302芯片二,SPI总线概念三,代码实践一,DS1302芯片一.DS1302的主要性能指标(1)DS1302实时时钟具有能计算2100年之前的秒、分、时、日、日期、星期、月、年的能力,还有闰年调整的能力。(2)内部含有31个字节静态RAM,可提供用户访问。(3)采用串行数据传送方式,使得管脚数量最少,简单SPI 3线接口。(4)工作电压范围宽:2.0~5.5V。(5)工作电流:2.0
一,DS1302芯片
一.DS1302的主要性能指标
(1)DS1302实时时钟具有能计算2100年之前的秒、分、时、日、日期、星期、月、年的能力,还有闰年调整的能力。
(2)内部含有31个字节静态RAM,可提供用户访问。
(3)采用串行数据传送方式,使得管脚数量最少,简单SPI 3线接口。
(4)工作电压范围宽:2.0~5.5V。
(5)工作电流:2.0V时,小于300nA。
(6)时钟或RAM数据的读/写有两种传送方式:单字节传送和多字节传送方式。
(7)采用8脚DIP封装或SOIC封装。
(8)与TTL兼容,Vcc=5V。
(9)可选工业级温度范围:-40C~+85C。
(10)具有涓流充电能力。
(11)采用主电源和备份电源双电源供应。
(12)备份电源可由电池或大容量电容实现。
二.引脚功能
DS1302的引脚如图所示
引脚 | 功能 |
---|---|
X1、X2 | 32.768KHz晶振接入引脚 |
GND | 接地 |
RST | 复位引脚,低电平有效,操作时高电平 |
I/O | 数据输入/输出引脚,具有三态功能 |
SCLK | 串行时钟输入引脚 |
Vcc1 | 工作电源引脚 |
Vcc2 | 备用电源引脚。 接入电池断电时提供1302电源 |
三.DS1302的寄存器
DS1302有一个控制寄存器、12个日历、时钟寄存器和31个RAM,可读写.
DS1302有下列几组寄存器:
① DS1302有关日历、时间的寄存器共有12个,其中有7个寄存器(读时81h~8Dh,写时80h~8Ch),存放的数据格式为BCD码形式。
小时寄存器(85h、84h):位7用于定义DS1302是运行于12小时模式还是24小时模式。当为1时选12小时制,当为0时选24小时制。当12小时制时,D5位为1是上午,D5位为0是下午,D4为小时的十位。当24小时制时,D5、D4位为小时的十位。
秒寄存器(81h、80h):位7定义为时钟暂停标志(CH)。当该位置为1时,时钟振荡器停止,DS1302处于低功耗状态;当该位置为0时,时钟开始运行。
控制寄存器(8Fh、8Eh):位7是写保护位(WP),其它7位均置为0。写保护寄存器中的WP为写保护位,当WP=1,写保护,当WP=0未写保护,当对日历、时钟寄存器或片内RAM进行写时WP应清零,当对日历、时钟寄存器或片内RAM进行读时WP一般置1。
②DS1302有关RAM的地址
DS1302中附加31字节静态RAM的地址如图4所示。
DS1302片内有31个RAM单元,对片内RAM的操作有两种方式:单字节方式和多字节方式。当控制命令字为C0H ~ FDH时为单字节读写方式,命令字中的D5~D1用于选择对应的RAM单元,其中奇数为读操作,偶数为写操作。
当控制命令字为FEH、FFH时为多字节操作(表中的RAM突发模式),多字节操作可一次把所有的RAM单元内容进行读写。FEH为写操作,FFH为读操作。
③ DS1302的工作模式寄存器
所谓突发模式是指一次传送多个字节的时钟信号和RAM数据。突发模式寄存器如图5所示。
④此外,DS1302还有充电寄存器等。
四.数据输入输出(I/O)
在控制指令字输入后的下一个SCLK时钟的上升沿时,数据被写入DS1302,数据输入从低位即位0开始。同样,在紧跟8位的控制指令字后的下一个SCLK脉冲的下降沿读出DS1302的数据,读出数据时从低位0位到高位7。
DS1302是通过SPI串行总线跟单片机通信的,当进行一次读写操作时最少得读写两个字节,第一个字节是控制字节,就是一个命令,告诉DS1302是读还是写操作,是对RAM还是对CLOK寄存器操作。第二个字节就是要读或写的数据了。
单字节读写:只有在SCLK为低电平时,才能将CE置为高电平。所以在进行操作之前先将SCLK置低电平,然后将CE置为高电平,接着开始在IO上面放入要传送的电平信号,然后跳变SCLK。数据在SCLK上升沿时,DS1302读写数据,在SCLK下降沿时,DS1302放置数据到IO上。
二,SPI总线概念
SPI接口的全称是“Serial Peripheral Interface”,意为串行外围接口,SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。 SPI接口是在CPU和外围低速器件之间进行同步串行数据传输,在主器件的移位脉冲下,数据按位传输,高位在前,地位在后,为全双工通信,数据传输速度总体来说比I2C总线要快,速度可达到几Mbps。SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。
三,代码实践
原理图:
ds1302.h
#ifndef __DS1302_H__
#define __DS1302_H__
void delay(void);
void delay900ms(void);
void ds1302_write_reg(unsigned char addr, unsigned char value);
unsigned char ds1302_read_reg(unsigned char addr);
void ds1302_read_time(void);
void ds1302_write_time(void);
void debug_print_time(void);
#endif
ds1302.c
#include <reg51.h>
#include <intrins.h>
#include "uart.h"
#include "ds1302.h"
/************** 全局变量定义 *************************************/
// 定义SPI的三根引脚
sbit DSIO = P3^4;
sbit RST = P3^5;
sbit SCLK = P3^6;
// 因为51单片机的设计本身RAM比较少而Flash稍微多一些,像这里定义的数组内部
// 的内容是不会变的(常量数组),我们就可以使用code关键字,让编译器帮我们
// 把这个数组放在flash中而不是ram中,这样做可以省一些ram。
unsigned char code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
unsigned char code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
unsigned char time[7]; // 用来存储读取的时间的,格式是:秒分时日月周年
// 有用函数
void delay(void)
{
unsigned char i;
for (i=0; i<3; i++);
}
void delay900ms(void) //误差 -0.000000000205us
{
unsigned char a,b,c;
for(c=127;c>0;c--)
for(b=128;b>0;b--)
for(a=24;a>0;a--);
}
// 向ds1302的内部寄存器addr写入一个值value
void ds1302_write_reg(unsigned char addr, unsigned char value)
{
unsigned char i = 0;
unsigned char dat = 0;
// 第1部分: 时序起始
SCLK = 0;
delay();
RST = 0;
delay();
RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
delay();
// 第2部分: 写入第1字节,addr
for (i=0; i<8; i++)
{
dat = addr & 0x01; // SPI是从低位开始传输的
DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
delay(); // 读走之后,一个小周期就完了
SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
delay();
addr >>= 1; // 把addr右移一位
}
// 第3部分: 写入第2字节,value
for (i=0; i<8; i++)
{
dat = value & 0x01; // SPI是从低位开始传输的
DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
delay(); // 读走之后,一个小周期就完了
SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
delay();
value = value >> 1; // 把addr右移一位
}
// 第4部分: 时序结束
SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
delay();
RST = 0; // RST拉低意味着一个大周期的结束
delay();
}
// 从ds1302的内部寄存器addr读出一个值,作为返回值
unsigned char ds1302_read_reg(unsigned char addr)
{
unsigned char i = 0;
unsigned char dat = 0; // 用来存储读取到的一字节数据的
unsigned char tmp = 0;
// 第1部分: 时序起始
SCLK = 0;
delay();
RST = 0;
delay();
RST = 1; // SCLK为低时,RST由低变高,意味着一个大的周期的开始
delay();
// 第2部分: 写入要读取的寄存器地址,addr
for (i=0; i<8; i++)
{
dat = addr & 0x01; // SPI是从低位开始传输的
DSIO = dat; // 把要发送的bit数据丢到IO引脚上去准备好
SCLK = 1; // 制造上升沿,让DS1302把IO上的值读走
delay(); // 读走之后,一个小周期就完了
SCLK = 0; // 把SCLK拉低,是为了给下一个小周期做准备
delay();
addr >>= 1; // 把addr右移一位
}
// 第3部分: 读出一字节DS1302返回给我们的值
dat = 0;
for (i=0; i<8; i++)
{
// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值
// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续
// 读取下一个bit
tmp = DSIO;
dat |= (tmp << i); // 读出来的数值是低位在前的
SCLK = 1; // 由于上面SCLK是低,所以要先拉到高
delay();
SCLK = 0; // 拉低SCLK制造一个下降沿
delay();
}
// 第4部分: 时序结束
SCLK = 0; // SCLK拉低为了后面的周期时初始状态是对的
delay();
RST = 0; // RST拉低意味着一个大周期的结束
delay();
// 第5部分:解决读取时间是ff的问题
DSIO = 0;
return dat;
}
void ds1302_read_time(void)
{
unsigned char i = 0;
for (i=0; i<7; i++)
{
time[i] = ds1302_read_reg(READ_RTC_ADDR[i]);
}
}
void ds1302_write_time(void)
{
unsigned char i = 0;
// 准备好要写入的时间
time[0] = 0x24; // 对应 24s
time[1] = 0x39; // 对应 39m
time[2] = 0x11; // 对应 11h
time[3] = 0x11; // 对应 6日
time[4] = 0x06; // 对应 12月
time[5] = 0x02; // 对应 星期2
time[6] = 0x20; // 对应 2016年
ds1302_write_reg(0x8E, 0x00); // 去掉写保护
for (i=0; i<7; i++)
{
ds1302_write_reg(WRITE_RTC_ADDR[i], time[i]);
}
ds1302_write_reg(0x8E, 0x80); // 打开写保护
}
// 通过串口将7个时间以二进制方式输出在串口助手上
void debug_print_time(void)
{
unsigned char i = 0;
while (1)
{
// 1 从DS1302读取时间
ds1302_read_time();
// 2 for循环内打印一组7个时间
for (i=0; i<7; i++)
{
uart_send_byte(time[i]);
}
// 3 延时900ms后再继续下个周期
delay900ms();
}
}
uart.h
#ifndef __UART_H__
#define __UART_H__
#include <reg51.h>
void uart_init(void);
void uart_send_byte(unsigned char c);
#endif
uart.c
#include "uart.h"
// 串口设置为: 波特率9600、数据位8、停止位1、奇偶校验无
// 使用的晶振是11.0592MHz的,注意12MHz和24MHz的不行
void uart_init(void)
{
// 波特率9600
SCON = 0x50; // 串口工作在模式1(8位串口)、允许接收
PCON = 0x00; // 波特率不加倍
// 通信波特率相关的设置
TMOD = 0x20; // 设置T1为模式2
TH1 = 253;
TL1 = 253; // 8位自动重装,意思就是TH1用完了之后下一个周期TL1会
// 自动重装到TH1去
TR1 = 1; // 开启T1让它开始工作
ES = 1;
EA = 1;
}
// 通过串口发送1个字节出去
void uart_send_byte(unsigned char c)
{
// 第1步,发送一个字节
SBUF = c;
// 第2步,先确认串口发送部分没有在忙
while (!TI);
// 第3步,软件复位TI标志位
TI = 0;
}
main.c
#include "uart.h"
#include "ds1302.h"
void main(void)
{
uart_init();
ds1302_write_time();
debug_print_time();
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)