關鍵字:SPI,Flash,WRPERR
目錄預覽
1 引言2 問題3 問題解決4 小結
01 引言
在STM32的應用中,SPI算是用的比較多的外設了,也是單片機最常見外設之一。客戶說它執行了關閉SPI的代碼,竟然會導致Flash中的WRPERR標志置位,致使應用碰到一些問題。這就奇怪了,SPI和內部Flash看起來是風馬牛不相及的事情,為什么會發生這種事呢?一起來看看吧。
02 問題
2.1 問題起源
客戶在使用STM32L072RBT6的時候,使用STM32 CubeL0庫,在程序編寫時,發現執行關閉SPI代碼時,會導致Flash的寫保護錯誤標志WRPERR置位,導致其后面準備寫EEPROM的時候,就無法對EEPROM寫入了。
客戶使用兩個標志flag1和flag2,來觀察WRPERR標志的變化。代碼如圖1所示。
圖1.用戶測試代碼
在執行這個代碼時,前面flag1還等于0,執行到flag2那句,就變成flag2等于1了,同樣地取了WRPERR標志位的值。所以客戶就懷疑執行_HAL_SPI_DISABLE()會把Flash的WRPERR標志置1了。
因為在對EEPROM編程中,需要先調用位于stm32l0xx_hal_flash.c中的FLASH_WaitForLastOperation()函數,此函數中,將會對Flash所有錯誤標志進行檢查,如果出現了錯誤,它則返回HAL_ERROR,導致后續對EEPROM的編程不會被執行。
2.2 問題重現
使用NUCLEO-L053R8來驗證客戶的這個問題。在STM32Cube_FW_L0_V1.10.0ProjectsSTM32L052R8-NucleoExamplesSPISPI_FullDuplex_ComPolling例程中直接進行修改測試。
首先,把客戶的測試代碼加到例程中SPI初始化之后的位置。如圖2所示。
圖2.測試代碼1(位于SPI初始化之后)
編譯,并在線調試,發現并沒有出現客戶所描述的問題。如圖3所示。
圖3.測試代碼1結果(位于SPI初始化之后)
可以看到,WRPERR的值并沒有被置1,Flag1和Flag2的值也都是0。那么,為什么客戶說他那邊會有這個問題呢?
再回頭仔細看一下客戶的測試代碼,發現客戶的測試代碼中并沒有對SPI進行初始化,其_HAL_SPI_DISABLE()代碼是放在其他外設初始化之后的。
好,那么再來修改一下測試代碼,把客戶這三句測試代碼挪動到SPI初始化之前,如圖4所示。
圖4.測試代碼2(位于SPI初始化之前)
編譯,并在線調試,這時,會驚奇地發現客戶所描述地問題來了。其結果如圖5所示。
圖5.測試代碼2結果(位于SPI初始化之前)
可以看到,這時Flash的WRPERR標志位置1了,測試代碼中,flag2的值也跟flag1不同了。
再做一個實驗,將此處的HAL庫寫法,改成直接操作寄存器,來試一下。測試代碼變成是圖6這樣的。
圖6.測試代碼3(位于SPI初始化之前,直接操作寄存器)
編譯,在線調試,這次又驚喜地發現,問題不見了。結果如圖7所示。
圖7.測試代碼3結果(位于SPI初始化之前,直接操作寄存器)
三種操作,為什么只有第二種方式有問題呢?而且為什么錯的偏偏是Flash的寫保護錯誤標志WRPERR呢?接下來可以分析一下它們的反匯編代碼,看看到底是哪里出問題了。
2.3 反匯編分析
對于三種情況,把反匯編拉出來看最清楚其操作過程了。
先分析第一種情況——測試代碼位于SPI初始化之后。其反匯編如圖8所示。
圖8.測試代碼1的反匯編(位于SPI初始化之后)
從之前的Watch窗口,知道flag1的地址為 0x2000000c,flag2的地址為0x2000000d。
現在對三句C語言測試語句的反匯編語句進行解析,如下:
可以看到,這段匯編是一點問題都沒有的。
接下來,先分析第三種情況——也就是測試代碼放在SPI初始化之前,但是使用直接操作寄存器的方式。其反匯編如圖9所示。
圖9.測試代碼3的反匯編(位于SPI初始化之前,直接操作寄存器)
從之前的Watch窗口,知道flag1的地址為0x2000000c,flag2的地址為0x2000000d。
現在對三句C語言測試語句的反匯編語句進行解析,如下:
可以看到,這段匯編也是一點問題都沒有的。
最后,再來分析一下有問題的第二種情況,也就是測試代碼放在SPI初始化之前,但是使用_HAL_SPI_DISABLE()關閉SPI的情況。其反匯編如圖10所示。
圖10.測試代碼2的反匯編(位于SPI初始化之前)
從之前的Watch窗口,知道flag1的地址為0x20000008,flag2的地址為0x20000009。
現在對三句C語言測試語句的反匯編語句進行解析,如下:
可以看到,問題出在哪了?問題就出在“STR R3,[R 2]”這個語句上,這個語句在0x00000000這個位置寫值,而0x00000000此時映射的是Flash的地址0x08000000,也就是Stack Pointer的位置。如圖11和圖12所示。
圖11.0x00000000地址的數據
圖12.0x08000000地址的數據
首先,這個位置本來就不應該被修改。
第二,因為沒有對Flash程序存儲器進行解鎖,就往里邊寫值,就會造成寫保護錯誤,導致WRPERR標志位置位。所以,可以明白為什么WRPERR會被置位了。
可是關鍵的問題在哪兒呢?在執行“LDR R2,[R0,#4]”這條語句時,R2本來應該是SPI2_CR1的地址,但是它竟然是0x00000000!如圖13所示。
圖13.0x2000000c地址的數據
從Watch窗口來看一下SpiHandle的情況。如圖14所示。
圖14.SpiHandle(未初始化)
從圖14可以看到,其實剛才的0x2000000c地址就是SpiHandle結構體的地址,也是SpiHandle.Instance的地址,而SpiHandle.Instance的值為0。SpiHandle.Ins tance.CR1的地址為0x0,導致顯示它裝載的值是Stack pointer的值0x20000468,這里本應該是SPI2_CR1的地址和SPI2_CR1的值。
也就是因為這里的問題,才會導致了后面的WRPERR錯誤。
2.4 代碼分析
再回到代碼這邊來看一下,有問題的代碼究竟是有什么情況。客戶的代碼主要就是一句關閉SPI的語句“_HAL_SPI_DISABLE(&SpiHandle);”。
這個語句是怎么解析的?它再stm32l0xx_hal_spi.h中解析,如圖15所示。
圖15._HAL_SPI_DISABLE函數
看到這個函數時,看到了重要的字眼——“Instance”!就明白是什么問題了,因為這個SpiHandle.Instance還沒有被初始化呢!這也說明了為什么在圖14中,看到的SpiHandle.Instance的值為0x0,而SpiHandle.Instance. CR2的值為0x20000468。關鍵就在于這個SpiHandle. Instance還沒有初始化。
所以,把客戶的測試代碼放在SPI初始化代碼之后沒有問題,就是因為這個SpiHandle.Instance已經被初始化過了。所以,它不會有問題。
03 問題解決
本來客戶的代碼就沒有必要這么寫,因為SPI都沒初始化,對它進行關閉并沒有什么意義。
如果非要在這里關閉SPI的話,那就要先對SpiHandle.Instance進行初始化才行。如圖16所示。
圖16._HAL_SPI_DISABLE函數
加了“SpiHandle.Instance=SPIx;”初始化后,再跑這段代碼,就不會出現客戶所說的問題了。
現在再來看一下SpiHandle的情況。
圖17.SpiHandle(SpiHandle.Instance已初始化)
經過對SpiHandle.Instance的初始化,這里就可以看到SpiHandle.Instance的值為0x40003800了,為SPI2外設寄存器的基地址,而且可以看到SpiHandle.Instance. CR1的地址就是SPI2_CR1的地址0x40003800,值也是SPI2_CR1的值0x0了。
04 小結
在用戶代碼中,SpiHandle只是定義了SPI_HandleTypeDef結構體,其各種參數并還沒有進行實際初始化。在沒有初始化的前提下,對其進行操作時不對的,也是危險的,應該在寫代碼的時候引起重視。
使用HAL庫的時候,如果要對一個外設進行任何的操作,請務必記得它是被初始化過的。否則,出了問題可能都不一定知道。
完整內容請點擊“閱讀原文”下載原文檔。
長按掃碼關注公眾號
更多資訊,盡在STM32
▽點擊“閱讀原文”,可下載原文檔
原文標題:應用筆記 | 關閉SPI會導致WRPERR錯誤的問題分析
文章出處:【微信公眾號:STM32單片機】歡迎添加關注!文章轉載請注明出處。
-
單片機
+關注
關注
6037文章
44569瀏覽量
636211 -
STM32
+關注
關注
2270文章
10906瀏覽量
356484
原文標題:應用筆記 | 關閉SPI會導致WRPERR錯誤的問題分析
文章出處:【微信號:STM32_STM8_MCU,微信公眾號:STM32單片機】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論