序言
久聞STM32硬件I2C坑多,之前做的項(xiàng)目淺嘗主機(jī)通信就偶爾遇到總線鎖死的bug,網(wǎng)上解決方案也很多,用著也還行。然而作為從機(jī)就是另一個(gè)大坑了,官方例程少,網(wǎng)上資料少,api也說(shuō)的不明不白。本文整合各位博主分享的資料,記錄和分享調(diào)試linux主機(jī)與STM32的I2C通信過(guò)程中遇到的問(wèn)題和解決方案,最終在STM32L051C8T單片機(jī)實(shí)現(xiàn)DMA方式的I2C從機(jī)。
單片機(jī)資源緊張,性能低但是實(shí)時(shí)性高。要充分發(fā)揮單片機(jī)實(shí)時(shí)特性,在處理低速I(mǎi)O時(shí)應(yīng)該盡量用硬件方式實(shí)現(xiàn),盡可能利用硬件處理數(shù)據(jù)。DMA就是解放CPU負(fù)載的利器。
HAL庫(kù)API分析
一般來(lái)說(shuō)HAL庫(kù)的通信io類(lèi)API分為polling阻塞,IT和DMA方式。而I2C分主從模式,不同I2C器件有不同的協(xié)議細(xì)節(jié),在此基礎(chǔ)還要向上支持SMBus/PMBus等協(xié)議,導(dǎo)致API冗雜。API大概可以按以下方式組合:
【主機(jī)/從機(jī)】-【序列】-【阻塞/中斷/DMA】-【收/發(fā)】
其中主機(jī)模式特有【內(nèi)存存取】模式
例如:
HAL_I2C_Master_Transmit,?為【主機(jī)阻塞方式普通發(fā)送】
HAL_I2C_Mem_Read_DMA,為【主機(jī)DMA方式讀從機(jī)內(nèi)存】
HAL_I2C_Slave_Seq_Transmit_DMA,為【從機(jī)序列DMA方式發(fā)送】
【主從機(jī)】決定是誰(shuí)發(fā)送SCL
【阻塞/中斷/DMA】決定單片機(jī)內(nèi)部存取數(shù)據(jù)方式,影響CPU和總線使用率
【收/發(fā)】決定數(shù)據(jù)傳輸方向
參考大佬對(duì)HAL庫(kù)Seq相關(guān)API的分析。【內(nèi)存存取】和【序列】是對(duì)基礎(chǔ)收發(fā)API的更高級(jí)封裝,前者可以通過(guò)單個(gè)函數(shù)實(shí)現(xiàn)對(duì)從設(shè)備內(nèi)存的存取,典型用處是存取EEPROM。但是實(shí)際上大多數(shù)I2C設(shè)備通信協(xié)議不如EEPROM簡(jiǎn)單,所以這個(gè)API簡(jiǎn)單使用但局限。后者【序列】方式彌補(bǔ)了適用局限,這類(lèi)API允許主從機(jī)對(duì)連續(xù)占用和監(jiān)聽(tīng),即主機(jī)可以不產(chǎn)生Stop而產(chǎn)生ReStart信號(hào),大大擴(kuò)展了HAL庫(kù)的適用條件。
然而如同更新HAL_UARTEx_ReceiveToIdle之前一樣,STM32L0固件V1.12.0版本不支持任意長(zhǎng)度數(shù)據(jù)收發(fā),只能收發(fā)定長(zhǎng)數(shù)據(jù)。實(shí)際使用中不可能要求主機(jī)收發(fā)數(shù)據(jù)長(zhǎng)度固定不變。經(jīng)過(guò)閱讀代碼和測(cè)試,發(fā)現(xiàn)從機(jī)【序列】方式收發(fā)不定長(zhǎng)數(shù)據(jù)會(huì)導(dǎo)致I2C句柄報(bào)錯(cuò)。主機(jī)提前停止收發(fā)數(shù)據(jù),從機(jī)都會(huì)報(bào)NACK錯(cuò)誤,導(dǎo)致無(wú)法進(jìn)入正確回調(diào)函數(shù),進(jìn)行下一輪通信。
代碼分析
STM32硬件i2c從機(jī)DMA: 基于HAL庫(kù)函數(shù)的STM32單片機(jī)I2C從機(jī)代碼,DMA(Seq)方式通信。 - Gitee.com

定義收發(fā)數(shù)組,初始化模塊變量。這里將addr和slave_rx變量放在一起方便DMA接受數(shù)據(jù)時(shí),第一個(gè)數(shù)據(jù)直接填入結(jié)構(gòu)體第一個(gè)變量??紤]到主機(jī)寫(xiě)入不一定從0地址開(kāi)始,所以收發(fā)數(shù)組要獨(dú)立。dir暫時(shí)沒(méi)用,只湊齊4字節(jié)。rxlen是接受長(zhǎng)度,不包含地址字節(jié)。

當(dāng)主機(jī)發(fā)數(shù)據(jù)不滿DMA計(jì)數(shù)時(shí),進(jìn)入這個(gè)回調(diào)函數(shù),需要我們字節(jié)計(jì)算收到的字節(jié)數(shù)。并開(kāi)啟下一輪通信。

監(jiān)聽(tīng)到總線廣播從機(jī)地址,進(jìn)入到這個(gè)回調(diào)函數(shù),在這里判斷收發(fā)并初始化監(jiān)聽(tīng)。注意這里的【I2C_LAST_FRAME】參數(shù),會(huì)影響到DMA傳輸完成時(shí)的回調(diào)函數(shù),這里這樣配置會(huì)導(dǎo)致DMA傳輸完成后不調(diào)用I2C_DMASlaveReceiveCplt(初始化監(jiān)聽(tīng)時(shí)注冊(cè)的)。詳見(jiàn)這里。
當(dāng)然如果主機(jī)收發(fā)的字節(jié)數(shù)剛好和DMA計(jì)數(shù)一樣,我們也應(yīng)當(dāng)要開(kāi)啟新一輪傳輸。于是這里先實(shí)現(xiàn)DMA傳輸完成的回調(diào)函數(shù),放到<stm32l0xx_it.c>對(duì)應(yīng)的DMA終端里面,要注意判斷DMA通道。

從機(jī)監(jiān)聽(tīng)入口函數(shù),在主函數(shù)或初始化時(shí)調(diào)用即可。
參考資料
總結(jié)一下首次使用HAL庫(kù)STM32f030硬件IIC從機(jī)中斷收發(fā)
STM32 HAL I2C(IIC)通信的序列傳輸(restart condition)
簡(jiǎn)書(shū)也太難用了