项目任务:
1、DS3231实时时钟芯片功能与特性
(从http://www.alldatasheet.com检索并下载DS3231说明书,介绍:芯片主要功能、性能参数、封装形式与引脚、典型电路连接方法等)
2、I2C通信方式原理与应用
(I2C是各类传感器与模块与Arduino通信的最常用方式之一,重点介绍I2C的特点、Arduino的相关引脚、相关库的安装与使用)
3、 DS3231实时时钟模块的原理图
(检索DS3231实时时钟模块的电路图,说明电路各部分的功能,说明模块各引脚的功能)
4、 DS3231实时时钟的基本实现
(利用I2C库和DS3231库实现从串口每隔1S输出一次年月日星期时分秒信息,实现从串口输入“YYYYMMDDHHMMSS”格式的信息后,实现调表功能,写出包含完整注释的代码)
RTC
英文全称:Real-time clock,中文名称:实时时钟
指可以像时钟一样输出实际时间的电子设备,一般会是集成电路,因此也称为时钟芯片。
它为人们提供精确的实时时间,或者为电子系统提供精确的时间基准,目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源
DS3231就是众多RTC芯片中的一种!
但是,了解到Arduino自身就具有内置计时器时,为什么我们这个项目要用DS3231为Arduino项目提供单独的RTC?
看到一个硕大的电池没有~?
原因就是RTC模块使用电池运行,即使我们重新编程微控制器或断开主电源,也可以保持时间运行,当下一次再次使用模块时不用再次进行调表,它将持续运行时间。
芯片主要功能
翻译出来就是:
1.尺寸:38mm(长)22mm(宽)14mm(高)
2.重量:8g
3.工作电压:3.3--5.5V
4.时钟芯片:高精度时钟芯片DS3231
5.时钟精度:0-40℃范围内,精度2ppm,年误差约1分钟
6.带2个日历闹钟
7.可编程方波输出
8.实时时钟产生秒、分、时、星期、日期、月和年计时,并提供有效期到2100年的闰年补偿
9.芯片内部自带温度传感器,精度为±3℃
10.存储芯片:AT24C32(存储容量32K)
11.IIC总线接口,最高传输速度400KHz(工作电压为5V时)
12.可级联其它IIC设备,24C32地址可通过短路A0/A1/A2修改,默认地址为0x57
13.带可充电电池LIR2032,保证系统断电后,时钟任然正常走动
需要我们重点掌握的就是:
1、DS3231时钟模块的精准是很高的!在0-40℃的温度下可达到百万分之而秒。
2、商用级别的模块允许工作的温度范围是0到70℃
工业级别的模块允许工作的温度范围是-40到85℃
3、可以提供有效期到2100年的闰年补偿
4、两个日历闹钟
5、3.3V工作电压(这里说明一下~我们买的模块是已经封装好了的,其内部包含5v到3.3v的电压转换装置,所以在我们使用时可以直接使用5v电源供电而不会“日照香炉生紫烟”)
6、有一个精确度在±3℃的数字温度传感器
总结来说有以下功能:
DS3231是一款低成本、高精度的实时时钟
可以保持小时、分钟和秒 以及日、月和年等信息
还可以自动补偿闰年和少于31天的月份
两个日历闹钟
温度传感器
封装形式与引脚
接下来我们将用到的是Vcc、GND、SCL、SDL
这里N.C.——None Connect,无连接即该引脚与器件内部功能电路不做连接,是一个空引脚。
典型电路连接方法
DS3231连接arduino nano
arduino uno与arduino Nano连接方式相同~
模块的电路图
说明电路各部分的功能(了解即可。。。)
DS3231的主要组成部分有8个模块,划分为4个功能组:TCXO、电源控制、按钮复位和RTC。
1.32kHz的TCXO
TCXO包括温度传感器、振荡器和控制逻辑。控制器读取片上温度传感器输出,使用查表法确定所需的电容,加上AGE寄存器的老化修正。然后设置电容选择寄存器。仅在温度变化或者用户启动的温度转换完成时,才加载包括AGE寄存器变化的新值。VCC初次上电时就会读取温度值,然后每隔64s读取一次。
2.DS3231的内部寄存器及功能
DS3231寄存器地址为00h~12h,分别用于存放秒、分、时、星期、日期及闹钟设置信息。在多字节访问期间,如果地址达到RAM空间的结尾12h处,将发生卷绕,此时定位到开始位置即00h单元。DS3231的时间和日历信息通过读取相应的寄存器来设置和初始化。用户辅助缓冲区用于防止内部寄存器更新时可能出现的错误。读取时间和日历寄存器时,用户缓冲区在任何START条件下或者寄存器指针返回到零时与内部寄存器同步。时间信息从这些辅助寄存器读取,此时时钟继续保持运行状态。这样在读操作期间发生主寄存器更新时可以避免重新读取寄存器。以控制寄存器(地址为0EH)为例,可以控制实时时钟、闹钟和方波输出。其各bit定义如下表。
3.DS3231的电源控制
电源控制功能由温度补偿电压基准(VPF)和监视VCC电平的比较器电路提供。当VCC高于VPF时,DS3231由VCC供电,当VCC低于VPF但高于VBAT时,DS3231由VCC供电;当VCC低于VPF并低于VBAT时,DS3231由VBAT供电。为保护电池,VBAT首次加到器件时振荡器并不启动,除非加载VCC,或者向器件写入一个有效的I2C地址。典型的振荡器启动时间在1s以内。在VCC加电后或者有效的I2C地址写入后大约2s,器件会测量一次温度,并使用计算的修正值校准振荡器。一旦振荡器运行,只要电源(VCC或者VBAT)有效就会一直保持工作状态。器件每隔64s进行一次温度测量并校准振荡器频率。
4.DS3231的时钟和日历RTC
可以通过读取适当的寄存器字节获得时钟和日历信息。通过写入适当的寄存器字节设定或者初始化时钟和日历数据。时钟和日历寄存器的内容采用二-十进制编码(BCD)格式。DS3231运行于12小时或者24小时模式。小时寄存器的第6位定义为12或24小时模式选择位。该位为高时,选择12小时模式。在12小时模式下,第5位为AM/PM指示位,逻辑高时为PM。
5.DS3231的复位按钮,DS3231具有连接至RST输出引脚的按钮开关功能。
6.DS3231与AT89C2051单片机的接口电路
说明模块各引脚的功能
32kHz:以32 kHz频率输出;
VCC:用于主电源的DC引脚,连接3.3V或5V电源;
/INT/SQW:低电平有效中断或方波输出:是低电平有效复位引脚;
N.C.:无连接,外部必须接地;
GND:接地;
VBAT:备用电源输入;
SDA:串行数据输入、输出;
SCL:串行时钟输入。
I2C是各类传感器与模块与Arduino通信的最常用方式之一
I2C的全称为:Inter-Integrated Circuit
中文名字:内部集成电路
可以读作"I-squared-C",在中国常被读作"I方C"。
I2C是Philips公司在1980年代,为了让主板、嵌入式系统用以连接低速周边装置,而开发的一种简单的双向二线制同步串行总线,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
它具有如下特点:
I2C是 master(主) & slave(从) 的设计
整个线路上只可有一个 master, 其他的都是 slave
只能 master 与 slave之间进行通讯
slave 与 slave 之间是不可以通讯的
只有 master 可以主动向 slave 发送资料或提出请求
slave 只可因应 master 的请求而回传资料, 不可以主动发送资料给 master
主机(master)可以用作主机发送器和主机接收器
I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址,主从设备之间就通过这个地址来确定与哪个器件进行通信
I2C总线上的主设备与从设备之间以字节(8位)为单位进行 串行的八位双向数据传输
半双工通信方式
它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,两根通讯线一根为控制时钟线(SCL——Serial Clock用于同步设备间的数据传输时钟;另一根为数据线(SDA——Serial Data)用于携带数据
Arduino nano的相关引脚:A4 (SDA), A5 (SCL)
其他arduino板子相关引脚:
相关库的使用:
Arduino中使用I2C通信可直接调用Wire.h库,这个库允许Arduino链接其他I2C设备,以类的形式对I2C通信协议进行了封装。
Wire库以外挂库的形式放置于libraries文件夹内。
与内建库不同的是,用户在使用I2C库时,需要手动加入I2C库的头文件:
#include
同时,为方便用户使用,库文件预创建了Wire对象,这样用户就可以在程序中直接使用Wire实现I2C通讯了。
它从软件上彻底屏蔽了I2C的底层协议内容,这也是Arduino的设计原则,让用户更加关注业务层面,而不是实现层面。
通信原理:
通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
信号种类:
1. 起始信号.
定义:SCL线为高电平期间,SDA线由高电平向低电平的变化。
2. 终止信号
定义:SCL线为高电平期间,SDA线由低电平向高电平的变化
3. 读写数据信号
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高低电平状态才允许变化。
4. 应答信号
I2C总线协议规定,每传送一个字节数据,都要有一个应答信号以确定数据传送是否被对方接收。应答信号由接收设备产生,在SCL为高电平期间,接收设备将SDA拉低为低电平,表示数据传输正确。
wire库中常用的函数(使用时在其前面加上——wire.,了解即可):
1.Begin()、Begin(address)
初始化Wire库,并针对性初始化为主机或是从机。
上述两种形式的区别如下:
Begin()初始化为主机,
Begin(address)初始化为从机,并为从机设置地址(0-127)。
2.Write()
主机向从机写数据,或从机为响应主机请求向主机写数据。
注意:当工作于主机向从机写数据时,
该语句要位于beginTransmission()和endTransmission()语句之间。
有以下三种具体形式,描述如下:
Wire.write(value) – 写字符。
Wire.write(string)–写字符串
Wire.write(data, length)-写指定长度的数组data。
3.requestFrom()
用于主机向从设备请求数据,
此后就可以用available()和read()来具体操作从设备返回的数据了。
形式为:Wire.requestFrom(address,quantity)Address指明从设备的地址,quantity指明请求的字节数。
4.Available()
返回接收缓存(数组)里的字符数,以便后续使用read()具体读取。
具体使用中,对主机而言,必须事先调用requestFrom函数;对从机而言,常在onReceive事件中使用。
5.Read()
从接收缓存(数组)中读取数据。
6.注册事件
除了上述几个方法外,Wire库还定义了两个事件:
onReceive()和onRequest()。
都是为从设备注册一个事件函数,用于响应不同的事件。
onReceive()用于从机接收到主机字符的响应,事件中执行从设备的写操作。
onRequest()用于从机接收到主机上传数据通知的响应,事件中执行从设备的读操作。
值得注意的是我们在编写程序的过程中并没有用到这些函数,是否真的没有用到呢? 其实由于arduino自身的特点,一些函数已经被封装过了,我们虽然没有看到他们出现,但确实被使用了~
DS3231库
DS3231库有很多种,如下图
每一种库文件都不太一样,大家可以根据自己的需要进行选择.
这里先介绍怎样将库导入至arduino中:
第一步
第二步,找到库文件,选中,打开
第三步,右下角显示已加入库表明操作成功!
链接:https://pan.baidu.com/s/1ihGOOqJDp6em0ur-LBDCAg
提取码:0oh0
连接里放了以上五个库,经过一一比较,我选择了Sodaq_DS3231-master库文件
在库文件中可以看到keyword名字的文件夹,打开可以看到使用方法,这里给出他的数据类型与函数:
# Datatypes (KEYWORD1)
Sodaq_DS3231 KEYWORD1
DateTime KEYWORD1
# Methods and Functions (KEYWORD2)
second KEYWORD2
minute KEYWORD2
hour KEYWORD2
date KEYWORD2
month KEYWORD2
year KEYWORD2
active KEYWORD2
dayOfWeek KEYWORD2
get KEYWORD2
begin KEYWORD2
setDateTime KEYWORD2
enableInterrupts KEYWORD2
disableInterrupts KEYWORD2
clearINTStatus KEYWORD2
convertTemperature KEYWORD2
getTemperature KEYWORD2
now KEYWORD2
了解了这些就让我们开始制作吧!
目标:利用I2C库和DS3231库实现从串口每隔1S输出一次年月日星期时分秒信息,实现从串口输入“YYYYMMDDHHMMSS”格式的信息后,实现调表功能,写出包含完整注释的代码
先完成第一步:利用I2C库和DS3231库实现从串口每隔1S输出一次年月日星期时分秒信息
代码如下:
#include //引入wire.h库函数
#include "Sodaq_DS3231.h"//引入DS3231函数
String weekDay[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat","Sun"};//设置星期
void setup ()
{
Serial.begin(57600);//设置比特率
Wire.begin();//使用wire.h库函数
rtc.begin();
}
void loop ()
{
DateTime now = rtc.now(); //获取当前时间
Serial.print(now.year(), DEC);//以十进制形式输出年的ASCII编码值
Serial.print('/');
Serial.print(now.month(), DEC);//以十进制形式输出月的ASCII编码值
Serial.print('/');
Serial.print(now.date(), DEC);//以十进制形式输出日的ASCII编码值
Serial.print(' ');
Serial.print(now.hour(), DEC);//以十进制形式输出小时的ASCII编码值
Serial.print(':');
Serial.print(now.minute(), DEC);//以十进制形式输出分钟的ASCII编码值
Serial.print(':');
Serial.print(now.second(), DEC);//以十进制形式输出秒的ASCII编码值
Serial.print(" ");
Serial.print(weekDay[now.dayOfWeek()-1]);//输入星期
Serial.println();
delay(1000);//延迟一秒钟
}
实验效果:
https://www.bilibili.com/video/av57978989
再完成第二步:可通过串口修改时间
比较遗憾的是,arduino并没有split()函数供我们使用把一串字符分隔开,但是我们可以利用简单的算法来完成,我们也可以将这个算法写成一个库函数供自己使用或者分享到互联网上,做一个真正的创客。
代码如下:
#include //引入wire.h库函数
#include "Sodaq_DS3231.h"//引入DS3231库函数
DateTime dt;//定义一个DateTime类的结构体dt
#define numdata_length 7
String comdata = "";//定义一个数组定初始化为0
String weekDay[7]= { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat","Sun" };//定义星期数组
void setup ()
{
Serial.begin(57600);
Wire.begin();
rtc.begin();
}
void loop ()
{
if(Serial.available()//实时监测串口是否有信息输入
{
int yy, MM, dd, w, hh, mm, ss; //定义表示时间的变量
int numdata[numdata_length] = {0};//定义数组并初始化为0
int flag = 0;//定义flag标志
int j = 0;
while (Serial.available() > 0)//如果串口有信息输入则执行
{
comdata += char(Serial.read());//强制类型转换,按字节一个个读取串口输入的信息到字符串中
delay(2);
flag = 1;//有信息输入flag至1
}
if(flag == 1)//如果有信息输入
{
for(int i = 0; i < comdata.length() ; i++)
{//这一块代码的功能是将一串字符串按“,”进行分割为一个个小的字符串并存储
if(comdata[i] == ',')
{
j++;
}
else
{
numdata[j] = numdata[j] * 10 + (comdata[i] - '0'); }
}//将分割好的字符串分别放到相对应的变量中
yy = numdata[0];
MM = numdata[1];
dd = numdata[2];
w = numdata[3];
hh = numdata[4];
mm = numdata[5];
ss = numdata[6];
comdata = String("");//字符串清空
flag = 0;//flag置零
}
DateTime dt(yy, MM, dd, hh, mm, ss, w);
rtc.setDateTime(dt);//执行修改时间操作
}
DateTime now = rtc.now(); //获取当前时间
//以下代码同上一个实验
Serial.print(now.year());
Serial.print('/');
Serial.print(now.month());
Serial.print('/');
Serial.print(now.date());
Serial.print(' ');
if(now.hour()<10){
Serial.print("0");
Serial.print(now.hour());
Serial.print(':');
}
else{
Serial.print(now.hour());
Serial.print(':');
}
if(now.minute()<10){
Serial.print("0");
Serial.print(now.minute());
Serial.print(':');
}
else{
Serial.print(now.minute());
Serial.print(':');
}
if(now.second()<10){
Serial.print("0");
Serial.print(now.second());
Serial.print(' ');
}
else{
Serial.print(now.second());
Serial.print(' ');
}
Serial.print(weekDay[now.dayOfWeek()-1]);
Serial.println();
delay(1000);//延迟1秒
}
实验效果:
因为用的markdown编辑的,但是不支持上传视频所以将视频上传至B站供大家观看~~
演示视频点击此处