一個雙肩背包 有多難? 戳一下試試看! →_→ 【主題】:詳細解析FPGA與STM32的SPI通信(二) 【作者】:LinCoding 【聲明】:轉載、引用,請注明出處 本篇文章承接——詳細解析FPGA與STM32的SPI通信(一),真是內容有點多,不得不分成兩篇文章來講。上文說道用FPGA來模仿STM32發出的SPI的協議。 1、SPI_Receiver模塊的程序: module spi_receiver ( input clk, //global clock input rst_n, //global reset input spi_cs, input spi_sck, input spi_mosi, output reg [7:0] rxd_data, output reg rxd_flag ); 第一部分是輸入輸出定義,沒什么可說的,對于接收數據的模塊,要增加接收完成標志信號,以便其他模塊讀取數據。 第二部分是一個重點: 首先,由于FPGA作為從機,接收STM32所發出的CS,SCK和MOSI信號,因此對于此類異步信號,需要利用主時鐘做同步處理,最常用的方法就是打兩拍,這在按鍵消抖的文章中有講過。 其次,由于STM32的SPI模式選擇為SPI_CPOL_Low和SPI_CPHA_1Edge這個模式,因此要在SCK時鐘的上升沿進行采樣,所以定義了mcu_read_flag這個信號,以捕獲SCK的上升沿。 最后,還要知道8位的數據什么時候讀取完畢了,根據上篇文章中示波器中的圖,可以采用CS的上升沿作為數據讀取完畢標志,因此定義了mcu_read_done信號,來監測CS的上升沿,但是由于STM32在復位階段會有CS的抖動,因此最好加上rxd_cnt==8這個條件,以使得數據準確無誤! //----------------------------------- //sample input MOSI reg [7:0] rxd_data_r; always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin rxd_cnt <= 4'd0; rxd_data_r <= 8'd0; end else if ( ! mcu_cs ) if ( mcu_read_flag ) begin rxd_data_r[3'd7-rxd_cnt] <= mcu_data; rxd_cnt <= rxd_cnt + 1'b1; end else begin rxd_data_r <= rxd_data_r; rxd_cnt <= rxd_cnt; end else begin rxd_data_r <= rxd_data_r; rxd_cnt <= 4'd0; end end第三部分就是進行數據的采樣,看圖說話,筆者在testbench中發了0xaa,0x55和0xff三個數,可以看到,都可以完美檢測到。 這里有一個問題需要注意: 能否把上述代碼的else if 部分改寫成以下代碼? else if ( mcu_read_flag && ! mcu_cs ) begin rxd_data_r[3'd7-rxd_cnt] <= mcu_data; rxd_cnt <= rxd_cnt + 1'b1; end else begin rxd_data_r <= rxd_data_r; rxd_cnt <= rxd_cnt; end 這樣看起來使得代碼很簡潔,但是卻沒有地方寫rxd_cnt <= 4'd0;使得rxd_cnt無法恢復初值。因此筆者修改如下: 理想很美好,感覺可以了,看仿真吧: 結果只能識別第一個0xaa,因為缺少一個mcu_read_flag把rxd_cnt清零!因此沒有辦法,只能寫成最開始那種形式! //----------------------------------- //output always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin rxd_data <= 8'd0; rxd_flag <= 1'b0; end else if ( mcu_read_done ) begin rxd_data <= rxd_data_r; rxd_flag <= 1'b1; end else begin rxd_data <= rxd_data; rxd_flag <= 1'b0; end end第四部分是同步輸出rxd_data和rxd_flag,這在按鍵消抖的實驗中已經用過了,見以下仿真圖: ===================================================== 2、下面是SPI_Transfer模塊的程序: module spi_transfer ( input clk, //global clock input rst_n, //global reset input spi_cs, input spi_sck, output reg spi_miso, input txd_en, input [7:0] txd_data, output reg txd_flag );第一部分是輸入輸出定義,需要說明的是對于發送類的模塊,無論是串口發送,SPI發送,都需要發送使能信號,如本例中的txd_en。 當然了,有發送使能,大家會想到什么? 是使用狀態機的IDLE來等待使能信號的到來!筆者在——《詳細解析74HC595驅動程序》這篇文章中說過!因此寫Verilog程序只要掌握了相應的套路,模式,其實一點也不難!當然,就像接收模塊的rxd_flag一樣,少不了發送完成標志信號txd_flag,以供其他模塊使用。 //----------------------------------- //synchronize the input signal reg spi_cs_r0, spi_cs_r1; reg spi_sck_r0, spi_sck_r1; always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin spi_cs_r0 <= 1'b1; spi_cs_r1 <= 1'b1; spi_sck_r0 <= 1'b0; spi_sck_r1 <= 1'b0; end else begin spi_cs_r0 <= spi_cs; spi_cs_r1 <= spi_cs_r0; spi_sck_r0 <= spi_sck; spi_sck_r1 <= spi_sck_r0; end end wire mcu_cs = spi_cs_r1; wire mcu_write_flag = ( ~spi_sck_r0 & spi_sck_r1) ? 1'b1 : 1'b0; //sck negedge capture wire mcu_write_done = ( spi_cs_r0 & ~spi_cs_r1 ) ? 1'b1 : 1'b0; //cs posedge capture wire mcu_write_start = ( ~spi_cs_r0 & spi_cs_r1 ) ? 1'b1 : 1'b0; //cs negedge capture 第二部分和spi_receiver的那部分類似,就不多做介紹了! 第三部分就是長長的發送狀態機了,首先在IDLE態等待使能信號的到來,使能信號到來之后,進入發送狀態。 有一點需要注意,筆者的發送狀態,第一位數據的發送時以CS信號的下降沿作為標志,之后的數據發送均以SCK的下降沿作為標志,這是為何?請看仿真圖: 可以看到當FPGA給STM32發送數據時,STM32會在SCK的上升沿進行讀取,如果FPGA僅僅在SCK的下降沿進行設置數據的話,SCK的第一個上升沿,由于FPGA還沒有設置數據,導致STM32采到的高電平,也就是無論發什么數據,8位數據的最高位都是1,這是不合理的,因此,第一個數據必須在CS變為低電平的時候就設置好,之后在SCK的下降沿設置,這樣可以完美發送8位數據! 如圖所示,示波器實時采集到的數據,3號通道的是MOSI,4號通道的是MISO,可見MOSI此時正在發送的是01010111,也就是87,而MISO此時發送的是01010110,也就是86,一切正常! //----------------------------------- //output always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) txd_flag <= 1'b0; else txd_flag <= mcu_write_done; end最后一部分是產生txd_flag信號,雖然很簡單,但是筆者還是要說兩句,為何不寫成以下形式呢? //----------------------------------- //output always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) txd_flag <= 1'b0; else if ( mcu_write_done ) txd_flag <= 1'b1; else txd_flag <= 1'b0; end寫成上述代碼,一點問題沒有,但是不簡潔,因此推薦第一種,事實上,在筆者的按鍵消抖中,就是第一種用法! 最后呢,一切都是那么完美,完美的時序,完美的結果!
|
|