关于STM32驱动DS1302的一点思考

发布者:salahc1983最新更新时间: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  


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

    

}


在驱动DS1302的时候,我遇到的基本上就是上面这些情况了。如果还有朋友遇到其他情况,可以一起讨论。


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

上一篇:stm32F103状态机矩阵键盘
下一篇:基于STM32的DS1302时钟芯片驱动

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

STM32到CONST的全局变量
程序如下: const int globalConstDat = 12; int globalDat = 11; int main(void) { int localDat = 6; const int localConstDat = 7; USART_Configuration(); //ptint to PC from USART1 printf( &globalConstDat = 0x%p, &globalDat = 0x%prnrn , &globalConstDat, &globalDat); printf( &localDat = 0x%p, &localConstDat = 0x%prn , &localDat
[单片机]
STM32简易交通灯设计(定时可调)
//按键key1 设置红灯时间 按键key2 设置绿灯时间 按键key3 设置黄灯时间 #include stm32f10x_lib.h #include sys.h #include delay.h #include usart.h #include stdlib.h static int G=0,R=0,Y=0; //设置绿,红,黄灯时间变量 #include lcd.h #include key.h #include time.h //----------- 按键函数---------- void scan_key1() { static u8 i,j; if(
[单片机]
<font color='red'>STM32</font>简易交通灯设计(定时可调)
STM32学习日志——主函数框架:时间轮
读大学时候,老师曾教了一种叫时间轮的方法,当时没好好学习。自己在写程序时经常乱用延时,一方面占用CPU资源,原本单片机可以执行更多的任务,却被用来延时,特别浪费,另一方面可能出现各种各样的问题。 使用时间轮的方法,可以使单片机像人一样,以时间为尺度,来规划任务。人可以决定今天几点吃饭,几点看书,几点睡觉。 而单片机依靠定时器的中断,每隔0.5ms进入一次中断,可以设定在任意的时间点执行合适的任务,比如需要usart发送比较重要的数据的,可以让它以1ms间隔发送,比如不重要的数码管显示,可以让他200ms执行一次。而这个框架可以直接套用于主程序。 配置定时器 主程序框架 主程序框架
[单片机]
STM32中SysTick在3.5固件库中的应用(1)
一、SysTick STM32内核中有一个系统定时器,它是一个24位递减计数器。工作原理是系统时基定时器设定初值并使能后,每经过1个系统时钟周期,计数值就减,当计数值减到0时,系统定时器会重新自动重装初值,并继续下一次计数,同时内部的COUNTFLAG标志位会置位。触发中断。 在很早的固件库中,提供了很多函数,来对SysTick进行设置,但是到了3.5版本的标准固件库中,移除了相关驱动函数,用户必须调用CMSIS 定义的函数,其中CMSIS只提供了一个Systick设置的函数,替代了STM32原来有的所有的驱动函数,这样做的目的,可能是简化Systick 的设置,可是降低了用户对SysTick的可控性。 在CMSIS中提供的函数是
[单片机]
两种方法实现stm32的8个串口通信
100引脚的stm32f103系列的stm32只有5个内置的串口控制器,那么如何实现8个串口通信呢? 方法1: 用普通的io模拟串口通信。这里除了IO端口外,还需要一个定时来控制发送和接收的波特率。定时器的定时单位一般为1位数据的发送时间。假设波特率为vRate,那么定时时间为1/vRate;但是如果波特率比较大的话(256000bps),定时时间一般要 (1/VRate),因为一些其他额外指令的执行时间,不能忽略不计了。这种方法可以支持更多的端口. 方法2:USART的复用功能重映射,USART3支持3个端口(PB10-PB11,PD9-PD10),USART2支持2个端口(PA2-PA3,PD5,PD6),USART1
[单片机]
上拉电阻与下拉电阻的定义及作用
什么是上拉电阻? 将一个不确定信号(高或低电平),通过一个电阻与电源VCC相连,固定在高电平; 什么是下拉电阻? 将一个不确定信号(高或低电平),通过一个电阻与地GND相连,固定在低电平。 图:左边的是上拉电阻示意图,右边的是下拉电阻示意图(无内容无关联) 上拉电阻与下拉电阻用在什么场合? 答:用在数字电路中,存在高低电平的场合。 上拉电阻与下拉电阻怎么接线? 答:上拉电阻:电阻一端接VCC,一端接逻辑电平接入引脚(如单片机引脚) 下拉电阻:电阻一端接GND,一端接逻辑电平接入引脚(如单片机引脚) 上拉电阻与下拉电阻的作用 1、提高输出引脚的驱动能力: 例如,当STM32的CPU引脚输出高电平,但由于后续电路的影响,输出的
[单片机]
上拉电阻与下拉电阻的定义及作用
STM32CubeMX+TrueSTUDIO开发STM32的全新方式
记得第一次接触STM32,是在大一暑假的时候,学长们可以说是手把手教我们这群小学弟(当然,还有几个小学妹),首先,我要向我们的社团及学长们致敬!当时用的STM32开发板都是学长们自己设计的,由于我们是跟着学长从51单片机过渡来的,所以IDE还是用的Keil,现在还记得学长一步步教我们破解的骚操作,哈哈。STM32编程不像51,include一个reg51.h之后就可以写main函数了,STM32的配置涉及时钟数、GPIO口、中断优先级等,一套流程下来代码量还是比较多的,所以学长给我们做了一套模板工程,自己开发的时候,只需要用keil打开模板工程,对配置部分的代码进行复制粘贴修改就可以了。这套模板对我的STM32学习影响很大,在暑期
[单片机]
基于STM32的内部Flash读写操作
本文主要介绍STM32多种的内部Flash读写方式和读写长文件的功能函数怎样编写。阅读完本文可以使你能够正常的完成Flash读写操作。 介绍 STM32 FLASH 不同型号的 STM32,其 FLASH 容量也有所不同,最小的只有 16K 字节,最大的则达到了1024K 字节。本次实验选用的STM32 开发板是F103ZET6,其 FLASH 容量为 512K 字节,属于大容量产品(另外还有中容量和小容量产品),大容量产品的闪存模块组织如图 所示: STM32 的闪存模块由:主存储器、信息块和闪存存储器接口寄存器等 3 部分组成。 主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品
[单片机]
基于<font color='red'>STM32</font>的内部Flash读写操作
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

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

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

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