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上的時間進行一次更新。下圖是程序運行的效果: