September 28, 2016
作者:dengshuai_super
出處:http://blog.csdn.net/dengshuai_super/article/details/52688654
聲明:轉載請註明作者及出處。
百度百科定義:
SPI(Serial Peripheral Interface–串行外設接口)總線系統是一種同步串行外設接口,它可以使MCU與各種外圍設備以串行方式進行通信以交換信息。SPI有三個寄存器分別爲:控制寄存器SPCR,狀態寄存器SPSR,數據寄存器SPDR。外圍設備包括FLASHRAM、網絡控制器、LCD顯示驅動器、A/D轉換器和MCU等。
SPI接口應用:
串行flash的讀寫擦除命令通過SPI接口進行通信。
CPU芯片與FPGA通過SPI接口進行通信。
其他功能集成電路芯片參數寄存器配置。(例如DAC芯片內部有很多寄存器(因爲芯片有很多功能,要通過設置寄存器不同的開關來打開或關閉相應的功能,一上電去初始化寄存器)需要我們去配置。FPGA一上電也是通過配置芯片裏邊來讀取數據,然後配置FPGA內部的SRAM。FPGA是讀取FLASH裏邊的串行數據,讀取完校驗完才配置到我們的FPGA的SRAM中去)
速度比串口快,而且是同步傳輸。
某DAC芯片的SPI配置時序(數字模擬轉換器(英語:Digital to analog converter,英文縮寫:DAC)是一種將數字信號轉換爲模擬信號(以電流、電壓或電荷的形式)的設備。)
SPI(Serial Peripheral Interface)
下邊是四線SPI(三線的SPI是把SDI和SDO合併成了一個SDIO雙向的)
SCLK主機給從機的系統時鐘
SDI主機輸出給從機的數據。
SDO從機輸出給主機的數據
CS:chip select 片選信號,此信號可以使一個主機控制多個從機,此信號有效表示此從機被選中通信。(片選可以理解成選片,很多芯片掛在同一總線上的時候,需要有一個信號來區別總線上的數據和地址由哪個芯片處理,這時就需要一個片選信號CS(chip select),一般是在劃分地址空間時,由邏輯電路產生的。在數字電路設計中,一般開路輸入管腳呈現爲高電平,因此片選信號絕大多數情況下是一個低電平。
帶-n的是低電平有效;不帶的是高電平有效。)
查看文檔DAC3283.pdf
在這種有命令和數據的情況下,可以把命令和數據都存到我們的RAM中去,這樣我們在讀出來的時候就按照比特,一個比特一個比特的給它發過去就可以了,可以簡化邏輯。
這樣雖然佔用了更多的ram空間,但是節省邏輯空間。因爲Block Memory它大小是固定的,比如說它裏邊的深度是256,比如cycloneIV最小的是m9k,那麼它也會佔用整個9k的memory,就算用裏面的一點也會把整塊佔用,佈局佈線和綜合。
因此可以把存儲數據和命令都存儲到ram,方便我們操作。
案例實現:首先數據存儲到RAM或ROM中,然後再從RAM或ROM中讀出來再發送。
這裏有32個寄存器,像這種配置,有些時候我們配置的時候不能連續的配置兩個寄存器,每配置完一個寄存器要把使能拉高,停一拍。做FLASH擦出或讀寫的時候中間有個保護時間,就是說你兩次命令需要間隔多長時間。我們中間要隔一定的週期數再發第二個命令。所以,我們發送完一個數據的時候,我們等待一段時間再去發送第二個命令。所以我們把這個順序流程定義成一個狀態機,我們用狀態機的方式去控制它的流程。
狀態遷移圖:
圖片
建立一個ram的IP核:
//D:\VivadoProjects\FPGA_From_e_to_c\ex_7\quartus_prj\ipcore_dir\ram_16x32_sr_inst.v
ram_16x32_sr ram_16x32_sr_inst (
.address ( address_sig ),
.clock ( clock_sig ),
.data ( data_sig ),
.wren ( wren_sig ),
.q ( q_sig )
);
建立一個.mif文件,用來初始化ram:
--D:\VivadoProjects\FPGA_From_e_to_c\ex_7\quartus_prj\ipcore_dir\ram_16x32_sr.mif
-- Copyright (C) 1991-2013 Altera Corporation
-- Your use of Altera Corporation's design tools, logic functions
-- and other software and tools, and its AMPP partner logic
-- functions, and any output files from any of the foregoing
-- (including device programming or simulation files), and any
-- associated documentation or information are expressly subject
-- to the terms and conditions of the Altera Program License
-- Subscription Agreement, Altera MegaCore Function License
-- Agreement, or other applicable license agreement, including,
-- without limitation, that your use is for the sole purpose of
-- programming logic devices manufactured by Altera and sold by
-- Altera or its authorized distributors. Please refer to the
-- applicable agreement for further details.
-- Quartus II generated Memory Initialization File (.mif)
WIDTH=16;
DEPTH=32;
ADDRESS_RADIX=UNS;
DATA_RADIX=BIN;
CONTENT BEGIN
0 : 0000000001110000;
1 : 0000000100110001;
2 : 0000001000000000;
3 : 0000001100010000;
4 : 0000010011111111;
5 : 0000010100000000;
6 : 0000011000000000;
7 : 0000011100000000;
8 : 0000100000000000;
9 : 0000100101010101;
10 : 0000101010101010;
11 : 0000101101010101;
12 : 0000110010101010;
13 : 0000110101010101;
14 : 0000111010101010;
15 : 0000111101010101;
16 : 0001000010101010;
17 : 0001000100100100;
18 : 0001001000000010;
19 : 0001001111000010;
20 : 0001010000000000;
21 : 0001010100000000;
22 : 0001011000000000;
23 : 0001011100000100;
24 : 0001100010000011;
25 : 0001100100000000;
26 : 0001101000000000;
27 : 0001101100000000;
28 : 0001110000000000;
29 : 0001110100000000;
30 : 0001111000100100;
31 : 0001111101010010;
END;
//D:\VivadoProjects\FPGA_From_e_to_c\ex_7\design\spi_ctrl.v
module spi_ctrl(
input wire sclk,//主機給從機的系統時鐘50Mhz
input wire rst_n,
input wire work_en,//觸發配置操作的使能,屬於引入的一個讓狀態機是否工作的工作使能。
output reg conf_end,//配置結束,爲的是和其他模塊相連的時候有個標誌
output wire spi_clk,//50-60Mhz
output wire spi_sdi,//SDI主機輸出給從機的數據。
output wire spi_csn,//片選信號可以使一個主機控制多個從機,此信號有效表示此從機被選中通信。
input wire spi_sdo//從機輸出給主機的數據;;讀輸入管腳不進行編程
);
parameter IDLE = 5'b0_0001;
parameter WAIT = 5'b0_0010;
parameter R_MEM = 5'b0_0100;
parameter W_REG = 5'b0_1000;
parameter STOP = 5'b1_0000;
parameter H_DIV_CYC = 5'd25 - 1;
//先來一個分頻
reg [4:0] state;//狀態機的寄存器變量編碼方式採用獨熱碼
reg [4:0] div_cnt;//分頻器
reg clk_p=1'b0;//產生數據
wire clk_n;//提供spi的從機作爲數據採集,clk_n上升沿前邊用於建立時間,後邊用於保持時間,達到時序最優化
reg pose_flag;//標誌上升沿
reg [3:0] wait_cnt;
reg [3:0] shift_cnt;
reg [4:0] r_addr=0;
wire [15:0] r_data;
wire wren;
reg [15:0] shift_buf;//存儲讀出來的16位數據,
reg data_end;
reg sdi;
reg csn;
reg tck;
//分頻計數器
always @(posedge sclk or rst_n)
if(rst_n == 1'b0)
div_cnt <= 5'd0;
else if(div_cnt == H_DIV_CYC)
div_cnt <= 'd0;
else
div_cnt <=div_cnt + 1'b1;//當sclk震了24次之後div_cnt出現高電平,然後減到0,在震24次出現下一個高電平,一次把原來的sclk分頻了50倍
//分頻時鐘不允許做寄存器的觸發時鐘,也就是不能寫在always塊的觸發列表中
always @(posedge sclk or rst_n)
if(rst_n == 1'b0)
clk_p <= 1'b0;
else if(div_cnt == H_DIV_CYC)
clk_p <= ~clk_p;
assign clk_n =~clk_p;
//產生一個標誌信號,用於分頻同步的標誌信號
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
pose_flag <= 1'b0;
else if(clk_p == 1'b0 && div_cnt == H_DIV_CYC)
pose_flag <= 1'b1;
else
pose_flag <= 1'b0;
//等待計數器
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
wait_cnt <= 'd0;
else if(state == WAIT && pose_flag ==1'b1)
wait_cnt <= wait_cnt + 1'b1;
else if(state !=WAIT)
wait_cnt <= 4'd0;
//fsm
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
state <=IDLE;
else case(state)
IDLE : if(work_en ==1'b1)
state <=WAIT;
WAIT : if(wait_cnt[3] == 1'b1)//當計數到第八次的時候
state <=R_MEM;
R_MEM: state <= W_REG;
W_REG: if(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end != 1'b1)
state <=WAIT;
else if(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end == 1'b1)
state <= STOP;
STOP: state <= STOP;
default: state <= IDLE;
endcase
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
shift_cnt <= 'd0;
else if(state == W_REG && pose_flag == 1'b1)
shift_cnt <= shift_cnt + 1'b1;
else if(state != W_REG)
shift_cnt <=4'd0;
//讀memory的地址產生
always @(posedge sclk or negedge rst_n)
if(rst_n ==1'b0)
r_addr <= 'd0;
else if(state == R_MEM)
r_addr <= r_addr + 1'b1;
//data_end 最後一個需要移位的數據
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
data_end <= 1'b0;
else if(state == R_MEM && (&r_addr)==1'b1 )// 11111按位與等效於r_addr == 5'd31
data_end <= 1'b1;
assign wren =1'b0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
shift_buf <= 'd0;
else if(state == R_MEM)
shift_buf <= r_data;
else if(state == W_REG && pose_flag == 1'b1)
shift_buf <={shift_buf[14:0],1'b1};
//數據輸出
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
sdi <=1'b0;
else if(state == W_REG && pose_flag == 1'b1)
sdi <=shift_buf[15];
else if(state != W_REG)
sdi <= 1'b0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
csn <= 1'b1;
else if(state == W_REG)
csn <=1'b0;
else
csn <=1'b1;
always @ (posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
tck <= 1'b0;
else if(state == W_REG)
tck <= clk_n;
else
tck <=1'b0;
assign spi_clk = tck;
assign spi_csn = csn;
assign spi_sdi = sdi;
always @ (posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
conf_end <= 1'b0;
else if(state == STOP)
conf_end <= 1'b1;
ram_16x32_sr ram_16x32_sr_inst (
.address ( r_addr ),//讀地址
.clock ( sclk ),
.data ( 16'd0 ),//寫數據
.wren ( wren ),//寫使能高有效,讀使能低有效
.q ( r_data )//讀數據
);
endmodule
testbench:
//tb_ex_spi.v
`timescale 1ns/1ns
module tb_ex_spi;
reg sclk,rst_n;
reg work_en;
initial begin
rst_n = 0;
sclk =0;
#100;
rst_n = 1;
end
initial begin
work_en = 0;
#150
work_en = 1;
end
always # sclk = ~sclk;
spi_ctrl(
.sclk (sclk),//主機給從機的系統時鐘50Mhz
.rst_n (rst_n),
.work_en (work_en),//觸發配置操作的使能,屬於引入的一個讓狀態機是否工作的工作使能。
.conf_end (),//配置結束,爲的是和其他模塊相連的時候有個標誌
.spi_clk (),//50-60Mhz
.spi_sdi (),//SDI主機輸出給從機的數據。
.spi_csn (),//片選信號可以使一個主機控制多個從機,此信號有效表示此從機被選中通信。
.spi_sdo () //從機輸出給主機的數據;;讀輸入管腳不進行編程
);
endmodule
將D:\altera\13.1\quartus\eda\sim_lib\altera_mf.v (ram仿真需要的庫文件)拷貝到sim\altera_lib目錄下。
把ram_16x32_sr.mif拷貝到sim目錄下