久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    【精品博文】詳細解析FPGA與STM32的SPI通信(二)

     ChinaAET 2020-10-31

    一個雙肩背包

    有多難?

    戳一下試試看!

    →_→

    長摁識別

    【主題】:詳細解析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 );

      第一部分是輸入輸出定義,沒什么可說的,對于接收數據的模塊,要增加接收完成標志信號,以便其他模塊讀取數據

    //----------------------------------- //synchronize the input signal reg spi_cs_r0, spi_cs_r1; reg spi_sck_r0, spi_sck_r1; reg spi_mosi_r0,spi_mosi_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;                         spi_mosi_r0 <= 1'b0; spi_mosi_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;                         spi_mosi_r0 <= spi_mosi;         spi_mosi_r1 <= spi_mosi_r0; end end reg [3:0] rxd_cnt /*synthesis noprune*/; wire mcu_cs = spi_cs_r1; wire mcu_data= spi_mosi_r1; wire mcu_read_flag = ( spi_sck_r0 & ~spi_sck_r1) ? 1'b1 : 1'b0; //sck posedge capture wire mcu_read_done = ( spi_cs_r0 & ~spi_cs_r1 & (rxd_cnt == 4'd8) ) ? 1'b1 : 1'b0;

           第二部分是一個重點:

     首先,由于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無法恢復初值。因此筆者修改如下:

    else if ( mcu_read_flag && ! mcu_cs ) if ( rxd_cnt < 4'd8 )     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 <= 4'd0;     end     else begin rxd_data_r <= rxd_data_r; rxd_cnt <= rxd_cnt; end

     理想很美好,感覺可以了,看仿真吧:

          結果只能識別第一個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的那部分類似,就不多做介紹了!

    //----------------------------------- //FSM: encode localparam T_IDLE = 2'd0; localparam T_START = 2'd1; localparam T_SEND = 2'd2; localparam SPI_MISO_DEFAULT = 1'b1; //----------------------------------- //transfer FSM reg [1:0] txd_state; reg [3:0] txd_cnt /*synthesis noprune*/; always @ ( posedge clk or negedge rst_n ) begin if ( ! rst_n ) begin txd_cnt <= 4'd0; spi_miso <= SPI_MISO_DEFAULT; txd_state <= T_IDLE; end else case ( txd_state ) T_IDLE: begin txd_cnt <= 4'd0; spi_miso <= SPI_MISO_DEFAULT; if ( txd_en ) txd_state <= T_START; else txd_state <= T_IDLE; end T_START: begin if ( mcu_write_start ) begin     spi_miso <= txd_data[3'd7-txd_cnt[2:0]];     txd_cnt  <= txd_cnt + 1'b1;     txd_state<= T_SEND; end else begin     spi_miso <= spi_miso;     txd_cnt  <= txd_cnt;     txd_state<= T_START; end end T_SEND: begin if ( mcu_write_done ) txd_state <= T_IDLE; else txd_state <= T_SEND; if ( ! mcu_cs ) if ( mcu_write_flag )     begin         if ( txd_cnt < 4'd8 ) begin     spi_miso  <= txd_data[3'd7-txd_cnt[2:0]];     txd_cnt   <= txd_cnt + 1'b1; end         else begin             spi_miso  <= 1'b1;             txd_cnt   <= txd_cnt; end     end else begin spi_miso <= spi_miso; txd_cnt <= txd_cnt; end else     begin spi_miso <= SPI_MISO_DEFAULT; txd_cnt <= 4'd0;     end end default: begin txd_cnt <= 4'd0; spi_miso <= SPI_MISO_DEFAULT; txd_state <= T_IDLE; end endcase end

     第三部分就是長長的發送狀態機了,首先在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

     寫成上述代碼,一點問題沒有,但是不簡潔,因此推薦第一種,事實上,在筆者的按鍵消抖中,就是第一種用法!

    最后呢,一切都是那么完美,完美的時序,完美的結果!

      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 五月天天天综合精品无码| 欧美xxxx做受欧美.88| 久久精品国产亚洲AV无码偷窥| 扒开双腿猛进入喷水高潮叫声| 日本高清在线观看WWW色| 久久人搡人人玩人妻精品首页| 久久精品免视看国产成人| 成年福利片在线观看| 久久夜色精品国产嚕嚕亚洲AV| 天堂V亚洲国产V第一次| 国内精品一区二区三区 | 精品亚洲欧美无人区乱码| 99久久久国产精品免费无卡顿| 97夜夜澡人人爽人人模人人喊 | 免费无码一区无码东京热| 国产成人手机高清在线观看网站| 国产精品亚洲二区亚瑟| 人妻少妇久久久久久97人妻| 亚洲精品一区二区美女| 国产精品国产精品国产专区不卡| 久久99亚洲含羞草影院| 韩国免费a级毛片久久| 国产曰批视频免费观看完| 国产资源精品中文字幕| 色婷婷综合久久久久中文字幕 | 精品国偷自产在线视频99| 天堂mv在线mv免费mv香蕉| 成人无码视频97免费| 午夜免费无码福利视频麻豆| 中文字幕人成乱码中文乱码| 少妇高清一区二区免费看| 国产精品免费视频不卡| 97人妻人人揉人人躁人人| 天堂久久久久VA久久久久| 亚洲精品韩国一区二区| 草草影院精品一区二区三区 | 国产精品普通话国语对白露脸| 国产成人亚洲精品| 午夜A理论片在线播放| 婷婷丁香五月六月综合激情啪| 2021国产成人精品久久|