用:數(shù)據(jù)掉電保存與侵入檢測(cè)詳解)
1. 項(xiàng)目概述為什么需要BKP備份寄存器在嵌入式開發(fā)尤其是基于STM32這類微控制器的項(xiàng)目中我們經(jīng)常會(huì)遇到一個(gè)看似簡單卻至關(guān)重要的需求如何在系統(tǒng)掉電、復(fù)位甚至軟件跑飛的情況下依然能保存住幾個(gè)關(guān)鍵的數(shù)據(jù)比如一個(gè)設(shè)備的運(yùn)行總時(shí)長、用戶最后一次的設(shè)置參數(shù)、或者是一個(gè)需要掉電保持的計(jì)數(shù)器。你可能會(huì)想到外接一個(gè)EEPROM或者FRAM芯片這當(dāng)然是個(gè)好辦法但對(duì)于那些成本敏感、PCB空間有限或者僅僅需要保存少量數(shù)據(jù)比如十幾個(gè)字節(jié)的應(yīng)用來說這就顯得有點(diǎn)“殺雞用牛刀”了。STM32F103系列芯片內(nèi)部集成的BKPBackup Register備份寄存器就是為了解決這個(gè)痛點(diǎn)而生的。它本質(zhì)上是一小片由備用電源VBAT引腳供電的存儲(chǔ)區(qū)域。只要你在VBAT引腳上接了一個(gè)紐扣電池或者超級(jí)電容那么無論主電源VDD是否消失無論芯片是正常復(fù)位還是被“打暈”了這片區(qū)域里的數(shù)據(jù)都能“毫發(fā)無傷”地保留下來。我第一次在項(xiàng)目里用它來保存設(shè)備序列號(hào)和校準(zhǔn)參數(shù)時(shí)感覺就像給數(shù)據(jù)上了個(gè)“保險(xiǎn)箱”心里踏實(shí)多了。今天這篇筆記我們就拋開代碼深入STM32F103的BKP模塊原理層把它扒個(gè)底朝天。理解了原理你才能用得放心避得開坑。我們會(huì)從它的供電體系、訪問機(jī)制、與復(fù)位的關(guān)系一直聊到那個(gè)經(jīng)常讓人困惑的“侵入檢測(cè)”功能。無論你是剛開始接觸STM32還是想深化對(duì)芯片內(nèi)部資源的理解這篇內(nèi)容都會(huì)給你帶來收獲。2. BKP備份寄存器的核心架構(gòu)與供電原理要理解BKP首先要明白它的“獨(dú)立生存能力”從何而來。這完全依賴于一套獨(dú)立的供電體系。2.1 雙電源供電域主VDD與備用VBATSTM32F103內(nèi)部有一個(gè)清晰的電源域劃分。對(duì)于BKP相關(guān)電路它位于一個(gè)叫“備份域”的獨(dú)立區(qū)域。這個(gè)區(qū)域的供電有兩種可能主電源VDD供電當(dāng)芯片主電源正常時(shí)整個(gè)備份域由VDD通過內(nèi)部電路供電。此時(shí)即使VBAT引腳上接了電池電池也處于無負(fù)載狀態(tài)不會(huì)被消耗。備用電源VBAT供電一旦檢測(cè)到VDD掉電或電壓低于某個(gè)閾值電源切換電路會(huì)自動(dòng)、無縫地將備份域的供電切換到VBAT引腳上接入的備用電源。這個(gè)切換過程是硬件自動(dòng)完成的你的程序完全感知不到。注意這個(gè)VBAT引腳一定要妥善處理。如果你不需要掉電保持功能官方建議是將VBAT引腳連接到VDD。如果你需要那么典型接法是一個(gè)3V的紐扣電池如CR2032正極接VBAT負(fù)極接VSS。同時(shí)為了在VDD上電瞬間避免對(duì)電池反沖通常還會(huì)在VBAT通路上串聯(lián)一個(gè)低壓降的肖特基二極管。2.2 BKP寄存器陣列你的數(shù)據(jù)保險(xiǎn)箱在備份域里BKP寄存器就是我們要用的“保險(xiǎn)箱”。在STM32F103中這個(gè)保險(xiǎn)箱有42個(gè)16位寄存器BKP_DR1 ~ BKP_DR42。算一下總共是84個(gè)字節(jié)的存儲(chǔ)空間。別嫌少對(duì)于保存一些關(guān)鍵標(biāo)志、ID、校準(zhǔn)值或計(jì)數(shù)器這通常綽綽有余了。這些寄存器的關(guān)鍵特性在于它們的“非易失性”。但這種非易失性并非像Flash那樣通過浮柵晶體管實(shí)現(xiàn)而是通過其所在的供電域備份域始終不斷電來保證的。只要VBAT有電這片SRAM區(qū)域就不會(huì)丟失數(shù)據(jù)。因此從物理本質(zhì)上講BKP寄存器更像是“電池備份的SRAM”。2.3 訪問鑰匙PWR與BKP時(shí)鐘使能既然BKP在備份域而備份域又相對(duì)獨(dú)立那么訪問它就需要一點(diǎn)“特權(quán)”。你不能像操作GPIO寄存器那樣直接寫。這里涉及兩個(gè)關(guān)鍵步驟開啟備份域訪問權(quán)限這是通過電源控制寄存器PWR_CR中的DBPDisable Backup Domain Write Protection位來實(shí)現(xiàn)的。默認(rèn)情況下為了安全備份域是寫保護(hù)的。你必須先設(shè)置這個(gè)位為1才能修改備份域內(nèi)的寄存器包括BKP和RTC。這就像進(jìn)保險(xiǎn)箱房間前需要先打開第一道門禁。使能BKP接口時(shí)鐘BKP本身是一個(gè)外設(shè)它的接口掛載在APB1總線上。因此在訪問BKP寄存器之前必須通過RCC_APB1ENR寄存器使能BKP的時(shí)鐘。這是所有外設(shè)操作的前提沒有時(shí)鐘芯片就無法與這個(gè)外設(shè)模塊通信。// 典型的初始化步驟原理示意非完整代碼 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); // 這個(gè)函數(shù)內(nèi)部就是設(shè)置PWR_CR-DBP位只有完成了這兩步你才能安全地對(duì)BKP_DRx進(jìn)行讀寫操作。我剛開始時(shí)就忘了使能PWR時(shí)鐘結(jié)果死活寫不進(jìn)去數(shù)據(jù)排查了半天才找到這個(gè)原因。3. BKP與復(fù)位系統(tǒng)的關(guān)系何謂“真正的復(fù)位”這是理解BKP數(shù)據(jù)何時(shí)會(huì)丟失的關(guān)鍵。STM32的復(fù)位有很多種上電復(fù)位、外部引腳復(fù)位、看門狗復(fù)位、軟件復(fù)位等等。但它們對(duì)備份域的影響是不同的。備份域有一個(gè)獨(dú)立的、由VBAT供電的“備份域復(fù)位發(fā)生器”。只有特定的復(fù)位源能觸發(fā)它一旦觸發(fā)整個(gè)備份域包括BKP寄存器和RTC會(huì)被重置你的寶貴數(shù)據(jù)就沒了。會(huì)導(dǎo)致備份域復(fù)位的操作有VDD或VBAT上電芯片第一次通電或者備用電池新裝上。從待機(jī)模式Standby喚醒這是一個(gè)關(guān)鍵點(diǎn)當(dāng)芯片從低功耗的待機(jī)模式被喚醒時(shí)會(huì)產(chǎn)生一個(gè)備份域復(fù)位。這意味著如果你使用待機(jī)模式來省電那么在喚醒后BKP里的數(shù)據(jù)會(huì)清零。這是一個(gè)非常重要的設(shè)計(jì)因?yàn)榇龣C(jī)模式會(huì)關(guān)閉幾乎所有電源域喚醒相當(dāng)于一次“熱啟動(dòng)”。軟件對(duì)備份域復(fù)位寄存器的操作通過設(shè)置RCC_BDCR寄存器中的BDRST位可以主動(dòng)觸發(fā)備份域復(fù)位用于需要徹底清空備份數(shù)據(jù)的場景。不會(huì)導(dǎo)致備份域復(fù)位的操作有外部復(fù)位引腳NRST復(fù)位窗口看門狗WWDG或獨(dú)立看門狗IWDG超時(shí)復(fù)位軟件復(fù)位通過SYSRESETREQ從停機(jī)模式Stop喚醒停機(jī)模式只關(guān)閉核心時(shí)鐘和部分電源域備份域依然由VBAT供電因此BKP數(shù)據(jù)得以保留。這是停機(jī)模式和待機(jī)模式在數(shù)據(jù)保持上的一個(gè)重大區(qū)別。簡單來說你可以這樣記只要VBAT不斷電并且芯片沒有經(jīng)歷“上電”或“從待機(jī)模式喚醒”這兩種情況BKP里的數(shù)據(jù)就會(huì)一直存在。這個(gè)特性決定了你在設(shè)計(jì)低功耗和數(shù)據(jù)保持策略時(shí)的選擇。4. 侵入檢測(cè)Tamper Detection功能深度解析BKP模塊還有一個(gè)高級(jí)功能侵入檢測(cè)。它通過一個(gè)特定的引腳TAMPER通常與PC13復(fù)用來檢測(cè)非法的物理入侵。這個(gè)功能的名字聽起來很酷原理其實(shí)不難理解但配置和使用上有不少細(xì)節(jié)。4.1 侵入檢測(cè)的工作原理PC13/TAMPER引腳可以被配置為侵入檢測(cè)引腳。你可以選擇該引腳上的電平變化上升沿或下降沿作為侵入事件。一旦檢測(cè)到侵入事件硬件會(huì)自動(dòng)完成以下幾件事產(chǎn)生侵入中斷如果中斷使能會(huì)跳轉(zhuǎn)到侵入中斷服務(wù)程序。清除所有BKP數(shù)據(jù)寄存器這是侵入檢測(cè)的核心安全功能所有BKP_DRx寄存器的值會(huì)被硬件自動(dòng)清零以防敏感數(shù)據(jù)被竊取。置位狀態(tài)標(biāo)志在BKP控制狀態(tài)寄存器中會(huì)有一個(gè)標(biāo)志位被置起表明發(fā)生過侵入事件。這個(gè)功能的典型應(yīng)用場景是安全設(shè)備。比如一個(gè)加密狗如果檢測(cè)到外殼被非法打開通過一個(gè)連接到TAMPER引腳的微動(dòng)開關(guān)就立即擦除內(nèi)部存儲(chǔ)的密鑰。又或者在智能電表中防止有人通過物理手段篡改數(shù)據(jù)。4.2 配置要點(diǎn)與常見誤區(qū)雖然功能強(qiáng)大但侵入檢測(cè)用起來要格外小心一個(gè)配置不當(dāng)就可能誤觸發(fā)導(dǎo)致數(shù)據(jù)被意外清空。關(guān)鍵配置步驟使能侵入引腳時(shí)鐘PC13的時(shí)鐘在APB2上需要先使能GPIOC的時(shí)鐘。配置侵入引腳將PC13配置為浮空輸入模式。特別注意不能將它配置為推挽輸出等模式否則可能無法正確檢測(cè)侵入事件甚至導(dǎo)致引腳沖突。配置侵入檢測(cè)通過BKP_TPCR寄存器選擇有效的侵入邊沿上升沿、下降沿或兩者都有效并決定是否使能侵入中斷。清除侵入標(biāo)志在使能侵入檢測(cè)前最好先讀一次BKP_CSR寄存器中的侵入標(biāo)志位TEF并將其清零以清除可能存在的舊狀態(tài)。使能侵入檢測(cè)最后一步設(shè)置BKP_TPCR寄存器中的TEN位為1啟動(dòng)檢測(cè)功能。一個(gè)我踩過的坑引腳復(fù)用沖突。PC13這個(gè)引腳是個(gè)“大忙人”它除了作為普通IO和TAMPER在有些封裝上還與RTC的校準(zhǔn)時(shí)鐘輸出等功能復(fù)用。如果你在程序中既想用它做普通IO比如驅(qū)動(dòng)一個(gè)LED又開啟了侵入檢測(cè)那就必然會(huì)導(dǎo)致沖突。結(jié)果就是IO控制失靈侵入檢測(cè)可能誤觸發(fā)。所以在設(shè)計(jì)之初就要規(guī)劃好引腳功能避免復(fù)用沖突。如果一定要用侵入檢測(cè)最好將這個(gè)引腳專門預(yù)留出來并做好硬件防誤觸處理比如上拉/下拉。5. BKP寄存器的讀寫操作機(jī)制與數(shù)據(jù)管理理解了原理和架構(gòu)最后我們來看看如何具體地“擺弄”這些寄存器。操作本身很簡單但背后的數(shù)據(jù)管理策略值得思考。5.1 直接的存儲(chǔ)器映射訪問BKP數(shù)據(jù)寄存器BKP_DR1 ~ BKP_DR42是直接映射到備份域地址空間的。在標(biāo)準(zhǔn)外設(shè)庫中你可以通過BKP_WriteBackupRegister函數(shù)來寫通過BKP_ReadBackupRegister函數(shù)來讀。本質(zhì)上這些函數(shù)就是在操作對(duì)應(yīng)的內(nèi)存地址。// 寫入數(shù)據(jù)到BKP_DR10 BKP_WriteBackupRegister(BKP_DR10, 0xABCD); // 從BKP_DR10讀取數(shù)據(jù) uint16_t myData BKP_ReadBackupRegister(BKP_DR10);操作本身是原子的16位訪問無需擔(dān)心讀寫過程中的斷電問題因?yàn)橹灰猇BAT在這個(gè)操作就能完成或完全沒發(fā)生。5.2 數(shù)據(jù)校驗(yàn)策略防止數(shù)據(jù)損壞雖然BKP很可靠但在極端情況下如電池電量極低導(dǎo)致電壓不穩(wěn)也不能100%排除數(shù)據(jù)出錯(cuò)的可能。因此對(duì)于非常重要的數(shù)據(jù)建議采用簡單的軟件校驗(yàn)策略而不是直接相信讀出來的值。一種簡單有效的校驗(yàn)方法和校驗(yàn)或CRC。例如你用了BKP_DR1 ~ BKP_DR5這5個(gè)寄存器來保存一個(gè)20字節(jié)的有效數(shù)據(jù)包。那么你可以再拿出一個(gè)寄存器比如BKP_DR6來存儲(chǔ)這個(gè)數(shù)據(jù)包的CRC16校驗(yàn)值。每次寫入數(shù)據(jù)時(shí)計(jì)算CRC并一同寫入。每次上電讀取數(shù)據(jù)后先根據(jù)讀出的數(shù)據(jù)重新計(jì)算CRC再與BKP_DR6中存儲(chǔ)的校驗(yàn)值對(duì)比。如果一致說明數(shù)據(jù)可信如果不一致則說明數(shù)據(jù)可能已損壞應(yīng)使用默認(rèn)值或嘗試從其他備份中恢復(fù)。// 偽代碼示例帶校驗(yàn)的數(shù)據(jù)寫入 void SaveCriticalData(uint8_t *data, uint16_t length) { uint16_t crc CalculateCRC16(data, length); // 將數(shù)據(jù)和CRC寫入到連續(xù)的BKP寄存器中... // 例如: Write data to DR1-DR5, Write crc to DR6 } // 偽代碼示例帶校驗(yàn)的數(shù)據(jù)讀取 bool LoadCriticalData(uint8_t *buffer, uint16_t length) { // 從BKP寄存器中讀出數(shù)據(jù)和存儲(chǔ)的CRC... uint16_t stored_crc ReadCRCFromBKP(); uint16_t calculated_crc CalculateCRC16(buffer, length); return (stored_crc calculated_crc); }這個(gè)小技巧能極大地提高數(shù)據(jù)的可靠性其原理和網(wǎng)絡(luò)傳輸、文件存儲(chǔ)中的校驗(yàn)是一樣的。5.3 寄存器用途規(guī)劃建議42個(gè)寄存器怎么分配才合理我建議根據(jù)數(shù)據(jù)的性質(zhì)和重要性進(jìn)行分組規(guī)劃系統(tǒng)關(guān)鍵數(shù)據(jù)區(qū)DR1-DR10存放設(shè)備唯一ID、硬件版本、系統(tǒng)啟動(dòng)次數(shù)等一旦設(shè)定幾乎不變且對(duì)系統(tǒng)啟動(dòng)至關(guān)重要的數(shù)據(jù)。這部分?jǐn)?shù)據(jù)校驗(yàn)可以簡單些。用戶參數(shù)區(qū)DR11-DR30存放用戶配置、校準(zhǔn)參數(shù)、運(yùn)行模式等。這部分?jǐn)?shù)據(jù)可能經(jīng)常修改建議每組參數(shù)配套一個(gè)校驗(yàn)寄存器。運(yùn)行狀態(tài)區(qū)DR31-DR40存放上次掉電時(shí)的狀態(tài)、錯(cuò)誤日志指針、臨時(shí)計(jì)數(shù)器等。這部分?jǐn)?shù)據(jù)變化頻繁可以考慮使用簡單的“魔數(shù)”校驗(yàn)在數(shù)據(jù)開頭或結(jié)尾寫入一個(gè)固定的已知值讀取時(shí)檢查這個(gè)值是否正確。保留區(qū)DR41-DR42預(yù)留以備不時(shí)之需。清晰的規(guī)劃能讓你的代碼更易維護(hù)也避免了未來因寄存器用途混亂而導(dǎo)致的bug。6. 常見問題排查與實(shí)戰(zhàn)經(jīng)驗(yàn)心得理論講完了最后分享一些在實(shí)際項(xiàng)目中摸爬滾打總結(jié)出來的經(jīng)驗(yàn)和常見問題。這些往往是數(shù)據(jù)手冊(cè)不會(huì)詳細(xì)告訴你但又能讓你事半功倍或者避免踩坑的干貨。6.1 數(shù)據(jù)讀寫失敗排查清單當(dāng)你發(fā)現(xiàn)BKP數(shù)據(jù)寫不進(jìn)去或者讀出來不對(duì)時(shí)可以按照以下清單逐一排查時(shí)鐘使能了嗎這是最常見的原因。確認(rèn)RCC_APB1PeriphClockCmd已經(jīng)同時(shí)使能了PWR和BKP的時(shí)鐘。少一個(gè)都不行。備份域?qū)懕Wo(hù)解除了嗎確認(rèn)在操作BKP前已經(jīng)調(diào)用了PWR_BackupAccessCmd(ENABLE)。這個(gè)保護(hù)位在每次復(fù)位后都是關(guān)閉的必須在初始化階段重新開啟。VBAT引腳接對(duì)了嗎如果需要掉電保持檢查VBAT引腳上的電池或電容是否連接良好電壓是否正常測(cè)量VBAT引腳對(duì)地電壓。如果沒接電池VDD掉電后數(shù)據(jù)必然丟失。經(jīng)歷了備份域復(fù)位嗎檢查你的系統(tǒng)是否發(fā)生了會(huì)導(dǎo)致備份域復(fù)位的操作比如剛從待機(jī)模式喚醒。如果是數(shù)據(jù)被清空是正?,F(xiàn)象。侵入檢測(cè)誤觸發(fā)了嗎如果你使能了侵入檢測(cè)功能檢查PC13引腳是否受到干擾如靜電、毛刺導(dǎo)致硬件誤認(rèn)為侵入事件發(fā)生從而清空了數(shù)據(jù)??梢杂檬静ㄆ髯ヒ幌略撘_的波形。地址或寄存器編號(hào)寫錯(cuò)了嗎仔細(xì)檢查代碼中對(duì)BKP_DRx的編號(hào)是否正確確保讀寫的是同一個(gè)寄存器。6.2 低功耗模式下的選擇Stop vs Standby這是一個(gè)非常重要的設(shè)計(jì)抉擇直接關(guān)系到BKP數(shù)據(jù)的生死存亡。如果你的應(yīng)用需要極低的功耗且可以接受在喚醒后丟失BKP數(shù)據(jù)或能在喚醒后從其他存儲(chǔ)介質(zhì)恢復(fù)那么可以選擇Standby模式。Standby模式功耗最低但喚醒后相當(dāng)于一次備份域復(fù)位。如果你的應(yīng)用需要低功耗但同時(shí)必須保留BKP數(shù)據(jù)那么必須選擇Stop模式。Stop模式功耗比Standby高但依然遠(yuǎn)低于運(yùn)行模式。在Stop模式下只要VBAT有電備份域就一直保持BKP數(shù)據(jù)安然無恙。喚醒后程序從進(jìn)入Stop的地方繼續(xù)執(zhí)行所有寄存器包括BKP狀態(tài)保持不變。我的經(jīng)驗(yàn)是在大多數(shù)需要數(shù)據(jù)保持的場合Stop模式是更常用、更安全的選擇。除非你的功耗要求極其苛刻并且數(shù)據(jù)可以丟棄或重建否則不要輕易使用Standby模式。6.3 電池壽命的粗略估算如果你使用紐扣電池為VBAT供電電池能撐多久這里有個(gè)非常粗略的估算方法幫助你選型。備份域在VBAT供電下的典型電流消耗在數(shù)據(jù)手冊(cè)中通常記為I_{VBAT}。對(duì)于STM32F103這個(gè)值在幾個(gè)微安μA的量級(jí)我們假設(shè)為5μA。一顆標(biāo)準(zhǔn)的CR2032紐扣電池容量大約是220mAh毫安時(shí)。那么理論續(xù)航時(shí)間 電池容量 / 消耗電流。 計(jì)算220 mAh / 0.005 mA 44000 小時(shí)。 44000小時(shí) ≈ 5年。注意這是一個(gè)極其理想化的數(shù)值實(shí)際壽命會(huì)大打折扣因?yàn)殡姵刈苑烹奀R2032的年自放電率可能在1%-2%。侵入檢測(cè)引腳如果使能可能會(huì)增加漏電流。RTC如果使能也會(huì)消耗額外的電流通常也是微安級(jí)。溫度影響低溫下電池容量會(huì)下降。所以在實(shí)際設(shè)計(jì)中如果希望設(shè)備在掉電后保持?jǐn)?shù)據(jù)數(shù)年除了選擇容量合適的電池更重要的是在軟件上優(yōu)化不用的功能一定要關(guān)閉比如不用侵入檢測(cè)時(shí)就徹底禁用它并確保硬件設(shè)計(jì)上VBAT通路的漏電流盡可能小。對(duì)于要求更高的場合可以考慮使用超級(jí)電容其循環(huán)壽命更長但需要計(jì)算好充電時(shí)間和保持時(shí)間。