FPGA-based data acquisition system (1)

table of Contents

Overall system design

1. Serial port receiver module

Two, serial port sending module

Three, key debounce module

Four, ROM module


Download link of this design project file (including notes): https://download.csdn.net/download/qq_33231534/12450178 


Overall system design

This design is mainly to use ADC and DAC. The main realization of the functional process is: firstly send control signals to FPGA through serial port, control DAC chip tlv5618 for DA replacement, the converted data is stored in ROM, and the data in ROM is read when conversion starts Perform read conversion. Secondly, use the button to control the adc128s052 to perform analog-to-digital conversion 100 times. The analog-to-digital conversion data is stored in the FIFO, and then the data is read from the FIFO and displayed on the PC through the serial port. The overall system block diagram is as follows:

Figure 1: Overall block diagram of FPGA data acquisition system

 As can be seen from the figure, the system mainly includes 9 modules: serial port receiving module, key debounce module, key control module, ROM module, DAC drive module, ADC drive module, synchronous FIFO module, FIFO control module, serial port transmission module . The functions of each module are as follows:

(1) Serial port receiving module (UART_Byte_Rx.v): complete the serial port data reception, convert serial data into parallel data and output.

(2) Key debounce module (key_filter.v): for key debounce, it can output a pulse key press sign and key press time sign.

(3) Button control module (key_ctrl.v): When the DA has been outputting analog signals, press the button to control ADC conversion 100 times.

(4) ROM module (single_port_rom.v): stores the data converted by DA, and can store sine wave data.

(5) DAC drive module (dac_driver.v): digital-to-analog conversion drive module, connected to an external DAC chip, providing DAC chip clock and data signals, etc.

(6) ADC drive module (adc_driver.v): analog-to-digital conversion drive module, connected to the external ADC chip, providing ADC chip clock and control signals, etc.

(7) Synchronous FIFO module (sync_fifo.v): Store the data after ADC conversion.

(8) FIFO control module (fifo_ctrl.v): When there is data in FIFO, the data in FIFO is converted into data that can be sent by UART serial port.

(9) Serial port sending module (Uart_Byte_Tx.v): The data converted by the FIFO control module is sent to the serial port through the serial port sending module and displayed on the PC side.

(10) DAC control module (dac_ctrl.v): When receiving the command specified by the serial port, it starts to convert the sine data of the ROM to DAC.


1. Serial port receiver module

The serial port receiving module has been written before, and it can be used directly here. Here the serial port data format includes: 1 start bit, 8 data bits, 1 stop bit, no parity bit. The module interface list is entered as follows:

Table 1.1: Serial port receiving module interface list
Signal name I / O Digits Function description
clk I 1 System clock 50MHz
rst_n I 1 System reset
rs232_tx I 1 Serial data port
baud_set I 3 Baud rate selection signal
data_byte THE 8 Parallel data output
rx_done THE 1 Receive 1 byte data complete flag

The code is as follows: UART_Byte_Rx.v
 

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 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

 The simulation graphics are as follows:


Two, serial port sending module

 Corresponding to the serial port receiving module here, 8-bit data is sent, plus a start bit and a stop bit. The module interface signal list is as follows:
 

Table 2.1: List of serial port sending module interface signals
Signal name I / O Digits Function description
clk I 1 System clock 50MHz
rst_n I 1 System reset
send_en I 1 Send enable
data_byte I 8 Data sent
baud_set I 3 Baud rate setting
rs232_tx THE 1 FPGA converts data into serial data and sends it out
tx_done THE 1 Send data complete flag
uart_state THE 1 Serial port sending status, 1 means busy, 0 means idle

The code is as follows: Uart_Byte_Tx.v
 


//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 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

The simulation waveform is as follows:


Three, key debounce module

This is a necessary module to use buttons. The signal list is entered below, and this module is universal.

Table 3.1: Signal list of key debounce module
Signal name I / O Digits Function description
clk I 1 System clock 50MHz
rst_n I 1 System reset
key_in I 1 Key input
key_flag THE 1 Output a pulse key valid signal
key_state THE 1 Output button status, 1 means not pressed, 0 means pressed

The code is as follows: key_filter.v

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 潘洪峰的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

Four, ROM module

Skip the key control module here, first finish some basic modules, and then describe the control module.

The ROM module is a read-only memory, and there are three ways to write this module.
(1) Use the IP core of quartus (I use quartus prime 17.1), select 1-PORT, and set the parameters

 


(2) Create a new verilog HDL file, select Edit—>Insert Template, and select according to the following figure to directly generate a template, which can be used directly.


(3) Write a ROM file by yourself, the same as the code generated by the method (2), you can set the data bit width and address width.

Note the ROM initialization statement in the code: $readmemb("./sin_12bit.txt", rom); initialize by reading an external file, the initialization file can be typed by yourself or generated by Excel or matlab. Among them, $readmemb("./sin_12bit.txt", rom); the usage of the sentence is also introduced in my blog, so you can check it out if you need it.

The verilog file generated is as follows: 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

The simulation graphics are as follows:

The data inside is the sin function 12-bit wave file that I generated on matlab. You can see that it reads the data of the corresponding address on the rising edge of clk.

 

 

 

Guess you like

Origin blog.csdn.net/qq_33231534/article/details/106274827