基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢問題深入分析與優化( 三 )

  • 每次發送1字節,加適當延時,保證發送相鄰兩字節之間的間隔盡量短 。
  • 目前我采用的就是第二種方法,第一種方法就留給大家驗證并開發吧(其實就是我懶) 。來看一下我的現實代碼:
    void hi_spi_delay(void){volatile unsigned int tmp = 0;while (tmp++ < 30);}void spi_write_byte(unsigned char dat){unsigned short spi_data = https://www.huyubaike.com/biancheng/0;spi_data = dat;ssp_writew(SSP_DR, spi_data);hi_spi_delay();}【基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢問題深入分析與優化】這個延遲函數一定要根據編譯器、CPU主頻、SPI時鐘頻率等實際測量后進行調整 。經過我的反復調整和測量 , 最終把循環計數設置為了30,來看一下示波器抓到的波形
    基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢問題深入分析與優化

    文章插圖
    間隔 65ns,也就是每字節耗時 225us,大約相當于 36MHz 的SPI時鐘頻率 。
    改成其他值行不行呢?這是我的測量結果
    • 30 可以保證在50MHz時鐘下 , 每兩字節之間間隔 65ns,
    • 32 可以保證在50MHz時鐘下,每兩字節之間間隔 80ns , 
    • 35 可以保證在50MHz時鐘下,每兩字節之間間隔 100ns,但是,重點來了!循環計數小于29或不加延時的間隔是 60ns,似乎達到了某種限制 , 具體原因沒找到,我擔心有丟數據的風險,所以將循環計數設置為了30 , 這個值也正好是可以明顯觀察到延時函數有效的最小值,SPI 速率雖然沒有真正達到理論值,但是對于目前的使用場景來說已經足夠了 。
    前面說了,延時函數一定要根據實際情況進行修改,確保每字節之間有一定間隔 。假設你換了海思的另外一款芯片,或者使用了其他基于 ssp-pl022 控制器的芯片,也遇到了類似的 SPI 速率低的問題 , 但手頭沒有示波器進行測量怎么辦?假設你所使用的芯片提供了精確到10ns的延時函數,直接拿來用就行 。沒有這樣的函數沒關系,可以做一個簡單的估算,估算結果與實際肯定有偏差,但不管怎么說這個數值也算比較靠譜的 。我們來一步一步分析這個延時函數如何實現 。
    首先是 volatile 關鍵字,開發調試期間為了方便分析問題,編譯優化選項往往設置為 -O0,不論加不加這個關鍵字都沒問題,但正式程序的編譯優化選項一般都會設置為-O2,-Os,-O3 , 這種情況下編譯器會直接把這個函數優化掉,所以必須加 volatile 關鍵字 。
    確定好你所使用的編譯器和編譯優化參數,有的芯片廠商會提供多個版本的編譯器,或者后來編譯器更新了,編譯器不同可能會導致代碼行為不同,編譯優化參數不同也會導致代碼行為不同,假設最終發布代碼使用-Os,那么測試期間也使用-Os,總之確定好這兩點,在之后的開發過程中不要更改 。
    確定好你的延時函數的循環怎么寫,包括但不限于
    while (tmp++ < 30);tmp = 30while(1) {if(tmp-- == 0){break;}};tmp = 30while (tmp--);for (tmp=0; tmp<30; tmp++);無論怎么寫都能達到延時的作用,有的編譯器可能非常聰明 , 發現循環中什么都沒做,最終這4種寫法都被優化成了相同的匯編代碼 , 但有的編譯器可能不會,總之你不能保證編譯器會把他們優化成相同的匯編代碼,所以確定了延時函數的寫法以后在之后的開發過程中不要更改 。
    下一步將循環計數設置為比較大的數 , 例如十萬,一百萬,執行這個函數并計算耗時,不論是用秒表,還是用 gettimeofday,或者是 time 命令,總之最終目的是算出1個循環耗時多少 。假設我測量出循環百萬次耗時5.3ms,那么循環一次耗時就是5.3ns 。向上取整按一次循環耗時6ns計算,為什么這樣做?自己想 。
    假設 SPI 的時鐘速率是50MHz,發送數據位寬是 8bit,則發送1次耗時 1s / 50MHz * 8 = 160ns,延時循環的次數為 160 / 6 = 26.6,向上取整為27次 ??紤]到函數調用的耗時、讀寫寄存器的耗時等,實際兩次發送之間的間隔肯定比它略長 , 但無論怎么說,27 就是我們估算出來的延時循環計數 。
    當然還有更靠譜一點的估算方法 。目前我的延時函數及對應的匯編代碼如下:
    void hi_spi_delay(void){volatile unsigned int tmp = 0;while (tmp++ < 30);}Dump of assembler code for function hi_spi_delay:0x00010454 <+0>:mov r3, #00x00010458 <+4>:sub sp, sp, #80x0001045c <+8>:str r3, [sp, #4]0x00010460 <+12>: ldr r3, [sp, #4]0x00010464 <+16>: cmp r3, #290x00010468 <+20>: add r3, r3, #10x0001046c <+24>: str r3, [sp, #4]0x00010470 <+28>: bls 0x10460 <hi_spi_delay+12>0x00010474 <+32>: add sp, sp, #80x00010478 <+36>: bxlrEnd of assembler dump.

    推薦閱讀