前一段在做时钟的过程中写了两篇关于DS3231的文章,具体见链接
《8266+DS3231时钟之开发个时钟遇到的N个坑【一】》
《8266+ds3231时钟之arduino官网发布的DS3231库的分析【二】》
有兴趣的可以去看看,如果觉得对你有帮助,请点个赞。
今天有点时间,具体在DS3231库的基础上,把实现过程代码呈现出来。我会把整个钟的制作过程,分析及代码几往篇写出来,是对自已制作的一个总结,也希望能帮助到和我一样对嵌入式系统有兴趣的人。
上一篇对DS3231库的分析里,对每一个函数及作用进行了分析。但在实际写代码的过程中,其实还是有不少地方需要注意的,因此这里根据时钟的使用过程顺序,分析一下具体的实现。
概述
我的时钟的主要功能:
1、时钟的显示(主要显示日期和时分)
2、时钟的自动对时
3、闹钟一的设置与取消
4、闹钟二的设置与取消
1、首先当然是硬件连接
2、其次当然是软件环境配置
要用DS3231,配置DS3231的相应使用环境 ,首先当然是需要引入相关的头文件,生成对象。具体代码如下:
#include <Wire.h>
#include <DS3231.h>
//时钟管脚定义
#define SCL 5 //GPIO5 对应NODEMCU的d1 I2C-SCL
#define SDA 4 //GPIO4 对应NODEMCU的d2 I2C-SDA
#define INT_SQW 14 //GPIO14 对应NODEMCU的 D5 中断及方波1HZ
//.......
DS3231 RTC; //创建DT3231时钟对象
3、闹钟数据结构的设置
/对应APP状态的各类数据结构///
struct AlarmSet_type{
//闹钟设置数据结构
byte flag=0;
byte ADay=0;
byte AHour=0;
byte AMinute=0;
byte ASecond=1;//怀疑没有0秒,所以设一个1秒
byte AlarmBits1=0x08; //闹钟一控制字的设置,详见我上一篇《arduino官网发布的DS3231库的分析》
byte AlarmBits2=0x40; //闹钟二控制字的设置,详见我上一篇《arduino官网发布的DS3231库的分析》
bool ADy=false;//日期以月日格式。
bool AH12=false; //24小时制
bool APM=false; //
};
AlarmSet_type Alarm1; //建立闹钟一
AlarmSet_type Alarm2; //建立闹钟二
AlarmSet_type NowAlarm;//暂存现在的闹钟设置
Alarm1 、Alarm2 、NowAlarm 用于接下来设置闹钟或同步APP与闹钟的数据时用。这里需要先定义一下。
4、初始化
void setup(){
//....................
Wire.begin(); //启动I2c总线
时钟初始化 同步闹钟///
//enableOscillator(bool TF, bool battery, byte frequency)
//当第一位TF为true 控制寄存器的 ~EOSC to 0 and INTCN to 0.,
//注意该函数把EOSC置0则启动振荡器,同时把把INTCN置0(第二位battery为true VCC<VPF时会输出1HZ方波)
//使用时要用turnOnAlarm()把INTCN打开,否则会一直输出方波中断
RTC.enableOscillator(true,false,0);
RTC.checkIfAlarm(1); //该函数检测完后状态寄存器A1F或A2F就把标志位清0
RTC.checkIfAlarm(2);
RTC.turnOnAlarm(1); //置INTCN为1,否则在INT/SQW会一直输出1HZ方波
RTC.turnOnAlarm(2);
/设置闹钟中断输出设置秒闪功能///
pinMode(INT_SQW,INPUT_PULLUP);
interrupts();
attachInterrupt(digitalPinToInterrupt(INT_SQW),alarm_interrupt,FALLING);
//8266经过实践发现,只支持CHANGE RISING FALLING 。DS3231在闹钟启动时,会在int/sqw接口处输出一个低电平做为中断信号。
secondTicker.attach(1,second_interrupt,SecondInterruptMode);//每秒触发一次带参数的中断程序
//...................
}
经过以上几个步骤,DS3231就可以正常我们的要求开始工作了。接下来的大量工作就是和具体的显示,设置,以及其它辅助功能相关了。
5、显示
由于我搭的实验环境用的是某宝上买的基于TM1638显示驱动的一个现成模块。下一篇文章我会具体介绍这个模块的使用,这里只需要知道这部分是针对DS3231的具体显示功能就行了。下面先上一张该 模块的图,有个直观的认识:
由于是实验环境,为了省事,我用的这个8数码管的显示模块,把前四个数码管定义成月日的显示。后四个数码管定义为时分的显示。
因此对应的显示函数如下:
void GetTime(){
//给显示变量赋值
showtime[0]=RTClib::now().month()/10; //月
showtime[1]=RTClib::now().month()%10;
showtime[2]=RTClib::now().day()/10; //日
showtime[3]=RTClib::now().day()%10;
showtime[4]=RTClib::now().hour()/10; //时
showtime[5]=RTClib::now().hour()%10;
showtime[6]=RTClib::now().minute()/10; //分
showtime[7]=RTClib::now().minute()%10;
}
上面这段代码只是把对应的DS3231赋值给showtime[]数组,该 数组用于对应TM1638的显示,由于要显示秒的功能,因此在设计时有一个秒中断函数负责具体的时间显示:
ICACHE_RAM_ATTR void second_interrupt(int mode){
//时钟每秒中断
if (mode==0){
//模式0 用于秒闪灯工作以及显示时间。模式1可能是显示提示正在配网等
//显示时间
if ( showtime[7]!=RTClib::now().minute()%10 ) {
//只要比较,分不同就重新显示
GetTime(); //获取DS3231的时间
TM1638.Disp8(showtime); //显示
}
}
}
6、对时函数
void autoAdjust(){
//自动对时
Serial.println("<<<<<<<如果联网了,才执行自动对时功能>>>>>>>>>>");
if (WiFi.status() == WL_CONNECTED){
RTC.setClockMode(false); // set to 24h
RTC.setYear((Blinker.year()%100)); //取年的最后两位
RTC.setMonth(Blinker.month());
RTC.setDate(Blinker.mday());
RTC.setDoW(Blinker.wday());
RTC.setHour(Blinker.hour());
RTC.setMinute(Blinker.minute());
RTC.setSecond(Blinker.second());
}
}
由于我用的是blinker点灯科技的APP,所以自动对时我就不需要去写复杂的NTP授时程序 了,直接从Blinker的对应时间函数里读需出来就行了。因为BLINKER的时间函数了是从NTP相关函数封装来的,所有也是用的互联网授时网站的标准时间。
7、闹钟设置
功能上,我取消了复杂又难以理解的按键设置功能,取而代之的是用APP上的操作来设置闹钟。这些具体实现在以后介绍,这里只需要知道 一下在APP上设置好时分后,把设置下载到DC3231。这此代码是放在对应的BLINKER的回设函数上
void btnOne_callback(const String & state){
RTC.setA1Time(Alarm1.ADay,Alarm1.AHour,Alarm1.AMinute,Alarm1.ASecond,Alarm1.AlarmBits1,Alarm1.ADy,Alarm1.AH12,Alarm1.APM);
RTC.turnOnAlarm(1);
if (RTC.checkAlarmEnabled(1)){
//检测控制寄存器中的A1IE位置1否
texOne.print("每天响铃"+String(Alarm1.AHour)+"时"+String(Alarm1.AMinute)+"分");
};
}
8、闹钟取消
void btnCancel1_callback(const String & state){
RTC.turnOffAlarm(1); //只设置了控制寄存器中的A1IE位置0,INTCN位不变
}
9、闹钟中断触发
ICACHE_RAM_ATTR void alarm_interrupt(){
Serial.println("alarm 中断启动。。。。。。。");
ReadData(EEPROMBase3,Mp3Data);
if (Mp3Data.flag=1){
//有设置就播设置的音乐,没有的话默认播第一首
//循环播放
mp3.SetCmdByte(0x06,CMD_assign_cyclic,0x00,(uint8_t)Mp3Data.music>>8,(uint8_t)(Mp3Data.music&0x00FF));
}else{
mp3.SetCmdByte(0x06,CMD_assign_cyclic,0x00,0x00,0x01);
}
}
中断程序只要知道是去触发闹铃的语名就行了。
到这里,时钟相关的功能已都基本实现,上面涉及的到功能对应的关键语句都已完整呈现。