说起来这个功能确实有点**肋,电子秤上显示时间,但是不得不说对于在外面摆摊的小商小贩来说,确实有那么点用。
毕竟时不时的看看时间,心里有谱嘛。
实时时钟我才用的DS1302芯片,不过说实话,这个芯片真的不怎么样,如果不经常进行校准的话,一个星期能快个10分钟不成问题。
但是我们的目的是学习。在得到了正确的时间之后,再想办法进行自动校时吧。
实际上我之前也写过关于蓝牙校时的一个程序,但是我暂时不会使用Java写一个Android的客户端进行一键校时。只能利用手机上的串口助手进行校时操作。
但是相对还是比较方便的,毕竟解决了重新下载程序进行校准时间的问题。
这个蓝牙校时有时间在分享到21ic吧,有兴趣的朋友可以看看我的博客:
https://www.cnblogs.com/qsyll0916/p/8977364.html
还是进入正题——DS1302的使用。
其实能查到很多资料,但是能为我们所用的不是很多。在使用一个芯片时,我一般时按照一下步骤去学习: 1、芯片介绍; 2、查看引脚定义; 3、外围电路 4、分析时序图; 5、模仿着编写驱动程序,然后自己动手写驱动。 6、实现功能。 下面我就按照这个顺序去学习这款芯片; 一、芯片介绍 DS1302是DALLAS(达拉斯)公司出的一款涓流充电时钟芯片,2001年DALLAS被MAXIM(美信)收购,因此我们看到的DS1302的数据手册既有DALLAS的标志,又有MAXIM的标志;
DS1302实时时钟芯片广泛应用于电话、传真、便携式仪器等产品领域,他的主要性能指标如下:
1、DS1302是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且还有软年自动调整的能力,可以通过配置AM/PM来决定采用24小时格式还是12小时格式。
2、拥有31字节数据存储RAM。
3、串行I/O通信方式,相对并行来说比较节省IO口的使用。
4、DS1302的工作电压比较宽,大概是2.0V~5.5V都可以正常工作。采用双电源供电,当主电源比备用电源高0.2V时,由主电源供电,否则采用备用电源,一般是一个纽扣电池。
5、DS1302这种时钟芯片功耗一般都很低,它在工作电压2.0V的时候,工作电流小于300nA。
6、DS1302共有8个引脚,有两种封装形式,一种是DIP-8封装,芯片宽度(不含引脚)是300mil,一种是SOP-8封装,有两种宽度,一种是150mil,一种是208mil。 二、引脚定义
三、外围电路
一般与单片机IO口相连时要加上拉电阻,提高 IO 口的驱动能力,这样信号比较稳定,计时也比较准确。 到此可以实现引脚的配置: /*----------------------------------
**函数名称:DS1302_GPIO_Init
**功能描述:DS1302引脚初始化
**参数说明:无
**作者:Andrew
**日期:2018.1.25
-----------------------------------*/
static void DS1302_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//开启GPIOD的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//设置GPIO的基本参数
GPIO_InitStruct.GPIO_Pin = DS1302_SCK_PIN | DS1302_CE_PIN ;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //这两个普通端口设为推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //输出速度50MHz
GPIO_Init(DS1302_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = DS1302_IO_PIN; //这里最好设成开漏,当然也可以普通推挽,但是需要后面一直切换输入输出模式
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出,需要接上拉,不需要切换输入输出了。
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //输出速度50MHz
GPIO_Init(DS1302_PORT, &GPIO_InitStruct);
}
四、分析时序图
这是单字节写入的时序图,可见,先拉高使能端,进行使能选择,然后在时钟上升沿写入一个字节。 DS1302在进行读写操作时最少读写两个字节,第一个是控制字节,就是一个命令,说明是读还是写操作,第二个时需要读写的数据。 对于单字节写,只有在SCLK为低电平时才能将 CE 置高电平,所以刚开始将SCLK 置低,CE置高,然后把需要写入的字节送入 IO口,然后跳变SCLK,在SCLK下降沿时,写入数据
五、编写驱动程序 有了 上面的分析,我们就可以学着编写驱动程序了,可以把驱动程序分为几个模块来写,由底层慢慢往上累加, 先实现单个字节的读写操作,然后在实现对外的数据读写接口函数 /*----------------------------------
**函数名称:DS1302_WriteByte
**功能描述:DS1302写一个字节操作,从最低位开始写入
**参数说明:byte_1为要写入的字节
**作者:Andrew
**日期:2018.1.25
-----------------------------------*/
static void DS1302_WriteByte(u8 byte_1)
{
u8 i = 0;
u8 t = 0x01;
for(i = 0;i<8;i++)
{
if((byte_1 & t) != 0) //之前的问题出在这里,32的位带操作不能赋值0和1之外的值。
{
DS1302_DATOUT = 1;
}
else
{
DS1302_DATOUT = 0;
}
DS1302_delay_us(2);
DS1302_SCK = 1; //上升沿写入
DS1302_delay_us(2);
DS1302_SCK = 0;
DS1302_delay_us(2);
t<<= 1;
}
DS1302_DATOUT = 1; //释放IO,后面读取的话会准确很多
DS1302_delay_us(2); //因为如果写完之后IO被置了低电平,开漏输出模式下读取的时候会有影响,最好先拉高,再读取
}
/*----------------------------------
**函数名称:DS1302_WriteData
**功能描述:DS1302写数据
**参数说明:addr:为要写入的地址
data_:为要写入的数据
**作者:Andrew
**日期:2018.1.25
-----------------------------------*/
static void DS1302_WriteData(u8 addr,u8 data_)
{
DS1302_CE = 0; DS1302_delay_us(2);
DS1302_SCK = 0; DS1302_delay_us(2);
DS1302_CE = 1; DS1302_delay_us(2); //使能片选信号
DS1302_WriteByte((addr<<1)|0x80); //方便后面写入,转化之后是地址寄存器的值,
DS1302_WriteByte(data_);
DS1302_CE = 0; DS1302_delay_us(2);//传送数据结束,失能片选
DS1302_SCK = 0; DS1302_delay_us(2);//拉低,准备下一次写数据
}
/*----------------------------------
**函数名称:DS1302_ReadByte
**功能描述:DS1302读取一个字节,上升沿读取
**参数说明:无
**作者:Andrew
**日期:2018.1.25
-----------------------------------*/
static u8 DS1302_ReadByte(void)
{
u8 i = 0;
u8 data_ = 0;
//DS1302_DAT_INPUT(); //因为上面已经把端口设置为开漏,电路外部接了山拉电阻,可以不切换输入输出模式,直接使用。
DS1302_SCK = 0;
DS1302_delay_us(3);
for(i=0;i<7;i++) //这里发现设为8的话输出数据不对,很乱
{
if((DS1302_DATIN) == 1)
{
data_ = data_ | 0x80; //低位在前,逐位读取,刚开始不对,估计是这个的问题
}
data_>>= 1;
DS1302_delay_us(3);
DS1302_SCK = 1; //因为刚开始SCK是低电平,这里拉高作为第一个上升沿
DS1302_delay_us(3);
DS1302_SCK = 0;
DS1302_delay_us(3);
}
return (data_);
}
/*----------------------------------
**函数名称:DS1302_ReadData
**功能描述:DS1302读取数据
**参数说明:addr:为需要读取数据的地址
**作者:Andrew
**日期:2018.1.25
-----------------------------------*/
static u8 DS1302_ReadData(u8 addr)
{
u8 data_ = 0;
DS1302_CE = 0; DS1302_delay_us(2);
DS1302_SCK = 0; DS1302_delay_us(2);
DS1302_CE = 1; DS1302_delay_us(2); //读写操作时CE必须为高,切在SCK为低时改变
DS1302_WriteByte((addr<<1)|0x81); //写入读时间的命令
data_ = DS1302_ReadByte();
DS1302_SCK = 1; DS1302_delay_us(2);
DS1302_CE = 0; DS1302_delay_us(2);
DS1302_DATOUT = 0; DS1302_delay_us(3); //这里很多人说需要拉低,但是我发现去掉这个也可以显示啊,不过为了保险,还是加上。
DS1302_DATOUT = 1; DS1302_delay_us(2);
return data_;
}
然后就可以读取内部时间 /*----------------------------------
**函数名称:DS1302_ReadTime
**功能描述:DS1302读取时间到缓冲区
**参数说明:无
**作者:Andrew
**日期:2018.1.25
-----------------------------------*/
void DS1302_ReadTime(void)
{
u8 i;
for(i = 0;i<7;i++)
{
init_time[i] = DS1302_ReadData(i);
}
}
读取到数据后,还不是最终要显示的时间,因为我们读取到的是BCD码,需要进行转ASCII码 //BCD码转换ASCII码
TIME.year = ((init_time[6]&0x70)>>4)*10 + (init_time[6]&0x0f); //高三位加低四位
TIME.month = ((init_time[4]&0x70)>>4)*10 + (init_time[4]&0x0f);
TIME.date = ((init_time[3]&0x70)>>4)*10 + (init_time[3]&0x0f);
TIME.week = ((init_time[5]&0x70)>>4)*10 + (init_time[5]&0x0f);
TIME.hour = ((init_time[2]&0x70)>>4)*10 + (init_time[2]&0x0f);
TIME.minute = ((init_time[1]&0x70)>>4)*10 + (init_time[1]&0x0f);
TIME.second = ((init_time[0]&0x70)>>4)*10 + (init_time[0]&0x0f);
这里结构体中数据就是我们最终要显示的时间 然后利用我之前的一篇帖子中提到的OLED的操作,就可以实现OLED显示时间了: 具体显示效果
|