关于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) != ) //之前的问题出在这里,32的位带操作不能赋值0和1之外的值。
{
DS1302_DATOUT = ;
}
else
{
DS1302_DATOUT = ;
}
这样就可以正常通信了。但是如果不能像上面那样处理的话,现象是一直读出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 = {}; //显示时间的结构体
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 = ;
u8 t = 0x01; for(i = ;i<;i++)
{
if((byte_1 & t) != ) //之前的问题出在这里,32的位带操作不能赋值0和1之外的值。
{
DS1302_DATOUT = ;
}
else
{
DS1302_DATOUT = ;
} DS1302_delay_us();
DS1302_SCK = ; //上升沿写入
DS1302_delay_us();
DS1302_SCK = ;
DS1302_delay_us(); t<<= ;
}
DS1302_DATOUT = ; //释放IO,后面读取的话会准确很多
DS1302_delay_us(); //因为如果写完之后IO被置了低电平,开漏输出模式下读取的时候会有影响,最好先拉高,再读取
} //地址写数据
static void ds1302_writedata(u8 addr,u8 data_)
{
DS1302_CE = ; DS1302_delay_us();
DS1302_SCK = ; DS1302_delay_us();
DS1302_CE = ; DS1302_delay_us(); //使能片选信号 ds1302_writebyte((addr<<)|0x80); //方便后面写入,转化之后是地址寄存器的值,
ds1302_writebyte(data_);
DS1302_CE = ; DS1302_delay_us();//传送数据结束,失能片选
DS1302_SCK = ; DS1302_delay_us();//拉低,准备下一次写数据
} //读取一个字节,上升沿读取
static u8 ds1302_readbyte(void)
{
u8 i = ;
u8 data_ = ; //因为上面已经把端口设置为开漏,电路外部接了山拉电阻,可以不切换输入输出模式,直接使用。
// DS1302_DAT_INPUT(); DS1302_SCK = ;
DS1302_delay_us();
for(i=;i<;i++) //这里发现设为8的话输出数据不对,很乱
{
if((DS1302_DATIN) == )
{
data_ = data_ | 0x80; //低位在前,逐位读取,刚开始不对,估计是这个的问题
}
data_>>= ;
DS1302_delay_us(); DS1302_SCK = ;
DS1302_delay_us();
DS1302_SCK = ;
DS1302_delay_us();
}
return (data_);
} //读取寄存器的值
static u8 ds1302_readdata(u8 addr)
{
u8 data_ = ; DS1302_CE = ; DS1302_delay_us();
DS1302_SCK = ; DS1302_delay_us();
DS1302_CE = ; DS1302_delay_us(); //读写操作时CE必须为高,切在SCK为低时改变 ds1302_writebyte((addr<<)|0x81); //写入读时间的命令
data_ = ds1302_readbyte(); DS1302_SCK = ; DS1302_delay_us();
DS1302_CE = ; DS1302_delay_us();
DS1302_DATOUT = ; DS1302_delay_us(); //这里很多人说需要拉低,但是我发现去掉这个也可以显示啊,不过为了保险,还是加上。
DS1302_DATOUT = ; DS1302_delay_us(); return data_;
} void DS1302_Init(void)
{
u8 i = ; ds1302_gpio_init(); //端口初始化 DS1302_CE = ; DS1302_delay_us();
DS1302_SCK = ; DS1302_delay_us(); i = ds1302_readdata(0x00); //读取秒寄存器, if((i & 0x80) != )//通过判断秒寄存器是否还有数据来决定下次上电的时候是否初始化时间,就是掉电保护
{
ds1302_writedata(,0x00); //撤销写保护,允许写入数据,0x8e,0x00 for(i = ;i<;i++)
{
ds1302_writedata(i,init_time[i]);
}
}
ds1302_writedata(,0x80);//打开写保护功能,防止干扰造成的数据写入。
} //************
void ds1302_readtime(void) //读取时间
{
u8 i;
for(i = ;i<;i++)
{
init_time[i] = ds1302_readdata(i);
}
} static void DS1302_delay_us(u16 time)
{
u16 i = ;
while(time--)
{
i = ; //自己定义
while(i--);
}
} //显示实时时间
void display_real_time(void)
{ ds1302_readtime(); //先获取时间到缓冲区 //BCD码转换ASCII码
TIME.year = ((init_time[]&0x70)>>)* + (init_time[]&0x0f); //高三位加低四位
TIME.month = ((init_time[]&0x70)>>)* + (init_time[]&0x0f);
TIME.date = ((init_time[]&0x70)>>)* + (init_time[]&0x0f);
TIME.week = ((init_time[]&0x70)>>)* + (init_time[]&0x0f);
TIME.hour = ((init_time[]&0x70)>>)* + (init_time[]&0x0f);
TIME.minute = ((init_time[]&0x70)>>)* + (init_time[]&0x0f);
TIME.second = ((init_time[]&0x70)>>)* + (init_time[]&0x0f); OLED_ShowNum(,,TIME.hour,,); OLED_ShowChar(,,':'); OLED_ShowNum(,,TIME.minute,,); OLED_ShowChar(,,':'); OLED_ShowNum(,,TIME.second,,); }
在驱动DS1302的时候,我遇到的基本上就是上面这些情况了。如果还有朋友遇到其他情况,可以一起讨论。
关于STM32驱动DS1302实时时钟的一点思考的更多相关文章
- stm32驱动DS1302芯片
天时可以自动调整,且具有闰年补偿功能.工作电压宽达2.5-5.5V.采用双电源供电(主电源和备用电源),可设置备用电源充电方式,提供了对后背电源进行涓细电流充电的能力.DS1302的外部引脚分配如下图 ...
- 【蓝桥杯单片机12】实时时钟DS1302的基本操作
[蓝桥杯单片机12]实时时钟DS1302的基本操作 广东职业技术学院 欧浩源 实时时钟DS1302几乎是蓝桥杯“单片机设计与开发”每年必考的内容,虽然在竞赛现场有提供一个底层读写寄存器的库文件,但是作 ...
- STC8H开发(十四): I2C驱动RX8025T高精度实时时钟芯片
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...
- stm32——RTC实时时钟
stm32——RTC实时时钟 一.关于时间 2038年问题 在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作.所有使用UNIX时间表示时间的程序都将将受其影响,因为它们以自19 ...
- linux 实时时钟(RTC)驱动【转】
转自:http://blog.csdn.net/yaozhenguo2006/article/details/6820218 这个是linux内核文档关于rtc实时时钟部分的说明,此文档主要描述了rt ...
- RTC实时时钟驱动
RTC(Real-Time Clock)实时时钟为操作系统提供了一个可靠的时间,并且在断电的情况下,RTC实时时钟也可以通过电池供电,一直运行下去. RTC通过STRB/LDRB这两个ARM指令向CP ...
- stm32 rtc 实时时钟
STM32的实时时钟是一个独立的定时器 通常会在后备区域供电端加一个纽扣电池,当主电源没有电的时,RTC不会停止工作 若VDD电源有效,RTC可以触发秒中断.溢出中断和闹钟中断 备份寄存器BKP 备份 ...
- 轻松吃透实时时钟芯片DS1302软硬件设计,看完秒懂
今天我们来讨论一款老掉牙的实时时钟芯片DS1302.什么是实时时钟(RealTime Clock, RTC)呢?为什么我们需要它呢?假设你使用单片机实现万年历应用,一般的做法是这样的:设置中断后判断1 ...
- STC8H开发(十三): I2C驱动DS3231高精度实时时钟芯片
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...
随机推荐
- 正则表达过滤表单隐藏元素,组装post数据
<form name="form1" action="'.$serverUrl.'" method="post" > <i ...
- MySQL:表的操作 知识点难点总结:表完整性约束及其他常用知识点二次总结🙄
表操作 一 : 修改表表表表表表表表表: ALTER TABLE 语法 1. 改表名rename alter table 表名 rename 新表名 2. 增加字段add alter table 表名 ...
- Python的特性(property)
特性(property) 特性是对类的一个特定属性进行拦截,在操作这个属性时,执行特定的函数,对属性的操作进行拦截. 特性的实现 特性使用property类来实现,也可以使用property装饰器实现 ...
- Python入门-数据类型之字符串
字符串详解 没那么多废话,直接介绍字符串使用.继续往下看~~~ 字符串定义: *1.引号包围,不可变(指的是不可以对字符串进行修改)得序列(凡是能够通过索引取值的都是序列). *2.不可变对象(字符串 ...
- JS大小写字母转换
var a = "ABCd"; console.log(a.toLowerCase());//转换成小写 console.log(a.toUpperCase());//转换成大写
- vue2.0 项目build后资源文件报错404的解决方案
当vue项目build后,我们会看到css.js报错404的问题: 那我们就去找错误原因吧. 首先,查看build后的dist文件目录 可以看出,js.css在index.html的同级目录下: 然后 ...
- CSS Content 属性
content 属性,用在after,before伪元素上,用于在选中的元素前后插入内容. 插入的内容多种多样, 纯文字 h1::after{ content:"h1后插入内容" ...
- 【Bootstrap简单用法】
一.下载及使用 参考网站:http://www.bootcss.com/ 1.使用 BootCDN 提供的免费 CDN 加速服务(同时支持 http 和 https 协议) <!-- 最新版本的 ...
- GUI—ST_emWin移植
GUI-ST_emWin移植 一.st_emwin移植 1.stemwin源文件下载:ST官网 https://my.st.com/content/my_st_com/en/search.html#q ...
- UE4中FString转UTF8及UTF8转FString
FString转UTF8 FString szMsg = "test msg"; TCHAR* pSendData = szMsg.GetCharArray().GetData() ...