備電源管理實戰(zhàn))
做電池供電的便攜設(shè)備最煩人的往往不是業(yè)務(wù)代碼而是電源那一攤子事。以前我做一個小型手持項目光電源部分就用了三顆LDO、一顆升壓、一顆充電管理芯片再加上一堆分立MOS和RC延時電路PCB上密密麻麻全是電源布線的影子。后來換成 PCA9422 和 PIC18F4610 這套組合——一顆多通道PMIC負責把電變出來、把電池充進去一顆8位單片機負責說“現(xiàn)在該用多少伏、該關(guān)哪一路”——電源板面積直接縮小一半待機功耗也更好控了。這篇文章就把這次完整電源管理項目的選型思路、硬件接法、固件狀態(tài)機和實測踩坑一次講透適合正在做手持表、便攜儀表、小型物聯(lián)網(wǎng)設(shè)備的開發(fā)者。1. 先想清楚這對組合各負責哪塊供電活1.1 為什么不用一堆分立芯片拼很多人一提到電源管理第一反應(yīng)是“我需要幾路就放幾路電源芯片”。思路沒有錯但等你真正做系統(tǒng)級設(shè)計問題就來了。三路DCDC要三套電感、三個反饋分壓電阻還要考慮每一路的啟動順序。充電部分單獨一顆芯片快充、恒流、截止邏輯全得靠MCU或比較器盯著。想給負載動態(tài)調(diào)壓那基本是重新畫一塊板。分立方案的另一個痛點是待機功耗。普通DCDC在輕載下的靜態(tài)電流經(jīng)常到幾十上百微安幾路加起來一個本來設(shè)計成500uA待機的系統(tǒng)光電源芯片就吃掉一半預(yù)算。PCA9422這類集成PMIC的好處在于內(nèi)部有多路BUCK和LDO還有專門的低功耗工作模式輕載時自動進入PFM整顆芯片的靜態(tài)功耗可以壓到比較低的水平系統(tǒng)級待機就很好做。再加上集成PMIC通常自帶充電管理、ADC采樣、中斷輸出和I2C寄存器控制面。供電、充電、電壓監(jiān)測、動態(tài)調(diào)壓這些原本要靠五六顆芯片加一堆邏輯才能做的事現(xiàn)在一顆芯片加一個單片機就能全部覆蓋。硬件BOM減少組裝和測試成本也跟著降量產(chǎn)一致性比手工拼出來的方案好一截。1.2 兩者的分工硬件執(zhí)行 vs 策略大腦PCA9422負責的是“電力的物理變換”把電池電壓變成穩(wěn)定的多路電源軌給電池充電監(jiān)測電壓電流和溫度在出現(xiàn)過流、過溫時做硬件級保護。但這些行為具體怎么執(zhí)行給哪一路設(shè)定多少伏什么時候充電、什么時候切到低功耗需要一個大腦來決策。PIC18F4610在這里就是那個大腦。有人會覺得奇怪做個電源管理怎么還要上單片機其實不是“上單片機”而是很多系統(tǒng)本來就有一顆主控只是大多數(shù)時候它只顧著跑業(yè)務(wù)邏輯沒有把供電策略當回事。把電源管理職責放到一顆老資格的8位單片機身上性價比很高。PIC18F4610有MSSP模塊可以做I2C主模式有10位ADC可以備份采樣電池電壓有EEPROM可以存校準參數(shù)和日志有外部中斷引腳接PMIC的中斷輸出還有多種休眠模式配合低功耗。這些能力做電源策略完全夠用沒必要為了一個寄存器配置功能去上更高端的平臺。下面是這套系統(tǒng)里兩者的典型分工功能由誰執(zhí)行說明多路電壓輸出PCA9422三路Buck加一路LDO輸出由寄存器設(shè)定動態(tài)調(diào)壓DVSPCA9422執(zhí)行PIC決策PIC寫目標電壓PMIC完成調(diào)壓電池充電PCA9422CC/CV過程由PMIC內(nèi)部邏輯完成充電策略PIC18F4610根據(jù)供電來源、電量、溫度決定充電電流和門限狀態(tài)切換PIC18F4610運行、空閑、休眠、關(guān)斷的狀態(tài)機故障保護PCA9422硬件過流、過壓、過溫觸發(fā)硬件保動同時中斷通知PIC電壓電流監(jiān)測PCA9422內(nèi)部ADCPIC可讀PIC讀取結(jié)果做軟件策略1.3 什么場景下這套方案才值得上不是說所有項目都要用PMIC加MCU的組合。如果產(chǎn)品只需要一路3.3V降壓、負載固定、沒有電池一顆簡單的LDO或DCDC反而是最優(yōu)解。但下面幾種情況我強烈建議直接參考這套架構(gòu)系統(tǒng)需要三路以上電源軌且不同運行狀態(tài)下電壓要求不同產(chǎn)品帶單節(jié)鋰電池需要充電管理、電量監(jiān)測和充電狀態(tài)上報待機電流要求嚴格需要把部分電源軌在睡眠時關(guān)掉主核降到低電壓運行需要遠程或本地配置供電參數(shù)比如產(chǎn)線校準、固件升級后修改電壓配置。我這次做的設(shè)備四條全占所以當時基本沒猶豫就定了這個架構(gòu)。2. PCA9422硬件底細三路Buck、充電與I2C控制面2.1 一顆芯片里的“供電五臟”把PCA9422的框圖展開看它基本就是一塊小型開發(fā)板該有的電源系統(tǒng)全集。芯片內(nèi)部集成了三路同步降壓Buck轉(zhuǎn)換器每一路都可以通過I2C寄存器調(diào)節(jié)輸出電壓適合給主控核心、內(nèi)存、外設(shè)等不同功耗的負載供電。同步整流結(jié)構(gòu)讓輕載和滿載效率都能維持在比較高的水平不需要像非同步方案那樣外掛續(xù)流二極管。除了三路Buck芯片還有一路LDO輸出。這部分我一般用作“常電域”也就是關(guān)機后仍然需要保持供電的RTC或者是喚醒邏輯。LDO靜態(tài)電流小雖然效率不如Buck但它干凈、便宜、響應(yīng)快專門給低功耗的常開電路供電很合適。充電部分支持單節(jié)鋰離子或鋰聚合物電池采用典型的恒流恒壓兩段式充電。芯片內(nèi)部可以設(shè)置充電電流、終止電流、充滿電壓以及輸入限流。比較關(guān)鍵的是芯片內(nèi)置了電源路徑管理適配器插入時系統(tǒng)優(yōu)先從適配器取電同時給電池充電適配器移除后自動切到電池供電。這個功能在便攜設(shè)備里非常實用因為用戶插拔充電器的瞬間系統(tǒng)不能斷電不能復(fù)位。I2C控制面是整套設(shè)計的核心。PCA9422作為I2C從機PIC作為主機通過讀寫寄存器完成電壓設(shè)置、狀態(tài)查詢、中斷處理和充電參數(shù)配置。芯片還帶一個中斷輸出腳接PIC的外部中斷輸入這樣PMIC這邊一旦出現(xiàn)充電完成、電池低、過溫等事件PIC可以立刻得到通知不用輪詢。2.2 原理圖階段就要定下來的幾件事硬件設(shè)計里我吃過不少虧這里把踩坑總結(jié)成幾條經(jīng)驗都是畫原理圖時就要確定的第一I2C上拉電阻和上拉電壓源的選擇。PCA9422的I2C引腳一般在1.2V到3.3V范圍內(nèi)工作上拉電阻要接到芯片允許的I/O電壓上而不是直接接到電池電壓。總線速度按400kHz設(shè)計時常用的上拉值在2.2k到4.7k之間具體看總線上掛了多少器件、走線有多長。如果總線上接了多顆芯片走線又長4.7k往往不夠改用2.2k更穩(wěn)。第二中斷引腳一定要留出來。PCA9422的中斷腳通常是開漏輸出需要外部上拉到合適的電平然后接到PIC的INT0或INT1。千萬別省這根線否則后面所有狀態(tài)都靠輪詢系統(tǒng)響應(yīng)和低功耗都會很被動。第三DVS控制引腳的設(shè)計。PCA9422支持通過I2C改電壓也支持通過外部引腳快速切換預(yù)置電壓檔。我的習慣是預(yù)留兩顆GPIO連到PIC方便以后做硬件級快速調(diào)壓。即使初期只在軟件里調(diào)壓原理圖上也把這兩個引腳引出來板子拿回來再想加就麻煩了。第四供電軌規(guī)劃。我給這套系統(tǒng)定的分配是Buck1給主控核心Buck2給存儲器和其他數(shù)字外設(shè)Buck3給模擬前端或者射頻部分LDO專門給RTC和喚醒邏輯。這樣分的好處是各路的負載相對固定調(diào)試時某一路電壓異常能立刻定位到具體功能模塊。布局布線方面Buck的電感屬于開關(guān)節(jié)點要離I2C走線遠一點反饋走線避開電感正下方。PMIC的輸入輸出電容按照數(shù)據(jù)手冊參考值來10uF加100nF的組合各軌都別省不然動態(tài)響應(yīng)和紋波都會給你顏色看。3. PIC18F4610側(cè)初始化MSSP走I2C主模式的完整起手式3.1 先把I2C主模式跑起來PCA9422配置再強PIC這邊I2C驅(qū)動寫不好一切都是白搭。PIC18F4610的MSSP模塊配置成I2C主模式的流程比較固定關(guān)鍵是把模式、時鐘和引腳三件事搞對。MSSP的寄存器很多人在第一次接觸時容易暈實際要動的就那么幾個SSPCON1決定模式和使能SSPADD決定時鐘頻率SSPCON2處理應(yīng)答和啟??刂芐SPIF是中斷標志。把模塊配置成I2C主模式SSPCON1的低四位寫0b1000SSPEN位置1。時鐘頻率由振蕩器頻率分頻得到公式是SPADD (FOSC / 4) / I2C_FREQ - 1。下面是我在16MHz主頻、400kHz總線上用的初始化代碼可以直接抄#define FOSC 16000000UL #define I2C_FREQ 400000UL void i2c_master_init(void) { // SCL/SDA分別配置為輸入由MSSP模塊驅(qū)動 TRISCbits.TRISC3 1; // SCL TRISCbits.TRISC4 1; // SDA SSPCON1 0x28; // I2C主模式SSPEN1SSPM0b1000 SSPCON2 0x01; // ACKEN1允許應(yīng)答位控制 SSPADD (uint8_t)(((FOSC / 4) / I2C_FREQ) - 1); SSPSTAT 0x80; // 常規(guī)速率模式SMBus相關(guān)位保持清零 }寫字節(jié)和啟停操作的核心是等SSPIF置位同時檢查ACKSTAT狀態(tài)。我封裝了一組底層函數(shù)讀寫寄存器都靠它static void i2c_wait_until_done(void) { // 用一個計數(shù)器做超時避免總線異常時程序卡死 uint16_t timeout 0xFFFF; while (!PIR1bits.SSPIF timeout) { timeout--; } if (timeout 0) { // 總線異常做一次復(fù)位或者進入錯誤處理 i2c_bus_recover(); } PIR1bits.SSPIF 0; } uint8_t i2c_write_reg(uint8_t dev_addr, uint8_t reg, uint8_t val) { uint8_t rc; SSPCON2bits.SEN 1; // 發(fā)送啟動條件 i2c_wait_until_done(); rc i2c_write_byte(dev_addr 1); // 7位地址左移一位寫方向 if (rc I2C_NACK) return rc; rc i2c_write_byte(reg); if (rc I2C_NACK) return rc; rc i2c_write_byte(val); if (rc I2C_NACK) return rc; SSPCON2bits.PEN 1; // 發(fā)送停止條件 i2c_wait_until_done(); return I2C_OK; }讀寄存器稍微多一步先寫寄存器地址然后發(fā)重復(fù)啟動條件再切到讀方向讀回數(shù)據(jù)并發(fā)送NACK作為最后一字節(jié)。這樣的讀流程在MSSP里很成熟網(wǎng)上資源也多就不貼完整代碼了。3.2 上電時序匹配先喂飽大腦再開肌肉很多人上電就翻車問題出在PIC和PMIC誰先有電這件事上。我在原理圖階段把PIC的VDD接到了PCA9422的LDO輸出LDO由電池或適配器直接供電只要系統(tǒng)有電LDO就輸出穩(wěn)定3.3VPIC先跑起來。三路Buck默認關(guān)閉由PIC在初始化完成后才逐個打開。這樣既保證主控先于大功率負載就緒又避免了上電瞬間各路電源軌相互競爭電流。PIC啟動后的初始化流程我建議按這個順序延時10ms左右等LDO輸出穩(wěn)定、PMIC內(nèi)部電路完成復(fù)位配置I2C主模式和中斷引腳讀取PCA9422的芯片ID寄存器確認通信正常。這個步驟別省等于握手能過濾掉一大批硬件虛焊問題配置充電相關(guān)參數(shù)輸入限流、充電電流、充滿電壓、終止電流依次配置三路Buck的目標電壓先設(shè)電壓再接使能打開各電源軌的使能位每打開一路延時一下確認讀回的狀態(tài)寄存器正確關(guān)掉這條初始化用的I2C通道或者切換到低速率進入正常業(yè)務(wù)。有朋友遇到過開機瞬間整機電流特別大的現(xiàn)象后來發(fā)現(xiàn)是PIC一邊初始化I2C一邊又把大功率外設(shè)的GPIO拉起來了。正確做法是初始化完成前對外設(shè)保持復(fù)位狀態(tài)等Buck穩(wěn)定后再釋放復(fù)位。這個順序在固件里要寫死不然每次上電都像在賭運氣。4. 固件里的電源狀態(tài)機與充電流程4.1 我把狀態(tài)機拆成六檔電源管理固件的核心是一個狀態(tài)機不推薦在前臺循環(huán)里用一堆if去判斷電源狀態(tài)。我這次按運行場景拆了六個狀態(tài)每個狀態(tài)定義清楚“哪些軌開、電壓多少、CPU什么頻率、外設(shè)什么狀態(tài)”切換條件也集中管理。狀態(tài)Buck1Buck2Buck3LDO典型電流BOOT默認電壓關(guān)閉關(guān)閉開30mARUN1.2V1.8V3.3V開80~200mAIDLE0.9V1.8V3.3V開20mASLEEP關(guān)閉關(guān)閉3.3V開3mAOFF關(guān)閉關(guān)閉關(guān)閉開300uACHARGE默認電壓按需按需開依充電電流而定狀態(tài)切換的核心動作就兩類改一部份Buck電壓或者關(guān)閉某一路。DVS用得到位的話IDLE狀態(tài)把核心電壓從1.2V降到0.9V功耗能降不少而切換到RUN也只需一條I2C寫指令。下面這個函數(shù)就是做這件事的void system_set_state(system_state_t next) { switch (next) { case STATE_IDLE: pmic_write_reg(REG_BUCK1_VOUT, 0x??); // 0.9V配置值 // 等待PMIC內(nèi)部調(diào)壓完成再繼續(xù)下一步 while (!pmic_read_reg(REG_STATUS) ST_BUCK1_OK); break; case STATE_RUN: pmic_write_reg(REG_BUCK1_VOUT, 0x??); // 1.2V配置值 while (!pmic_read_reg(REG_STATUS) ST_BUCK1_OK); break; case STATE_SLEEP: pmic_write_reg(REG_BUCK_EN, 0x??); // 關(guān)閉Buck1和Buck2 break; default: break; } current_state next; }寫電壓寄存器的時候有一個細節(jié)目標電壓值要折算成芯片要求的編碼格式很多PMIC的電壓步進是10mV或者25mV不能直接把毫伏數(shù)寫進去。這個折算公式在數(shù)據(jù)手冊里有我會在頭文件里寫一個宏或者轉(zhuǎn)換函數(shù)統(tǒng)一處理避免在業(yè)務(wù)代碼里到處出現(xiàn)魔法數(shù)字。4.2 充電管理別只寫幾個寄存器就完事PCA9422的充電器硬件上很省心CC/CV過程由內(nèi)部邏輯完成PIC只需要做三件事配置參數(shù)、監(jiān)測狀態(tài)、處理異常。配置這塊我根據(jù)設(shè)備電池規(guī)格定了充電電壓4.2V充電電流0.5C終止電流設(shè)為滿充電流的十分之一左右。輸入限流要分場景接適配器時放高一點接普通電腦USB口時放低。這塊用一根檢測腳或者讀PMIC的輸入源狀態(tài)寄存器來判斷比在固件里硬編碼靠譜。充電過程中PIC要持續(xù)關(guān)注幾個中斷事件充電完成、充電失敗、電池低、溫度異常。我把PMIC的中斷輸出接到PIC的INT0引腳中斷服務(wù)函數(shù)里讀一次中斷狀態(tài)寄存器判斷事件類型清標志后做對應(yīng)處理void __interrupt() isr(void) { if (INT0IF) { uint8_t st pmic_read_reg(REG_INT_STATUS); if (st ST_CHARGE_DONE) { charge_done_flag 1; } if (st ST_VBAT_LOW) { system_set_state(STATE_SLEEP); } pmic_write_reg(REG_INT_STATUS, 0xFF); // 按手冊要求清中斷 INT0IF 0; } }有個細節(jié)容易被忽略中斷狀態(tài)寄存器讀完之后要主動清除而且很多PMIC的清中斷方式是寫1清除不能改成寫0。如果清不干凈中斷腳會一直拉低PIC的中斷標志也會反復(fù)觸發(fā)系統(tǒng)就像卡死在隨機位置。我在調(diào)試時吃了一次這個虧排查了半天才發(fā)現(xiàn)是清中斷語句寫反了。4.3 低功耗狀態(tài)下輪詢就是敵人進入OFF或SLEEP狀態(tài)后PIC最好進入休眠用外部中斷喚醒。這時候如果繼續(xù)用定時器輪詢PMIC狀態(tài)不僅把CPU喚醒功耗吃掉了還讓系統(tǒng)電流憑空多出一截。我的做法是進入低功耗狀態(tài)前把所有沒必要的定時器關(guān)掉I2C總線保持高電平不釋放PIC執(zhí)行SLEEP指令。喚醒來源只留三路PMIC中斷腳、喚醒按鍵、RTC定時喚醒。每次喚醒后用最快的速度讀一下PMIC狀態(tài)寄存器判斷要不要切狀態(tài)不需要處理就立刻再次睡眠。這樣整個系統(tǒng)在OFF狀態(tài)的實測電流能穩(wěn)定在300uA上下比我最初設(shè)計預(yù)算的500uA還要好。5. 實測排坑輸出電壓、I2C應(yīng)答和DVS毛刺5.1 第一個坑寫完電壓寄存器輸出還是默認值樣機第一次上電I2C通信正常芯片ID也讀回來了但用示波器量Buck1輸出紋絲不動是默認的1.0V我明明寫了0x配置值想讓它輸出1.2V。排查鏈路是這樣的先檢查代碼里配置值是不是算錯把寄存器讀回打印出來發(fā)現(xiàn)寫入的值沒錯。然后懷疑是不是使能位沒置位把使能寄存器整個讀回來發(fā)現(xiàn)Buck1的狀態(tài)是“使能但未就緒”。最后定位到原因我在初始化時先寫了輸出電壓寄存器再寫的使能寄存器但中間沒有等電壓穩(wěn)壓完成而PMIC對于“使能狀態(tài)下修改電壓”有一套影子寄存器機制某些字段需要在特定條件下才會更新到硬件。解決辦法是嚴格按數(shù)據(jù)手冊的時序來先降低寫入節(jié)奏每寫一個寄存器后延時再讀回驗證打開使能后查詢電源正常狀態(tài)位確認穩(wěn)壓完成再繼續(xù)。從那以后我把“寫地址-寫數(shù)據(jù)-讀回-比對”這個三步校驗做成了所有PMIC寄存器寫入的標準流程實測下來通信異?;径际怯布栴}而不是代碼問題了。5.2 第二個坑I2C偶發(fā)無應(yīng)答加1ms延時又好了系統(tǒng)跑起來之后出現(xiàn)了一個特別邪門的現(xiàn)象I2C通信大部分時間正常偶爾一次讀充電狀態(tài)時返回NACK加了1ms延時之后又好了。這種隨機故障最折磨人因為它不會穩(wěn)定復(fù)現(xiàn)。我懷疑過三件事總線電容太大、上拉電阻阻值太高、MSSP速率過快。先用邏輯分析儀抓波形發(fā)現(xiàn)SDA在啟動條件后有一處明顯的邊沿變緩加上總線還掛了另一顆傳感器總電容確實偏大了。處理辦法是換上拉電阻從4.7k降到2.2k同時把I2C速率從400kHz降到100kHz壞幀率直接從“偶爾一次”變成“一次都沒有”。這里也想提醒大家I2C線上跑著多顆芯片、走線又長時別迷信400kHz的“標準速率”。對電源管理這種低頻控制場景100kHz完全夠用穩(wěn)定性卻高一個數(shù)量級。我后來把I2C速率固定在100kHz運行幾個月沒再出現(xiàn)通信故障。5.3 第三個坑DVS調(diào)低電壓系統(tǒng)反而復(fù)位數(shù)動態(tài)調(diào)壓看起來很美但我在把核心電壓從1.2V降到0.9V的時候系統(tǒng)卻偶發(fā)復(fù)位。這位復(fù)位不是掉電引起的因為LDO供電的常電域沒掉復(fù)位的只有主核心域。逐層分析后發(fā)現(xiàn)DVS調(diào)低電壓的瞬間主控還掛著比較重的負載電壓從一個臺階跳到另一個臺階如果PMIC的調(diào)壓速度很快而負載電流還沒來得及降下來瞬時電壓可能跌到低于主控最低工作電壓觸發(fā)了內(nèi)置的掉電復(fù)位。解決思路分兩步。第一步軟件上先讓負載進入輕載模式再降電壓把主控時鐘降下來關(guān)閉不需要的外設(shè)然后再寫電壓寄存器。第二步如果PMIC支持調(diào)壓速率配置把調(diào)壓斜率調(diào)緩一點讓電壓平滑過渡。硬件上我在Buck1的輸出再加了一顆22uF的陶瓷電容增加一點瞬態(tài)裕量從那之后這個復(fù)位再沒出現(xiàn)過。這套“先降負載、再調(diào)電壓、確認完成、再恢復(fù)負載”的流程現(xiàn)在已經(jīng)成為我所有DVS項目的標配。5.4 順帶說說低功耗實測方法很多朋友問低功耗怎么測才能測準我的經(jīng)驗是不要只看數(shù)據(jù)手冊要實際用電流探頭在供電入口測。開機狀態(tài)下600uA以下用普通萬用表串聯(lián)測是可以的但休眠狀態(tài)300uA左右時萬用表的壓降已經(jīng)會影響系統(tǒng)供電了最好用電流探頭加示波器或者用專門的功耗分析儀。測的時候還要把I2C訪問的頻率降下來。我最初調(diào)試時,SLEEP狀態(tài)下PIC雖然休眠了,但I2C引腳沒有完全釋放每一段時間還有個泄漏路徑。逐個外設(shè)排除時用了一個簡單方法把每個外設(shè)的供電都改成獨立跳線測功耗時一根一根跳線拔掉看電流變化。這套方法雖然土但定位快最終把系統(tǒng)待機從2mA降到了300uA。最后再分享一個小技巧整套方案調(diào)通之后建議在固件里加一個調(diào)試模式通過PMIC的ADC把各路電壓、充電電流、溫度實時上報。這個模式平時不開啟但出問題時能幫你少焊很多飛線和少測很多點。我自己在后續(xù)項目里保留了這套架構(gòu)還打算把DVS的調(diào)壓曲線做成自動標定讓產(chǎn)線可以根據(jù)每臺設(shè)備的個體差異微調(diào)電壓配置真正做到每一臺都工作在最優(yōu)功耗點上。