SMBus是一種類似於I2C的通訊協議,簡單來說,你可以把它看成I2C,只是它的通訊速率比較慢,一般來說,I2C的通訊速率是100KHz~400KHz,但是SMBus的通訊速率只有10KHz~100KHz。但是SMBus有它的優點,比如:
- 使用 SMBus,設備還可以提供它的生產信息,告訴系統它的型號,部件號等,針對掛起事件保存它的狀態,報告不同類別的錯誤,接收控制參數,並返回它的狀態等;
- 因為SMBus的通訊速率較慢,所以允許單一主機與 CPU 和多個主從硬盤通訊並收發數據;
- SMBus為系統和電源管理相關的任務提供一條控制總線。一個系統利用SMBus可以和多個設備互傳信息,而不需使用獨立的控制線路;
- SMBus提醒模式,這個功能一般是跟廣播呼叫地址一起應用的,此功能需要一條帶中斷的可選信號(SMBALERT),那些希望與主設備進行通訊的從設備可以通過這根線發信號給主設備,主機處理該中斷並通過提醒響應地址ARA(Alert Response Address,地址值為0001100x)訪問所有SMBALERT設備,只有那些把SMBALERT拉低的從設備才能應答ARA。主機執行一個修改過的接收字節操作。由從發送設備提供的7位設備地址被放在字節的7個最高位上,第八個位可以是0或1。如果多個設備把SMBALERT拉低,最高優先級設備(最小的地址)將在地址傳輸期間通過標准仲裁贏得通信權。
- 超時錯誤,SMBus定義一個時鍾低超時,35ms的超時。SMBus規定TLOW:SEXT為從設備的累積時鍾低擴展時間。SMBus規定TLOW:MEXT為主設備的累積時鍾低擴展時間。
I2C和SMBus的區別:
SMBus
|
I2C
|
最大傳輸速度 100kHz
|
最大傳輸速度400kHz
|
最小傳輸速度 10kHz
|
無最小傳輸速度
|
35ms時鍾低超時
|
無時鍾超時
|
固定的邏輯電平
|
邏輯電平由VDD決定
|
不同的地址類型(保留、動態等) 7位、
10位和廣播呼叫從地址類型
|
不同的總線協議(快速命令、
處理呼叫等) 無總線協議
|
以上內容參考百度百科:https://baike.baidu.com/item/smbus%E5%8D%8F%E8%AE%AE/5636057?fr=aladdin
SMBus通訊協議在電源管理系統應用很廣泛,現在手頭上的一個案子也是通過SMBus去跟電池(這顆電池上有一個GAUGE IC,用於采集電池信息以及管理電池的作用)通訊,MCU用的是華大HC32F460系列的單片機。
華大的MCU和STM32 的MCU都自帶有硬件SMBus的功能,最開始的時候我想通過硬件SMBus來通訊,但是因為官方例程以及網上都沒能找到相關的資料,且咨詢過FAE也沒有可參考的信息,由於進度的關系沒有太長時間給我研究,所以我還是采用了模擬SMBus。關於模擬SMBus的代碼網上都可以找到,但是可能作者也沒有經過仔細地測試驗證,其可靠性還是有所欠缺,比如在通訊的過程中有時會出現通訊不上的情況,過一段時間后又可以通訊上。這個問題我猜測是時序上的問題,但是如何優化時序,以達到最佳的通訊效果,需要比較長的時間進行調試,而且我也試過優化時序,雖然段時間內好像問題得到很大的改善,但是實際上還是不能從根本上解決。
雖然模擬SMBus通訊不太穩定,但是我加了糾錯機制后,基本上能滿足客戶的需求,所以事情暫時告一段落。但是問題存在總是令人不安,所以我開始研究起HC32F460的硬件SMBus。
在我的案子上,用到了兩組SMBus,第一路SMBus的SCL是PB6,SDA是PB7,第一路SMBus的SCL是PB13,SDA是PB14,。查過手冊后發現有一個比較麻煩的問題就是這兩組引腳,雖然都可以復用為I2C,但是都只能復用為I2C3,如果兩組SMBus都采用硬件SMBus的話,就會沖突。
以上是HC32F460系列用戶手冊上的引腳功能表上的部分截圖,圖中可以看到引腳后面具備的功能就可以通過軟件配置去復用成對應的功能引腳,從圖中可以看到這幾個引腳的Func0~Func31都沒有I2C的功能,那么只能寄希望於Func32~Func63,而這幾個引腳的Func31~Func63對應的都是Func_Grp2,那么看一下Func32~63表中Func_Grp2中有沒有具備I2C的功能就可以知道這幾個引腳能不能復用為I2C的功能,從下圖中可以看到,Func_Grp2中確實有I2C的功能,但是只有I2C3這一種。
所以,這兩組SMBus只能復用為I2C3的功能,為了避免沖突,只能是分時復用,也就是說當我啟用第一組SMBus的時候,我需要把第二組SMBus的SCL和SDA引腳配置為普通的GPIO,把第一組SMBus的SCL和SDA引腳配置為I2C3_SCL和I2C3_SDA,啟用第二組的時候則要反過來。配置引腳功能的時候涉及到一個重要的寄存器:功能選擇寄存器(PFSRxy,x=A~H,y=0~15),如圖:
結合上面的Func32~63的介紹,可以知道I2C3_SCL是Func49,I2C3_SDA是Func48,所以這個寄存器FSEL[5:0]應該配置為0x31或者0x30。可以通過調用官方提供的庫函數進行配置:
extern en_result_t PORT_SetFunc(en_port_t enPort, uint16_t u16Pin, \ en_port_func_t enFuncSel, en_functional_state_t enSubFunc);
最后一個形參默認填Disable,這個參數是配置這個寄存器的b8位(副功能許可)的。
接下來,貼出我的初始化代碼:
uint8_t SMBus_Init(uint8_t SMBusNum) { stc_i2c_init_t stcI2cInit; stc_clk_freq_t stcClkFreq; stc_i2c_smbus_init_t stcSmBusInit; /* Initialize I2C port*/ //根據要啟用的具體某一組SMBus,配置其引腳為I2C3_SCL和I2C3_SDA功能,並把另外一組引腳配置為普通的GPIO if(SMBusNum == SMBus_Num1) { PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_2_SCL_PIN, Func_Gpio, Disable); PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_2_SDA_PIN, Func_Gpio, Disable); PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_1_SCL_PIN, Func_I2c3_Scl, Disable); PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_1_SDA_PIN, Func_I2c3_Sda, Disable); } else { PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_1_SCL_PIN, Func_Gpio, Disable); PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_1_SDA_PIN, Func_Gpio, Disable); PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_2_SCL_PIN, Func_I2c3_Scl, Disable); PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_2_SDA_PIN, Func_I2c3_Sda, Disable); } /* Enable I2C Peripheral*/ PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_I2C3, Enable); I2C_DeInit(I2C_CH); //初始化之前先復位I2C /* Get system clock frequency */ CLK_GetClockFreq(&stcClkFreq); MEM_ZERO_STRUCT(stcI2cInit); stcI2cInit.u32Baudrate = I2C_BAUDRATE; //宏定義,80 stcI2cInit.u32Pclk3 = stcClkFreq.pclk3Freq; stcI2cInit.enI2cMode = I2cMaster; //主設備 stcI2cInit.u32SclTime = 0ul; I2C_Init(I2C_CH, &stcI2cInit); //I2C初始化 MEM_ZERO_STRUCT(stcSmBusInit); stcSmBusInit.enHostAdrMatchFunc = Disable; stcSmBusInit.enDefaultAdrMatchFunc = Disable; stcSmBusInit.enAlarmAdrMatchFunc = Disable; I2C_SmbusConfig(I2C_CH, &stcSmBusInit); 使能I2C功能 I2C_Cmd(I2C_CH, Enable); //使能SMBus功能 I2C_SmBusCmd(I2C_CH,Enable); return I2C_RET_OK; }
I2C基本功能函數:
/** ****************************************************************************** ** \brief Send start or restart condition ** ** \param none ** ** \retval Process result ** - I2C_RET_ERROR Send start or restart failed ** - I2C_RET_OK Send start or restart success ******************************************************************************/ uint8_t Master_StartOrRestart(uint8_t u8Start) { uint32_t u32TimeOut = TIMEOUT; en_flag_status_t enFlagBusy = Reset; en_flag_status_t enFlagStartf = Reset; /* generate start or restart signal */ //if(GENERATE_START == u8Start) if(!u8Start) { /* Wait I2C bus idle */ while(Set == I2C_GetStatus(I2C_CH, I2C_SR_BUSY)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } I2C_GenerateStart(I2C_CH , Enable); } else { /* Clear start status flag */ I2C_ClearStatus(I2C_CH, I2C_CLR_STARTFCLR); /* Send restart condition */ I2C_GenerateReStart(I2C_CH , Enable); } /* Judge if start success*/ u32TimeOut = TIMEOUT; while(1) { enFlagBusy = I2C_GetStatus(I2C_CH, I2C_SR_BUSY); enFlagStartf = I2C_GetStatus(I2C_CH, I2C_SR_STARTF); if(enFlagBusy && enFlagStartf) { break; } if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } return I2C_RET_OK; } /** ****************************************************************************** ** \brief Send slave address ** ** \param u16Adr The slave address ** ** \retval Process result ** - I2C_RET_ERROR Send failed ** - I2C_RET_OK Send success ******************************************************************************/ uint8_t Master_SendAdr(uint8_t u8Adr) { uint32_t u32TimeOut = TIMEOUT; /* Wait tx buffer empty */ while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TEMPTYF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } /* Send I2C address */ I2C_SendData(I2C_CH, u8Adr); //if(E2_ADDRESS_W == (u8Adr & 0x01u)) if(!(u8Adr & 0x01u)) /* C-STAT MISRAC2004-13.7 */ { /* If in master transfer process, Need wait transfer end*/ uint32_t u32TimeOut = TIMEOUT; while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TENDF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } } /* Check ACK */ u32TimeOut = TIMEOUT; while(Set == I2C_GetStatus(I2C_CH, I2C_SR_NACKDETECTF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } return I2C_RET_OK; } /** ****************************************************************************** ** \brief Send data to slave ** ** \param pTxData Pointer to the data buffer ** \param u32Size Data size ** ** \retval Process result ** - I2C_RET_ERROR Send failed ** - I2C_RET_OK Send success ******************************************************************************/ uint8_t Master_WriteData(uint8_t *pTxData, uint32_t u32Size) { uint32_t u32TimeOut = TIMEOUT; while(u32Size--) { /* Wait tx buffer empty */ u32TimeOut = TIMEOUT; while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TEMPTYF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } /* Send one byte data */ I2C_SendData(I2C_CH, *pTxData++); /* Wait transfer end*/ u32TimeOut = TIMEOUT; while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TENDF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } /* Check ACK */ u32TimeOut = TIMEOUT; while(Set == I2C_GetStatus(I2C_CH, I2C_SR_NACKDETECTF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } } return I2C_RET_OK; } /** ****************************************************************************** ** \brief General stop condition to slave ** ** \param None ** ** \retval Process result ** - I2C_RET_ERROR Send failed ** - I2C_RET_OK Send success ******************************************************************************/ uint8_t Master_Stop(void) { uint32_t u32TimeOut; /* Wait I2C bus busy */ u32TimeOut = TIMEOUT; while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_BUSY)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } I2C_GenerateStop(I2C_CH, Enable); /* Wait STOPF */ u32TimeOut = TIMEOUT; while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_STOPF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } return I2C_RET_OK; } /** ****************************************************************************** ** \brief Receive data from slave ** ** \param pTxData Pointer to the data buffer ** \param u32Size Data size ** ** \retval Process result ** - I2C_RET_ERROR Process failed ** - I2C_RET_OK Process success ******************************************************************************/ uint8_t Master_RevData(uint8_t *pRxData, uint32_t u32Size) { uint32_t u32TimeOut = TIMEOUT; for(uint32_t i=0ul; i<u32Size; i++) { /* if the last byte receive, need config NACK*/ if(i == (u32Size - 1ul)) { I2C_NackConfig(I2C_CH, Enable); } /* Wait receive full flag*/ u32TimeOut = TIMEOUT; while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_RFULLF)) { if(0ul == (u32TimeOut--)) { return I2C_RET_ERROR; } } /* read data from register*/ *pRxData++ = I2C_ReadData(I2C_CH); } return I2C_RET_OK; }
獲取電池信息:
uint8_t Get_Battery_Info(uint8_t slaveAddr, uint8_t Comcode,uint8_t *data,uint8_t size) { uint8_t u8Ret = I2C_RET_OK; uint8_t *cmd; *cmd = Comcode; Ddl_Delay1ms(5ul); /* I2C master data read*/ u8Ret = Master_StartOrRestart(GENERATE_START); if(u8Ret != I2C_RET_OK) { printf("Error : 1"); } JudgeResult(u8Ret); u8Ret = Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_W); if(u8Ret != I2C_RET_OK) { printf("Error : 2"); } JudgeResult(u8Ret); u8Ret = Master_WriteData(cmd,1); if(u8Ret != I2C_RET_OK) { printf("Error : 3"); } JudgeResult(u8Ret); u8Ret = Master_StartOrRestart(GENERATE_RESTART); if(u8Ret != I2C_RET_OK) { printf("Error : 1"); } u8Ret = Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_R); if(u8Ret != I2C_RET_OK) { printf("Error : 4"); } JudgeResult(u8Ret); u8Ret = Master_RevData(data, size); if(u8Ret != I2C_RET_OK) { printf("Error : 5"); } JudgeResult(u8Ret); u8Ret = Master_Stop(); if(u8Ret != I2C_RET_OK) { printf("Error : 6"); } JudgeResult(u8Ret); return I2C_RET_OK; }
華大的硬件SMBus(I2C)庫函數跟STM32的庫函數(HAL庫)還是有比較大的差別的,比如讀從設備的數據的時候,HAL庫函數可能調用一個函數就搞掂了,硬件會幫你實現整個數據交換的通訊過程,但是華大的不一樣,由上面的代碼你也可以看到,整個時序都需要程序員去操控,例如要采集電池的電壓值的時候,你需要先調用:
- Master_StartOrRestart(GENERATE_START);產生開始條件
- Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_W);發送從設備地址加寫位
- Master_WriteData(cmd,1);把采集電壓的命令發送到從設備
- Master_StartOrRestart(GENERATE_RESTART);重新開始
- Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_R);發送從設備地址加讀位
- Master_RevData(data, size);接受數據
- Master_Stop();產生停止條件
需要程序員對SMBus的通訊時序比較了解,目前測試了一個下午,還沒有發現通訊問題,所以應該說這個硬件SMBus可靠性還是比較高的。