基於FPGA的數據採集系統(一)

目錄

整體系統設計

一、串口接收模塊

二、串口發送模塊

三、按鍵消抖模塊

四、ROM模塊


本設計工程文件下載鏈接(包含註釋):https://download.csdn.net/download/qq_33231534/12450178 


整體系統設計

本設計主要是對ADC和DAC的使用,主要實現功能流程爲:首先通過串口向FPGA發送控制信號,控制DAC芯片tlv5618進行DA裝換,轉換的數據存在ROM中,轉換開始時讀取ROM中數據進行讀取轉換。其次用按鍵控制adc128s052進行模數轉換100次,模數轉換數據存儲到FIFO中,再從FIFO中讀取數據通過串口輸出顯示在pc上。其整體系統框圖如下:

圖1:FPGA數據採集系統整體框圖

 從圖中可以看出,該系統主要包括9個模塊:串口接收模塊、按鍵消抖模塊、按鍵控制模塊、ROM模塊、DAC驅動模塊、ADC驅動模塊、同步FIFO模塊、FIFO控制模塊、串口發送模塊。各個模塊的作用如下:

(1)串口接收模塊(UART_Byte_Rx.v):完成串口數據接收,將串行數據轉換成並行數據輸出。

(2)按鍵消抖模塊(key_filter.v):進行按鍵消抖,可輸出一個脈衝按鍵按下標誌和按鍵按下時間標誌。

(3)按鍵控制模塊(key_ctrl.v):當在DA一直輸出模擬信號時,按下按鍵控制ADC轉換100次。

(4)ROM模塊(single_port_rom.v):存儲DA轉換的數據,可存放正弦波形數據。

(5)DAC驅動模塊(dac_driver.v):數模轉換驅動模塊,與外部DAC芯片相連,提供DAC芯片時鐘和數據信號等。

(6)ADC驅動模塊(adc_driver.v):模數轉換驅動模塊,與外部ADC芯片相連,提供ADC芯片時鐘和控制信號等。

(7)同步FIFO模塊(sync_fifo.v):存放ADC轉換後的數據。

(8)FIFO控制模塊(fifo_ctrl.v):當FIFO中有數據時,將FIFO中的數據轉換成可以UART串口發送的數據。

(9)串口發送模塊(Uart_Byte_Tx.v):經過FIFO控制模塊轉換的數據通過串口發送模塊發送到串口,顯示在pc端。

(10)DAC控制模塊(dac_ctrl.v):當接收串口指定的指令時,開始將ROM的正弦數據進行DAC轉換。


一、串口接收模塊

前面已經寫過串口接收模塊,這裏可以直接拿來用。這裏串口發送數據格式包含:1位起始位、8位數據位、1位停止位,無校驗位。該模塊接口列表入下:

表1.1:串口發送模塊接口列表
信號名稱 I/O 位數 功能描述
clk I 1 系統時鐘50MHz
rst_n I 1 系統復位
rs232_tx I 1 串口串行數據發送數據口
baud_set I 3 波特率選擇信號
data_byte O 8 並行數據輸出
rx_done O 1 接收1字節數據完成標誌

代碼如下:UART_Byte_Rx.v
 

//-------------------------------------------------------------------
//https://mp.csdn.net/console/article PHF的CSDN   
//File name:           UART_Byte_Rx.v
//Last modified Date:  2020/5/22
//Last Version:        
//Descriptions:        工業級別串口數據接收模塊,防干擾。對每位數據內部採樣16個點,
//                     對中間6位數據進行判定數據是1還是0
//-------------------------------------------------------------------

module UART_Byte_Rx(
    input                   clk         ,//系統時鐘50MHz
    input                   rst_n       ,//系統復位
    input                   rs232_tx    ,//串口串行數據發送數據口
    input       [  2: 0]    baud_set    ,//波特率選擇信號
    output  reg [  7: 0]    data_byte   ,//並行數據輸出
    output  reg             rx_done      //接收1字節數據完成標誌,rx_done可以作爲輸出有效信號使用
);

reg   [ 13: 0]         baud_c   ;//波特率對應計數次數(4800bps-10416),(9600bps-5208),(19200bps-2604),
                                 //(38400bps-1302),(57600bps-868),(115200bps-434)

reg                    rs232_tx_ff0     ;
reg                    rs232_tx_ff1     ;
reg                    rs232_tx_ff2     ;
wire                   tx_neg_flag      ;
reg                    add_flag         ;

reg   [ 13: 0]         cnt0             ;
reg   [  3: 0]         cnt1             ;
reg   [  9: 0]         cnt2             ;
reg   [  3: 0]         cnt3             ;
reg   [  2: 0]         cnt_0            ;
reg   [  2: 0]         cnt_1            ;

wire                   add_cnt0         ;
wire                   end_cnt0         ;
wire                   add_cnt1         ;
wire                   end_cnt1         ;
wire                   add_cnt2         ;
wire                   end_cnt2         ;
wire                   add_cnt3         ;
wire                   end_cnt3         ;

//查找表
always  @(posedge clk or negedge rst_n)begin
     if(rst_n==1'b0)begin
        baud_c <= 5208;
    end
    else begin
        case(baud_set)
            0:      baud_c = 14'd10416;
            1:      baud_c = 14'd5208 ;
            2:      baud_c = 14'd2604 ;
            3:      baud_c = 14'd1302 ;
            4:      baud_c = 14'd868  ;
            5:      baud_c = 14'd434  ;
            default:baud_c = 14'd5208 ;//默認9600bps
        endcase
    end   
end

//打兩拍 防止亞穩態,同時scan negedge
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rs232_tx_ff0 <= 1;
        rs232_tx_ff1 <= 1;
        rs232_tx_ff2 <= 1;
    end
    else begin
        rs232_tx_ff0 <= rs232_tx;
        rs232_tx_ff1 <= rs232_tx_ff0;
        rs232_tx_ff2 <= rs232_tx_ff1;
    end
end
//掃描下降沿
assign tx_neg_flag = rs232_tx_ff2 && !rs232_tx_ff1;

//計數標誌信號
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        add_flag <= 0;
    end
    else if(tx_neg_flag) begin
        add_flag <= 1;
    end
    else if(rx_done)begin
    add_flag <= 0;
    end
end

//計數器,計數1bit數據長度
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1'b1;
    end
end

assign add_cnt0 = add_flag;
assign end_cnt0 = add_cnt0 && cnt0==baud_c-1;

//計數器,計數8位接收數據長度
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1'b1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1== 8;

//比特內部採樣點時鐘計數
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1'b1;
    end
end

assign add_cnt2 = add_flag;       
assign end_cnt2 = add_cnt2 && (cnt2== (baud_c/16)-1 || end_cnt0);   

//一個bit數據中16個採樣點計數
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt3 <= 0;
    end
    else if(add_cnt3)begin
        if(end_cnt3)
            cnt3 <= 0;
        else
            cnt3 <= cnt3 + 1'b1;
    end
end

assign add_cnt3 = add_cnt2 && cnt2== (baud_c/16)-1;       
assign end_cnt3 = end_cnt0 || (end_cnt2 && cnt3==16-1);   

//比特內選取6個採樣點是0或1計數
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        cnt_0 <= 0;
        cnt_1 <= 0;
    end
    else if(add_flag) begin
        if(cnt3>=6 && cnt3<=11)begin
            if(cnt2==baud_c/16/2 && rs232_tx_ff1==0)
                cnt_0 <= cnt_0 + 1'b1;
            else if(cnt2==baud_c/16/2 && rs232_tx_ff1==1)
                cnt_1 <= cnt_1 + 1'b1;
        end
        else if(end_cnt0)begin
            cnt_0 <= 0;
            cnt_1 <= 0;
        end

    end
    else begin
        cnt_0 <= 0;
        cnt_1 <= 0;
    end
end

//輸出並行數據data_byte
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data_byte <= 0;
    end
    else if(end_cnt0 && cnt1>0 && cnt1 <9) begin
        if(cnt_0 >= cnt_1)
            data_byte[cnt1-1] = 0;
        else if(cnt_0 < cnt_1)
            data_byte[cnt1-1] = 1;
    end
end

//輸出接收完成標誌信號
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rx_done <= 0;
    end
    else if(end_cnt1) begin
        rx_done <= 1;
    end
    else begin
        rx_done <= 0;
    end
end

endmodule

 其仿真圖形如下:


二、串口發送模塊

 這裏和串口接收模塊對應,發送的是8位數據,外加1位起始位和1位停止位。該模塊接口信號列表如下:
 

表2.1:串口發送模塊接口信號列表
信號名稱 I/O 位數 功能描述
clk I 1 系統時鐘50MHz
rst_n I 1 系統復位
send_en I 1 發送使能
data_byte I 8 發送的數據
baud_set I 3 波特率設置
rs232_tx O 1 FPGA將數據轉換成串行數據發出
tx_done O 1 發送數據完畢標誌
uart_state O 1 串口發送狀態,1爲忙,0爲空閒

代碼如下:Uart_Byte_Tx.v
 


//-------------------------------------------------------------------
//https://mp.csdn.net/console/article phf的CSDN   
//File name:           Uart_Byte_Tx.v
//Last modified Date:  2020/5/22
//Last Version:        
//Descriptions:        串口發送模塊,8位數據位、1位起始位和1位停止位、無校驗位
//-------------------------------------------------------------------
module Uart_Byte_Tx(
    input                   clk         , //系統時鐘
    input                   rst_n       , //系統復位
    input                   send_en     , //發送使能
    input   [ 7 : 0 ]       data_byte   , //發送的數據
    input   [ 2 : 0 ]       baud_set    , //波特率設置
    output  reg             rs232_tx    , //FPGA將數據轉換成串行數據發出
    output  reg             tx_done     , //發送數據完畢標誌
    output  reg             uart_state    //串口發送狀態,1爲忙,0爲空閒
);


reg   [ 13: 0]         baud_c   ;//(4800bps-10416),(9600bps-5208),(19200bps-2604),
                                 //(38400bps-1302),(57600bps-868),(115200bps-434)
wire  [  9: 0]         data_out      ;
reg   [ 15: 0]         cnt0          ; //1bit數據長度計數
reg   [  3: 0]         cnt1          ; //發送一字節數據對每個字節計數
wire                   add_cnt0      ; //計數器cnt0加一條件
wire                   add_cnt1      ; //計數器cnt1加一條件
wire                   end_cnt0      ; //計數器cnt0結束條件
wire                   end_cnt1      ; //計數器cnt1結束條件
reg   [  7: 0]         data_byte_ff  ; //發送使能時將發送的數據寄存下來

//波特率查找表
always  @(posedge clk or negedge rst_n)begin
     if(rst_n==1'b0)begin
        baud_c <= 5208;
    end
    else begin
        case(baud_set)
            0:      baud_c = 14'd10416;
            1:      baud_c = 14'd5208 ;
            2:      baud_c = 14'd2604 ;
            3:      baud_c = 14'd1302 ;
            4:      baud_c = 14'd868  ;
            5:      baud_c = 14'd434  ;
            default:baud_c = 14'd5208 ;//默認9600bps
        endcase
    end
    
end

//串口狀態標誌,0爲空閒,1爲忙
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        uart_state <= 0;
    end
    else if(send_en) begin
        uart_state <= 1;
    end
    else if(end_cnt1)begin
        uart_state <= 0;
    end
    else begin
        uart_state <= uart_state;
    end
end

//1bit數據長度計數
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1'b1;
    end
end

assign add_cnt0 = uart_state==1;
assign end_cnt0 = add_cnt0 && cnt0== baud_c-1;

//發送一字節數據對每個字節計數
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1'b1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1== 10-1;

//串口發送結束標誌
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        tx_done <= 0;
    end
    else if(end_cnt1) begin
        tx_done <= 1;
    end
    else begin
        tx_done <= 0;
    end
end

//發送使能時將發送的數據寄存下來
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data_byte_ff <= 0;
    end
    else if(send_en) begin
        data_byte_ff <= data_byte;
    end
end

//發送串行數據到串口
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rs232_tx <= 1;
    end
    else if(uart_state && cnt0==0) begin
        rs232_tx <= data_out[cnt1];
    end
end
assign data_out = {1'b1,data_byte_ff,1'b0};

endmodule

仿真波形如下:


三、按鍵消抖模塊

這是使用按鍵必須的模塊。其信號列表入下,此模塊可通用。

表3.1:按鍵消抖模塊信號列表
信號名稱 I/O 位數 功能描述
clk I 1 系統時鐘50MHz
rst_n I 1 系統復位
key_in I 1 按鍵輸入
key_flag O 1 輸出一個脈衝按鍵有效信號
key_state O 1 輸出按鍵狀態,1爲未按下,0爲按下

代碼如下:key_filter.v

//-------------------------------------------------------------------
//https://mp.csdn.net/console/article 潘洪峯的CSDN博客   
//File name:           key_filter.v
//Last modified Date:  2020/5/22
//Last Version:        
//Descriptions:        按鍵消抖模塊
//-------------------------------------------------------------------

module key_filter(
    input                   clk         ,//系統時鐘50MHz
    input                   rst_n       ,//系統復位
    input                   key_in      ,//按鍵輸入
    output   reg            key_flag    ,//輸出一個脈衝按鍵有效信號
    output   reg            key_state    //輸出按鍵狀態,1爲未按下,0爲按下
);


parameter IDLE      = 4'b0001      ;//空閒狀態,讀取按鍵按下的下降沿,讀取到下降沿轉到下一個狀態
parameter FILTER1   = 4'b0010      ;//計數20ms狀態,計數結束轉到下一個狀態
parameter STABLE    = 4'b0100      ;//數據穩定狀態,等待按鍵鬆開上升沿,讀取到上升沿轉到下一個狀態
parameter FILTER2   = 4'b1000      ;//計數20ms狀態,計數結束轉到空閒狀態

parameter TIME_20MS = 20'd1000_000 ;

reg   [  3: 0]         state_c      ;//寄存器改變狀態
reg   [  3: 0]         state_n      ;//現在狀態

wire                   IDLE_to_FILTER1  ;//IDLE狀態轉到FILTER1狀態條件
wire                   FILTER1_to_STABLE;//FILTER1狀態轉到STABLE狀態條件
wire                   STABLE_to_FILTER2;//STABLE狀態轉到FILTER2狀態條件
wire                   FILTER2_to_IDLE  ;//FILTER2狀態轉到IDLE狀態條件

reg                    key_in_ff0   ;
reg                    key_in_ff1   ;
reg                    key_in_ff2   ;

wire                   key_in_pos   ;//檢測上升沿標誌
wire                   key_in_neg   ;//檢測下降沿標誌

reg   [ 19: 0]         cnt          ;
wire                   add_cnt      ;
wire                   end_cnt      ;

//狀態機第一段,狀態轉換
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

//狀態機第二段,狀態轉換條件
always  @(*)begin
    case(state_c)
        IDLE   :begin
                    if(IDLE_to_FILTER1)
                        state_n = FILTER1;
                    else
                        state_n = state_c;
                end
        FILTER1:begin
                    if(FILTER1_to_STABLE)
                        state_n = STABLE;
                    else
                        state_n = state_c;
                end
        STABLE :begin
                    if(STABLE_to_FILTER2)
                        state_n = FILTER2;
                    else
                        state_n = state_c;
                end
        FILTER2:begin
                    if(FILTER2_to_IDLE)
                        state_n = IDLE;
                    else
                        state_n = state_c;
                end
        default:state_n = IDLE;
    endcase
end

//狀態轉換條件
assign IDLE_to_FILTER1   = key_in_neg   ;
assign FILTER1_to_STABLE = state_c==FILTER1 && end_cnt;
assign STABLE_to_FILTER2 = key_in_pos   ;
assign FILTER2_to_IDLE   = state_c==FILTER2 && end_cnt;

//打兩拍,防止亞穩態
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_in_ff0 <= 1;
        key_in_ff1 <= 1;
        key_in_ff2 <= 1;
    end
    else begin
        key_in_ff0 <= key_in;
        key_in_ff1 <= key_in_ff0;
        key_in_ff2 <= key_in_ff1;
    end
end

//下降沿和上升沿檢測
assign key_in_pos = (state_c==STABLE) ?(key_in_ff1 && !key_in_ff2):1'b0;
assign key_in_neg = (state_c==IDLE) ?(!key_in_ff1 && key_in_ff2):1'b0;

//計數20ms
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt <= 0;
    end
    else if(add_cnt)begin
        if(end_cnt)
            cnt <= 0;
        else
            cnt <= cnt + 1'b1;
    end
    else begin
        cnt <= 0;
    end
end

assign add_cnt = state_c==FILTER1 || state_c==FILTER2;       
assign end_cnt = add_cnt && cnt== TIME_20MS-1;

//key_flag按鍵按下輸出一個脈衝信號
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_flag <= 0;
    end
    else if(state_c==FILTER1 && end_cnt) begin
        key_flag <= 1;
    end
    else begin
        key_flag <= 0;
    end
end

//key_state按鍵按下狀態信號
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_state <= 1;
    end
    else if(state_c==STABLE || state_c==FILTER2) begin
        key_state <= 0;
    end
    else begin
        key_state <= 1;
    end
end
endmodule

四、ROM模塊

這裏先跳過按鍵控制模塊,先將一些基本模塊敘述完,再敘述控制模塊。

ROM模塊是一個只讀存儲器,有三種方法編寫這個模塊。
(1)使用quartus具有的IP核(我用的是quartus prime 17.1),選擇1-PORT,進行參數設置

 


(2)新建一個verilog HDL文件,選擇Edit—>Insert Template,按照下圖選擇可直接生成模板,可直接使用。


(3)自己編寫一個ROM文件,自己編寫的和(2)方法生成代碼一樣,可設定數據位寬和地址寬度。

注意代碼中ROM初始化語句:$readmemb("./sin_12bit.txt", rom);通過讀取外部文件進行初始化,初始化文件可以自己手打或者Excel或者matlab生成。其中$readmemb("./sin_12bit.txt", rom);語句用法在本人博客下也有介紹,需要可以去看看。

其生成的verilog文件如下:single_port_rom.v

module single_port_rom
#(parameter DATA_WIDTH=12, parameter ADDR_WIDTH=12)
(
	input [(ADDR_WIDTH-1):0] addr,
	input clk, 
	output reg [(DATA_WIDTH-1):0] q
);

	// Declare the ROM variable
	reg [DATA_WIDTH-1:0] rom[2**ADDR_WIDTH-1:0];

	initial
	begin
		$readmemb("./sin_12bit.txt", rom);
	end

	always @ (posedge clk)
	begin
		q <= rom[addr];
	end

endmodule

仿真圖形如下:

裏邊的數據是我在matlab上生成的sin函數12位波形文件。可以看到其在clk上升沿時讀出對應地址的數據。

 

 

 

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