STM32片內(nèi)包含一個(gè)32位的RTC定時(shí)器,可以通過(guò)外部時(shí)鐘提供32.768kHz計(jì)數(shù)時(shí)鐘,實(shí)現(xiàn)按秒的精確計(jì)數(shù),并且這個(gè)定時(shí)器在MCU復(fù)位或者斷電的時(shí)候,只要VBAT管腳能夠提供3V的供電,計(jì)數(shù)器將會(huì)繼續(xù)計(jì)數(shù)。
網(wǎng)上有很多人埋怨說(shuō)STM32的RTC時(shí)鐘不準(zhǔn),說(shuō)實(shí)話這完全是冤枉ST,RTC僅僅是個(gè)計(jì)數(shù)器,計(jì)數(shù)肯定是準(zhǔn)確的,如果說(shuō)時(shí)鐘不準(zhǔn),基本上是由于用了劣質(zhì)的晶振導(dǎo)致的。下圖這是Rainbow用到的RTC晶振:
考慮到很多鐵殼RTC晶振會(huì)出現(xiàn)不能起振、時(shí)鐘不準(zhǔn)確,我們特意選用了這種高精度的晶振,因此網(wǎng)上提到的時(shí)鐘不準(zhǔn)的問(wèn)題是可以避免的。
還有一些網(wǎng)友提到說(shuō)STM32的RTC耗電量很大,一顆紐扣電池只能維持6個(gè)月左右。我個(gè)人認(rèn)為能夠在斷電的情況下維持這么久已經(jīng)足夠了,試想想,如果一個(gè)設(shè)備半年都不用了,再次啟用的時(shí)候需要換個(gè)紐扣電池也是可以接受的,如果設(shè)備正常使用,是不會(huì)消耗紐扣電池的電量的。
由于Rainbow具有網(wǎng)絡(luò)接入能力,因此我們?cè)趯?duì)RTC類(lèi)庫(kù)做封裝的時(shí)候特地增加了從NTP服務(wù)器讀取網(wǎng)絡(luò)時(shí)間的功能。通過(guò)類(lèi)庫(kù)可以讀取網(wǎng)絡(luò)上標(biāo)準(zhǔn)32位的unix格式的時(shí)間戳,將這個(gè)時(shí)間戳寫(xiě)入到STM32的RTC計(jì)數(shù)器中,就可以實(shí)現(xiàn)Rainbow和網(wǎng)絡(luò)時(shí)間同步,之后Rainbow的RTC將按秒進(jìn)行計(jì)數(shù),可以繼續(xù)維持這個(gè)時(shí)鐘。
由于我們的計(jì)數(shù)值采用的是符合unix標(biāo)準(zhǔn)的時(shí)間戳,所以我們可以輕松通過(guò)這個(gè)時(shí)間戳計(jì)算出當(dāng)前的日期、時(shí)間、星期幾等,誰(shuí)叫STM32運(yùn)算能力有這么強(qiáng)呢?NTP的時(shí)間戳是從1900-01-01 00:00:00開(kāi)始到現(xiàn)在按照秒計(jì)數(shù)的,而unix的時(shí)鐘又是從1970年開(kāi)始的,所以我們將從NTP服務(wù)器獲取的時(shí)間戳減去從1900到1970的秒數(shù),寫(xiě)入到Rainbow的RTC計(jì)數(shù)器即可。
在軟件包的“Projects\RTC”文件夾包含了本文的完整工程,可以直接編譯、燒寫(xiě)和調(diào)試。程序代碼如下:
#include "WProgram.h"
#include "Ethernet.h"
#include "HardwareRTC.h"
#include "LiquidCrystal.h"
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
//發(fā)送NTP請(qǐng)求的本地端口
uint16_t localPort = 8888;
EthernetUDP Udp;
//NTP服務(wù)器地址
IPAddress timeServer(132, 163, 4, 101);
//定義LCD對(duì)象,使用d4-d7四條數(shù)據(jù)線進(jìn)行驅(qū)動(dòng),將rw接地
//我們共用到了6個(gè)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對(duì)象
HardwareRTC rtc;
void setup()
{
//兩行顯示,每行16個(gè)字符
lcd.begin(16, 2);
lcd.print("Waiting...");
uint8_t ethFlag = Ethernet.begin(mac);
//啟用RTC
rtc.begin();
//將RTC與網(wǎng)絡(luò)時(shí)間進(jìn)行同步
if(ethFlag)
{
Udp.begin(localPort);
rtc._udp = &Udp;
rtc._timeServer = &timeServer;
rtc.syncNetworkTime();
}
//重新初始化,兩行顯示,每行16個(gè)字符
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)
{
//初始化開(kāi)發(fā)板
boardInit();
setup();
while(1) loop();
}
這個(gè)程序使用了1602的LCD作為時(shí)鐘顯示,程序首先通過(guò)DHCP獲取到網(wǎng)絡(luò)參數(shù),如果獲取成功,則調(diào)用HardwareRTC類(lèi)的syncNetworkTime()方法,從NTP服務(wù)器同步時(shí)間戳到Rainbow,在之后的程序運(yùn)行中,每隔1秒對(duì)LCD上的時(shí)間進(jìn)行一次更新。下圖是程序運(yùn)行的效果: