fpga的spi的编程_UASP协议
寫在前面
SPI協議系列文章:
FPGA實現的SPI協議(一)—-SPI驅動
FPGA實現的SPI協議(二)—-基于SPI接口的FLASH芯片M25P16的使用
1、什么是SPI協議
SPI(Serial Peripheral Interface,串行外圍設備接口)通訊協議,是 Motorola 公司提出的一種同步串行接口技術,是一種高速、全雙工、同步通信總線,在芯片中只占用四根管腳用來控制及數據傳輸,廣泛用于 EEPROM、Flash、RTC(實時時鐘)、ADC(數模轉換 器)、DSP(數字信號處理器)以及數字信號解碼器上,是常用的、重要的低速通訊協議之一。
SPI 通訊協議的優點是支持全雙工通信,通訊方式較為簡單,且相對數據傳輸速率較快;缺點是沒有指定的流控制,沒有應答機制,在數據可靠性上有一定缺陷。
2、SPI協議詳述
2.1、SPI協議物理層
SPI 通訊設備的通訊模式是主從通訊模式,通訊雙方有主從之分,根據從機設備的數量,SPI 通訊設備之間的連接方式可分為一主一從和一主多從。
SPI總線傳輸只需要4根線就能完成,這四根線的作用分別如下:
- SCK (Serial Clock):時鐘信號線,用于同步通訊數據。由通訊主機產生,決定了通訊的速率,不同的設備支持的最高時鐘頻率不同
- MOSI (Master Output, Slave Input):主設備輸出/從設備輸入引腳。主機的數據從這條信號線輸出,從機由這條信號線讀入主機發送的數據,數據方向由主機到從機
- MISO (Master Input,Slave Output):主設備輸入/從設備輸出引腳。主機從這條信號線讀入數據,從機的數據由這條信號線輸出到主機,數據方向由從機到主機
- CS(Chip Select):片選信號線。當有多個 SPI 從 設備與 SPI 主機相連時,設備的其它信號線 SCK、MOSI 及 MISO 同時并聯到相同的 SPI 總線上,即無論有多少個從設備,都共同使用這 3 條總線;而每個從設備都有獨立的片選信號線,即有多少個從設備,就有多少條片選信號線。相當于由SPI構成的通信系統中,通過CS片選信號來決定通信的從機設備是哪一臺。通信期間低電平有效,表示對應從機被選中
2.2、SPI 協議層
SPI總線傳輸一共有4種模式,這4種模式分別由時鐘極性(CPOL,Clock Polarity)和時鐘相位(CPHA,Clock Phase)來定義,其中CPOL參數規定了SCK時鐘信號空閑狀態的電平,CPHA規定了數據是在SCK時鐘的上升沿被采樣還是下降沿被采樣。
SPI總線的極性–時鐘極性
時鐘極性決定SPI總線空閑時的時鐘信號是高電平還是低電平。CPOL = 1:表示空閑時是高電平;CPOL = 0:表示空閑時是低電平。
SPI總線的相位–時鐘相位
時鐘相位決定SPI總線從哪個跳變沿開始采樣數據。CPHA = 0:在時鐘信號SCK的第1個跳變沿采樣;CPHA = 1:在時鐘信號SCK的第2個跳變沿采樣。
這四種模式的時序圖如下圖所示:
- 模式0:CPOL= 0,CPHA=0。SCK串行時鐘線空閑是為低電平,數據在SCK時鐘的上升沿被采樣,數據在SCK時鐘的下降沿切換
- 模式1:CPOL= 0,CPHA=1。SCK串行時鐘線空閑是為低電平,數據在SCK時鐘的下降沿被采樣,數據在SCK時鐘的上升沿切換
- 模式2:CPOL= 1,CPHA=0。SCK串行時鐘線空閑是為高電平,數據在SCK時鐘的下降沿被采樣,數據在SCK時鐘的上升沿切換
- 模式3:CPOL= 1,CPHA=1。SCK串行時鐘線空閑是為高電平,數據在SCK時鐘的上升沿被采樣,數據在SCK時鐘的下降沿切換
經常用到的是模式0和模式3(畢竟在下降沿采集數據的還是少)。下圖描述了4種模式數據線MOSI和MISO的數據切換(Toggling)位置和數據采樣位置的關系。
2.3、SPI協議通信過程
下面以模式 0 為例,講解一下 SPI 基本的通訊過程:
SCK、MOSI、CS_N 信號均由主機控制產生, SCK 是時鐘信號,用以同步數據,MOSI 是主機輸出從機輸入信號,主機通過此信號線傳輸數據給從機,CS_N 為片選信號,用以選定從機設備,低電平有效;而 MISO 的信號由 從機產生,主機通過該信號線讀取從機的數據。MOSI 與 MISO 的信號只在 CS_N 為低電平的時候才有效,在 SCK 的每個時鐘周期 MOSI 和 MISO 傳輸一位數據。
在圖中的標號1處,CS_N 信號線由高變低,是 SPI 通訊的起始信號。CS_N 是每 個從機各自獨占的信號線,當從機在自己的 CS_N 線檢測到起始信號后,就知道自己被主 機選中了,開始準備與主機通訊。在圖中的標號6處,CS_N 信號由低變高,是 SPI 通訊的停止信號,表示本次通訊結束,從機的選中狀態被取消。
SPI 使用 MOSI 及 MISO 信號線來傳輸數據,使用 SCK 信號線進行數據同步。MOSI 及 MISO 數據線在 SCK 的每個時鐘周期傳輸一位數據,且數據輸入輸出是同時進行的。數據傳輸時,MSB 先行或 LSB 先行并沒有作硬性規定,但要保證兩個 SPI 通訊設備之間使用同樣的協定,一般都會采用MSB 先行模式。 MOSI 及 MISO 的數據在 SCK 的下降沿期間變化輸出, 在 SCK 的上升沿時被采樣。即在 SCK 的上升沿時刻,MOSI 及 MISO 的數據有效,高電平時表示數據“1”,為低電平時表示數據“0”。在其它時刻,數據無效,MOSI 及 MISO 為下一次表示數據做準備。
SPI 每次數據傳輸可以 8 位或 16 位為單位,每次傳輸的單位數不受限制。
2.4、SPI協議的特性
- SPI協議是一主多從的架構,通過片選信號CS來區分不同的從機(尋址方式)
- SPI協議是一種同步(Synchronous)傳輸協議,通信雙方通過主機生成的時鐘信號SCK來作為數據交換的基準信號
- SPI協議是一種全雙工的串行通信協議,通信過程中主從雙方均可進行數據交換
- SPI協議具有4中通信模式,依據雙方約定好的模式進行通信
2.5、SPI協議的優勢、劣勢
優勢:
- 全雙工串行通信
- 簡單的硬件結構
- 高速數據傳輸速率(相比UART、IIC)
- 靈活的數據傳輸方式,不限于8位,可以是任意大小的字
劣勢:
- 僅支持一個主設備
- 引腳略多(相比UART、IIC)
- 沒有硬件從機應答信號(主機可能在不知情的情況下無處發送)
3、驅動代碼的設計實現
接下來實現的SPI驅動代碼特性如下:MSB 先行;僅限模式0;每次傳輸8位(1個BYTE)。
3.1、接口定義與整體設計
SPI驅動的整體框圖、輸入輸出信號如下所示:
其中信號描述如下:
該模塊的使用方法如下:
- 拉高SPI傳輸開始信號spi_start一個周期,同時發送要傳輸的數據給data_send,等待數據發送完成后,該模塊會將發送完成標志信號send_done拉高一個周期,標志一個BYTE的數據通過SPI總線發送給了從機
- 同樣的,當接收完成標志信號rec_done被該模塊拉高后,則意味著,主機成功接收了一個BTYE從機發送過來的數據
- 當主機希望結束這次傳輸時,可將SPI結束信號spi_end拉高一個周期,則該模塊會在發送最后一個模塊后結束SPI傳輸,這也意味著,如果沒有結束到SPI結束信號,則SPI傳輸會一直進行,以便實現多個BYTE的SPI傳輸
3.2、Verilog代碼
Verilog代碼并不復雜,結合下圖的SPI通信過程,可以發現以下要點:
- SCK很適合使用系統時鐘的4分頻時鐘,因為在一個SCK內需要對其進行4次操作
- 分別使用生成的SCK的上升沿、下降沿對其移位發送數據、接收數據即可
- 此外從下圖可知,SPI的驅動非常適合使用狀態機編寫,有興趣可以自己嘗試一下
`timescale 1ns/1ns //時間單位/精度
// 模式0
module spi_drive
(
// 系統接口
input sys_clk , // 全局時鐘50MHz
input sys_rst_n , // 復位信號,低電平有效
// 用戶接口
input spi_start , // 發送傳輸開始信號,一個高電平
input spi_end , // 發送傳輸結束信號,一個高電平
input [7:0] data_send , // 要發送的數據
output reg [7:0] data_rec , // 接收到的數據
output reg send_done , // 主機發送一個字節完畢標志位
output reg rec_done , // 主機接收一個字節完畢標志位
// SPI物理接口
input spi_miso , // SPI串行輸入,用來接收從機的數據
output reg spi_sclk , // SPI時鐘
output reg spi_cs , // SPI片選信號,低電平有效
output reg spi_mosi // SPI輸出,用來給從機發送數據
);
reg [1:0] cnt; //4分頻計數器
reg [3:0] bit_cnt_send; //發送計數器
reg [3:0] bit_cnt_rec; //接收計數器
reg spi_end_req; //結束請求
//4分頻計數器
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt <= 2'd0;
else if(!spi_cs)begin
if(cnt == 2'd3)
cnt <= 2'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 2'd0;
end
// 生成spi_sclk時鐘
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_sclk <= 1'b0; //模式0默認為低電平
else if(!spi_cs)begin //在SPI傳輸過程中
if(cnt == 2'd0 )
spi_sclk <= 1'b0;
else if (cnt == 2'd2)
spi_sclk <= 1'b1;
else
spi_sclk <= spi_sclk;
end
else
spi_sclk <= 1'b0; //模式0默認為低電平
end
// 生成片選信號spi_cs
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_cs <= 1'b1; //默認為高電平
else if(spi_start) //開始SPI準備傳輸,拉低片選信號
spi_cs <= 1'b0;
//收到了SPI結束信號,且結束了最近的一個BYTE
else if(spi_end_req && (cnt == 2'd1 && bit_cnt_rec == 4'd0))
spi_cs <= 1'b1; //拉高片選信號,結束SPI傳輸
end
// 生成結束請求信號(捕捉spi_end信號)
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_end_req <= 1'b0; //默認不使能
else if(spi_cs)
spi_end_req <= 1'b0; //結束SPI傳輸后拉低請求
else if(spi_end)
spi_end_req <= 1'b1; //接收到SPI結束信號后就把結束請求拉高
end
// 發送數據過程--------------------------------------------------------------------
// 發送數據
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
spi_mosi <= 1'b0; //模式0空閑
bit_cnt_send <= 4'd0;
end
else if(cnt == 2'd0 && !spi_cs)begin //模式0的上升沿
spi_mosi <= data_send[7-bit_cnt_send]; //發送數據移位
if(bit_cnt_send == 4'd7) //發送完8bit
bit_cnt_send <= 4'd0;
else
bit_cnt_send <= bit_cnt_send + 1'b1;
end
else if(spi_cs)begin //非傳輸時間段
spi_mosi <= 1'b0; //模式0空閑
bit_cnt_send <= 4'd0;
end
else begin
spi_mosi <= spi_mosi;
bit_cnt_send <= bit_cnt_send;
end
end
// 發送數據標志
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
send_done <= 1'b0;
else if(cnt == 2'd0 && bit_cnt_send == 4'd7) //發送完了8bit數據
send_done <= 1'b1; //拉高一個周期,表示發送完成
else
send_done <= 1'b0;
end
// 接收數據過程--------------------------------------------------------------------
// 接收數據spi_miso
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
data_rec <= 8'd0;
bit_cnt_rec <= 4'd0;
end
else if(cnt == 2'd2 && !spi_cs)begin //模式0的上升沿
data_rec[7-bit_cnt_rec] <= spi_miso; //移位接收
if(bit_cnt_rec == 4'd7) //接收完了8bit
bit_cnt_rec <= 4'd0;
else
bit_cnt_rec <= bit_cnt_rec + 1'b1;
end
else if(spi_cs)begin
bit_cnt_rec <= 4'd0;
end
else begin
data_rec <= data_rec;
bit_cnt_rec <= bit_cnt_rec;
end
end
// 接收數據標志
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rec_done <= 1'b0;
else if(cnt == 2'd2 && bit_cnt_rec == 4'd7) //接收完了8bit
rec_done <= 1'b1; //拉高一個周期,表示接收完成
else
rec_done <= 1'b0;
end
endmodule
4、Testbench及仿真結果
4.1、單個BYTE的仿真
使用該SPI驅動,向從機發送單個BYTE數據8‘b01010101,觀察其仿真時序是否正確:
//------------------------------------------------
//--SPI驅動仿真(模式0,1個BYTE)
//------------------------------------------------
`timescale 1ns/1ns //時間單位/精度
//------------<模塊及端口聲明>----------------------------------------
module tb_spi_drive();
//系統接口
reg sys_clk ; // 全局時鐘50MHz
reg sys_rst_n ; // 復位信號,低電平有效
//用戶接口
reg spi_start ; // 發送傳輸開始信號,一個高電平
reg spi_end ; // 發送傳輸結束信號,一個高電平
reg [7:0] data_send ; // 要發送的數據
wire [7:0] data_rec ; // 接收到的數據
wire send_done ; // 主機發送一個字節完畢標志位
wire rec_done ; // 主機接收一個字節完畢標志位
//SPI物理接口
reg spi_miso ; // SPI串行輸入,用來接收從機的數據
wire spi_sclk ; // SPI時鐘
wire spi_cs ; // SPI片選信號
wire spi_mosi ; // SPI輸出,用來給從機發送數據
//仿真用
reg [3:0] cnt_send ; //發送數據計數器,0-15
//------------<例化SPI驅動模塊(模式0)>----------------------------------------
spi_drive spi_drive_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.spi_start (spi_start ),
.spi_end (spi_end ),
.data_send (data_send ),
.data_rec (data_rec ),
.send_done (send_done ),
.rec_done (rec_done ),
.spi_miso (spi_miso ),
.spi_sclk (spi_sclk ),
.spi_cs (spi_cs ),
.spi_mosi (spi_mosi )
);
//------------<設置初始測試條件>----------------------------------------
initial begin
sys_clk = 1'b0; //初始時鐘為0
sys_rst_n <= 1'b0; //初始復位
spi_start <= 1'b0;
data_send <= 8'd0;
spi_miso <= 1'bz;
spi_end <= 1'b0;
#80 //80個時鐘周期后
sys_rst_n <= 1'b1; //拉高復位,系統進入工作狀態
#30 //30個時鐘周期后拉高SPI開始信號,開始SPI傳輸
spi_start <= 1'b1;data_send <= 8'b01010101;
#20
spi_start <= 1'b0;
@(posedge send_done) //一個BYTE發送完成
spi_end <= 1'b1; #20 spi_end <= 1'b0; //拉高一個周期結束信號
end
//------------<設置時鐘>----------------------------------------------
always #10 sys_clk = ~sys_clk; //系統時鐘周期20ns
endmodule
仿真結果如下:
可以看到,在拉高了spi_start開始傳輸信號后,主機開始發送數據,MOSI上的數據分別是01010101,發送完一個BYTE的數據后,send_done拉高。此時拉高結束信號spi_end,就終結了這次SPI傳輸,完成了單個BYTE的SPI傳輸。
4.2、多個BYTE的仿真
使用該SPI驅動,依次向從機發送數據8‘d0~8‘d10,觀察其仿真時序是否正確:
//------------------------------------------------
//--SPI驅動仿真(模式0)
//------------------------------------------------
`timescale 1ns/1ns //時間單位/精度
//------------<模塊及端口聲明>----------------------------------------
module tb_spi_drive();
//系統接口
reg sys_clk ; // 全局時鐘50MHz
reg sys_rst_n ; // 復位信號,低電平有效
//用戶接口
reg spi_start ; // 發送傳輸開始信號,一個高電平
reg spi_end ; // 發送傳輸結束信號,一個高電平
reg [7:0] data_send ; // 要發送的數據
wire [7:0] data_rec ; // 接收到的數據
wire send_done ; // 主機發送一個字節完畢標志位
wire rec_done ; // 主機接收一個字節完畢標志位
//SPI物理接口
reg spi_miso ; // SPI串行輸入,用來接收從機的數據
wire spi_sclk ; // SPI時鐘
wire spi_cs ; // SPI片選信號
wire spi_mosi ; // SPI輸出,用來給從機發送數據
//仿真用
reg [3:0] cnt_send ; //發送數據計數器,0-15
//------------<例化SPI驅動模塊(模式0)>----------------------------------------
spi_drive spi_drive_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.spi_start (spi_start ),
.spi_end (spi_end ),
.data_send (data_send ),
.data_rec (data_rec ),
.send_done (send_done ),
.rec_done (rec_done ),
.spi_miso (spi_miso ),
.spi_sclk (spi_sclk ),
.spi_cs (spi_cs ),
.spi_mosi (spi_mosi )
);
//------------<設置初始測試條件>----------------------------------------
initial begin
sys_clk = 1'b0; //初始時鐘為0
sys_rst_n <= 1'b0; //初始復位
spi_start <= 1'b0;
data_send <= 8'd0;
spi_miso <= 1'bz;
spi_end <= 1'b0;
#80 //80個時鐘周期后
sys_rst_n <= 1'b1; //拉高復位,系統進入工作狀態
#30 //30個時鐘周期后拉高SPI開始信號,開始SPI傳輸
spi_start <= 1'b1; #20 spi_start <= 1'b0;
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
data_send <= 8'd0;
spi_end <= 1'b0;
cnt_send <= 4'd0;
end
else if(send_done)begin //數據發送完成
if(cnt_send == 4'd10)begin
cnt_send <= 4'd0;
spi_end <= 1'b1; //拉高結束標志,結束SPI傳輸過程
data_send <= 8'd0;
end
else begin
cnt_send <= cnt_send + 4'd1;
spi_end <= 1'b0;
data_send <= data_send + 4'd1; //發送數據累加
end
end
else begin
data_send <= data_send;
spi_end <= 1'b0; //其他時候保持SPI傳輸(不結束)
end
end
//------------<設置時鐘>----------------------------------------------
always #10 sys_clk = ~sys_clk; //系統時鐘周期20ns
endmodule
仿真結果如下:
可以看到,在拉高了spi_start開始傳輸信號后,主機一直在發送數據,MOSI上的數據分別是8‘d0~8‘d10,每次發送一個BYTE的數據后,send_done即拉高一次。當結束信號spi_end被拉高后,就終結了這次SPI傳輸。
5、其他
- 需要注意的是,由于沒有從機響應,所以MISO都是高阻態(藍色)
- 下篇文章再結合從機(FLASH芯片)進行仿真驗證接收數據功能
- 想要整個工程的朋友可以在評論區留下郵箱
總結
以上是生活随笔為你收集整理的fpga的spi的编程_UASP协议的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SAP CRM get_children
- 下一篇: 无条件极值与条件极值