STM32片内包含一个32位的RTC定时器,可以通过外部时钟提供32.768kHz计数时钟,实现按秒的精确计数,并且这个定时器在MCU复位或者断电的时候,只要VBAT管脚能够提供3V的供电,计数器将会继续计数。
网上有很多人埋怨说STM32的RTC时钟不准,说实话这完全是冤枉ST,RTC仅仅是个计数器,计数肯定是准确的,如果说时钟不准,基本上是由于用了劣质的晶振导致的。下图这是Rainbow用到的RTC晶振:
考虑到很多铁壳RTC晶振会出现不能起振、时钟不准确,我们特意选用了这种高精度的晶振,因此网上提到的时钟不准的问题是可以避免的。
还有一些网友提到说STM32的RTC耗电量很大,一颗纽扣电池只能维持6个月左右。我个人认为能够在断电的情况下维持这么久已经足够了,试想想,如果一个设备半年都不用了,再次启用的时候需要换个纽扣电池也是可以接受的,如果设备正常使用,是不会消耗纽扣电池的电量的。
由于Rainbow具有网络接入能力,因此我们在对RTC类库做封装的时候特地增加了从NTP服务器读取网络时间的功能。通过类库可以读取网络上标准32位的unix格式的时间戳,将这个时间戳写入到STM32的RTC计数器中,就可以实现Rainbow和网络时间同步,之后Rainbow的RTC将按秒进行计数,可以继续维持这个时钟。
由于我们的计数值采用的是符合unix标准的时间戳,所以我们可以轻松通过这个时间戳计算出当前的日期、时间、星期几等,谁叫STM32运算能力有这么强呢?NTP的时间戳是从1900-01-01 00:00:00开始到现在按照秒计数的,而unix的时钟又是从1970年开始的,所以我们将从NTP服务器获取的时间戳减去从1900到1970的秒数,写入到Rainbow的RTC计数器即可。
在软件包的“Projects\RTC”文件夹包含了本文的完整工程,可以直接编译、烧写和调试。程序代码如下:
#include "WProgram.h"
#include "Ethernet.h"
#include "HardwareRTC.h"
#include "LiquidCrystal.h"
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
//发送NTP请求的本地端口
uint16_t localPort = 8888;
EthernetUDP Udp;
//NTP服务器地址
IPAddress timeServer(132, 163, 4, 101);
//定义LCD对象,使用d4-d7四条数据线进行驱动,将rw接地
//我们共用到了6个IO:RS、E、D4-D7,RW接低电平
//本程序接法:
// RS => PC0
// E => PC2
// D4-D7 => PA0、PA2、PA4、PA6
LiquidCrystal lcd(PC0, PC2, PA0, PA2, PA4, PA6);
//RTC对象
HardwareRTC rtc;
void setup()
{
//两行显示,每行16个字符
lcd.begin(16, 2);
lcd.print("Waiting...");
uint8_t ethFlag = Ethernet.begin(mac);
//启用RTC
rtc.begin();
//将RTC与网络时间进行同步
if(ethFlag)
{
Udp.begin(localPort);
rtc._udp = &Udp;
rtc._timeServer = &timeServer;
rtc.syncNetworkTime();
}
//重新初始化,两行显示,每行16个字符
lcd.begin(16, 2);
}
void loop()
{
while(1)
{
lcd.setCursor(0, 0);
tm t = rtc.getTime();
lcd.print(t.tm_year);
lcd.print('-');
if(t.tm_mon < 9) lcd.print('0');
lcd.print(t.tm_mon + 1);
lcd.print('-');
if(t.tm_mday < 10) lcd.print('0');
lcd.print(t.tm_mday);
lcd.setCursor(0, 1);
if(t.tm_hour < 10) lcd.print('0');
lcd.print(t.tm_hour);
lcd.print(':');
if(t.tm_min < 10) lcd.print('0');
lcd.print(t.tm_min);
lcd.print(':');
if(t.tm_sec < 10) lcd.print('0');
lcd.print(t.tm_sec);
//每隔1秒刷新
delay(1000);
}
}
int main(void)
{
//初始化开发板
boardInit();
setup();
while(1) loop();
}
这个程序使用了1602的LCD作为时钟显示,程序首先通过DHCP获取到网络参数,如果获取成功,则调用HardwareRTC类的syncNetworkTime()方法,从NTP服务器同步时间戳到Rainbow,在之后的程序运行中,每隔1秒对LCD上的时间进行一次更新。下图是程序运行的效果: