打印
[创新制造展示]

自制简易电子秤(第五篇——OLED显示实时时间)

[复制链接]
3057|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
说起来这个功能确实有点**肋,电子秤上显示时间,但是不得不说对于在外面摆摊的小商小贩来说,确实有那么点用。
毕竟时不时的看看时间,心里有谱嘛。
实时时钟我才用的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显示时间了:

具体显示效果




相关帖子

沙发
小小电子爱好者| | 2018-7-27 19:28 | 只看该作者
来了

使用特权

评论回复
板凳
Andrew55|  楼主 | 2018-7-27 20:39 | 只看该作者

恩,谢谢你的持续关注。

使用特权

评论回复
地板
大漠小孤狼| | 2018-8-23 21:35 | 只看该作者
能加qq吗,楼主

使用特权

评论回复
5
Andrew55|  楼主 | 2018-9-1 23:24 | 只看该作者

请问有什么事吗

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

20

帖子

4

粉丝