PMBus 常見問題除錯:NACK、匯流排卡住、Timeout、數值錯誤一次解決
PMBus / SMBus 除錯實戰:找不到裝置、7 位元與 8 位元位址混淆、特定命令 NACK、讀值錯誤、SDA 被拉住的匯流排解鎖(含 C 語言範例)、SMBus Timeout 與 Clock Stretching、寫入沒反應、SMBALERT# 一直拉低,附除錯流程圖與檢查清單。
除錯流程
flowchart TD
A["PMBus 通訊有問題"] --> B{"位址有 ACK 嗎?<br>(i2cdetect / 邏輯分析儀)"}
B -- "沒有" --> C["1. 找不到裝置<br>位址、供電、上拉、Mux"]
B -- "有" --> D{"所有命令都失敗?"}
D -- "是" --> E{"SDA 或 SCL<br>一直是低電位?"}
E -- "是" --> F["4. 匯流排卡住<br>執行 Bus Recovery"]
E -- "否" --> G["5. Timeout / Clock Stretching"]
D -- "只有部分命令" --> H["2. 特定命令失敗<br>讀 STATUS_CML"]
D -- "通訊正常" --> I{"數值不對?"}
I -- "是" --> J["3. 數值錯誤<br>格式、位元組順序、PAGE"]
I -- "寫入沒效果" --> K["6. 寫入沒反應<br>寫保護、ON_OFF_CONFIG"]
1. 找不到裝置:位址沒有 ACK
最常見:7 位元與 8 位元位址混淆
規格書和工具對「位址」的寫法不一致,是第一名的問題來源:
| 寫法 | 範例 | 誰會這樣寫 |
|---|---|---|
| 7 位元位址 | 0x58 | Linux i2c-tools、smbus2、多數 SMBus 規格 |
| 8 位元寫入位址 | 0xB0 | 部分規格書、邏輯分析儀 |
| 8 位元讀取位址 | 0xB1 | 部分規格書、邏輯分析儀 |
規格書寫「位址 B0h」時,Linux 上要用的是 0x58(0xB0 >> 1)。直接把 0xB0 當 7 位元位址,Linux 會回報位址無效(超過 0x7F);反過來,把 0x58 填進要求 8 位元位址的 MCU 函式庫,實際送出的會是 0x2C 的位址,當然沒有回應。
其他原因
| 檢查項目 | 說明 |
|---|---|
| 電源有沒有供電給 PMBus 介面 | CRPS 這類電源的 PMBus 控制器由待機電源供電,沒插 AC 或待機電源異常時不會回應 |
| 位址設定腳位 | 很多電源用 A0 / A1 等腳位(或插槽位置)決定位址,確認實際接法 |
| 上拉電阻 | SDA / SCL 必須有上拉電阻;太大會讓上升邊太慢,太小則拉不下來 |
| 電壓準位 | 主控端與電源的 SMBus 電壓要相容,必要時加電位轉換 |
| I2C Mux / Switch | 伺服器常在 BMC 和電源之間放 I2C 多工器(例如 PCA9548),沒有先選擇正確的通道就看不到電源 |
| 匯流排上有其他裝置拉住線路 | 拔掉其他裝置逐一排除 |
2. 特定命令失敗:位址有 ACK,但命令 NACK 或回傳錯誤
位址回應正常但某些命令失敗時,先讀 STATUS_CML (7Eh),它會告訴你電源為什麼拒絕:
| STATUS_CML 位元 | 意義 | 常見原因 |
|---|---|---|
| b7 | 無效 / 不支援的命令 | 電源沒有實作這個命令,用 QUERY (1Ah) 確認 |
| b6 | 無效 / 不支援的資料 | 寫入值超出範圍、保留位元不為 0、格式錯誤 |
| b5 | PEC 錯誤 | PEC 算錯,或線路雜訊,詳見 PEC 解析 |
| b1 | 其他通訊錯誤 | 交易長度不對,例如對 Byte 命令做 Word 讀取 |
另外幾個常見原因:
- 交易類型用錯:
VOUT_MODE是 Byte、READ_VOUT是 Word、MFR_ID是 Block,查 PMBus 命令總表 確認。 - 寫入保護:
WRITE_PROTECT (10h)開啟時,大部分寫入會被拒絕。 - 多路輸出沒選對 PAGE:多輸出電源要先寫
PAGE (00h)選擇輸出,否則讀寫的是另一路。
3. 數值錯誤:通訊正常,但數字不對
| 症狀 | 可能原因 |
|---|---|
| 電壓大到離譜 | READ_VOUT 用了 LINEAR11 解碼,應該用 LINEAR16 + VOUT_MODE |
| 溫度、電流出現 2000 多 | LINEAR11 的尾數沒有做二補數轉換 |
| 數值像是亂碼 | 位元組順序讀反,PMBus 一律低位元組在前 |
| 電流差好幾倍 | Direct 格式的電流係數沒有考慮檢流電阻 |
| 讀值偶爾跳一下 | 雜訊干擾,啟用 PEC 就能找出錯誤的交易 |
| 讀到另一路輸出的值 | 多路輸出電源的 PAGE 沒有設定正確 |
不確定換算對不對時,可以把原始值貼到 PMBus 數值換算工具 驗證。
4. 匯流排卡住:SDA 一直是低電位
為什麼會卡住
最常見的情況:主控端在交易進行到一半時重置(MCU 重開機、BMC 驅動重新載入)。Slave 正在送資料位元,剛好送出 0 並拉住 SDA,等待下一個 SCL 脈衝,但主控端已經不記得這筆交易了。結果 SDA 一直被拉低,主控端也沒辦法送出 Start。
解法一:送 9 個 SCL 脈衝 + STOP(I2C 通用)
主控端把 SCL / SDA 切成 GPIO,送出最多 9 個時脈,讓 Slave 把剩下的位元送完並釋放 SDA,接著送出 STOP:
/* 依平台實作:開汲極 GPIO 操作與延遲 */
void scl_release(void); /* SCL 輸出高阻抗(由上拉拉高) */
void scl_drive_low(void);
void sda_release(void);
void sda_drive_low(void);
int sda_read(void);
void delay_us(unsigned us);
/* I2C / SMBus Bus Recovery:成功回傳 0,SDA 仍被拉住回傳 -1 */
int i2c_bus_recover(void)
{
sda_release();
for (int i = 0; i < 9 && !sda_read(); i++) {
scl_drive_low();
delay_us(5);
scl_release();
delay_us(5);
}
if (!sda_read())
return -1;
/* STOP:SCL 為高時,SDA 由低變高 */
scl_drive_low();
delay_us(5);
sda_drive_low();
delay_us(5);
scl_release();
delay_us(5);
sda_release();
delay_us(5);
return 0;
}
9 個脈衝的原因:Slave 最多卡在一個位元組的 8 個資料位元 + 1 個 ACK 之中,9 個時脈一定能讓它走完。
解法二:利用 SMBus Timeout
SMBus / PMBus 裝置有一個 I2C 沒有的特性:SCL 低電位超過 Timeout(約 25 ~ 35 ms)時,裝置必須重置自己的通訊介面。所以主控端也可以把 SCL 拉低 35 ms 以上再放開,讓所有 SMBus 裝置釋放匯流排。
Linux 上,許多 I2C 控制器驅動內建 Bus Recovery,偵測到匯流排卡住時會自動嘗試。
5. Timeout 與 Clock Stretching
主控端停頓太久,Slave 自己放棄
SMBus 的 Timeout 是雙面刃:它能防止匯流排永久卡住,但主控端在交易中途停頓超過 25 ~ 35 ms,PMBus 裝置就會放棄這筆交易。後續讀到的資料可能錯位,或直接 NACK。
常見原因與對策:
| 原因 | 對策 |
|---|---|
| 用 GPIO 軟體模擬 I2C,中途被中斷打斷 | 改用硬體 I2C,或在交易期間關閉中斷 |
| 作業系統排程延遲 | 使用核心 I2C 驅動,避免在使用者空間逐位元控制 |
| 除錯時在交易中途下中斷點 | 中斷點設在交易完成之後 |
Clock Stretching 不相容
Slave 準備資料需要時間時,可以拉住 SCL 讓主控端等待(Clock Stretching)。電源的 MCU 在讀取 ADC、寫入 NVM 時常常這樣做。
- 主控端不支援 Clock Stretching 時,會在 Slave 還沒準備好時繼續送時脈,讀到錯誤資料。已知 Raspberry Pi 的硬體 I2C 對 Clock Stretching 處理有問題,可以降低時脈或改用
i2c-gpio軟體 I2C。 - 時脈太快:用
CAPABILITY (19h)確認電源支援的最高速度,不確定時先用 100 kHz。
6. 寫入沒反應
| 症狀 | 原因 |
|---|---|
寫 OPERATION 不會開關機 | ON_OFF_CONFIG 的 CMD = 0,電源只聽 CONTROL 腳位 |
| 所有寫入都被拒絕 | WRITE_PROTECT (10h) 開啟 |
| 電壓設定沒有變 | 超過 VOUT_MAX,被限制在上限;或電源不開放調整 |
| 設定在重新上電後消失 | 只寫入 RAM,沒有用 STORE_USER_ALL (15h) 等命令存入 NVM |
| 連續寫入後面幾筆失敗 | 前一個命令還在處理(例如寫入 NVM),STATUS_WORD 的 BUSY 位元為 1,要等待後再送 |
7. SMBALERT# 一直是低電位
| 原因 | 處理方式 |
|---|---|
| 故障還存在 | 清除後會立刻重新拉低,先排除故障,詳見 CLEAR_FAULTS |
| 多個裝置共用 SMBALERT# | 用 ARA(0x0C)找出是哪一個裝置發出的 |
| 只讀 STATUS 沒有清除 | 讀 STATUS 不會釋放 SMBALERT#,要送 CLEAR_FAULTS |
| 預期中的警告一直觸發 | 用 SMBALERT_MASK (1Bh) 屏蔽 |
除錯工具
| 工具 | 用途 |
|---|---|
| 邏輯分析儀 | 看實際波形:位址、ACK / NACK、時序、Clock Stretching。許多軟體提供 I2C / SMBus 解碼 |
| 示波器 | 看上升邊、雜訊、電壓準位,判斷上拉電阻是否合適 |
| i2c-tools | 快速讀寫單一命令,排除主控端程式的問題,見 Python / Linux 實作 |
| PMBus 數值換算工具 | 驗證 LINEAR11 / LINEAR16 / Direct 換算、PEC、STATUS_WORD 解碼 |
檢查清單
遇到問題時依序確認:
- ☐ 位址是 7 位元(
0x58),不是 8 位元(0xB0) - ☐ 電源的 PMBus 介面有供電(待機電源)
- ☐ SDA / SCL 有上拉,電壓準位相容
- ☐ I2C Mux 已切到正確通道
- ☐ 時脈不超過電源支援的速度(先用 100 kHz)
- ☐ 命令的交易類型正確(Byte / Word / Block)
- ☐ 讀過
STATUS_CML,確認沒有命令或 PEC 錯誤 - ☐ 數值格式正確(LINEAR11 / LINEAR16 / Direct),位元組低位在前
- ☐ 多路輸出電源已設定
PAGE - ☐ 寫入後有讀回確認
延伸閱讀: