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 語言位元版 / 查表版實作。
為什麼需要 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) | Master | Slave |
| 讀取(Read Byte / Word、Block Read、Process Call) | Slave | Master |
規則很直覺:最後送出資料的一方,負責在最後附上 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/W | PEC 每次都錯 |
讀取交易漏掉 Addr+R(B1h) | 讀取 PEC 每次都錯,寫入正常 |
| Block 交易漏掉 Byte Count | Block 交易 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_CMLb5 = 1。 - 先用
CAPABILITY (19h)b7 確認元件支援 PEC。
延伸閱讀: