Verilog实现串口通讯(UART)

Verilog实现串口通讯(UART)

本代码参考了野火的相关教程,实现了发送和接收回环,同时可以通过串口数据控制LED灯的亮灭,在电脑发送数据时要选择HEX发送模式,发送16进制的数据进行控制。

UART协议中,在空闲时为高电平。在常用的一位停止位和无校验位的设置中,起始位为低电平,紧接着是8位的数据位,最后是一位高电平的停止位。

接收模块

​ 接收模块的主要设计是将起始位下降沿的检测作为接收系统的开始信号,通过起始信号和bit位的计数信号相结合就可以得到一个贯穿整体运行过程的使能信号,通过对使能信号的判断就可以实现对系统的控制。

​ 另一个关键问题是电平采集的时间,应该 在一个bit的中间位置进行采集,这样可以有效避免采集电平不稳定的问题。

接收模块的代码如下:

module uart_receive (
    input  clk,
    input  rst_n,
    input  uart_rx,
    output reg receive_done,
    output reg  [7:0]   uart_data
);
parameter  SYSTERM_CLK = 50_000_000;               //系统时钟频率
parameter  UART_BPS    = 115200;                     //串口波特率
localparam BPS_COUNT_MAX   = SYSTERM_CLK/UART_BPS;     //为得到指定波特率
                                                   //需要对系统时钟计数BPS_COUNT次
reg      [7:0]       reg_data;//接受数据缓存
reg      [3:0]       bit_count;//接收数据时用于计数接收到了多少位
reg      [12:0]      bps_count;//用于按照时钟计算一个字节的时间
reg                  start_bit;//检测到起始位的下降沿之后触发一个时钟的高电平
reg                  reg1     ;
reg                  reg2     ;
reg                  reg3     ;           
reg                  bit_flag ;//在一个电平的中间位置产生高电平标志
reg                  work_en  ;//在本标志位高电平时,接受工作开始,在低电平时工作结束
reg                  rx_flag  ;//在数据缓存器存满了之后产生一个高电平

//插入两级寄存器进行数据同步,用来消除亚稳态
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg1 <= 1'b1;//因为UART的空闲状态是高电平,所以复位时电平要设置为高电平
    end
    else        begin
        reg1 <= uart_rx;
    end
end    
//插入两级寄存器进行数据同步,用来消除亚稳态
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg2 <= 1'b1;//因为UART的空闲状态是高电平,所以复位时电平要设置为高电平
    end
    else        begin
        reg2 <=reg1;
    end        
end

//插入两级寄存器进行数据同步,用来消除亚稳态
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg3 <= 1'b1;//因为UART的空闲状态是高电平,所以复位时电平要设置为高电平
    end
    else        begin
        reg3 <=reg2;
    end        
end
//start_bit用来检测起始位的下降沿
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        start_bit <= 1'b0;
    end
    else    if ((reg3)&&(!reg2)) begin
        start_bit <= 1'b1;
    end
    else
        start_bit <= 1'b0;
end
//work_en,在本标志位高电平时,接受工作开始,在低电平时工作结束
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        work_en <= 1'b0;
    end
    else    if (start_bit) begin
        work_en <= 1'b1;
    end
    else    if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
        work_en <= 1'b0;
    end
    else
        work_en <= work_en;  
end
//bps_count用来计数计算波特率
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bps_count <= 13'b0;
    end
    else    if ((bps_count == BPS_COUNT_MAX -1) || (work_en == 0)) begin
        bps_count <= 13'b0;//只有在工作使能的状态下才会计数
    end
    else    if (work_en == 1) begin
        bps_count <= bps_count + 1'b1;
    end
    else
        bps_count <= bps_count;    
end
//bit_flag,在一个字节的中点输出一个时钟的高电平
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_flag <= 1'b0;
    end
    else    if (bps_count == BPS_COUNT_MAX/2-1) begin
         bit_flag <= 1'b1;
    end
    else
        bit_flag <= 1'b0;       
end
//bit_count,用于计数当前接收到了第几bit
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
       bit_count <= 4'b0;  
    end
    else    if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
        bit_count <= 4'b0;
    end
    else    if (bit_flag == 1'b1) begin
        bit_count <= bit_count + 1'b1;
    end
    else
        bit_count <= bit_count;       
end
//reg_data表示接收数据的缓存
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reg_data <= 8'b0;
    end
    else    if ((bit_flag == 1) && (work_en == 1) && (bit_count <= 4'd8) && (bit_count >= 4'd1)) begin
        reg_data <= {reg3,reg_data[7:1]};
    end
    else
        reg_data <= reg_data;     
end
//rx_flag在接收缓存满了之后输出一个时钟的高电平
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rx_flag <= 1'b0;
    end
    else    if ((bit_count == 4'd8) && (bit_flag == 1'b1)) begin
        rx_flag <= 1'b1;
    end
    else
        rx_flag <= 1'b0;       
end
//uart_data将数据缓存器中的数据导出
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        uart_data <= 8'b0;
    end
    else    if (rx_flag == 1'b1) begin
        uart_data <= reg_data;
    end
    else
        uart_data <= uart_data;       
end
//receive_done导出接收完成的信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        receive_done <= 1'b0;
    end
    else    if (rx_flag == 1'b1) begin
        receive_done <= 1'b1;
    end
    else
        receive_done <= 1'b0;       
end



endmodule //uart_receive

发送模块

​ UART的发送模块的实现过程比接收过程稍简单,因为发送模块无需考虑采集电平的位置,只需要在接收到发送使能之后,通过计算每一个bit的时间,按照顺序将数据发送出去即可。

​ 代码如下:

module uart_send (
    input   wire                clk,
    input   wire                rst_n,
    input   wire      [7:0]     uart_in_data,
    input   wire                uart_in_flag,
    output  reg                 uart_tx
);
parameter  SYSTERM_CLK = 50_000_000;               //系统时钟频率
parameter  UART_BPS    = 115200;                     //串口波特率
localparam BPS_COUNT_MAX   = SYSTERM_CLK/UART_BPS;     //为得到指定波特率
                                                   //需要对系统时钟计数BPS_COUNT次
reg      [12:0]      bps_count;//用于按照时钟计算一个字节的时间
reg      [3:0]       bit_count;//接收数据时用于计数接收到了多少位
reg                  bit_flag ;//在一个电平的中间位置产生高电平标志
reg                  work_en  ;//在本标志位高电平时,接受工作开始,在低电平时工作结束

//bps_count波特率计数器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bps_count <= 13'b0;
    end
    else    if ((bps_count ==BPS_COUNT_MAX - 1'b1) || (work_en == 1'b0)) begin
        bps_count <= 13'b0;
    end
    else    if (work_en == 1'b1) begin
        bps_count <= bps_count + 1'b1;
    end
    else
        bps_count <= bps_count;     
end
//work_en发送工作使能
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        work_en <= 1'b0;
    end
    else    if ((bit_count == 4'd9) && (bit_flag == 1'b1)) begin
        work_en <= 1'b0;
    end
    else    if (uart_in_flag == 1'b1) begin
        work_en <= 1'b1;
    end
    else
        work_en <= work_en;       
end
//bit_flag每一个bit的信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_flag <= 1'b0;
    end
    else    if (bps_count == 13'b1) begin
         bit_flag <= 1'b1;
    end
    else
        bit_flag <= 1'b0;
end
//bit_count字节计数
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_count <= 4'b0;
    end
    else    if ((bit_flag == 1'b1) && (work_en == 1'b1)) begin
        bit_count <= bit_count + 1'b1;
    end
    else    if ((bit_count == 4'd9) && (bit_flag == 1'b1)) begin
        bit_count <= 4'b0;
    end
    else
        bit_count <= bit_count;        
end
//uart_tx
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        uart_tx <= 1'b1;//串口传输,空闲时为高电平
    end
    else    if (bit_flag == 1'b1) begin
        case (bit_count)
            0:  uart_tx <= 1'b0           ;
            1:  uart_tx <= uart_in_data[0]; 
            2:  uart_tx <= uart_in_data[1]; 
            3:  uart_tx <= uart_in_data[2]; 
            4:  uart_tx <= uart_in_data[3]; 
            5:  uart_tx <= uart_in_data[4]; 
            6:  uart_tx <= uart_in_data[5]; 
            7:  uart_tx <= uart_in_data[6]; 
            8:  uart_tx <= uart_in_data[7]; 
            9:  uart_tx <= 1'b1           ;
            default uart_tx <= 1'b1       ;               
        endcase
    end
end
endmodule //uart_send

顶层模块

​ 顶层只需将发送和接收模块例化即可,同时加入了LED管脚,可以在发送和接收回环的过程中实现对4个LED灯的控制。

​ 代码如下:

module uart_top (
    input  wire clk,
    input  wire rst_n,
    input  wire uart_rx,
    output wire uart_tx,
    output wire [3:0] led
);
parameter  SYSTERM_CLK = 26'd50_000_000;               //系统时钟频率
parameter  UART_BPS    = 17'd115200;                     //串口波特率

wire       flag;
wire [7:0] data;
assign led = data[3:0];
uart_receive 
#(
    .SYSTERM_CLK   (SYSTERM_CLK   ),
    .UART_BPS      (UART_BPS      )
)
u_uart_receive(
    .clk          (clk          ),
    .rst_n        (rst_n        ),
    .uart_rx      (uart_rx      ),
    .receive_done (flag ),
    .uart_data    (data    )
);

uart_send 
#(
    .SYSTERM_CLK   (SYSTERM_CLK   ),
    .UART_BPS      (UART_BPS      )
)
u_uart_send(
    .clk          (clk          ),
    .rst_n        (rst_n        ),
    .uart_in_data (data ),
    .uart_in_flag (flag ),
    .uart_tx      (uart_tx      )
);


endmodule //uart_top

你可能感兴趣的:(FPGA学习,fpga,verilog)