历史上的今天
返回首页

历史上的今天

今天是:2025年06月25日(星期三)

正在发生

2018年06月25日 | 关于STM32驱动DS1302的一点思考

2018-06-25 来源:eefocus

之前用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的时候,我遇到的基本上就是上面这些情况了。如果还有朋友遇到其他情况,可以一起讨论。


推荐阅读

史海拾趣

Bivar公司的发展小趣事

在电子行业的竞争中,Bivar公司意识到单打独斗难以取得长久的成功。因此,公司积极寻求与其他企业的合作机会,共同开发新产品、拓展市场。通过与上下游企业的紧密合作,Bivar不仅降低了成本、提高了效率,还获得了更多的资源和支持,进一步巩固了市场地位。

Blue Sky Research公司的发展小趣事

Blue Sky Research深知人才是企业发展的核心。因此,公司一直注重人才培养和团队建设。通过招聘优秀的研发人员、销售人员和管理人员,公司打造了一支高素质、专业化的团队。同时,公司还建立了完善的培训体系和晋升机制,为员工提供广阔的职业发展空间。这些措施不仅提升了员工的归属感和忠诚度,也为公司的长期发展奠定了坚实的基础。

长园维安(CYGWAYON)公司的发展小趣事

长园维安一直注重产品质量管理。公司建立了完善的质量管理体系,通过ISO9001、TS16949等认证,确保产品质量的稳定性和可靠性。此外,长园维安还积极推行6Sigma等质量管理方法,不断提高产品质量和客户满意度。这些措施使长园维安赢得了客户的信赖和好评。

Galaxy Microelectronics公司的发展小趣事

江苏飞翼智能科技有限公司成立于2023年,该公司迅速将无人机技术与大数据技术相结合,实现了从传统无人机表演企业向无人机应用型企业的转型。通过自主研发和技术创新,飞翼智能的无人机在地理测绘、土方开挖计算、三维建模等领域展现出强大实力。例如,在地理测绘领域,无人机通过镜头采集地面照片,结合后期处理,能够高效完成数据测绘,大大降低了人工用量,提高了工作效率。这一技术的成功应用,不仅为公司赢得了市场认可,也推动了无人机技术的智能化发展。

BEKA Associates Ltd公司的发展小趣事

随着全球电子市场的不断发展,BCD Semi(Diodes)积极寻求国际化拓展的机会。公司通过与国外知名企业的合作,成功进入了多个海外市场。在国际市场上,BCD Semi凭借其高品质的产品和专业的服务,赢得了客户的信赖和支持。同时,公司还积极参加国际电子展会和交流活动,与全球同行进行深入的交流与合作,不断提升自身的国际影响力。

Comair Rotron公司的发展小趣事

1947年,James Van Ryan在美国纽约伍德斯托克创立了Comair公司,初期主要专注于风扇和风机产品的制造。当时,电子行业正处于起步阶段,对散热和空气流动的需求日益增长。Comair凭借其出色的技术和产品质量,很快在电子行业中崭露头角,为早期的电子设备提供了可靠的散热解决方案。

问答坊 | AI 解惑

TTL CMOS

不能光分享别人的啊…

查看全部问答>

关于单片机开发问题?

请教大师: 原有一单片机系统它接受速度、温度、位移等传感器的信号,其中位移传感器是人为控制输入的,通过单片机系统CPU处理器计算处理输出脉冲数据信号到歩进电机或伺服电机的驱动电路,最终来控制歩进电机或伺服电机的正向和反向运转以及运转 ...…

查看全部问答>

帮忙:请问谁有BCM5241的应用图纸

我找了好久都没有找以,谁有可以给我一份吗 WAJS998@163.COM 谢谢了…

查看全部问答>

关于nano2410移植bsp的问题

我是个新手,最近领导让搞wince,买了一块友善之臂nano2410的板子,32m dram;64m flash。打算做一些wince 方面的开发。但是nano2410只有nk和eboot文件,不提供bsp和sdk。所以我打算找一个其他的bsp移植上去,同时在pb中生成sdk,用于vs中的开发。 ...…

查看全部问答>

STM32FSMC驱动彩屏.

用STM32 FSMC 16位总线驱动 220*176 的彩色OLED屏幕 (65536色)刷全屏的时候可以明显看到刷的过程.. 是正常的么?..这样的话那怎么播放画阿..…

查看全部问答>

STM32F10x,在进入ISP后,BOOT0管脚一直有脉冲

   会导致ISP失败吗??     ISP过程中,BOOT0需要恒为低电平吗?      /* RM0008第23页:   在系统复位后,SYSCLK的第4个上升沿,BOOT管脚的值将 ...…

查看全部问答>

TMS320C6472 多核 DSP & EVM 工业及嵌入式应用

$(\'swf_Ewr\').innerHTML=AC_FL_RunContent(\'width\', \'550\', \'height\', \'400\', \'allowNetworking\', \'internal\', \'allowScriptAccess\', \'never\', \'src\', encodeURI(\'http://player.youku.com/player.php/sid/XMzQ4NTI4NDk2/v.sw ...…

查看全部问答>

使用MSP430注意的问题

1.#i nclude<>指要在编辑器设定目录下,#i nclude\"\"指的是在当前工程目录下。2.要调用另一个文件中的函数,要把这个函数文件放到当前工程目录下,并且在工程中添加此文件。3.命名中不能有-,比如:byq-ee会认为是错误的,要用下划线。4.用IAR ...…

查看全部问答>

提问+MCU开发方式

   大家来说说硬件库开发方式和寄存器开发方式吧。51等8位机就不用说了,也没有多少寄存器呢。说说16位的430呢,我个人觉得呢寄存器不是太多呢,当然也不是说把它背下来呢,个人觉得这样也太占我的“ROM”了。   TI也针对5XX和 ...…

查看全部问答>