关于STM32驱动DS1302实时时钟的一点思考
关于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 <stm32f10x.h> #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,0x00for(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实时时钟的一点思考相关推荐
- 4.7 51单片机-DS1302 实时时钟芯片
4.7 DS1302 实时时钟芯片 4.7.1 原理图介绍 图4-7-1 图4-7-2 驱动DS1302之前,实验板上需要将JP595跳线帽和J11跳线帽断开.JP1302跳线帽接上. 4.7.2 D ...
- 基于51单片机的DS1302实时时钟
本讲内容: 介绍DS1302实时时钟芯片基本知识,演示DS1302例程. DS1302实时时钟: DS1302能提供包括秒.分.时.日期.月份和年份信息.闰年可自行调整,可选择12小时制和24小时制, ...
- 51单片机学习笔记-8 DS1302实时时钟
8 DS1302实时时钟 [toc] 注:笔记主要参考B站江科大自化协教学视频"51单片机入门教程-2020版 程序全程纯手打 从零开始入门". 注:工程及代码文件放在了本人的Gi ...
- DS1302实时时钟芯片
DS1302 是 DALLAS(达拉斯) 公司推出的一款涓流充电时钟芯片, 2001 年 DALLAS 被 MAXIM(美信) 收购,因此我们看到的 DS1302 的数据手册既有 DALLAS 的标志 ...
- 树莓派基础实验32:DS1302实时时钟模块实验
一.介绍 现在有很多流行的串行时钟芯片,如DS1302,DS1307,PCF8485等,由于简单的接口,低成本和易用性,他们被广泛应用于电话.传真.便携式仪器等产品领域.在本实验中,我们将使用DS ...
- 【STM32学习】实时时钟 —— RTC
[STM32学习]实时时钟 -- RTC 零.参考 一.工作原理 1.RTC介绍 2.工作过程 二.相关寄存器 三.代码说明 1.rtc初始化 2.关于中断 3.中断配置代码(仅供参考) 3.1 秒中 ...
- STM32之RTC实时时钟
RTC实时时钟简介: STM32的RTC外设,实质是一个掉电后还继续运行的定时器,从定时器的角度来看,相对于通用定时器TIM外设,它的功能十分简单,只有计时功能(也可以触发中断).但是从掉电还能继续运 ...
- 【STM32】RTC实时时钟,步骤超细详解,一文看懂RTC
什么是RTC RTC (Real Time Clock):实时时钟 RTC是个独立的定时器.RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能.修改计数器的值可以重新设置当 ...
- arm linux应用调用rtc接口,ARMLinux驱动RTC(实时时钟)驱动分析
硬件平台:FL2440(S3C2440) 内核版本:Linux 2.6.28本文引用地址:http://www.eepw.com.cn/article/201611/317629.htm 主机平台:U ...
最新文章
- 如何设计一门语言(七)——闭包、lambda和interface
- hadoop重命名文件_面试系列:深入理解hadoop架构体系
- 阿里巴巴的AI算法程序媛是怎样的一种存在?
- 产品经理最痛苦的事情
- 最新pvz服务器补偿码,阴阳师:补偿来了!大量活动导致服务器崩溃,现已修复且下发补偿...
- NoSql数据库确实非常适合网站
- puppetmaster 自动签名
- 下载mooc视频字幕
- VSCode安装教程详细简单版
- python中使用什么表示代码块、不需要使用大括号_PYTHON基础语法
- 《计算机视觉与图像:八大热点公司以及九大应用场景》
- 特斯拉如何饕餮中国红利?
- 工业物联网实施的6个常见误解
- Windows鼠标指针美化
- Bookmark Sentry – 检查重复、删除死链书签 Chrome扩展
- Unity3dC#分布式游戏服务器ET框架介绍-组件式设计
- Centos7修改时区、时间
- 远程计算机的凭据无法工作,Windows远程桌面时提示凭证不工作问题的解决办法...
- 论文笔记:AAAI 2021 Beyond Low-frequency Information in Graph Convolutional Networks
- 《安富莱嵌入式周报》第227期:2021.08.23--2021.08.29
热门文章
- C#_完整的RSA操作类
- 关于读构建之法后的疑惑
- BZOJ 1603: [Usaco2008 Oct]打谷机
- Jzoj3931【NOIP2014day1官方数据】联合权值
- 给交叉编译工具建立软连接用脚本
- 如何运维能让网站稳定高效--稳定篇
- 单词evolve 	pro	legacy	 launcher session
- liunx(3)-内核模块编写与系统调用
- m_Orchestrate learning system---二十四、thinkphp里面的ajax如何使用
- #180111mysql启动错误