在CUBEMX下,使用STM32F103 SPI做從站的筆記

 

  之前做STM32的項目, 一直都用的是標準固件庫。最近有個比較簡單的項目,就想試試ST強推的開發工具cubemx。

  用了下來,感覺CUBEMX的 HAL庫做得很模塊化,讓一些用戶遠離了底層。但是也有缺點:

    1. 各種模塊,應用都層次化了,所以調用關係也比自己寫繁瑣得多。

    2.雖然簡化了很多應用的開發過程,但也是因爲把驅動模塊化了,但不靈活,面對一些特殊點的場合,就容易出現問題。

    3.一旦代碼出問題,找起故障來很麻煩,在各種函數中跳來跳去。比如我在SPI中遇到問題,要查故障,從總中斷,跳到TX子服務,然後又執行一個註冊的中斷處理函數,最後去執行用戶回調函數。跳來跳去的。

  結論: 即便是用CUBEMX來做項目,還是需要看STM32的用戶手冊,去了解各種寄存器,各種外設的特點,不然只知道簡單用法,不知道執行原理,是沒辦法排查故障和實現任務的。

  好了,現在說說STM32用來做SPI從站的問題。

一  CUbe MX生成代碼

  首先把SYS,時鐘等設好(不多說了)。然後就開始SPI的設置。

  我把SPI pin 設置爲從站,關閉 NSS,並將PA4作爲EXTI4 。使用EXTI4作爲一個數據幀的起始標誌。

  當然,也可以不用EXTI4做幀起始,而用定時器來識別幀的起始字節( 通信時間間隔大於XXMS,代表開始了新的通信幀)。

    

  在 configuration中,做參數設置:

       

  NVIC Setting中開啓SPI中斷。

       

  最後在project菜單裏,執行Generate Code ,生成代碼即可。

 

二  如何在程序中實現基本的SPI通信。

  對於基礎運用,相當簡單:

  在主函數中,執行: 

      HAL_SPI_TransmitReceive_IT(&hspi1, TXbuf,RXbuf,CommSize);

  當SPI上出現了 CommSize個字節的數據後,中斷函數會調用回調:

      HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)

  這在HAL中是個弱申明的函數,我們只需要做一個自己的同名回調函數,實現預定功能即可,編譯時會自動替換。

  就靠這兩個函數,基本的通信就沒問題了。

三  是不是很簡單?!然而事實是殘酷的,這還沒完。SPI從站的問題:

  如果就這樣使用,你會發現從站數據(MISO),有時能準確到達主站,有時卻整體後移了一個字節導致數據不完整。並且回調函數也不是很靈,時而執行,時而不執行。

  跟蹤代碼,你會發現,執行中,有兩種情況:

      1. 執行HAL_SPI_TransmitReceive_IT 後,即便主站沒有發起通信,從站也立刻跳入了TX_ISR中斷。 這種情況,程序執行時正常的。

  2.執行HAL_SPI_TransmitReceive_IT 後,未跳入TX_ISR。當主站發起通信時,從站先跳入TX中斷執行DR=S0,然後跳入RX中斷執行R0=DR。看起來沒問題,然而,結果就是主站收到的數據不正確。而且HAL_SPI_TxRxCpltCallback回調函數頁沒有執行,HAL認爲 “故障:DR寄存器中還有數據沒有發出”。 

  情況2是如何產生的呢?簡單的說,就是某次通信出現故障後,TXE無法正確清空(置1),導致通信錯位。

  具體的說,就要從STM32的SPI的通信原理說起了。

  一次正確的執行流程,時序圖是如下的:

 

  是不是看起來有點懵?

  簡單的說,就是需要按照如下順序做通信:

  1.通信前,執行HAL_TransmationRecive_IT() .

  2. 由於DR此時一般爲空,所以產生了TXE中斷 ,將SendByte0 先寫入DR寄存器。

  3.當第一個CLK到來時,DR中的SendByte0會放到總線上,此時,DR空就會產生一個TXE中斷!將SendByte1寫入DR寄存器。

  4.再執行7個CLK 。

  5.第8個CLK完成後,發生RX中斷,DR= RecByte0!

  6.第9個CLK,又發生了TXE中斷...

  上面是正常情況,此時 ,TXE中斷需要比 RXE中斷先產生兩個回合,才能保證SendByte 和 RecByte都依次完整到傳輸!

  如果由於某種原因(比如上次通信失敗),TXE最初是0,則發送DR會丟失第一個中斷。這將導致SendByte推遲一個字節!

  可惜的是,TXE是隻讀寄存器,無法通過手動清理來使其恢復爲空。也就是說,一旦通信發生過一次錯誤,那麼,在使用HAL庫的情況下, 這個錯誤就無法消除。

  並且,由於丟失了第一個TXE中斷,HAL在計算已發送字節的時候,會發現少發送了一個字節,這會導致回調函數“HAL_SPI_TxRxCpltCallback”無法執行。

 

四 解決的辦法

  一種解決辦法:對HAL庫做修改

    在HAL_TransmationRecive_IT()中, 增加代碼,判斷TXE是否空。如果空,則正常執行; 如果爲滿,則直接把SendByte0寫入DR寄存器,並執行TxXferCount--;相當於手動執行了第一次中斷服務。

     但這個辦法有個麻煩的地方,就是以後每次使用CUBEMX填寫功能塊時,編譯器會自動把HAL_TransmationRecive_IT()函數恢復,我們就需要不斷的去修改它的代碼。

  另一種解決辦法:

    不論TXE的狀態,先執行 DR = SendByte0;

    然後,再執行HAL_TransmationRecive_IT()。此後,在SPI通信前,TXE將會一直保持滿的狀態。

    Hspi-> TxXferCount-- ,手動減少發送倒計數。

    這個方法的好處是,不用修改HAL代碼,更新程序不受影響。

 

  可見,如果對SPI的執行原理不瞭解, 單純使用HAL,還是容易出現一些問題的。

       使用HAL,雖然讓項目更快,普通情況下也更安全,但是爲了解決一些特殊的情況,我們還是要掌握STM32的用戶手冊,多看看寄存器和執行原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章