:基于MAX17048電量計與MT32F006的調(diào)試與避坑)
1. 為什么這塊板上最終選了MAX17048電量計選型與算法差異1.1 傳統(tǒng)查表法為什么總在關(guān)鍵時刻拉胯我手里這個項目是一臺手持終端帶鋰電池供電顯示電量的需求從一開始就被提了出來。最初方案很簡單MCU用ADC采集電池電壓再做分段查表把電壓區(qū)間映射成25%、50%、75%這些檔位。原型階段一切正常但整機聯(lián)調(diào)時問題全出來了——設備一旦運行高負載功能電池電壓會瞬間跌落查表法直接把這個壓降當成電量下降屏幕上電量嘩嘩地掉。更夸張的是負載撤掉后電壓又彈回來電量也跟著彈回去。客戶看到的現(xiàn)象就是電量在8%到30%之間反復橫跳沒過多久直接關(guān)機。這個問題本質(zhì)上是電池化學特性決定的。鋰電池在帶載和空載狀態(tài)下的開路電壓差異很大尤其在內(nèi)阻偏大的電池上脈沖負載會導致端電壓瞬間掉幾百毫伏這足以讓查表法的判斷徹底失效。查表法還解決不了另一個問題同一電壓值在不同溫度、不同老化程度、不同放電倍率下對應的真實剩余電量完全不同靜態(tài)映射表根本沒有辦法覆蓋這些場景。1.2 Model Gauge不數(shù)電荷而是“看曲線認狀態(tài)”MAX17048采用的是Maxim的Model Gauge算法從名稱就能看出來它走的不是庫侖計那套“數(shù)電子”的路子。傳統(tǒng)庫侖計需要串聯(lián)一個采樣電阻靠積分電流隨時間累加來估算電量精度確實不錯但缺點是電阻會持續(xù)消耗功率而且時間久了會產(chǎn)生累積誤差必須定期做“滿充校準”或者“空電校準”來糾偏。MAX17048的原理更像一個“模擬專家”。它內(nèi)部固化了一個典型鋰電池的放電特性模型運行時持續(xù)采樣電池電壓結(jié)合放電曲線和負載變化動態(tài)估算剩余電量。它不需要采樣電阻外圍電路極其簡單只需要一顆旁路電容就能工作。對于手持設備這種應用場景它直接給出SOC百分比精度大概在±1%到±2%的水平不需要你做任何標定和補償。還有一個讓我決定選它的優(yōu)勢讀出來的電量曲線非常平滑。因為算法內(nèi)部有動態(tài)濾波機制即使設備突然進入大電流負載狀態(tài)SOC也不會像查表法那樣“嚇人地跳水”。這對用戶體感影響非常大。1.3 為什么主控選MT32F006MT32F006是一顆國產(chǎn)Cortex-M0內(nèi)核MCU48MHz主頻內(nèi)置I2C、UART、SPI等常用外設功耗控制做得不錯價格也合適非常適合這種量產(chǎn)的便攜設備。選它沒有特別花哨的理由純粹是綜合考慮了成本、供貨穩(wěn)定性、外設資源和功耗需求之后的決定。I2C外設雖然不像軟件模擬那樣靈活但跑100kHz的標準模式通信完全沒有問題。這次實戰(zhàn)的核心就是把這顆主控和MAX17048通過I2C總線接起來把電壓、SOC這些數(shù)據(jù)穩(wěn)定讀出來同時把通信過程中遇到的各種“坑”一并記錄下來。2. 硬件接線里的物理層細節(jié)開漏輸出、上拉電阻與電平匹配2.1 這次項目的接線方案MAX17048的引腳不多真正用到的就更少。VCC直接接電池正極這是它和普通傳感器最大的區(qū)別——它不是從MCU的系統(tǒng)電源取電而是直接測量電池電壓。GND接電池負極和系統(tǒng)地SCL和SDA接到MT32F006的PB6和PB7兩個引腳都配上4.7k上拉電阻到3.3V。電池正極和GND之間放了一顆1uF的陶瓷電容位置盡量靠近芯片的VCC引腳這是數(shù)據(jù)手冊明確要求的不能省。板上還預留了一組測試點SCL、SDA、GND三個測試點并排放在板邊這個是后來排障時被驗證為極其必要的設計。后面講調(diào)試工具時會詳細說為什么。2.2 開漏加一拉I2C的物理基石I2C協(xié)議規(guī)定所有設備的SCL和SDA引腳都必須是開漏輸出結(jié)構(gòu)外部加上拉電阻把總線拉高。為什么不用推挽輸出這是很多初學者最容易困惑的地方。開漏輸出的本質(zhì)是設備只能主動把總線拉低不能主動拉高。總線默認由外部上拉電阻保持在“高”電平任何設備想發(fā)數(shù)據(jù)只需要把線拉低。這樣一來多個設備掛在同一根線上也不會發(fā)生“一個輸出高、一個輸出低”的短路沖突這正是I2C支持一主多從、甚至多主通信的物理基礎??梢杂靡粋€生活化的類比一條走廊的燈開關(guān)全部是“只能按下斷電”的按鈕燈默認亮著。誰要發(fā)信號就按一下自己的按鈕把燈熄滅松開后又恢復亮。這樣不管走廊里裝了多少個按鈕永遠不會出現(xiàn)兩個按鈕互扯的情況。開漏結(jié)構(gòu)就是這么工作的。I2C還有線與特性只要總線上任何一個設備把線拉低其他設備讀到的就是低電平。因此從機應答ACK本質(zhì)上就是在第9個時鐘周期把SDA拉低一下主機通過讀取這個電平就知道從機在不在、有沒有正確處理數(shù)據(jù)。2.3 上拉電阻值不是隨便選的一次計算就明白上拉電阻的取值直接影響通信質(zhì)量。取值太大總線上升沿變緩因為總線電容要通過電阻充電RC時間常數(shù)大取值太小雖然上升沿陡了但設備拉低時的灌電流會變大可能把低電平頂?shù)匠^規(guī)格允許的閾值。I2C標準模式下上升時間要求不超過1000ns1μs。假設總線上掛了兩顆芯片、PCB走線約5cm估算總線電容在100pF到200pF之間取最不利的200pF計算R_pullup 1000ns / (200pF × 0.8473) ≈ 5.9kΩ所以4.7kΩ是一個合理的取值留有一定余量。如果板子走線很長或者總線上掛載設備很多總線電容變大就要適當減小上拉電阻比如2.2kΩ或者1kΩ。但是要注意在3.3V系統(tǒng)里用1kΩ上拉灌電流約為3.3mA對于大多數(shù)從機來說是安全的但如果在低電壓系統(tǒng)或者從機驅(qū)動能力比較弱時就可能出問題這個我們后面避坑篇還會提到。還有一個需要注意的細節(jié)很多MCU內(nèi)部也有可配置的上拉電阻但內(nèi)部上拉阻值通常很大約30kΩ到50kΩ只能兜底不能替代外部上拉。外部上拉電阻的存在是必須的否則I2C在高速翻轉(zhuǎn)時根本達不到電平要求。3. MAX17048寄存器地圖通信之前先搞清楚數(shù)據(jù)長什么樣3.1 先看最重要的三個寄存器MAX17048的寄存器數(shù)量不算多但一開始容易看花眼。我建議先把注意力放在三個核心寄存器上把這三個搞明白項目就能跑起來了。第一個是VCELL0x02這是16位電壓寄存器實時反映電池電壓分辨率是0.625mV/LSB。讀取出來的原始值右移4位后再乘以0.625得到的就是毫伏數(shù)。第二個是SOC0x04也是16位寄存器但真正有用的只是低字節(jié)直接代表當前剩余電量百分比讀出來就是整數(shù)百分比連轉(zhuǎn)換都不用做。第三個是CONFIG0x1C負責配置報警閾值、休眠使能等選項。這三個寄存器的地址和用途在代碼注釋里應該寫清楚重要程度完全不在一個等級上。VCELL和SOC是數(shù)據(jù)源CONFIG是行為控制其他的如VERSION0x06、STATUS0x0A基本屬于“錦上添花”。3.2 CONFIG寄存器寫保護別忘了解鎖這里要特別提醒一個細節(jié)MAX17048的CONFIG寄存器不是想寫就能寫的它帶一個寫保護機制。直接向0x1C地址寫數(shù)據(jù)是無效的必須先把存儲的配置數(shù)據(jù)搬到“緩沖”里。正確流程是先發(fā)送一條命令給寄存器0x78這條命令的作用是解鎖寫保護然后在5秒內(nèi)完成對CONFIG寄存器的寫入寫完后再向0x79發(fā)送命令把配置寄存器鎖回去防止后續(xù)誤寫。我剛接觸這顆芯片時完全不知道這個機制按照普通EEPROM的寫法直接寫CONFIG讀回來發(fā)現(xiàn)永遠是默認值一度以為是I2C通信本身出了問題排查了整整半天。這個教訓后面會展開講。3.3 配置低功耗工作模式低功耗設備對休眠電流的要求非??量?。MAX17048提供了一個SLEEP模式使能后芯片進入低功耗狀態(tài)但仍然保持電壓監(jiān)測功能。對于本項目我把SLEEP使能打開同時配置了電量報警閾值——當SOC低于某個設定值時通過狀態(tài)寄存器或者中斷引腳通知主控。需要強調(diào)的是進入低功耗前電池電量計不能斷電否則就失去監(jiān)測意義了。SLEEP模式下功耗降低但SOC數(shù)據(jù)不會丟失喚醒后直接讀取即可。4. I2C全流程實操時序、數(shù)據(jù)幀與MT32F006代碼實現(xiàn)4.1 I2C數(shù)據(jù)幀到底長什么樣I2C協(xié)議的數(shù)據(jù)幀結(jié)構(gòu)并不復雜一條完整的讀寫事務由若干“幀”組成。首先是起始條件STARTSCL保持高電平時SDA從高跳變到低表示總線開始通信。緊接著是設備地址幀7位設備地址加上1位讀寫方向位方向位為0表示寫為1表示讀。MAX17048的7位地址默認是0x36左移一位后寫地址就是0x6C讀地址就是0x6D。地址幀之后是從機應答位ACK。之后根據(jù)讀寫方向主機繼續(xù)發(fā)送寄存器地址或者讀取數(shù)據(jù)。每傳輸8位數(shù)據(jù)接收方都必須回應一個ACK只有讀到最后一個字節(jié)時主機故意不發(fā)ACK發(fā)送NACK表示“我讀完了不用再發(fā)”然后產(chǎn)生停止條件STOPSCL高電平時SDA從低跳變到高結(jié)束本次通信。I2C還有一個細節(jié)非常容易被忽略SDA上的數(shù)據(jù)只能在SCL為低電平期間變化SCL為高電平期間SDA必須保持穩(wěn)定。這是協(xié)議能夠正確采樣的根本保證也是分析時序圖時判斷起始條件、停止條件、數(shù)據(jù)位的最關(guān)鍵依據(jù)。4.2 讀寄存器時序拆解讀取MAX17048的VCELL寄存器完整流程如下主機先發(fā)一個起始條件然后發(fā)送0x6C器件地址寫方向等待從機ACK接著發(fā)送0x02目標寄存器地址等待ACK然后主機會再次發(fā)起一個起始條件重復起始條件Repeated START發(fā)送0x6D器件地址讀方向從機ACK后開始輸出數(shù)據(jù)先是寄存器高字節(jié)主機返回ACK再是低字節(jié)主機這次返回NACK最后主機發(fā)送停止條件。實際上很多I2C外設在讀數(shù)據(jù)之前要求先“偽寫”寄存器地址MAX17048也是如此。使用軟件模擬I2C時老老實實按這個流程寫就行。使用硬件I2C時部分MCU的庫函數(shù)會把“寫寄存器地址”和“讀數(shù)據(jù)”封裝成一個組合函數(shù)但底層時序仍然是這段邏輯。4.3 軟件I2C代碼實現(xiàn)在MCU開發(fā)和調(diào)試階段我強烈建議先用GPIO軟件模擬I2C等通信完全穩(wěn)定了再切換到硬件I2C。軟件I2C雖然CPU占用高一點但每一步時序都在自己掌控之中出了問題很容易定位。下面是核心代碼。// 引腳定義PB6SCLPB7SDA均配置為開漏輸出 #define SCL_H() GPIO_SetBits(GPIOB, GPIO_PIN_6) #define SCL_L() GPIO_ResetBits(GPIOB, GPIO_PIN_6) #define SDA_H() GPIO_SetBits(GPIOB, GPIO_PIN_7) #define SDA_L() GPIO_ResetBits(GPIOB, GPIO_PIN_7) #define SDA_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_PIN_7) // 軟件延時約5us對應100kHz時鐘 static void i2c_delay(void) { for (volatile int i 0; i 50; i); } // 起始條件SCL高SDA由高變低 static void i2c_start(void) { SDA_H(); SCL_H(); i2c_delay(); SDA_L(); i2c_delay(); SCL_L(); i2c_delay(); } // 停止條件SCL高SDA由低變高 static void i2c_stop(void) { SDA_L(); SCL_H(); i2c_delay(); SDA_H(); i2c_delay(); } // 主機發(fā)送一個字節(jié)返回從機ACK狀態(tài) static uint8_t i2c_write_byte(uint8_t data) { for (int i 0; i 8; i) { if (data 0x80) SDA_H(); else SDA_L(); data 1; SCL_H(); i2c_delay(); SCL_L(); i2c_delay(); } // 第9個時鐘釋放SDA讀取從機ACK SDA_H(); SCL_H(); i2c_delay(); uint8_t ack (SDA_READ() 0) ? 1 : 0; SCL_L(); i2c_delay(); return ack; } // 主機讀取一個字節(jié)ack1時發(fā)送ACK最后一個字節(jié)傳0發(fā)送NACK static uint8_t i2c_read_byte(uint8_t ack) { uint8_t data 0; SDA_H(); // 釋放SDA交給從機控制 for (int i 0; i 8; i) { data 1; SCL_H(); i2c_delay(); if (SDA_READ()) data | 0x01; SCL_L(); i2c_delay(); } SCL_L(); if (ack) { SDA_L(); // 拉低SDA表示ACK } else { SDA_H(); // 保持高表示NACK } SCL_H(); i2c_delay(); SCL_L(); i2c_delay(); SDA_H(); return data; } // 讀取MAX17048指定寄存器返回16位數(shù)據(jù) uint16_t max17048_read_reg(uint8_t reg) { uint16_t data 0; i2c_start(); i2c_write_byte(0x6C); // 器件地址寫 i2c_write_byte(reg); // 寄存器地址 i2c_start(); // 重復起始條件 i2c_write_byte(0x6D); // 器件地址讀 data (uint16_t)i2c_read_byte(1) 8; // 讀高字節(jié)回復ACK data | (uint16_t)i2c_read_byte(0); // 讀低字節(jié)回復NACK i2c_stop(); return data; }主循環(huán)里這樣用int main(void) { // 初始化GPIO、UART等外設 while (1) { uint16_t vcell_raw max17048_read_reg(0x02); float voltage (float)(vcell_raw 4) * 0.625f / 1000.0f; uint8_t soc (uint8_t)(max17048_read_reg(0x04) 0xFF); printf(Voltage: %.3fV, SOC: %d%%\r\n, voltage, soc); delay_ms(1000); } }VCELL原始值右移4位的原因需要解釋一下16位寄存器中低4位是無效位只有高12位是有效電壓數(shù)據(jù)每LSB對應0.625mV。右移4位后再乘以0.625單位是mV再除以1000轉(zhuǎn)成V。實測3.7V左右的電池讀出來約3.70V左右與萬用表誤差在幾十毫伏范圍內(nèi)。4.4 切換硬件I2C的注意點軟件模擬跑通之后就可以切換成MT32F006的硬件I2C外設了。配置要點有三個一是把I2C引腳復用功能打開二是配置為標準模式100kHz或快速模式400kHz三是確認引腳工作在開漏模式不能配置成推挽。硬件I2C真正需要注意的問題是中斷和狀態(tài)標志。很多開發(fā)者在讀2字節(jié)數(shù)據(jù)時習慣在接收中斷里每次都清除標志位結(jié)果多讀了字節(jié)或者丟字節(jié)。正確做法是理解硬件外設的狀態(tài)機發(fā)送寄存器地址后發(fā)起重復起始條件然后連續(xù)接收兩個字節(jié)在接收最后一個字節(jié)前要預先設置好下次應答為NACK接收完成后立刻發(fā)停止條件。如果覺得這些狀態(tài)機邏輯比較繞我的建議是可以不用硬件I2C的外設繼續(xù)跑軟件I2C項目中功耗影響并不大。I2C通信本身只在讀取瞬間發(fā)生幾百微秒的事務時間對整體功耗幾乎無感。5. 避坑實錄五個真實故障的完整排查鏈路5.1 現(xiàn)象一上拉電阻“小了”反而不通信第一批樣板出來之后有個同事反饋說他的板子I2C通信時好時壞邏輯分析儀抓到波形正常但實際讀取經(jīng)常超時。后來發(fā)現(xiàn)他那塊板為了追求上升沿陡峭把上拉電阻換成了1kΩ。表面上看上升沿變快是好事但問題出在拉低方向。MAX17048的I2C引腳驅(qū)動能力有限在1kΩ上拉的情況下要把SDA從高拉到低于0.4V灌電流要達到(3.3-0.4)/10002.9mA這已經(jīng)接近從機引腳的驅(qū)動上限。再疊加板上的寄生電容和走線電阻實際低電平被抬高到0.6V以上超過了I2C協(xié)議允許的低電平閾值。MCU端的邏輯門限雖然還能識別但從機內(nèi)部判斷ACK時可能已經(jīng)紊亂表現(xiàn)出來就是“偶爾通信失敗”。用示波器看就非常明顯SDA低電平并不是漂亮的0V而是一個緩慢爬升的斜坡偶爾會沖到1V以上。解決辦法很簡單把上拉電阻換回4.7kΩ問題消失。5.2 現(xiàn)象二寫CONFIG寄存器始終不生效這是我個人踩過最深的一個坑。功能需求是配置報警閾值我按照常規(guī)的I2C寫寄存器流程向0x1C地址寫入配置值結(jié)果無論怎么讀都是默認值0x971C對應默認SOC報警閾值。第一反應是I2C地址搞錯了檢查一遍沒問題第二反應是寫數(shù)據(jù)字節(jié)順序反了對調(diào)一下還是不對。后來翻Datasheet才看到那一段CONFIG寄存器寫入前需要先發(fā)送解鎖命令到0x78寄存器寫入配置后再發(fā)送鎖定命令到0x79。這個機制在普通傳感器里非常少見估計是為了防止系統(tǒng)跑飛時誤改關(guān)鍵配置。正確流程如下void max17048_write_config(uint16_t config) { // 解鎖 i2c_start(); i2c_write_byte(0x6C); i2c_write_byte(0x78); i2c_stop(); // 寫CONFIG寄存器 i2c_start(); i2c_write_byte(0x6C); i2c_write_byte(0x1C); i2c_write_byte((config 8) 0xFF); i2c_write_byte(config 0xFF); i2c_stop(); // 鎖定 i2c_start(); i2c_write_byte(0x6C); i2c_write_byte(0x79); i2c_stop(); }寫完后再讀0x1C數(shù)據(jù)正確寫入。這個坑的核心啟示是陌生芯片第一次使用前一定要把完整的寄存器描述讀完尤其是帶“Command Write”字樣的寄存器往往藏著類似的操作約定。5.3 現(xiàn)象三新板首讀SOC精度離譜第一個樣板調(diào)試完成后我對這塊板子做了一次完整的充放電測試發(fā)現(xiàn)一個奇怪現(xiàn)象滿電狀態(tài)下讀出的SOC只有91%斷電靜置一晚后再上電SOC變成了47%而且電壓顯示還是正常的。這里就要理解Model Gauge算法的初始狀態(tài)了。MAX17048出廠固化了典型電池的放電模型但每顆電池的實際特性和出廠SOC狀態(tài)并不可知。第一次上電時芯片只能根據(jù)電池電壓和內(nèi)置的默認曲線估算一個初始SOC這個估算值和真實值之間可能存在偏差尤其是長期儲存、自放電嚴重的電池。解決辦法不是去寫什么“修正值”而是讓芯片經(jīng)歷一次完整的學習周期把電池充滿到100%然后正常使用放電到關(guān)機再充滿。完成一個完整循環(huán)后算法會自動校準電池曲線之后的SOC精度會逐步提高。量產(chǎn)階段如果有條件建議在產(chǎn)線上對每一塊電池執(zhí)行一次“充滿-靜置-讀取校準”流程能夠顯著減少客戶首用的誤差感。5.4 現(xiàn)象四硬件I2C讀回來的數(shù)據(jù)多了一位這個問題出現(xiàn)在從軟件I2C切換到硬件I2C之后。用邏輯分析儀抓包發(fā)現(xiàn)讀取SOC寄存器時主機返回了三個字節(jié)第一個字節(jié)是錯誤的0xFF后面兩個字節(jié)才是真實的SOC數(shù)據(jù)。反復調(diào)整都解決不了。后來逐一對照硬件I2C的狀態(tài)寄存器才發(fā)現(xiàn)問題出在“NACK發(fā)送時機”上。使用硬件I2C的庫函數(shù)時有些實現(xiàn)了“連續(xù)讀取”接口在接收緩沖器空中斷里讀到第一個字節(jié)時硬件會自動準備應答ACK如果代碼在中斷里又手動設置了一次ACK就會產(chǎn)生多一次讀操作。最終的做法是完全依靠庫函數(shù)提供的事件處理機制不在中斷里手動操作ACK位。讀取兩個字節(jié)的數(shù)據(jù)時第一個字節(jié)由硬件自動回ACK第二個字節(jié)需要提前配置NACK然后觸發(fā)停止條件。這個細節(jié)只有在完全理解硬件外設狀態(tài)機之后才會意識到也是軟件模擬和硬件外設之間最大的思維差異。5.5 現(xiàn)象五低功耗喚醒后I2C總線卡死設備進入低功耗模式后用外部按鍵喚醒主控嘗試讀取電量計數(shù)據(jù)時整個程序卡死在等待ACK的循環(huán)里。用示波器看SDA電平發(fā)現(xiàn)一直保持低電平。這是I2C總線“死鎖”的典型癥狀。死鎖原因是在MCU進入低功耗前I2C通信可能正好執(zhí)行到一半比如已經(jīng)發(fā)出起始條件但數(shù)據(jù)沒傳完從機正在等待剩余的時鐘脈沖。MCU突然斷電外設SCL不再翻轉(zhuǎn)從機就一直把SDA拉低等待總線被卡死。解決辦法是讓MCU在喚醒后先執(zhí)行一次“總線恢復”操作手動切換SCL為GPIO模式連續(xù)產(chǎn)生9個時鐘脈沖讓從機完成當前傳輸并釋放SDA然后產(chǎn)生一個停止條件最后再重新初始化I2C外設。代碼實現(xiàn)如下void i2c_bus_recover(void) { // 確認SDA被拉死才需要執(zhí)行 for (int i 0; i 9; i) { SCL_H(); i2c_delay(); SCL_L(); i2c_delay(); } i2c_start(); i2c_stop(); }這個“9個時鐘脈沖”的恢復方法本質(zhì)上是利用I2C協(xié)議的狀態(tài)機設計無論從機處于什么狀態(tài)只要在9個SCL脈沖內(nèi)沒有收到完整的字節(jié)它就會放棄當前事務釋放總線。這個技巧在總線上掛多個從機時同樣適用是非常實用的兜底方案。6. 用邏輯分析儀驗證通信抓包、看懂波形、定位問題6.1 連接與設置調(diào)試I2C邏輯分析儀是我最依賴的工具。連接非常簡單邏輯分析儀的CH0接SCLCH1接SDA共地接GND。如果手頭只有雙通道的型號就夠用CH2可以接中斷引腳用來觀察報警觸發(fā)但不是必須。關(guān)鍵是采樣率設置。100kHz的I2C理論上2MHz采樣率就能還原波形但為了抓取邊沿毛刺和時序異常建議至少8MHz采樣率有條件的話直接上20MHz。觸發(fā)方式選擇“下降沿觸發(fā)”通道選SDA因為I2C的任何一次通信都是從SDA的下降沿起始條件開始的這樣抓到的數(shù)據(jù)包一定是一次完整的事務。解碼設置選擇I2C協(xié)議地址寬度選7位地址模式地址填0x36。這樣波形窗口里會直接顯示主機發(fā)出的是寫地址0x6C還是讀地址0x6D讀回的數(shù)據(jù)也會自動按字節(jié)拆分非常直觀。6.2 從波形判斷通信是否健康邏輯分析儀能直接看到通信時序是否符合協(xié)議規(guī)范。抓一次讀取VCELL寄存器的完整流程起始條件后是0x6C寫地址從機在第9個時鐘回ACK接著是0x02寄存器地址再回ACK然后是重復起始條件0x6D讀地址從機回ACK然后輸出高字節(jié)、低字節(jié)主機最后回NACK停止條件。整個數(shù)據(jù)流一目了然。特別要關(guān)注的是ACK/NACK的位置。如果邏輯分析儀顯示從機返回了NACK最常見的原因是從機沒收到正確的寄存器地址或者從機的I2C地址根本不對。如果波形顯示地址收發(fā)正常但數(shù)據(jù)永遠是0xFF可能是從機供電沒起來或者芯片處于復位狀態(tài)。波形健康度的判斷標準也很簡單SCL和SDA的上升沿應該陡峭不應該看到明顯的斜坡低電平應該接近0VSCL高電平時間不能太短。如果上升沿呈明顯的圓弧狀大概率是總線電容過大或者上拉電阻過大。6.3 我常用的快速排查順序經(jīng)過這幾個項目我總結(jié)出一套排查順序先看波形有沒有——如果邏輯分析儀完全抓不到數(shù)據(jù)先檢查MCU代碼有沒有運行到I2C初始化再用萬用表量SCL/SDA電平是否被拉死再看ACK有沒有——如果波形只到發(fā)送地址就停了重點查地址是否正確、從機供電是否正常最后看數(shù)據(jù)對不對——如果ACK都有但數(shù)據(jù)不對重點查字節(jié)序、寄存器地址、讀寫方向。還有一個比較容易被忽視的點邏輯分析儀的探頭地線要盡量短否則地線本身會引入噪聲在高速采樣下可能看到很多假毛刺。地線越長環(huán)路天線效應越明顯這個問題在調(diào)試高頻信號時會被放大但對100kHz的I2C來說只要地線別飛太長基本沒問題。從個人習慣來說我會在每一個新項目的I2C調(diào)試階段先花半天時間把生產(chǎn)環(huán)境里可能出現(xiàn)的異常情況在測試板上模擬一遍飛線加長減少總線電容、換不同阻值的上拉電阻、反復上下電測試喚醒時序。這些異常樣本抓下來的波形存成截圖后面再遇到類似問題翻出對比圖基本就能快速定位。這個項目跑完我對I2C的感觸是協(xié)議本身很簡單難的是物理層和時序邊緣態(tài)的把控。上拉電阻、總線電容、從機驅(qū)動能力、喚醒時序每一個不起眼的細節(jié)都可能在量產(chǎn)或者低溫環(huán)境下突然給你上一課。把這些問題提前在測試階段暴露出來比事后救火要省心太多。