关于STM32驱动DS1302实时时钟的一点思考

发布者:见贤思奇异果最新更新时间:2018-06-25 来源: eefocus关键字:STM32  驱动DS1302  实时时钟 手机看文章 扫描二维码
随时随地手机看文章

之前用51驱动过DS1302,没用多久就输出了正确的时间。当时以为这块芯片其实没啥,很简单。但是现在用STM32做项目,用到同样的芯片,以为这有何难,只要把那个程序拿过来复制黏贴改一下IO设置不就行了?但是事情远没有想想的那么简单。               


经过3天的挣扎,现在才知道当时自己是多么天真。


关于DS1302的基本操作可以看这里:http://www.cnblogs.com/qsyll0916/p/7712695.html


好了,废话少说了,进入正题。


首先DS1302读写方式属于3线SPI。CE、SCK、IO。其中IO口属于双向IO口,我们读写都要经过这个IO口。在用51开发的时候,因外他是准双向IO,不需要我们额外关心他的输入输出设置。需要输出的时候直接写            P0^1 = 1;   


需要检测外部输入的时候直接写       if(P0^1 == 1)   ,


都很方便,但是方便的同时带来的是读写速度上的限制。那么在STM32中,每个IO口都有8种输出模式。复杂的同时也意味着每一种模式都是专门定制的,带来了速度上的优势。所以在移植这个程序的时候,就需要注意这个双向IO的设置问题。一开始我也不是很懂,各种百度查资料,各种问人。最后才知道有两种方式可以实现双向的IO读写设置。


第一:


#define DS1302_DATIN        PBin(6)  

#define DS1302_DATOUT       PBout(6)


#define DS1302_DAT_INPUT()     {GPIOB->CRL&= 0XF0FFFFFF;GPIOB->CRL|= 8<<24;}  //设置成上拉或者下拉输入模式,需要外接上拉电阻

#define DS1302_DAT_OUTPUT()    {GPIOB->CRL&= 0XF0FFFFFF;GPIOB->CRL|= 3<<24;}  //设置成最大50M的通用推挽输出

通过简单的寄存器操作,可以实现输入输出的快速切换。需要在端口处接上拉电阻。


第二:



    GPIO_InitTypeDef GPIO_InitStruct;  

    

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  


    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);


把IO配置成开漏输出模式。必须外接上拉电阻,不然读出的全是低电平。这样就不用一直切换输入输出模式,可以直接像51那样,直接使用。


双向IO的配置基本上就是上面所说的两种情况,但是可能还有其他方式,但是我目前就只知道这个方式,后面学习了在补充。


配置完IO之后就开始对IO进行操作,从而读写DS1302,获取实时时间。读写的时候特别需要注意。网上很多教程都没有提到这一点,估计是太基础了吧。但是这个小小的问题却困扰了我很长时间。就是在进行对DS1302的写命令和数据的操作的时候,我们需要按照从低位到高位依次发送数据。发送数据的时候:


在51里面我们经常会这样写:{    SCIO = byte_1 & 0x01;  byte_1 >> = 1;    }


那么在32中我们可以这样写吗?:   {    PBout(6) =  byte_1 & 0x01;  byte_1 >> = 1;    }


绝对不行。虽然看上去没有什么问题,也不会报错,但是却就是不同正确通信。很气人。


原因是PBout(6)这样的操作是属于STM32的位带操作。但是在CM3中不允许位带操作赋值除0和1以外的数。


也就是说上面那种操作方式是给  PBout(6) 赋值2,3,4,之类的数,但是stm32却不能理解这是什么意思。因为它只认识0和1!!!


所以我们可以简单的这样处理:



        if((byte_1 & t) != 0)     //之前的问题出在这里,32的位带操作不能赋值0和1之外的值。

        {

            DS1302_DATOUT = 1;

        }

        else

        {

            DS1302_DATOUT = 0;

        }


这样就可以正常通信了。但是如果不能像上面那样处理的话,现象是一直读出85这个数。关于读出85这个数,除了上面我提到的这种特殊情况,网上还是有很多人有这方面的经验的,我总结了一下,大致是下面这几种情况:


1、读取完时间后没有把DATA_IO引脚拉低。导致显示问号和85等一些乱七八糟的东西。但是我加了。

2、电压不够,小于4.6V。但是这个网上有争议,我接的是5V,实测4.8V,应该没问题。

3、没有接上拉电阻。我只在需要双向IO的地方加了上拉电阻,利用的是板子上预留的IIC的SDA,上面有一个4.7K的上拉,我把IO接在了这里。应该也没问题

4、仿真时序不对。但是我之前用这个时序在51上面实现过一样的功能,现在移植到32上应该也没什么问题啊,延时时间也仿真了,严格按照1us的延时仿真的。


但是在我的实验过程中还是发现了很多其他的现象,再次也记录下来,防止有人遇到相同的问题,就能不浪费那么多时间。


1、确实要注意DS1302的电压,最好不要用STM32开发板上面的3.3V,反正我是没做出来。如果用外部电源给DS1302供电的话,需要将外部电源和开发板共地,不然读出全是85.


2、DS1302如果需要修改时间。需要把初始化函数里面的上电保护去掉,再次下载重置的时间,然后再把上电保护那段给添加上去,防止复位后时间被重置。


目前我的问题就是这么多。在一一解决了上述问题之后,就能准确的读取时间了,实测一天24H误差不超过2s,还是很准的了。


好了,下面就是源程序了。


首先是DS1302的头文件,主要是一些位带操作和预定义



#ifndef __DS1302_H

#define __DS1302_H


#include

#include "hardware.h"


//相对应的IO口配置

#define DS1302_PORT         GPIOB


#define DS1302_SCK_PIN         GPIO_Pin_7        //时钟

#define DS1302_IO_PIN         GPIO_Pin_6    //双向IO口,

#define DS1302_CE_PIN         GPIO_Pin_5   //片选使能,当需要读写的时候,置高位


#define DS1302_SCK          PBout(7)  //位带操作,可直接给高低电平,但是切记不能给0.1之外的数。切记

#define DS1302_CE           PBout(5)

#define DS1302_DATIN        PBin(6)  

#define DS1302_DATOUT       PBout(6)

//存放时间

typedef struct _time{ 


    u8 second;

    u8 minute;

    u8 hour;

    u8 date;

    u8 month;

    u8 week;

    u8 year;


}my_time;

void DS1302_Init(void);

void ds1302_readtime(void);

void display_real_time(void);  //显示实时时间


#endif


DS1302操作源文件:



#include "ds1302.h"

#include "spi.h"


//READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};//读取时间的命令地址,已经通过读写操作来直接实现这些地址

//WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};//写时间的命令地址


my_time TIME = {0};  //显示时间的结构体

u8 init_time[] = {0x00,0x28,0x21,0x27,0x12,0x06,0x17}; //初始化时间:秒 分 时 日 月 周 年



static void ds1302_gpio_init(void);

static void ds1302_writebyte(u8 byte_1);//写一个字节; byte是保留字,不能作为变量

static void ds1302_writedata(u8 addr,u8 data_);//给某地址写数据,data是c51内部的关键字,表示将变量定义在数据存储区,故此处用data_;

static u8 ds1302_readbyte(void);//读一个字节

static u8 ds1302_readdata(u8 addr);//读取某寄存器数据;

static void DS1302_delay_us(u16 time);  //简单延时1us



//基本IO设置

static void ds1302_gpio_init(void)

{

    GPIO_InitTypeDef GPIO_InitStruct;  

    

    //开启GPIOD的时钟  

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, 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);


}


//写一个字节

//数据和地址都是从最低位开始传输的

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被置了低电平,开漏输出模式下读取的时候会有影响,最好先拉高,再读取

}


//地址写数据

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);//拉低,准备下一次写数据

}


//读取一个字节,上升沿读取

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;

        DS1302_delay_us(3);

        DS1302_SCK = 0;

        DS1302_delay_us(3);

    }

     return (data_);

}


//读取寄存器的值

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_;

}


void DS1302_Init(void)

{

     u8 i = 0;

    

    ds1302_gpio_init();  //端口初始化

    

    DS1302_CE = 0;  DS1302_delay_us(2);

    DS1302_SCK = 0; DS1302_delay_us(2);  

    

    i  = ds1302_readdata(0x00);  //读取秒寄存器,


     if((i & 0x80) != 0)//通过判断秒寄存器是否还有数据来决定下次上电的时候是否初始化时间,就是掉电保护

    {

         ds1302_writedata(7,0x00); //撤销写保护,允许写入数据,0x8e,0x00


        for(i = 0;i<7;i++)

        {

            ds1302_writedata(i,init_time[i]);

        }

    }

         ds1302_writedata(7,0x80);//打开写保护功能,防止干扰造成的数据写入。

}


//************

void ds1302_readtime(void)   //读取时间

{

      u8 i;

      for(i = 0;i<7;i++)

      {

         init_time[i] = ds1302_readdata(i);

      }

}


static void DS1302_delay_us(u16 time)

{    

   u16 i = 0;  

   while(time--)

   {

      i = 5;  //自己定义

      while(i--);

   }

}


//显示实时时间

void display_real_time(void)

{

    

    ds1302_readtime();   //先获取时间到缓冲区

    

    //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_ShowNum(48,0,TIME.hour,2,16);

    

    OLED_ShowChar(64,0,':');


    OLED_ShowNum(72,0,TIME.minute,2,16);

    

    OLED_ShowChar(88,0,':');

    

    OLED_ShowNum(96,0,TIME.second,2,16);

    

}


关键字:STM32  驱动DS1302  实时时钟 引用地址:关于STM32驱动DS1302实时时钟的一点思考

上一篇:stm32+DS1302+TM1638驱动程序
下一篇:趣谈STM32的ADC与DMA

推荐阅读最新更新时间:2024-03-16 16:06

STM32 字节对齐 #pragma pack
1、对齐原则 min(sizeof(word ), 4) = 2,因此是 2 字节对齐,而不是我们认为的 4 字节对齐。 1)每个成员分别按自己的方式对齐,并能最小化长度; 2)复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度; 3)对齐后的结构体整体长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。 对于数组,比如 char a : 它的对齐方式和分别写 3 个 char 是一样的,也就是说它还是按 1 个字节对齐; 如果是 typedef char Array3 : Array3 这种类型的对齐方式还是按 1 个字节
[单片机]
STM32学习第一周之 循环显示RGBLED的七种颜色
连接 RGB LED模块,使得该模块能够依次显示 7 种颜色的光,并且可以循环显示。 注意:添加一个 RGBLED.c 和 RGBLED.h 文件 RGBLED.h 文件里面,对 RGBLED 使用的引脚和端口进行宏定义 RGBLED.c 文件里面,设计一个 RGBLED_Init 函数,一个 RGBLED_Display(颜色值) 函数 RGBLED.h #ifndef __RGBLED_H #define __RGBLED_H #include sys.h #define R PEout(7) #define G PEout(8) #define B PEout(9) #define RED 1 #de
[单片机]
STM32平衡小车】一篇快速上手MPU6050
一、MPU6050基础知识储备 六轴=三轴陀螺仪+三轴加速度计 而我们通常说的九轴的含义是在主I2C接口,接上磁力传感器,即是九轴数据。 我们直接得到的是加速度计(测出加速度)和陀螺仪(测出角速度),我们实际使用的是姿态角,分别为俯仰角(pitch)、偏航角(yaw)和横滚角(roll)。这之间需要借助MPU6050的DMP模块把原始数据转换为四元数,然后四元数可以直接通过公式转化为姿态角。 俯仰(pitch) - x轴 翻滚(roll) - y轴 偏航(yaw) - z轴 二、引脚接线图 三、需要注意的AD0 MPU6050作为一个IIC从机设备的时候,有8位地址,高7位的地址是固定的,就是WHOAM
[单片机]
【<font color='red'>STM32</font>平衡小车】一篇快速上手MPU6050
一种基于STM32的心电采集仪设计方案
如今,心血管类疾病已经成为威胁人类身体健康的重要疾病之一,而清晰有效的心电图为诊断这类疾病提供了依据,心电采集电路是心电采集仪的关键部分,心电信号属于微弱信号,其频率范围在0.03~100 Hz之间,幅度在0~5 mV之间,同时心电信号还掺杂有大量的干扰信号,因此,设计良好的滤波电路和选择合适的控制器是得到有效心电信号的关键。基于此,本文设计了以STM32为控制核心,AD620和OP07为模拟前端的心电采集仪,本设计简单实用,噪声干扰得到了有效抑制。   1 总体设计方案   心电采集包括模拟采集和数字处理两部分,本设计通过AgCl电极和三导联线心电采集线采集人体心电信号,通过前置放大电路,带通滤波电路,50 Hz双T陷波后
[单片机]
一种基于<font color='red'>STM32</font>的心电采集仪设计方案
如何调整STM32单片机中flash与时钟速率之间的关系
void Flash_Init(void) { // 调整flash与时钟速率之间的关系 FLASH-》ACR |= FLASH_ACR_LA TE NCY; } void Flash_Unlock(void) { // FLASH-》CR 的第7位为解锁的标志位或者上锁的操作位 while(FLASH-》CR & FLASH_CR_LOCK) { FLASH-》KEYR = FLASH_FKEY1; FLASH-》KEYR = FLASH_FKEY2; } } void Flash_Lock(void) { FLASH-》CR |= FLASH_CR_LOCK; } void Flash_Clear_All_Flag(voi
[单片机]
如何调整<font color='red'>STM32</font>单片机中flash与时钟速率之间的关系
STM32单片机学习笔记(5):ADC模数转换器
项目简介 利用CubMX生成基于32单片机的HAl库工程,然后编写程序在proteus上仿真验证。本项目最适合没有开发板的同学学习,零成本利用仿真软件率先入门STM32单片机。这是第五部分针对STM32单片机内置ADC模块的学习和理解。 硬件模块 STM32F103R4 LDR 串口模块 软件工具 CubMX Proteus KEIL 电路连接图 工作流程 首先创建一个CubMX工程,选择ADC模块 其余保持默认即可。 然后就是编写程序,程序代码在后面会列出来,这里简单说说自己遇到的一些问题。刚开始出来的结果全是0,但是网上有人说在实物开发板上是可以正常运行的。本来想放弃选用ADC0832模块的,不过不幸的事
[单片机]
<font color='red'>STM32</font>单片机学习笔记(5):ADC模数转换器
STM32 ADC基础内容
ADC,Analog-to-Digital Converter(模数转换器),其应用非常广泛,比如温度、湿度、压力、声音等传感器领域。 ADC的类型很多,STM32内部集成的ADC为逐次逼近型。STM32虽然是通用MCU芯片,但它内部集成的ADC也非常出色,不比一些专用ADC芯片差。 1 STM32 ADC 基础内容 STM32内部集成的ADC与型号有关,有16位、12位ADC,内部集成ADC多达4个,通道数多达40个,甚至更多。 1. ADC分辨率 分辨率决定了ADC的转换精度,按理说分辨率越高越好,但价格更贵。 STM32内部集成的ADC最高16位,2的16次方,即65536的分辨率。只有少数STM32才集成16位分辨
[单片机]
<font color='red'>STM32</font> ADC基础内容
使用STM32按键控制LED亮灭
实验环境 Matlab版本 :2021b 操作系统 :Win10专业版 硬件平台 :YF-STM32-ALPHA 1R4 模型与原理图 本次实验所用到的Simulink模型如图5.1所示,实验现象: 按键按下、松开LED循环实现翻转亮灭效果,当按键按下时,产生下降沿脉冲,通过一个计数器对下降沿脉冲进行0~1循环计数,计数到最大值时产生输出信号,输出信号为0时,控制LED熄灭,输出值为1时,控制LED点亮。 按键电路采用对电源负极方式连接,按键松开状态为高电平H,即逻辑1,按键按下状态为低电平L,即逻辑0。 图5.1 按键控制LED亮灭simulink模型 图5.2 按键控制LED亮灭原理图 图5.3 按键在开发板
[单片机]
使用<font color='red'>STM32</font>按键控制LED亮灭
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved