最近玩这个8266上瘾了,正好手上有一块DS3231,就想着做一个互联网时钟玩玩,哪知道本以为很简单的一个东西却做了我一周的时间,踩平了N个坑。既然掉坑里了又爬了出来就会有所收获,赶快过来记录一下,备忘备忘。
第一个坑,关于I2C引脚的问题。
我用的是nodeMCU 8266-E 的开发板,所查了一下乐鑫原厂的技术资料,资料上清楚的写着如下:
我也没多想,就把DS3231的SCL和SDA直接接上了IO4 和IO2。结果就是死活读不到数据。后来抱着试试的心态想着程序没错的话应该就是硬件的问题,上度娘上查了前人的文章,发现有两个博主连接的是GPIO5 和GPIO4 ,于是抱着试试的态度果断打出如下代码。
#define SCL 5 //d1 I2C-SCL
#define SDA 4 //d2 I2C-SDA
结果一运行,立刻搞定。。。就这么一个低级坑,就坑了我一天时间。要不是自已有怀疑精神,这个坑估计能把我埋了。
第二个坑更深了,但现在看来简单一句话就是DS3231在启动后,至少20秒内不能用setYear(),setHour()等这些个设置语句。
这里补充一下,我用的是arduino 的IDE环境。因此自然使用的是arduino提供的官方下载的DS3231驱动。https://www.arduino.cc/reference/en/libraries/ds3231/。
我的时钟原本就是在联上网后会有一个自动对时功能,从NTP上取下网络时间,然后设置给DS3231,就为了这个功能,恶梦开始了,程序一运行,怎么都无法读出准确的时间,反而读出一堆乱码。我又开始了自查程序。查了N遍怎么都不可能错了,然后开始分析官方提供的驱动,发现也没有问题。然后很偶然的把一开机就设置时间的功能去掉,发现一切正常上了。经过反复比较,反复查看DS3231的技术资料,都没有对这一情况进行说明。这里权猜测是因为DS3231在开机上电的一段时间里寄存器初始化等作业不允许用设置指定吧。但可以用读取指令。
第三个坑最深。。为了解决这个坑,上网各种搜查都没能找到好的办法,最后还是根据驱动程序源码并合理猜测才得以解决。这个坑是关于闹钟设置并输出中断的功能的。功能就是通过APP设置闹钟写入DS3231里,时间到后,必须从INT/SQW这个端口输出中断信号给8266,8266再去处理这个中断。但是我遇到几个问题。一是每次一设完闹钟参数,3231就直接输出了一个低电平,也就是说设完闹钟中断就启动了,不管时间到没到。二是我对8266读中断的模式设为LOW也就是低电平触发,但这里死活都没办法触发。。。这两个问题中的第一个必须涉及到寄存器的解释,
另一个是状态寄存器
详细的内容网上有大把的资料,需要的话可以去查,这里只讲和这个坑有关的内容。
根据上面的两个寄存器解释。要想让低电平有效的中断在闹钟启动后触发,必须完成这几个寄存器置位。
1 控制寄存器的INTCN置1
2 控制寄存器的A1IE和A2IE置1
3 闹钟时间到时对状态寄存器的A1F或A2F的置位
这样就能使中断触发。
可是我设置了1,2两部后,才用setA1Time()命令后,INT-SQW就一直处于低电平,也就是闹钟时间没到,中断就有效了。这让我很无语,又是一通各种查,把驱动源码又看了N遍。最好才明白了,3231会自已偷偷的置位A1F或A2F。也就是说,3231不会自已清除A1F或A2F的标志,必须手工去清除。搞清楚这点后,一切就简单 了。如下:
//时钟初始化 同步闹钟
//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方波)
RTC.enableOscillator(true,false,0);
RTC.checkIfAlarm(1); //该函数检测完后状态寄存器A1F或A2F就把标志位清0
RTC.checkIfAlarm(2);
ReadData(EEPROMBase1,Alarm1);
ReadData(EEPROMBase2,Alarm2);
if (Alarm1.flag==1){
RTC.setA1Time(Alarm1.ADay,Alarm1.AHour,Alarm1.AMinute,Alarm1.ASecond,Alarm1.AlarmBits1,Alarm1.ADy,Alarm1.AH12,Alarm1.APM);
//把控制寄存器的INTCN位和A1IE位置1,使能闹钟一的中断。一旦状态寄存器的A1F因为闹钟时间到而置1则在INTCN输出中断
RTC.turnOnAlarm(1);
if (RTC.checkAlarmEnabled(1)){
Serial.println("闹钟1启动,定时为每日的:"+String(Alarm1.AHour)+":"+String(Alarm1.AMinute));
DebugeTime(1,Temp);
};
}else {
RTC.turnOffAlarm(1);
}
if (Alarm2.flag==1){
RTC.setA2Time(Alarm2.ADay,Alarm2.AHour,Alarm2.AMinute,Alarm2.AlarmBits2,Alarm2.ADy,Alarm2.AH12,Alarm2.APM);
//把控制寄存器的INTCN位和A2IE位置1,使能闹钟一的中断。一旦状态寄存器的A2F因为闹钟时间到而置1则在INTCN输出中断
RTC.turnOnAlarm(2);
if (RTC.checkAlarmEnabled(2)){
Serial.println("闹钟2启动,定时为每日的:"+String(Alarm1.AHour)+":"+String(Alarm1.AMinute));
DebugeTime(2,Temp);
};
}else{
RTC.turnOffAlarm(2);
}
上面的关键就是那几个RTC对象相关的方法的执行顺序。
第二个问题就是关于8266中断触发的问题。由于用的是arduino IDE环境 所以经常会把arduino的语句与8266混用,尽管大多是一样的,但还是有区别。比如,中断的触发。arduino可以用(LOW, HIGH ,FALLING,CHANGE,RISING).而8266就只有三种(FALLING,CHANGE,RISING)。
先写到这里,详细的驱动程序 的分析明天再写。