SPI也是MCU最常見的對外通信口之一,由摩托羅拉在上世紀80年代中開發,用于嵌入式系統中器件之間的短距離數據通信,標準模式使用四條信號線。目前常見的應用器件有:LCD模組、以太網模塊、SPI串行Flash和很多傳感器等,大部分SD卡都具有SPI操作模式。
SPI的特點是主從結構、協議簡單、成本低廉、串行傳輸等,具有同步時鐘信號,傳輸速率可達幾兆至十幾兆(近來也有達到二、三十兆速率的器件),適合于中等數據量、點對點的傳輸環境。
1.1 SPI通信協議
SPI是點對點的全雙工串行通信協議,用于兩個設備間的通信?;镜倪B線方式是四條信號線,如下圖:
圖1.SPI信號線連接示意圖
四條信號線中有兩條數據線,分別用于主機向從機發送數據(MOSI: Master OutSlave In),和從機向主機發送數據(MISO: MasterIn Slave Out)。
主機器件通過SCK時鐘信號線向從機輸出時鐘,同時主機輸出SSEL信號作為從機的片選。
1.1.1 SPI數據傳輸
SPI的數據傳輸流程十分簡單,在每個SCK的時鐘周期中有如下操作:
▲主機在MOSI向從機發送一個數據位;
▲從機在MOSI上接收一個數據位;
▲從機在MISO向主機發送一個數據位;
▲主機在MISO上接收一個數據位。
一般SPI的主機和從機都是各由一個移位寄存器實現數據的發送與接收,示意圖如下:
圖2.SPI數據移位示意圖
在同一個時鐘信號的驅動下,主機和從機的移位寄存器同時向相同方向移位,經過n個時鐘周期的n次移位,主機的數據與從機的數據正好進行了交換。
通常每個字符數據的長度n=8或n=16。LPC800允許每個數據長度可以是1~16中任意數值。
1.1.2 SPI的時鐘信號
SPI的時鐘信號SCK除了頻率特性外,還需要考慮它的極性和相位,分別由CPOL和CPHA表示,具有以下意義:
▲時鐘極性:
CPOL=0:空閑時,時鐘信號為‘0’。時鐘信號的前沿為上升沿,后沿為下降沿。
CPOL=1:空閑時,時鐘信號為‘1’。時鐘信號的前沿為下降沿,后沿為上升沿。
▲時鐘相位:
CPHA=0:發送方在前一個時鐘周期的后沿改變輸出信號;接收方在時鐘周期的前沿采樣輸入信號。
CPHA=1:發送方在時鐘周期的前沿改變輸出信號;接收方在時鐘周期的后沿采樣輸入信號。
通常CPOL和CPHA的四種組合被定義為四種模式,如下表:
圖3.SPI四種操作模式波形示意圖
在模式0和模式2的SCK第一個時鐘邊沿之前,SPI主機利用內部時鐘在MOSI上輸出第一個數據位(最高位),SPI從機則使用前一個數據幀的最后一個邊沿輸出第一個數據位(最高位)。
1.1.3 SPI 設備的互連
SPI設備間的互連是主從關系,一個主設備可以連接多個從設備,主設備通過從設備片選信號區別與那個從設備進行通信。
圖4.SPI設備間互連示意圖
一個主機設備能夠連接的從機數量,由能夠輸出的片選信號(SSELn)的個數,和MOSI、SCK信號線的驅動能力限制。
1.2 LPC800的SPI特性
LPC800的SPI非常簡單,但配置豐富并且很方便使用。
▲每個數據幀的長度可以直接配置為1~16位的任一種,通過軟件操作還可以支持任意長度的數據幀。
▲支持主機模式或從機模式。
▲主機可以在發送數據時,不必理會從機返回的數據。這有助于優化軟件的操作,例如對LCD模組刷屏時(現實中有不少LCD模組是不可讀的),或寫入SPI存儲器時。
▲控制信息可以與發送的數據一起寫入寄存器,這樣可以實現各種靈活的操作要求。
▲最多有四個直接控制的從機片選信號,并且可以配置極性。
▲支持DMA操作。
▲可以靈活地控制每個數據幀中的各種時序。
1.3 SPI Flash讀寫例程 下面以幾個例程示范使用SPI對SPI Flash的 讀寫操作。
這些例程都是在LPC824-Lite上,對板上的W25Q32BV的操作,下面先抄錄這個存儲器芯片的部分命令格式,方便例程的理解。以下例程會用到表中帶陰影的命令。
表1.W25Q32BV部分命令列表
圖5.開發板上的SPI Flash線路圖
1.3.1 SPI的輪詢方式操作
以下所有的SPI例程都使用相同的初始化程序。
代碼片段1. SPI主機初始化函數
初始化函數非常簡單直接,和所有其它模塊的初始化過程一樣,都是“開啟時鐘→映射引腳→復位模塊→配置參數。
下圖顯示了所有SPI配置寄存器的配置位。
圖6.SPI配置寄存器(CFG)一覽
代碼片段1中只設置了CFG寄存器的第0、2位,其它位均為’0’。
LPC800的SPI發送數據/控制寄存器是非常有特色的。
圖7.SPI發送數據和控制寄存器一覽
上圖是完整的發送數據控制寄存器的所有位,用戶可以使用TXDATCTL寄存器,同時寫入數據和所有的控制位,也可以使用TXCTL單獨寫入控制位,或使用TXDAT單獨寫入數據位。
對于輸出至從機的SSEL片選信號,用戶可以隨時按照需要輸出對應的電平,甚至可以按字符單獨控制SSEL的電平。
如果需要使用超過16位的數據幀,則可以用EOF控制位實現。例如需要每個數據幀長度為24位,則可以輸出兩個12位的字符,在輸出第一個12位的字符時,配置EOF=0,在輸出第二個12位的字符時,配置EOF=1;也可以輸出3個8位字符,并在輸出第三個字符時,配置EOF=1實現。
由圖2可以看出,SPI在發送數據的同時,也會接收對方的數據,但很多時候在發送的過程中,程序并不需要關心接收到什么數據,例如在向LCD屏輸出數據時。在這種情況下,設置RXIGNORE=1可以讓SPI模塊不產生接收狀態位,也不會因為沒有讀出接收的數據而產生接收溢出等錯誤。
下面通過代碼,具體看看如何靈活使用這幾個寄存器。
下面定義兩個宏,用于發送與接收狀態的判斷:
再定義兩個宏,用于發送控制位的設置:
在代碼片段1的第08行把SSEL0映射到引腳P0_15,圖5中看到P0_15是SPI Flash的片選信號。因此這兩個宏中都設置了SSEL控制位為僅輸出SSEL0為有效,其它的SSEL片選均為無效(不選中)。
OUT_CTL將用于發送命令和數據時,此時不需要關系輸入的數據,所有設置RXIGNORE=1。
IN_DATA則用于需要接收數據時,向從機發送一串時鐘脈沖,此時SPI Flash不理會接收的數據,可以發送任意數值。
結合上面的初始化代碼和宏定義,下面的代碼用于讀入制造商代碼和芯片的代碼。
代碼片段2.讀取制造商ID和存儲器容量的輪詢函數
在上述代碼的第11、15行之前,并沒有WaitForSPI0txRdy語句,這是因為前面已經接收到有效數據,這表示前一次的發送已經完成,不再需要等待發送寄存器就緒,可以直接發送。
下面的代碼用于讀出設備ID,流程中與上面不同的只是發送命令串時,分別操作TXDAT和TXCTL。
代碼片段3.讀取制造商ID和設備ID的輪詢函數
最后是非常簡單的主函數,代碼片段4.SPI輪詢例程主函數
01 int main(void) 02 { 03 SPI0_init(); // SPI初始化 04 Read_JEDEC_ID(); 05 Read_Device_ID(); 06 07 while (1) { 08 } 09 }
1.3.2 SPI的中斷方式操作
下面是一種簡單的中斷方式操作的例程。
以讀取制造商ID和存儲器容量命令為例,這里我們通過TXDATCTL寄存器,每次都同時寫入發送控制字和要發送的數據,把每次要寫入TXDATCTL的內容事先保存在一個數組中,然后在中斷處理程序中,逐個把數組中的數據輸出到寄存器中。
數組定義如下:
const uint32_t CMD_JedecID[] = { | |
OUT_CTL | 0x9F, // 讀取制造商ID和存儲器容量命令 | |
IN_DATA | 0xFF, // 發送一個字符的脈沖,讀回制造商ID MF7~MF0 | |
IN_DATA | 0xFF, // 發送一個字符的脈沖,讀回存儲器ID | |
IN_DATA | 0xFF, CTL_EOT // 發送一個字符的脈沖,讀回存儲器容量 | |
}; |
再通過幾個變量,控制整個操作流程。
uint32_t Tx_Cnt; // 用于控制當前數組發送的進度 | |
uint32_t Tx_Num; // 用于記錄上述數組的長度 | |
uint32_t *Tx_Buf; // 一個指向發送數組的指針 | |
uint32_t Rx_Cnt; // 用于控制當前接收數據的進度 | |
uint8_t Rx_Buf[10]; // 用于存放接收到的數據 |
中斷處理程序和預備函數如下:
代碼片段5. 中斷模式讀取制造商ID和存儲器容量
上述代碼中的第06行,是為了防止由于無數據發送,不能清除STAT寄存器的發送就緒狀態,而導致的頻繁進入中斷。
在第20、21行分別是兩個循環語句,等待整個的發送與接收流程結束。數據送到發送寄存器后,發送并沒有結束,硬件還在逐位移位,需要等到SSEL回復高電平時,才能確認信號線上的發送已經結束。注意這兩個語句的順序不能顛倒,否則在一次都沒有進入中斷處理程序前,由于SSEL還未變為有效(低電平),而使等待SSEL的語句失去作用。
在這兩行等待語句之前,用戶程序可以做些其它事情,有效地利用CPU的時間。
上述代碼十分簡單,也很有效,但不適合處理較長的數據塊。下面使用另一種方法,用中斷方式實現對SPIFlash存儲單元的讀寫操作。
1.3.3 中斷方式訪問SPI Flash的完整例程
集中審視W25Q32BV的命令列表(見表1),可以歸納所有命令為以下四類:
1.僅發送命令(若有24位地址,也歸于命令字,下同),例如“寫使能”、“整片擦除”、“扇區擦除”等命令。
2.發送命令和發送數據緩沖區,例如“頁編程”命令。
3.發送命令和接收數據串,例如“讀制造商和設備ID”、“讀數據”等命令。
4.發送命令和就收狀態字,并等待某個指定狀態。
“寫狀態寄存器”命令既可以是上面第1類,也可以是第2類。
下面的結構體將用于分別向四個不同的中斷處理程序傳遞命令和數據緩沖區,結構體中的各個分量在不同的中斷程序中有不同的意義。
按照不同的命令類,分別使用四種中斷處理程序,分別處理不同的流程,用結構體中IRQHandler指定使用哪個流程。
下面是四個不同的中斷處理程序。
一. 僅發送命令流程
代碼片段6. 僅發送命令緩沖區處理流程
僅發送命令緩沖區處理流程中由于只有發送流程,在使用時只需要使能發送就緒中斷,所以上述處理中不需要判斷狀態寄存器而區分是發送還是接收。
這個處理流程的使用方式,以“寫使能”(Write Enable)命令介紹如下。
代碼片段7. 使用僅發送命令緩沖區處理流程實現“寫使能”命令
上述函數,在所有變量、狀態寄存器和中斷寄存器配置完畢后,第10行使能中斷后,所有產生的SPI0中斷處理,將會在第02行被引導到預先設定的SPI0_Cmd_IRQHandler()處理程序。
第12行的作用和代碼片段5的第20、21行作用相同,都是先等待發送命令緩沖區完成,然后再等待所有流程結束,即SSEL恢復到高電平。
二. 發送命令和數據緩沖區流程
代碼片段8. 發送命令和發送數據緩沖區處理流程
該處理流程的前半段很簡單,逐個發送命令緩沖區的數據,直到所有數據發送完畢。
發送數據緩沖區的流程,是和僅發送命令緩沖區處理流程一樣的,所以代碼片段8的后半段把數據緩沖區的參數,拷貝至命令緩沖區的控制變量中,然后轉入前面的僅發送命令緩沖區處理流程,完成發送數據緩沖區。
“頁編程”命令就是典型的發送命令和發送數據緩沖區處理流程,傳輸結構體的設置如下。
代碼片段9.使用發送命令和發送數據緩沖區處理流程實現“頁編程”命令
使用要編程的頁地址、數據緩沖區指針和數據長度調用這個函數,函數中逐項配置好傳輸結構體各個變量,然后通過代碼片段7的Execution()函數配置好相應寄存器,并等待傳輸流程結束。
三. 發送命令和接收數據串流程
代碼片段10. 發送命令和接收數據串處理流程
發送命令和接收數據串處理流程,比前面兩個流程增加了數據接收部分。在處理發送就緒(TXRDY)中斷部分又分為兩個階段,第一個階段是發送命令緩沖區。第二個階段是發送任意字符(此處為0xFF),用于產生從機輸出數據的時鐘信號;當只剩一個要接收的數據時,發送最后一個任意字符的同時,失能發送就緒中斷,此后將不再產生這個中斷,達到控制接收數據數目的目的。
在處理接收就緒(RXRDY)中斷中,直接讀出接收數據并保存到數據緩沖區。
“讀數據”、“讀芯片唯一ID”等命令都執行該流程,讀出的數據存放再Data_Buf指示的緩沖區。
代碼片段11. 使用發送命令和接收數據串處理流程實現“讀數據”
Page_Read()函數的參數與前面的Page_Program()基本相同:讀出的數據區地址、數據緩沖區指針和數據長度三個參數。
四. 發送命令和就收狀態字流程
該流程要求先發送命令字,然后讀出狀態字,檢測狀態字的指定位,如果所關心的狀態位不是希望的數值,則重復讀出狀態字-檢測狀態字的過程,直到所關心的狀態位達到期望的數值。
代碼片段12. 發送命令和就收狀態字處理流程
發送命令和就收狀態字處理流程與前面的發送命令和接收數據串處理流程一樣,在處理發送就緒(TXRDY)中斷部分也分為兩個階段,第一個階段是發送命令緩沖區。第二個階段是發送任意字符(此處為0xFF),用于產生從機輸出狀態字的時鐘信號;當接收到的狀態字與要求的數值匹配時,則通過再發送一個任意字符的方式,使SSEL變為無效(高電平)。
在處理接收就緒(RXRDY)中斷中,直接讀出接收數據并作為狀態字保存,留待第11行與預定的匹配數值進行檢測。
“頁編程”和幾個擦除命令之后都需要使用發送命令和就收狀態字處理流程,發送“讀狀態寄存器1”然后反復檢測“忙”標志位,直到編程或擦除命令完成,“忙”標志位變為’0’。
代碼片段13.使用發送命令和就收狀態字處理流程等待“忙”標志位
Wait_Status1()函數有兩個參數。mask一個屏蔽字,在需要檢測的狀態字的對應位為’1’,不需要檢測的對應位為’0’;value是需要檢測的狀態字的期望值,讀出的狀態字和mask屏蔽字進行“與”運算后的結果,需要和value相同。
例如等待“忙”標志位變為’0’的調用方式是:
五. 測試SPI Flash的主函數
介紹完所有的中斷處理流程,最后這個主函數就非常簡單了:
代碼片段14.測試SPI Flash主函數
運行這個程序后,讀者可以檢查Buffer緩沖區的前后半段,數據應該相同,表示寫入和讀出成功。
1.4 主機產生信號的時序控制 首先澄清一個概念——字符與數據幀。字符是指SPI發送或接收時,TXDAT、TXDATCTL或RXDAT寄存器中一次所容納的數據位。一個數據幀可以是直接對應一個字符,也可以是多個連續的字符組合。LPC800 的SPI模塊,可以在數據幀之間引入幀延遲,但不能在字符之間插入延遲,除非數據幀與字符長度相同。
圖8.字符與數據幀之間的關系
上圖中的上面一行,顯示了當一個數據幀的長度小于16位時,一個字符就是一個數據幀。圖的下面一行顯示了,當一個數據幀的長度大于16位時,一個數據幀中包含了若干個字符。
發送每個數據幀的最后一個字符時,設置發送控制寄存器的EOF位(見圖7),表示幀結束。使用EOF控制位,即可支持任意長度的數據幀。
SPI模塊中有一個延遲寄存器,可以為用戶提供多種時序延遲控制。延遲寄存器的各個控制位如下圖。
圖9.SPI延遲寄存器(DLY)控制位
1.4.1 片選信號與數據幀信號之間的間隔控制
PRE_DELAY和POST_DELAY域各有4位,可以分別配置為插入0~15個時鐘周期的延遲。
PRE_DELAY表示在SSEL變為有效后至開始傳輸數據之間,需要插入的延遲時間。
POST_DELAY表示在數據傳輸結束至SSEL變為無效之間,需要插入的延遲時間。
下面兩個圖顯示了PRE_DELAY和POST_DELAY的作用位置。
圖10.PRE_DELAY和POST_DELAY的作用位置示意圖(CPHA=0)
圖11.PRE_DELAY和POST_DELAY的作用位置示意圖(CPHA=1)
圖中顯示在SSEL變低之后插入了2個時鐘周期的延遲(PRE_DELAY=2);在最后一位(LSB)發送完畢后插入了1個時鐘周期的延遲(POST_DELAY=1)。
1.4.2 數據幀之間的間隔控制
FRAME_DELAY有4位,當發送的字符控制位EOT=1時,在字符的最后一位后插入0~15個時鐘周期的延遲。這個延遲有利于某些器件在接收到一個數據幀后,有足夠的時間進入下一幀的接收。
FRAME_DELAY的作用位置如下圖
圖12.FRAME_DELAY作用位置示意圖
1.4.3 兩次傳輸之間的間隔控制
TRANSFER _DELAY有4位,當發送的字符控制位EOF=1時,在SSEL變為無效(高電平)之后,需要至少有1~16個時鐘周期的延遲才能開始下一次傳輸,即SSEL至少要保持一個時鐘周期的高電平,才能再次變為低電平。這個延遲有利于某些器件在接收到一次傳輸的最后一個數據幀后,有足夠的時間進入下一次傳輸的接收。
TRANSFER _DELAY只是給出了SSEL保持無效(高電平)的長度低限,這個時間有可能因為軟件的原因變得更長,例如軟件需要時間準備下一個傳輸的數據。
TRANSFER _DELAY的作用位置如下圖
圖13.TRANSFER_DELAY的作用位置示意圖
審核編輯:湯梓紅
-
mcu
+關注
關注
146文章
16987瀏覽量
350302 -
通信協議
+關注
關注
28文章
857瀏覽量
40256 -
接口
+關注
關注
33文章
8497瀏覽量
150834 -
SPI
+關注
關注
17文章
1700瀏覽量
91319 -
串行外設接口
+關注
關注
0文章
14瀏覽量
3995
原文標題:LPC800前生今世:十一章 SPI串行外設
文章出處:【微信號:NXP_SMART_HARDWARE,微信公眾號:恩智浦MCU加油站】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論