[创新制造展示] 自制简易电子秤(第五篇——OLED显示实时时间)

[复制链接]
 楼主| Andrew55 发表于 2018-7-27 15:31 | 显示全部楼层 |阅读模式
说起来这个功能确实有点**肋,电子秤上显示时间,但是不得不说对于在外面摆摊的小商小贩来说,确实有那么点用。
毕竟时不时的看看时间,心里有谱嘛。
实时时钟我才用的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 口的驱动能力,这样信号比较稳定,计时也比较准确。

到此可以实现引脚的配置:

  1. /*----------------------------------
  2. **函数名称:DS1302_GPIO_Init
  3. **功能描述:DS1302引脚初始化
  4. **参数说明:无
  5. **作者:Andrew
  6. **日期:2018.1.25
  7. -----------------------------------*/
  8. static void DS1302_GPIO_Init(void)
  9. {
  10.          GPIO_InitTypeDef GPIO_InitStruct;  
  11.    
  12.     //开启GPIOD的时钟  
  13.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  

  14.     //设置GPIO的基本参数  
  15.     GPIO_InitStruct.GPIO_Pin = DS1302_SCK_PIN | DS1302_CE_PIN ;  
  16.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;    //这两个普通端口设为推挽输出  
  17.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    //输出速度50MHz  
  18.     GPIO_Init(DS1302_PORT, &GPIO_InitStruct);  

  19.          GPIO_InitStruct.GPIO_Pin = DS1302_IO_PIN;         //这里最好设成开漏,当然也可以普通推挽,但是需要后面一直切换输入输出模式
  20.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;     //开漏输出,需要接上拉,不需要切换输入输出了。
  21.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    //输出速度50MHz  
  22.     GPIO_Init(DS1302_PORT, &GPIO_InitStruct);

  23. }

四、分析时序图

这是单字节写入的时序图,可见,先拉高使能端,进行使能选择,然后在时钟上升沿写入一个字节。

DS1302在进行读写操作时最少读写两个字节,第一个是控制字节,就是一个命令,说明是读还是写操作,第二个时需要读写的数据。

对于单字节写,只有在SCLK为低电平时才能将 CE 置高电平,所以刚开始将SCLK 置低,CE置高,然后把需要写入的字节送入 IO口,然后跳变SCLK,在SCLK下降沿时,写入数据


五、编写驱动程序

有了 上面的分析,我们就可以学着编写驱动程序了,可以把驱动程序分为几个模块来写,由底层慢慢往上累加,

先实现单个字节的读写操作,然后在实现对外的数据读写接口函数

  1. /*----------------------------------
  2. **函数名称:DS1302_WriteByte
  3. **功能描述:DS1302写一个字节操作,从最低位开始写入
  4. **参数说明:byte_1为要写入的字节
  5. **作者:Andrew
  6. **日期:2018.1.25
  7. -----------------------------------*/
  8. static void DS1302_WriteByte(u8 byte_1)
  9. {
  10.         u8 i = 0;
  11.         u8 t = 0x01;
  12.        
  13.         for(i = 0;i<8;i++)
  14.         {
  15.                 if((byte_1 & t) != 0)     //之前的问题出在这里,32的位带操作不能赋值0和1之外的值。
  16.                 {
  17.                         DS1302_DATOUT = 1;
  18.                 }
  19.                 else
  20.                 {
  21.                         DS1302_DATOUT = 0;
  22.                 }
  23.                
  24.                 DS1302_delay_us(2);
  25.                 DS1302_SCK = 1;  //上升沿写入
  26.                 DS1302_delay_us(2);
  27.                 DS1302_SCK = 0;
  28.                 DS1302_delay_us(2);
  29.                
  30.                 t<<= 1;
  31.         }
  32.         DS1302_DATOUT = 1;      //释放IO,后面读取的话会准确很多
  33.         DS1302_delay_us(2);     //因为如果写完之后IO被置了低电平,开漏输出模式下读取的时候会有影响,最好先拉高,再读取
  34. }

  35. /*----------------------------------
  36. **函数名称:DS1302_WriteData
  37. **功能描述:DS1302写数据
  38. **参数说明:addr:为要写入的地址
  39.                         data_:为要写入的数据
  40. **作者:Andrew
  41. **日期:2018.1.25
  42. -----------------------------------*/
  43. static void DS1302_WriteData(u8 addr,u8 data_)
  44. {       
  45.         DS1302_CE = 0;                DS1302_delay_us(2);       
  46.         DS1302_SCK = 0;                DS1302_delay_us(2);       
  47.         DS1302_CE = 1;                DS1302_delay_us(2);        //使能片选信号
  48.        
  49.         DS1302_WriteByte((addr<<1)|0x80);        //方便后面写入,转化之后是地址寄存器的值,
  50.         DS1302_WriteByte(data_);
  51.         DS1302_CE = 0;                DS1302_delay_us(2);//传送数据结束,失能片选
  52.         DS1302_SCK = 0;     DS1302_delay_us(2);//拉低,准备下一次写数据
  53. }

  54. /*----------------------------------
  55. **函数名称:DS1302_ReadByte
  56. **功能描述:DS1302读取一个字节,上升沿读取
  57. **参数说明:无
  58. **作者:Andrew
  59. **日期:2018.1.25
  60. -----------------------------------*/
  61. static u8 DS1302_ReadByte(void)
  62. {
  63.         u8 i = 0;
  64.         u8 data_ = 0;
  65.        
  66.         //DS1302_DAT_INPUT();  //因为上面已经把端口设置为开漏,电路外部接了山拉电阻,可以不切换输入输出模式,直接使用。
  67.        
  68.         DS1302_SCK = 0;
  69.         DS1302_delay_us(3);
  70.        
  71.         for(i=0;i<7;i++)   //这里发现设为8的话输出数据不对,很乱
  72.         {
  73.                 if((DS1302_DATIN) == 1)
  74.                 {
  75.                         data_ = data_ | 0x80;        //低位在前,逐位读取,刚开始不对,估计是这个的问题
  76.                 }
  77.                 data_>>= 1;
  78.                 DS1302_delay_us(3);
  79.                
  80.                 DS1302_SCK = 1;  //因为刚开始SCK是低电平,这里拉高作为第一个上升沿
  81.                 DS1302_delay_us(3);
  82.                 DS1302_SCK = 0;
  83.                 DS1302_delay_us(3);
  84.         }
  85.          return (data_);
  86. }

  87. /*----------------------------------
  88. **函数名称:DS1302_ReadData
  89. **功能描述:DS1302读取数据
  90. **参数说明:addr:为需要读取数据的地址
  91. **作者:Andrew
  92. **日期:2018.1.25
  93. -----------------------------------*/
  94. static u8 DS1302_ReadData(u8 addr)
  95. {
  96.         u8 data_ = 0;

  97.         DS1302_CE = 0;                DS1302_delay_us(2);
  98.         DS1302_SCK = 0;                DS1302_delay_us(2);
  99.         DS1302_CE = 1;                DS1302_delay_us(2);   //读写操作时CE必须为高,切在SCK为低时改变
  100.        
  101.         DS1302_WriteByte((addr<<1)|0x81);   //写入读时间的命令
  102.         data_ = DS1302_ReadByte();
  103.        
  104.         DS1302_SCK = 1;          DS1302_delay_us(2);
  105.         DS1302_CE = 0;            DS1302_delay_us(2);
  106.         DS1302_DATOUT = 0;  DS1302_delay_us(3);  //这里很多人说需要拉低,但是我发现去掉这个也可以显示啊,不过为了保险,还是加上。
  107.         DS1302_DATOUT = 1;  DS1302_delay_us(2);

  108.         return data_;
  109. }

然后就可以读取内部时间

  1. /*----------------------------------
  2. **函数名称:DS1302_ReadTime
  3. **功能描述:DS1302读取时间到缓冲区
  4. **参数说明:无
  5. **作者:Andrew
  6. **日期:2018.1.25
  7. -----------------------------------*/
  8. void DS1302_ReadTime(void)
  9. {
  10.           u8 i;
  11.           for(i = 0;i<7;i++)
  12.           {
  13.              init_time[i] = DS1302_ReadData(i);
  14.           }
  15. }

读取到数据后,还不是最终要显示的时间,因为我们读取到的是BCD码,需要进行转ASCII码

  1. //BCD码转换ASCII码
  2.         TIME.year =  ((init_time[6]&0x70)>>4)*10 + (init_time[6]&0x0f); //高三位加低四位
  3.         TIME.month = ((init_time[4]&0x70)>>4)*10 + (init_time[4]&0x0f);
  4.         TIME.date =  ((init_time[3]&0x70)>>4)*10 + (init_time[3]&0x0f);
  5.         TIME.week =  ((init_time[5]&0x70)>>4)*10 + (init_time[5]&0x0f);
  6.         TIME.hour =  ((init_time[2]&0x70)>>4)*10 + (init_time[2]&0x0f);
  7.         TIME.minute = ((init_time[1]&0x70)>>4)*10 + (init_time[1]&0x0f);
  8.         TIME.second = ((init_time[0]&0x70)>>4)*10 + (init_time[0]&0x0f);

这里结构体中数据就是我们最终要显示的时间

然后利用我之前的一篇帖子中提到的OLED的操作,就可以实现OLED显示时间了:

具体显示效果




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
小小电子爱好者 发表于 2018-7-27 19:28 | 显示全部楼层
来了
 楼主| Andrew55 发表于 2018-7-27 20:39 | 显示全部楼层

恩,谢谢你的持续关注。
大漠小孤狼 发表于 2018-8-23 21:35 | 显示全部楼层
能加qq吗,楼主
 楼主| Andrew55 发表于 2018-9-1 23:24 | 显示全部楼层

请问有什么事吗
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

20

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部