PMBus 系列約 5 分鐘

PMBus / SMBus PEC 校驗完整解析:CRC-8 計算範圍、讀寫範例與常見錯誤

SMBus / PMBus PEC(Packet Error Checking)完整解析:CRC-8 多項式與參數、每種交易要把哪些位元組算進去、Read Word 為什麼要包含兩次位址,附 Send Byte / Write Byte / Read Word / Process Call 實際 PEC 數值、常見錯誤對照與 C 語言位元版 / 查表版實作。

📘 參考規格書SMBus / PMBus · System Management Bus (SMBus) Specification原廠文件 ↗

為什麼需要 PEC

電源管理匯流排常常走在大電流、高 dv/dt 的電源板上,SDA / SCL 容易受到雜訊干擾。I2C 本身只有 ACK / NACK,無法發現資料被改錯。

想像一下:寫入 OPERATION 時,80h(開機)被雜訊打成 00h(立即關機),電源會直接照做。PEC 就是為了擋下這種錯誤:接收端算出來的 CRC 和收到的 PEC 不一致,就不執行這筆命令。

CRC-8 參數

PEC 的計算範圍

規則只有一條:交易中所有的位址、命令、資料位元組都要算進去,包含 Byte Count;不包含 Start、Stop、ACK / NACK 這些訊號。

包含不包含
位址位元組(7 位元位址左移 1 位 + R/W 位元)Start(S)、Repeated Start(Sr)、Stop(P)
命令碼ACK / NACK
Byte Count(Block 類交易)PEC 本身
所有資料位元組
讀取交易中的第二個位址位元組(Addr+R)

誰送 PEC、誰檢查

交易類型PEC 由誰送出由誰檢查
寫入(Send Byte、Write Byte / Word、Block Write)MasterSlave
讀取(Read Byte / Word、Block Read、Process Call)SlaveMaster

規則很直覺:最後送出資料的一方,負責在最後附上 PEC。

各種交易的實際範例

以下都以 PMBus 位址 0x58 為例,寫入位址位元組為 B0h(0x58 << 1 | 0),讀取位址位元組為 B1h(0x58 << 1 | 1)。

1. Send Byte:CLEAR_FAULTS (03h)

S | B0h | A | 03h | A | PEC | A | P

計算:CRC-8(B0 03) = 46h

2. Write Byte:OPERATION (01h) = 80h

S | B0h | A | 01h | A | 80h | A | PEC | A | P

計算:CRC-8(B0 01 80) = 76h

3. Write Word:VOUT_COMMAND (21h) = 1800h

S | B0h | A | 21h | A | 00h | A | 18h | A | PEC | A | P

計算:CRC-8(B0 21 00 18) = F8h(資料低位元組先傳,PEC 也照線上的順序計算)

4. Read Word:READ_VIN (88h) = F9CCh

S | B0h | A | 88h | A | Sr | B1h | A | CCh | A | F9h | A | PEC | NA | P

B0h、88h、B1h 由 Master 送出;CCh、F9h 和 PEC 由 Slave 送出。PEC 要把雙方送出的位元組全部算進去:

計算:CRC-8(B0 88 B1 CC F9) = 31h

5. Process Call:QUERY (1Ah) 查詢 21h

S | B0h | A | 1Ah | A | 01h | A | 21h | A | Sr | B1h | A | 01h | A | E0h | A | PEC | NA | P

計算:CRC-8(B0 1A 01 21 B1 01 E0) = FBh(兩個 Byte Count 都要算進去)

QUERY (1Ah)、SMBALERT_MASK (1Bh) 讀取、COEFFICIENTS (30h) 都是這種格式。

PEC 錯誤時會發生什麼

flowchart TD
    W["寫入交易<br>Master 送出 PEC"] --> WS{"Slave 檢查 PEC"}
    WS -- "正確" --> WOK["執行命令"]
    WS -- "錯誤" --> WE["不執行命令<br>STATUS_CML b5 = 1(PEC Failed)<br>拉低 SMBALERT#"]
    R["讀取交易<br>Slave 送出 PEC"] --> RS{"Master 檢查 PEC"}
    RS -- "正確" --> ROK["使用讀到的資料"]
    RS -- "錯誤" --> RE["丟棄資料,重新讀取"]
  • 寫入時 PEC 錯誤:PMBus 元件不會執行這筆命令,會設定 STATUS_CML 的 PEC Failed(b5),並透過 SMBALERT# 通知主控端。部分元件也會直接對 PEC 位元組回 NACK。
  • 讀取時 PEC 錯誤:Slave 不知道資料在路上被改了,只有 Master 能發現。主控端韌體必須自己比對,錯了就丟棄資料、重新讀取。

C 語言實作

位元運算版(程式最小)

#include <stddef.h>
#include <stdint.h>

/* CRC-8/SMBUS:多項式 0x07,初始值 0 */
uint8_t smbus_pec_update(uint8_t crc, uint8_t data)
{
    crc ^= data;
    for (int i = 0; i < 8; i++)
        crc = (crc & 0x80) ? (uint8_t)((crc << 1) ^ 0x07) : (uint8_t)(crc << 1);
    return crc;
}

uint8_t smbus_pec(const uint8_t *buf, size_t len)
{
    uint8_t crc = 0;
    while (len--)
        crc = smbus_pec_update(crc, *buf++);
    return crc;
}

smbus_pec_update() 一次處理一個位元組,適合在 I2C 中斷裡邊收邊算,不用先把整筆交易存起來。

查表版(速度最快)

用 256 Bytes 的表換取速度,每個位元組只要一次查表:

static uint8_t pec_table[256];

void smbus_pec_init_table(void)
{
    for (int i = 0; i < 256; i++) {
        uint8_t crc = (uint8_t)i;
        for (int b = 0; b < 8; b++)
            crc = (crc & 0x80) ? (uint8_t)((crc << 1) ^ 0x07) : (uint8_t)(crc << 1);
        pec_table[i] = crc;
    }
}

static inline uint8_t smbus_pec_update_fast(uint8_t crc, uint8_t data)
{
    return pec_table[crc ^ data];
}

讀取時驗證 PEC

/* 驗證 Read Word:addr 為 7 位元位址,rx[0..1] 為資料,rx[2] 為收到的 PEC */
int read_word_pec_ok(uint8_t addr, uint8_t cmd, const uint8_t rx[3])
{
    uint8_t frame[5] = {
        (uint8_t)(addr << 1),        /* Addr+W */
        cmd,
        (uint8_t)(addr << 1 | 1),    /* Addr+R */
        rx[0],                       /* 低位元組 */
        rx[1],                       /* 高位元組 */
    };
    return smbus_pec(frame, sizeof frame) == rx[2];
}

用範例 4 驗證:read_word_pec_ok(0x58, 0x88, (uint8_t[]){0xCC, 0xF9, 0x31}) 回傳 1。

實務操作

確認元件是否支援 PEC

讀取 CAPABILITY (19h),b7 = 1 代表支援 PEC。

i2cget -y 1 0x58 0x19 b

Linux(i2c-tools)

i2c-tools 的模式後面加上 p 就會自動計算與檢查 PEC:

i2cset -y 1 0x58 0x03 cp       # Send Byte + PEC(CLEAR_FAULTS)
i2cset -y 1 0x58 0x01 0x80 bp  # Write Byte + PEC(OPERATION = 80h)
i2cget -y 1 0x58 0x88 wp       # Read Word + PEC(READ_VIN),PEC 錯誤時會回報錯誤

在 Linux 驅動程式中,則是透過 I2C_CLIENT_PEC 旗標或 I2C_PEC ioctl 啟用。

MCU 硬體 PEC

許多 MCU 的 I2C 周邊內建 硬體 PEC 計算,例如 STM32 的 I2C 在 SMBus 模式下可以自動產生與檢查 PEC。用硬體計算可以省下 CPU 時間,但要注意讀取交易中 Repeated Start 之後的位址是否有被算進去,請以參考手冊為準。

常見錯誤總整理

錯誤結果
沒有把位址位元組算進去PEC 每次都錯
位址用 7 位元原值(58h),沒有左移加 R/WPEC 每次都錯
讀取交易漏掉 Addr+R(B1h)讀取 PEC 每次都錯,寫入正常
Block 交易漏掉 Byte CountBlock 交易 PEC 錯,其他正常
用錯 CRC-8 版本(例如 CRC-8/MAXIM)全部錯;用 "123456789" → F4h 檢查
Word 資料用「高位元組先」計算Word 交易 PEC 錯;要照線上順序(低位元組先)
讀取時沒有在韌體檢查 PEC雜訊造成的錯誤資料被當成正確值使用

結論

  • PEC 是 CRC-8/SMBUS:多項式 0x07、初始值 0,"123456789" → F4h。
  • 所有位址、命令、Byte Count、資料都要算進去,讀取交易包含兩次位址。
  • 寫入由 Master 送 PEC、Slave 檢查;讀取由 Slave 送 PEC、Master 韌體必須自己檢查。
  • 寫入 PEC 錯誤 → 命令不執行、STATUS_CML b5 = 1。
  • 先用 CAPABILITY (19h) b7 確認元件支援 PEC。

延伸閱讀: