Verilog語法_7(SPI接口驅動)

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目錄下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章