【 聲明:版權所有,歡迎轉載,請勿用于商業用途。 聯系信箱:feixiaoxing @163.com】
? ? ? ? 雖然串口用的地方比較多,實現起來也比較簡單。但是串口本身速度比較慢,不利于高速數據通信。而且單個串口沒有辦法和很多芯片設備通信。所以,人們在串口的基礎之上添加clk、cs信號,這樣就形成了基本的spi總線協議。它也是mcu/soc和fpga通信的重要方式之一。
1、spi和串口的區別
? ? ? ? 相比較串口,spi多了cs和clk。正因為有了cs,這樣fpga就可以通過它來區分不同的spi接口芯片。此時clk、rx、tx就可以完全復用。此外有了clk之后,數據的收發都可以借助于clk的邊沿來進行處理,處理的效率高了很多,甚至可以達到20~50M,快一點的話100M也是可以的,這不是串口可以比擬的。
2、spi和iic的區別
? ? ? ? 現在spi和iic比較,iic的收發是同一根pin,iic也有clk信號,但是沒有cs信號。這導致iic上的芯片都會接收總線上的數據,但是如果發現設備數據不是自己的,會直接扔掉。
3、spi和iic應用范圍非常廣
? ? ? ? 不僅spi norflash、spi nandflash,還是spi網絡芯片、spi adc、spi dac,spi和iic的應用范圍都是非常廣的。spi一般用于中高速芯片,iic和uart則用于低速芯片。所以說,如果掌握好了spi、iic、uart之后,基本上mcu能做的事情,都可以用fpga來完成。像rgb、mcu、vga這樣mcu做不來的視頻接口,也可以用fpga來完成。甚至于很多sdio接口的外設,都是可以轉換成spi訪問,tf卡就是一個典型的案例。
? ? ? ? spi本身只是一個總線,所以我們實際對芯片操作的時候,還需要了解操作的方式,比如怎么發命令,怎么發命令和地址,怎么讀數據。就拿spi nanflash來說,要做好這個工作需要分成這三步,
實現基本的spi協議;
實現訪問spi nandflash需要的各個命令;
根據需求訪問spi nandflash,做好讀、寫和狀態的校驗。
4、spi master和spi slave
? ? ? ? 一般把主動發起命令的設備稱之為master,被動接收命令的設備稱之為slave。當然fpga和spi nandflash通信時,此時fpga就是master,nandflash就是slave。如果此時mcu/soc和fpga通信,這種情況下mcu/soc就是master,fpga就是slave。
? ? ? ? 如果有同學不知道如何用spi寫slave,那么可以找一個spi nandflash的芯片手冊,把自己當成一個spi nand,去適配master就可以了。
5、spi的四種模式
? ? ? ? 根據時鐘極性cpol和時鐘相位cpha,就可以把spi分成四種工作模式。其中cpol,就是空閑狀態的時候,spi處于高電平還是低電平。而cpha,則告訴我們采樣的時候,應該是第一個邊沿采樣,還是第二個邊沿采樣。實際應用的時候,我們記住0-0就可以,即空閑狀態是0,第一個邊沿觸發的時候采樣。
6、發送和采樣數據
? ? ? ? spi的發送和采樣都是基于clock進行的。如果spi時鐘不是很快,那么可以通過計數分頻的辦法去解決。首先我們談一下發送。假設spi的工作模式是0-0,即空閑為0,上升沿采樣。這種情況下,只需要一個周期發送一個bit數據即可,每半個周期時鐘反轉一下,也就是上升沿的時候提示對方接收數據,因此數據肯定是提前準備好的。
? ? ? ? 采樣的時候一般是反過來的。以spi nandflash為例。fpga發送完命令之后,一般就可以開始準備采樣數據了。采樣的時候,其實和發送也是一樣的,在上升沿的時候開始采樣,因為對方數據發送一般是下降沿開始發送。所以在一個周期內spi clock做兩次翻轉就可以了。
7、spi協議的實現
? ? ? ? 所以這里spi的實現主要集中在底層領域,涉及到spi clock、cs、mosi、miso,可以好好看一下,
module spi_top(input clk,input rst,input rw,input rw_valid,input[7:0] w_data,output[7:0] r_data,output status,// about signaloutput cs,output reg tx,output reg spi_clk,input rx);reg[3:0] state;
reg[3:0] next_state;
reg[15:0] counter;
reg[3:0] num;reg rw_reg;
reg[7:0] w_data_reg;
reg[7:0] r_data_reg;localparam TIMER_INTERVAL = 16'd20;localparam IDLE = 4'h0;
localparam SPI_CLK = 4'h1;
localparam SPI_CHK = 4'h2;
localparam LAST_CLK = 4'h3;
localparam SPI_END = 4'h4;// about state machinealways@(posedge clk or negedge rst)if(!rst)state <= IDLE;else state <= next_state;always@(*)case(state)IDLE: beginif(rw_valid)next_state = SPI_CLK;elsenext_state = IDLE;endSPI_CLK: beginif(counter == TIMER_INTERVAL)next_state = SPI_CHK;elsenext_state = SPI_CLK;endSPI_CHK: beginif(num == 4'hf)next_state = LAST_CLK;elsenext_state = SPI_CLK;endLAST_CLK: beginif(counter == TIMER_INTERVAL)next_state = SPI_END;elsenext_state = LAST_CLK;endSPI_END:next_state = IDLE;default:next_state = IDLE;endcase// about cs, sometime can be used for multiple devicesassign cs = (state != IDLE) ? 0 : 1;// save rw dataalways@(posedge clk or negedge rst)if(!rst) beginrw_reg <= 0;w_data_reg <= 8'h0;endelse if(state == IDLE && rw_valid) beginrw_reg <= rw;w_data_reg <= w_data;end// about frequency divisonalways@(posedge clk or negedge rst)if(!rst)counter <= 16'h0;else if(state == SPI_CLK || state == LAST_CLK)counter <= counter + 1;elsecounter <= 16'h0;// about clk numberalways@(posedge clk or negedge rst)if(!rst)num <= 4'h0;else if(state == SPI_CHK)num <= num + 1;else if(state == IDLE)num <= 4'h0;//
// about spi clk
// this is very import, useful for low speed communication
//always@(posedge clk or negedge rst)if(!rst)spi_clk <= 0;else if(state == SPI_CLK && counter == TIMER_INTERVAL)spi_clk <= ~spi_clk;else if(state == IDLE)spi_clk <= 0;// about tx signalalways@(posedge clk or negedge rst)if(!rst)tx <= 0;else if(state == SPI_CLK && rw_reg)tx <= w_data_reg[0];else if(state == SPI_CHK && num <= 13 && rw_reg) // fix bug about num 2025.9.9tx <= w_data_reg[(num >> 1) +1];else if(state == IDLE)tx <= 0;// about rx signalalways@(posedge clk or negedge rst)if(!rst)r_data_reg <= 8'h0;else if(state == SPI_CHK && !num[0] && !rw_reg)r_data_reg[num >> 1] <= rx;else if(state == IDLE)r_data_reg <= 8'h0;assign r_data = r_data_reg;
assign status = (state == SPI_END);endmodule
? ? ? ? 實際跑起來,添加上testbench之后,仿真截圖是這樣的,