鐘方案:DS1302+紐扣電池實(shí)戰(zhàn)記錄)
STM32 項(xiàng)目掉電后時(shí)間歸零這件事我印象太深了。之前做一臺(tái)帶記錄功能的小設(shè)備客戶說“我明明斷電重啟了兩天時(shí)間怎么還在 1 月 1 號(hào)”當(dāng)時(shí)排查一圈發(fā)現(xiàn)是內(nèi)部 RTC 的 VBAT 引腳沒接好備用電池電壓根本進(jìn)不去。后來重新設(shè)計(jì)時(shí)我把方案改成了外部 DS1302 紐扣電池徹底解決了“掉電時(shí)間丟失”這個(gè)老大難順便還把整機(jī)的靜態(tài)功耗降了下來。這篇文章就把這次改造的完整過程記錄下來從硬件供電拓?fù)涞杰浖?qū)動(dòng)調(diào)試再到換電池保證時(shí)鐘不亂的細(xì)節(jié)一次性講透。1. 先認(rèn)清一個(gè)現(xiàn)實(shí)STM32 內(nèi)部 RTC 并不是“萬能的”很多人拿到 STM32 第一反應(yīng)就是“芯片內(nèi)部有 RTC直接用不就行了”。沒錯(cuò)STM32F103/F407 這類芯片確實(shí)自帶 RTC 模塊甚至專門留了 VBAT 引腳來接備用電池。但實(shí)際產(chǎn)品里“直接用內(nèi)部 RTC”往往會(huì)踩到幾個(gè)不容易發(fā)現(xiàn)的坑。1.1 內(nèi)部 RTC 的“掉電”并不等于徹底斷電STM32 內(nèi)部 RTC 的正常工作條件很苛刻主電源 VDD 掉電后必須由 VBAT 引腳持續(xù)供電。很多人畫原理圖時(shí)確實(shí)把 VBAT 接到了紐扣電池正極但忽略了 VBAT 在芯片內(nèi)部會(huì)連接到 RTC 和備份寄存器。如果電路板在 VBAT 路徑上串了電阻、接了二極管或者阻容濾波參數(shù)太大電池電壓在低功耗模式下可能被拉低到 RTC 復(fù)位的邊緣。還有一點(diǎn)STM32 的 VBAT 引腳如果懸空內(nèi)部 RTC 在 VDD 還在時(shí)能正常計(jì)時(shí)但一旦主電源斷開RTC 直接丟失全部數(shù)據(jù)。更麻煩的是很多 STM32 型號(hào)的 VBAT 不能給外部紐扣電池充電只能單向供電。如果你在 VBAT 上接的是可充電電池就需要額外設(shè)計(jì)充電電路復(fù)雜度一下就上去了。1.2 內(nèi)部 RTC 時(shí)鐘源的精度和穩(wěn)定性STM32 內(nèi)部 RTC 可以使用 LSI低速內(nèi)部 RC 振蕩器或 LSE外部 32.768kHz 晶振。LSI 本身是一顆 RC 振蕩器精度在不同溫度、不同電壓下會(huì)明顯變化。ST 數(shù)據(jù)手冊(cè)標(biāo)稱 LSI 在 25 度時(shí)典型值是 40kHz 左右但實(shí)際上誤差可能到百分之幾換算成一天的時(shí)間偏差直接就是幾十秒甚至幾分鐘。這對(duì)需要精確計(jì)時(shí)的設(shè)備來說是致命的。LSE 晶振方案精度會(huì)好很多但 STM32 的 LSE 起振電路對(duì)晶振的負(fù)載電容、PCB 布線、兩端電容虛焊都很敏感。很多新手畫板時(shí)把 32.768kHz 晶振放在 MCU 引腳很遠(yuǎn)的地方或者用了匹配電容不對(duì)的晶振導(dǎo)致要么不起振要么起振后頻率偏差大。調(diào)試起來要?jiǎng)佑檬静ㄆ髯ゲㄐ螌?duì)量產(chǎn)來說是不小的成本。單純從“供電獨(dú)立性”和“時(shí)鐘精度”兩個(gè)角度看外部 DS1302 方案的優(yōu)勢(shì)非常明顯它自帶電源切換邏輯只要紐扣電池有電哪怕 MCU 整板斷電DS1302 也照樣走時(shí)而且它使用的是外部 32.768kHz 晶振可以通過挑選高精度晶振來控制系統(tǒng)誤差。2. 硬件供電方案設(shè)計(jì)一顆紐扣電池是怎么做到“不掉電”的既然要用外部 RTC那供電拓?fù)渚褪钦麄€(gè)方案的核心。DS1302 有 VCC2 和 VCC1 兩個(gè)電源引腳官方手冊(cè)里的切換邏輯非常簡(jiǎn)單VCC2 接主電源VCC1 接紐扣電池芯片內(nèi)部會(huì)自動(dòng)比較兩個(gè)電源的電壓優(yōu)先使用電壓較高的一側(cè)。這個(gè)特性讓“主電掉電后無縫切換電池供電”變成了芯片自動(dòng)完成的事不需要外部 PMOS 切換電路也不用擔(dān)心切換瞬間時(shí)鐘數(shù)據(jù)沖突。2.1 DS1302 關(guān)鍵引腳與內(nèi)部電源切換邏輯DS1302 是 Maxim 的一顆老牌 RTC 芯片封裝有 DIP-8 和 SOIC-8引腳數(shù)量少非常適合手工焊接。它的引腳功能如下引腳名稱功能說明1VCC2主電源輸入典型 3.3V/5V2X132.768kHz 晶振輸入3X232.768kHz 晶振輸出4GND地5CE/RST片選/復(fù)位高電平有效6I/O數(shù)據(jù)輸入輸出三態(tài)接口7SCLK串行時(shí)鐘輸入下降沿采樣數(shù)據(jù)8VCC1備用電池正極接紐扣電池或超級(jí)電容內(nèi)部電源切換的判定條件看官方手冊(cè)和實(shí)際測(cè)量結(jié)果大概是當(dāng) VCC2 電壓比 VCC1 高 0.2V 以上時(shí)芯片內(nèi)部由 VCC2 供電一旦 VCC2 跌落到接近或低于 VCC1就自動(dòng)切換到 VCC1 供電。這個(gè)過程是純硬件行為DS1302 的時(shí)間寄存器和 RAM 數(shù)據(jù)不會(huì)因?yàn)榍袚Q而丟失所以在 MCU 主電源斷電時(shí)芯片只是換了個(gè)“供電源”繼續(xù)跑。需要注意DS1302 的 VCC1 最低工作電壓一般在 2.0V 左右如果紐扣電池電壓掉到 2.0V 以下芯片會(huì)停止計(jì)時(shí)并可能丟失數(shù)據(jù)。所以設(shè)計(jì)時(shí)不能在 VCC1 到電池之間加降壓二極管正極直接連電池即可。市面上有些模塊會(huì)把 VCC1 串聯(lián)一個(gè) 1N4148看似防反接但 0.7V 的壓降會(huì)直接讓電池可用電壓縮水很多非常不推薦。2.2 主電源、備用電源和上拉電阻的“電流回路”問題很多 DS1302 模塊的原理圖里VCC2 和 VCC1 之間還連著一條跳線或者直接通過一個(gè)二極管相連。這樣做對(duì)普通使用沒問題但如果你想追求低功耗就要特別小心了。DS1302 的 I/O 和 SCLK、CE 引腳在模塊上一般都有上拉電阻到 VCC2。VCC2 正常時(shí)上拉電阻正常工作但 VCC2 掉電后如果 VCC1 的電池仍然通過內(nèi)部電路和外部上拉電阻形成回路電流就會(huì)從 VCC1 漏到 I/O 引腳上。更糟糕的是I/O 引腳還連著 STM32 的 GPIOGPIO 內(nèi)部可能也有上下拉電阻或者處于輸入模式時(shí)的高阻態(tài)。這樣整條漏電通路會(huì)把紐扣電池的電白白消耗掉導(dǎo)致時(shí)鐘模塊“明明只有電池供電卻幾天就沒電”。我第一次畫板時(shí)用的成品模塊就是這種漏電設(shè)計(jì)。后來實(shí)際測(cè)下來電池電量在正常使用中大概三到四個(gè)月就掉到 2.5V 以下和理論壽命差了幾十倍。解決方法是使用 DS1302 時(shí)模塊上只保留 SCLK、I/O、CE 到 MCU 的連接不接任何到 VCC2 的上拉電阻如果 MCU 的 GPIO 內(nèi)部可以配置上下拉也建議把這三個(gè)引腳配置為復(fù)用推挽輸出或浮空輸入不要開內(nèi)部上拉。2.3 紐扣電池選型和壽命算賬紐扣電池選型最常用的肯定是 CR2032。單顆標(biāo)稱容量約 220mAh 到 240mAh具體看品牌和批次。DS1302 在掉電模式下也就是 VCC1 單獨(dú)供電時(shí)典型電流是 300nA 左右最壞情況會(huì)到 1μA。按 220mAh、300nA 算理論壽命高達(dá) 80 多年。但實(shí)際還要考慮電池自放電CR2032 年自放電率大概在 1%~2%、PCB 漏電流、上拉電阻漏電以及溫度影響。保守估計(jì)在常溫環(huán)境下能撐 5 到 8 年已經(jīng)遠(yuǎn)超很多產(chǎn)品的生命周期。如果你對(duì)體積有要求可以選 CR1220容量大概 40mAh壽命會(huì)縮短到兩年左右。用在消費(fèi)類小設(shè)備上結(jié)合換電池維護(hù)周期來選就夠。還有一種方案是使用超級(jí)電容比如 1F 或者 3F 的DS1302 的 VCC1 可以接超級(jí)電容然后通過 VCC2 對(duì)超級(jí)電容充電。超級(jí)電容支持反復(fù)充放電適合那些需要短時(shí)間斷電保持時(shí)鐘但又不希望換電池的場(chǎng)景。3. STM32 與 DS1302 的完整接線和實(shí)物連接細(xì)節(jié)DS1302 和 STM32 的通信只需要三根線再加上電源和地總共五根線。三根線分別是 SCLK時(shí)鐘、I/O數(shù)據(jù)、CE片選。由于 DS1302 的通信協(xié)議是類 SPI 但又不完全兼容所以直接拿硬件 SPI 外設(shè)去驅(qū)動(dòng)反而不方便最通用的就是用 GPIO 模擬時(shí)序。3.1 我用的引腳分配STM32F103C8T6 為例當(dāng)時(shí)手里的主控是 STM32F103C8T6引腳不多我選了 PC13、PC14、PC15 三個(gè)引腳來連接 DS1302STM32 引腳連接目標(biāo)說明PC13DS1302 CE輸出模式控制片選PC14DS1302 SCLK輸出模式產(chǎn)生時(shí)鐘脈沖PC15DS1302 I/O雙向數(shù)據(jù)線需配置開漏/推挽切換3.3VDS1302 VCC2主電源GNDDS1302 GND共地電池正極DS1302 VCC1備用電源這里有個(gè)細(xì)節(jié)PC14 和 PC15 在 STM32 上默認(rèn)對(duì)應(yīng) OSC32_IN 和 OSC32_OUT只有當(dāng)你不需要使用 LSE 外部低速晶振時(shí)它們才能做普通 GPIO。我這次項(xiàng)目本來就用內(nèi)部 HSI不需要 LSE所以直接復(fù)用。如果你在同一個(gè)項(xiàng)目里用了 LSE 晶振就別把這兩個(gè)引腳拿來接 DS1302 了否則引腳沖突會(huì)導(dǎo)致 LSE 無法起振。PC13 這個(gè)引腳還兼顧 RTC 校準(zhǔn)功能在不使用 RTC 校準(zhǔn)輸出時(shí)也可以當(dāng)普通 GPIO 用。不過要注意PC13 最大輸出電流較小驅(qū)動(dòng)能力有限但控制 DS1302 的 CE 信號(hào)完全沒問題。3.2 成品模塊的“坑”跳線、電容和晶振負(fù)載市面上常見的 DS1302 模塊通常是一塊小板上面集成 DS1302、32.768kHz 晶振、電池座、幾個(gè)上拉電阻和濾波電容。多數(shù)模塊默認(rèn)配置是兼容 Arduino 的會(huì)通過跳線把 VCC2 和 VCC1 連在一起方便沒有獨(dú)立電池時(shí)也能用。這種接法導(dǎo)致的結(jié)果是主電源掉電后電池會(huì)直接給 VCC2 上的其他器件供電。如果模塊上還帶有電源指示燈有些模塊會(huì)焊一個(gè) LED這個(gè) LED 就會(huì)一直亮著把電池電量快速耗盡。焊接或插接模塊時(shí)建議先仔細(xì)看一下絲印和原理圖。如果模塊上有 “VBAT” 和 “VCC” 跳線通常默認(rèn)是連在一起的。要改成獨(dú)立供電得把跳線斷開讓 VCC2 只接主電源VCC1 只接電池。如果模塊上沒有跳線只是把 VCC1 和 VCC2 通過一根 PCB 銅線短接你甚至需要用小刀割斷銅箔。操作前一定要拿萬用表量一下 VCC1 和 VCC2 之間是否導(dǎo)通免得改完以后電池路徑根本沒斷。另外DS1302 內(nèi)部的晶振電路對(duì)負(fù)載電容極其敏感。官方手冊(cè)要求 32.768kHz 晶振的負(fù)載電容在 6pF 左右這也是很多高品質(zhì) RTC 晶振的常見標(biāo)稱值。如果模塊上焊的晶振是 12.5pF 負(fù)載電容的實(shí)際頻率會(huì)偏離額定值每天可能差好幾秒。質(zhì)檢嚴(yán)格的話應(yīng)該在焊接后實(shí)測(cè)一下頻率用頻率計(jì)或示波器看 X1/X2 引腳波形誤差控制在 ±20ppm 以內(nèi)比較理想。3.3 DIY 電路板時(shí)的布局建議如果是自己畫板DS1302 的布局核心是晶振。晶振要盡量靠近 X1/X2 引腳走線短且等長不要在晶振下面穿其他高速信號(hào)線。晶振兩端的負(fù)載電容大小按晶振規(guī)格書選取很多 6pF 負(fù)載晶振在 DS1302 上甚至不需要外加電容但要預(yù)留焊盤位置方便調(diào)試時(shí)調(diào)整。VCC2 和 VCC1 的濾波電容建議各放一個(gè) 0.1μF靠近電源引腳。VCC1 到電池座的線路上不需要串聯(lián)電阻但可以加一個(gè)反接保護(hù)二極管嗎我的建議是不要加。二極管壓降會(huì)吃掉電池電壓導(dǎo)致電池電壓低于 2.0V 時(shí) DS1302 無法工作。正確做法是電池座的正負(fù)極絲印標(biāo)注清楚焊接時(shí)確認(rèn)方向。CR2032 如果是平裝電池座一般有防呆設(shè)計(jì)但如果是彈簧式裝反的概率還是不低的。4. 軟件驅(qū)動(dòng)實(shí)現(xiàn)和時(shí)序設(shè)計(jì)手寫一套 DS1302 驅(qū)動(dòng)DS1302 的數(shù)據(jù)手冊(cè)上寫得很清楚它的通信協(xié)議是類似 SPI 的三線制但每次讀寫都要先發(fā)一個(gè)命令字節(jié)然后才能讀寫數(shù)據(jù)。命令字節(jié)最低位是讀/寫控制位如果是 0 表示寫1 表示讀高 5 位是寄存器地址最高位固定為 1。整個(gè)過程每發(fā)一個(gè)字節(jié)都是低位在前LSB first這是它在軟件上和普通 SPI 最大的區(qū)別。4.1 為什么不用硬件 SPI而是用 GPIO 模擬DS1302 的操作是由 MCU 產(chǎn)生 SCLK然后 I/O 在 SCLK 的下降沿輸出或采樣數(shù)據(jù)。這其實(shí)是 SPI 模式 0 的變體但有兩個(gè)關(guān)鍵點(diǎn)導(dǎo)致硬件 SPI 不好用一是命令字節(jié)和后續(xù)數(shù)據(jù)字節(jié)之間SCLK 需要連續(xù)產(chǎn)生且不能違反 DS1302 的時(shí)序約束二是 I/O 方向需要頻繁切換寫完命令字節(jié)后要立刻把 I/O 從輸出模式切到輸入模式硬件 SPI 做這種半雙工切換比較麻煩GPIO 模擬反而直觀可控。用 GPIO 模擬的好處還有一點(diǎn)不占用固定的 SPI 外設(shè)引腳可以根據(jù)實(shí)際 PCB 布線任意分配。對(duì)于低速 RTC 芯片來說GPIO 翻轉(zhuǎn)速度完全夠用只要控制好延時(shí)時(shí)序穩(wěn)定度沒有問題。4.2 DS1302 讀寫一個(gè)字節(jié)的完整代碼以 STM32 標(biāo)準(zhǔn)外設(shè)庫為例下面這段代碼實(shí)現(xiàn)了一個(gè)最基本的字節(jié)寫入和字節(jié)讀取。寫入時(shí)在 SCLK 上升沿前把數(shù)據(jù)放到 I/O 上讀取時(shí)在 SCLK 下降沿后讀取 I/O 的電平。#define DS1302_CE_HIGH GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET) #define DS1302_CE_LOW GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET) #define DS1302_CLK_HIGH GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_SET) #define DS1302_CLK_LOW GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_RESET) #define DS1302_IO_HIGH GPIO_WriteBit(GPIOC, GPIO_Pin_15, Bit_SET) #define DS1302_IO_LOW GPIO_WriteBit(GPIOC, GPIO_Pin_15, Bit_RESET) /* 微秒級(jí)延時(shí)根據(jù)主頻調(diào)整 */ static void delay_us(uint32_t us) { uint32_t i; for (i 0; i us * 8; i) { __NOP(); } } void DS1302_WriteByte(uint8_t dat) { uint8_t i; GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽輸出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); for (i 0; i 8; i) { DS1302_CLK_LOW; if (dat 0x01) { DS1302_IO_HIGH; } else { DS1302_IO_LOW; } delay_us(1); DS1302_CLK_HIGH; delay_us(1); dat 1; } } uint8_t DS1302_ReadByte(void) { uint8_t i, dat 0; GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 浮空輸入外部上拉 GPIO_Init(GPIOC, GPIO_InitStructure); for (i 0; i 8; i) { dat 1; DS1302_CLK_HIGH; delay_us(1); DS1302_CLK_LOW; delay_us(1); if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15)) { dat | 0x80; } } return dat; }需要注意寫字節(jié)之前要把 I/O 配置為推挽輸出讀字節(jié)之前要切換成輸入模式。如果沒有做模式切換I/O 引腳一直保持輸出狀態(tài)去讀數(shù)據(jù)讀回來的會(huì)一直是自己輸出的電平永遠(yuǎn)讀不到 DS1302 里存儲(chǔ)的正確時(shí)間。4.3 突發(fā)模式讀寫時(shí)間和 BCD 碼轉(zhuǎn)換DS1302 的時(shí)間寄存器地址從秒到年分別是 0x80、0x82、0x84、0x86、0x88、0x8A、0x8C??梢砸粋€(gè)寄存器一個(gè)寄存器地讀寫也可以使用突發(fā)模式Burst Mode一次性連續(xù)讀寫 7 個(gè)字節(jié)。寫突發(fā)命令字節(jié)是 0xBE讀突發(fā)命令字節(jié)是 0xBF。我習(xí)慣使用突發(fā)模式因?yàn)榇a更簡(jiǎn)潔而且一次性把時(shí)間載入結(jié)構(gòu)體不容易出現(xiàn)讀取到一半發(fā)生進(jìn)位導(dǎo)致時(shí)間不連貫的問題。當(dāng)然如果對(duì)時(shí)序要求極高可以在讀秒寄存器的值時(shí)把 CE 拉低重讀直到連續(xù)兩次讀到的秒值一致這樣可以避免在秒進(jìn)位瞬間讀到不一致的時(shí)間。時(shí)間數(shù)據(jù)在 DS1302 內(nèi)部是以 BCD 碼二進(jìn)制編碼的十進(jìn)制存儲(chǔ)的。比如秒寄存器里存 0x35表示的其實(shí)是 35 秒而不是十六進(jìn)制的 0x3553 秒。所以寫入前要把整數(shù)時(shí)間轉(zhuǎn)換為 BCD 碼讀取后要把 BCD 碼還原成正常的十進(jìn)制數(shù)。最簡(jiǎn)單的方法是使用除法和取余uint8_t dec_to_bcd(uint8_t dec) { return ((dec / 10) 4) | (dec % 10); } uint8_t bcd_to_dec(uint8_t bcd) { return ((bcd 4) * 10) (bcd 0x0F); }下面是設(shè)置和讀取時(shí)間的完整例程包含了寫保護(hù)位處理typedef struct { uint8_t year; // 00~99 uint8_t month; // 01~12 uint8_t date; // 01~31 uint8_t hour; // 00~23 uint8_t minute; // 00~59 uint8_t second; // 00~59 uint8_t week; // 01~07 } RTC_TimeTypeDef; void DS1302_SetTime(RTC_TimeTypeDef *tm) { DS1302_CE_HIGH; // 關(guān)閉寫保護(hù) DS1302_WriteByte(0x8E); DS1302_WriteByte(0x00); DS1302_CE_LOW; // 進(jìn)入突發(fā)寫模式 DS1302_CE_HIGH; DS1302_WriteByte(0xBE); DS1302_WriteByte(dec_to_bcd(tm-second)); DS1302_WriteByte(dec_to_bcd(tm-minute)); DS1302_WriteByte(dec_to_bcd(tm-hour)); DS1302_WriteByte(dec_to_bcd(tm-date)); DS1302_WriteByte(dec_to_bcd(tm-month)); DS1302_WriteByte(dec_to_bcd(tm-week)); DS1302_WriteByte(dec_to_bcd(tm-year)); DS1302_CE_LOW; } void DS1302_GetTime(RTC_TimeTypeDef *tm) { uint8_t buf[7]; uint8_t i; DS1302_CE_HIGH; DS1302_WriteByte(0xBF); // 讀突發(fā)模式 for (i 0; i 7; i) { buf[i] DS1302_ReadByte(); } DS1302_CE_LOW; tm-second bcd_to_dec(buf[0]); tm-minute bcd_to_dec(buf[1]); tm-hour bcd_to_dec(buf[2]); tm-date bcd_to_dec(buf[3]); tm-month bcd_to_dec(buf[4]); tm-week bcd_to_dec(buf[5]); tm-year bcd_to_dec(buf[6]); }初始化時(shí)把 DS1302 的 CE 引腳拉低然后發(fā)送 0x8E 寫保護(hù)關(guān)閉命令。DS1302 的寫保護(hù)寄存器默認(rèn)值是 0x80即寫保護(hù)開啟如果不先關(guān)閉任何對(duì)時(shí)間寄存器的寫入都會(huì)被忽略這是很多新手最容易忽略的問題。設(shè)置完時(shí)間后可以再寫一次 0x8E 和 0x80 重新開啟寫保護(hù)防止后續(xù)程序誤改時(shí)間。4.4 “頻率計(jì)”調(diào)試技巧與精度校正DS1302 的時(shí)間精度完全取決于外部 32.768kHz 晶振。晶振本身有頻率偏差溫度變化還會(huì)引起漂移所以整機(jī)走時(shí)不可能做到絕對(duì)精準(zhǔn)。在實(shí)際調(diào)試時(shí)我會(huì)在 DS1302 的 X1 或 X2 引腳上引出一根測(cè)試線用頻率計(jì)測(cè)量晶振頻率。嚴(yán)格來說X1/X2 引腳直接測(cè)量會(huì)引入探頭電容導(dǎo)致頻率輕微改變但用來判斷晶振是否起振、頻率是否嚴(yán)重偏出范圍已經(jīng)足夠。如果實(shí)測(cè)頻率偏低或者偏高且偏差固定可以在軟件里做校準(zhǔn)。DS1302 雖然有數(shù)字校準(zhǔn)寄存器但最常用的做法是每秒對(duì)晶振計(jì)數(shù)然后軟件補(bǔ)償。比如實(shí)測(cè)頻率是 32767.5Hz一天就差大約 1.3 秒可以在驅(qū)動(dòng)里每隔一段時(shí)間統(tǒng)一修正一次。另一種思路是用外部高精度時(shí)間源比如通過串口或者 WiFi 拿到網(wǎng)絡(luò)時(shí)間在設(shè)備空閑時(shí)定期同步 DS1302這樣即使單顆晶振有誤差長期來看時(shí)間也能保持準(zhǔn)確。這里再聊一下“時(shí)鐘抖動(dòng)、頻偏和漂移”這幾個(gè)詞。抖動(dòng)是時(shí)鐘沿在短時(shí)間內(nèi)的隨機(jī)偏移主要影響高速信號(hào)時(shí)序?qū)?RTC 這種低速設(shè)備影響不大頻偏是晶振實(shí)際頻率和標(biāo)稱值的固定偏差導(dǎo)致每天都有穩(wěn)定的誤差漂移則是頻率隨溫度、電壓、老化等原因發(fā)生的緩慢變化。做產(chǎn)品時(shí)如果 DS1302 放在溫度變化劇烈的環(huán)境里就要用更高精度的溫補(bǔ)晶振或者定期接收外部時(shí)間同步。5. 常見問題速查與實(shí)戰(zhàn)避坑DS1302 雖然簡(jiǎn)單但實(shí)際項(xiàng)目中還是有不少經(jīng)典問題。我根據(jù)實(shí)際調(diào)試中踩過的坑整理了一個(gè)排查順序你可以按這個(gè)順序一步步查。5.1 時(shí)間不走、亂碼、寫不進(jìn)去三個(gè)經(jīng)典問題第一個(gè)問題是時(shí)間完全不走讀取出來一直是 00:00:00 或者不變。這種大概率是 32.768kHz 晶振沒有起振。用示波器或頻率計(jì)測(cè) X1 引腳如果沒有波形先檢查晶振是否虛焊、負(fù)載電容是否匹配、DS1302 供電是否正常。有些模塊的晶振外殼是金屬的焊接時(shí)焊錫把外殼和鄰近引腳連在一起也會(huì)導(dǎo)致停振。第二個(gè)問題是時(shí)間能讀出來但設(shè)置進(jìn)去以后不生效。檢查寫保護(hù)是否關(guān)閉命令字節(jié)是否正確。DS1302 的寫保護(hù)寄存器是 0x8E寫入 0x00 才能解除保護(hù)。每次上電時(shí)芯片默認(rèn)寫保護(hù)開啟所以初始化函數(shù)里必須先發(fā)送關(guān)閉寫保護(hù)命令。第三個(gè)問題是數(shù)據(jù)亂碼比如讀出來的月份是 19、秒是 78。這種情況通常是 BCD 碼轉(zhuǎn)換函數(shù)寫反了或者讀取時(shí) I/O 引腳沒有正確切換為輸入模式。還有一種可能是讀取過程中 SCLK 的上升沿和下降沿延時(shí)太短總線數(shù)據(jù)還沒穩(wěn)定就被采樣導(dǎo)致誤碼。我給讀寫函數(shù)加的 1μs 延時(shí)實(shí)際在 72MHz 主頻下跑完全沒問題但如果你用的主頻更高可以適當(dāng)延長到 2μs。5.2 電池裝反、漏電和“涓流充電”的正確姿勢(shì)DS1302 支持涓流充電功能寄存器地址是 0x90。當(dāng) VCC2 主電源存在時(shí)可以按設(shè)定好的電阻/二極管參數(shù)給 VCC1 上的充電電池供電。注意這個(gè)功能默認(rèn)是關(guān)閉的只有你往 0x90 寄存器寫入特定值才會(huì)啟用。寄存器數(shù)據(jù)結(jié)構(gòu)是高 4 位寫 1010 使能充電第 3、2 位選擇串聯(lián)二極管數(shù)量第 1、0 位選擇限流電阻阻值。千萬不要給普通 CR2032 紐扣電池開啟涓流充電。CR2032 是一次性鋰錳電池不可充電強(qiáng)行充電會(huì)導(dǎo)致電池內(nèi)部壓力升高、漏液甚至鼓包反過來損壞 PCB 和芯片。如果你要用可充電電池就選 LIR2032這是專門的可充電紐扣電池或者用超級(jí)電容。在實(shí)際項(xiàng)目里我一般選擇直接禁止涓流充電把 0x90 寄存器寫入 0x00讓 VCC1 只作為純備用電源。關(guān)于漏電前面提到過成品模塊的上拉電阻會(huì)造成電池額外消耗。最穩(wěn)妥的做法是把模塊上接在 SCLK/I/O/CE 三個(gè)引腳到 VCC2 的上拉電阻全部拆掉。如果你的板子是高速信號(hào)或者長走線確實(shí)需要上拉來保證信號(hào)完整性那可以把上拉電阻改接到 VCC2 的“受控電源”上也就是掉電后跟著主電源一起斷開的電源軌這樣就不會(huì)消耗電池電量。5.3 低功耗整機(jī)設(shè)計(jì)中DS1302 怎么和 STM32 深度睡眠共存很多手持設(shè)備在待機(jī)時(shí)需要進(jìn)入 STOP 模式甚至待機(jī)模式把整機(jī)電流降到微安級(jí)別。DS1302 在這種情況下有兩個(gè)作用一是作為實(shí)時(shí)時(shí)鐘維持當(dāng)前時(shí)間二是作為鬧鐘在特定時(shí)間通過中斷喚醒 MCU。但要注意DS1302 的鬧鐘功能需要 CE/SCLK/I/O 之外的中斷輸出引腳而很多 DS1302 模塊并沒有引出這個(gè)引腳甚至有些芯片型號(hào)根本沒有鬧鐘輸出功能。用之前要仔細(xì)看有沒有 INT 或者 /INT 引腳不要想當(dāng)然。如果 MCU 進(jìn)入 STOP 模式GPIO 保持之前配置的輸出狀態(tài)。為了讓 DS1302 在不工作時(shí)盡量少耗電可以在 MCU 睡眠前把 CE 引腳拉低SCLK 和 I/O 引腳都配置為浮空輸入或者輸出低電平避免在總線上產(chǎn)生電平?jīng)_突。實(shí)測(cè)這樣配置后DS1302 的靜態(tài)電流很小整機(jī)待機(jī)電流基本能控制在 10μA 以下。還有一點(diǎn)STM32 進(jìn)入 STOP 模式后如果 DS1302 和 MCU 共用同一個(gè) 3.3V LDO而 LDO 的靜態(tài)電流比較大那整機(jī)功耗未必能降下來。想要極低待機(jī)可以考慮用一顆帶使能引腳的 LDO由 MCU 的某個(gè) GPIO 控制在進(jìn)入 STOP 前關(guān)閉主電源只讓 DS1302 和電池回路繼續(xù)工作。6. 我踩過的坑和最后的幾條經(jīng)驗(yàn)最后說點(diǎn)實(shí)在的。DS1302 這個(gè)芯片看起來非常簡(jiǎn)單但真正做好“不掉電”卻需要仔細(xì)處理電源路徑、晶振選型和軟件時(shí)序。我最初用模塊時(shí)遇到過電池幾個(gè)月就沒電的問題后來發(fā)現(xiàn)是模塊上 VCC2 和 VCC1 的跳線沒有斷開LED 指示燈一直在漏電。還有一次寫代碼忘了關(guān)閉寫保護(hù)結(jié)果調(diào)了一整天所有寄存器寫入都無效最后翻數(shù)據(jù)手冊(cè)才發(fā)現(xiàn)這個(gè)問題。從產(chǎn)品化角度看如果你想把這套方案用到量產(chǎn)設(shè)備里我建議做這幾件事第一在 PCB 上預(yù)留 VCC1 電池電壓檢測(cè)點(diǎn)出廠前用自動(dòng)化測(cè)試設(shè)備量一下電池電壓防止裝到電池虛接或漏電的板子第二在固件里加一個(gè)“RTC 初始化標(biāo)志”在 DS1302 備份寄存器或外部 EEPROM 里寫入一個(gè)魔術(shù)字每次上電讀取時(shí)間時(shí)順便校驗(yàn)一下如果時(shí)間年份小于 2020 年就認(rèn)為 RTC 已經(jīng)失效主動(dòng)進(jìn)入待設(shè)置狀態(tài)而不是讓系統(tǒng)帶著錯(cuò)誤時(shí)間運(yùn)行第三設(shè)置時(shí)間時(shí)最好通過串口或上位機(jī)寫入不要每次都走默認(rèn)編譯時(shí)間因?yàn)楣碳幾g時(shí)間和實(shí)際出廠時(shí)間往往有幾天甚至幾個(gè)月的偏差。這套 DS1302 紐扣電池的組合優(yōu)點(diǎn)是成本低、電路簡(jiǎn)單、開發(fā)周期短。缺點(diǎn)是芯片本身比較老沒有內(nèi)置溫度補(bǔ)償精度上限確實(shí)不如高端的 RX8025 或甚至內(nèi)部 RTC 搭配溫補(bǔ)晶振。如果你需要更高精度可以換成 RX8025T 這類帶溫度補(bǔ)償?shù)?RTC但供電切換邏輯和軟件驅(qū)動(dòng)思路是相通的。把 DS1302 的底層吃透再遷移到其他 RTC 上基本就是改個(gè)寄存器映射的事。希望這份“加不掉電時(shí)鐘”的實(shí)戰(zhàn)記錄能幫到正在折騰 STM32 外部 RTC 的朋友。如果你也遇到類似問題歡迎交流尤其是晶振選型和低功耗漏電這兩塊很多時(shí)候細(xì)節(jié)決定了產(chǎn)品最終體驗(yàn)。