Verilog学习笔记(串口RS232,基于野火教程)

目录

一、串口简介  

 二、设计与实现

串口数据回环顶层模块设计

串口接收模块uart_rx

串口发送模块uart_tx

顶层模块rs32_top

三、上板验证  


一、串口简介  

Verilog学习笔记(串口RS232,基于野火教程)_第1张图片

其中SPI和I2C为同步通信接口,双方时钟频率相同。而UART属于异步通信接口,没有统一时钟,靠起始位和终止位来接收数据。

Verilog学习笔记(串口RS232,基于野火教程)_第2张图片

上图为 串口的通信方式,可以同时收发(全双工通信)。其中rx负责接收,tx负责发送,每次发送10bit数据(起始位+8bit数据+停止位),从最低位开始发送。

Verilog学习笔记(串口RS232,基于野火教程)_第3张图片

 Verilog学习笔记(串口RS232,基于野火教程)_第4张图片

波特率为每秒钟传输的码元数量,单位为Bps。而比特率为每秒传输的bit个数,单位为bps。比特率=波特率x单个调制状态对应的二进制数。在串口中比特率=比特率x1。

 Verilog学习笔记(串口RS232,基于野火教程)_第5张图片

 二、设计与实现

亚稳态,与建立时间和保持时间有关(可以参考这篇博客数字电路中的亚稳态产生原因和处理方法_IamSarah的博客-CSDN博客):

Verilog学习笔记(串口RS232,基于野火教程)_第6张图片

可以使用多级寄存器来减小亚稳态的危害(多延迟几拍)。

串口传输的波特率为9600,系统的时钟频率为50MHz,那么可以知道传输一位的时间为5208个时钟周期: 

 Verilog学习笔记(串口RS232,基于野火教程)_第7张图片

串口数据回环顶层模块设计

Verilog学习笔记(串口RS232,基于野火教程)_第8张图片

串口接收模块uart_rx

Verilog学习笔记(串口RS232,基于野火教程)_第9张图片

接收模块时序图设计:

Verilog学习笔记(串口RS232,基于野火教程)_第10张图片

接口模块verilog代码:

module uart_rx
#(
    parameter UART_BPS = 'd9600         , //波特率
    parameter CLK_FREQ = 'd50_000_000     //系统时钟频率
)
(
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire            rx          ,
    
    output  reg    [7:0]   po_data     ,
    output  reg            po_flag
);

parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;

//将rx信号延迟三拍,可以减少亚稳态的影响
reg          rx_reg1;
reg          rx_reg2;
reg          rx_reg3;

reg          start_flag; //起始位开始
reg          work_en; //计数使能信号
reg  [15:0]  baud_cnt; //计数器,每个码元到来的间隔时间
reg          bit_flag;
reg  [3:0]   bit_cnt;
reg  [7:0]   rx_data;
reg          rx_flag;

//rx信号延迟三拍,减少亚稳态的影响
always@(posedge sys_clk) begin
    rx_reg1 <= rx;
    rx_reg2 <= rx_reg1;
    rx_reg3 <= rx_reg2;
end

//接收数据开始信号start_flag
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        start_flag <= 1'b0;
    end
    else if((rx_reg3 == 1'b1) && (rx_reg2 == 1'b0) && (work_en == 1'b0)) begin
        start_flag <= 1'b1;
    end
    else begin
        start_flag <= 1'b0;
    end
end

//使能信号work_en
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        work_en <= 1'b0;
    end
    else if(start_flag == 1'b1) begin
        work_en <= 1'b1;
    end
    else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))begin
        work_en <= 1'b0;
    end
    else begin
        work_en <= work_en;
    end
end

//计数
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        baud_cnt <= 16'd0;
    end
    else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0)) begin
        baud_cnt <= 16'd0;
    end
    else begin
        baud_cnt <= baud_cnt + 1'b1;
    end
end

//让bit_flag在中间时刻拉高,中间时刻数据更稳定
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        bit_flag <= 1'b0;
    end
    else if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
        bit_flag <= 1'b1;
    end
    else begin
        bit_flag <= 1'b0;
    end
end

//bit计数器,计算收到的bit数目
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        bit_cnt <= 4'd0;
    end
    else if((bit_cnt == 4'd8) && (bit_flag == 1'b1)) begin
        bit_cnt <= 4'd0;
    end
    else if(bit_flag == 1'b1) begin
        bit_cnt <= bit_cnt + 1'b1;
    end
    else begin
        bit_cnt <= bit_cnt;
    end
end

//rx_data
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        rx_data <= 8'd0;
    end
    else if((bit_flag == 1'b1) && (bit_cnt != 4'd0)) begin
        rx_data <= {rx_reg3, rx_data[7:1]};
    end
    else begin
        rx_data <= rx_data;
    end
end

//rx_flag
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        rx_flag <= 1'b0;
    end
    else if((bit_cnt == 4'd8) && (bit_flag == 1'b1)) begin
        rx_flag <= 1'b1;
    end
    else begin
        rx_flag <= 1'b0;
    end
end

always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        po_data <= 8'd0;
        po_flag <= 1'b0;
    end
    else if(rx_flag == 1'b1)begin
        po_data <= rx_data;
        po_flag <= 1'b1;
    end
    else begin
        po_flag <= 1'b0;
    end
end

endmodule

仿真testbench代码:

`timescale 1ns/1ns
module tb_uart_rx();

reg sys_clk;
reg sys_rst_n;
reg rx;
wire [7:0] po_data;
wire       po_flag;

always #10 sys_clk = ~sys_clk;

task rx_bit(
    input   [7:0]   data
);
    integer i;
    for(i = 0;i < 10;i = i + 1) begin
        case(i)
            0:rx <= 1'b0;
            1:rx <= data[0];
            2:rx <= data[1];
            3:rx <= data[2];
            4:rx <= data[3];
            5:rx <= data[4];
            6:rx <= data[5];
            7:rx <= data[6];
            8:rx <= data[7];
            9:rx <= 1'b1;
        endcase
        #(5208*20);
    end
endtask

initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    rx <= 1'b1;
    #20
    sys_rst_n <= 1'b1;
    
end

initial begin
    #200
    rx_bit(8'd0);
    rx = 1'b1;
    #200
    rx_bit(8'd1);
    rx = 1'b1;
    #200
    rx_bit(8'd2);
    rx = 1'b1;
    #200
    rx_bit(8'd3);
    rx = 1'b1;
    #200
    rx_bit(8'd4);
    rx = 1'b1;

end

uart_rx
#(
    .UART_BPS('d9600)         , //波特率
    .CLK_FREQ('d50_000_000)     //系统时钟频率缩小100倍
)
uart_rx_inst
(
    .sys_clk        (sys_clk)       ,
    .sys_rst_n      (sys_rst_n)     ,
    .rx             (rx)            ,
    .po_data        (po_data)       ,
    .po_flag        (po_flag)
);

endmodule

仿真结果:

串口发送模块uart_tx

 Verilog学习笔记(串口RS232,基于野火教程)_第11张图片

发送模块时序图设计:

Verilog学习笔记(串口RS232,基于野火教程)_第12张图片 模块verilog代码:

module uart_tx
#(
    parameter UART_BPS = 'd9600         , //波特率
    parameter CLK_FREQ = 'd50_000_000     //系统时钟频率
)
(
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire   [7:0]    pi_data     ,
    input   wire            pi_flag     ,
    output  reg             tx
);

parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;

reg          work_en; //计数使能信号
reg  [15:0]  baud_cnt; //计数器,每个码元到来的间隔时间
reg          bit_flag;
reg  [3:0]   bit_cnt;
reg  [7:0]   pi_data_reg;

always@(posedge sys_clk) begin
    if(pi_flag == 1'b1) begin
        pi_data_reg <= pi_data;
    end
    else if(bit_cnt != 4'd0 && bit_cnt != 4'd9 && bit_flag == 1'b1) begin
        pi_data_reg[6:0] <= pi_data_reg[7:1];
    end
    else begin
        pi_data_reg <= pi_data_reg;
    end
end

//work_en信号控制
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        work_en <= 1'b0;
    end
    else if(pi_flag == 1'b1) begin
        work_en <= 1'b1;
    end
    else if(work_en == 1'b1 && bit_flag == 1'b1 && bit_cnt == 4'd9) begin
        work_en <= 1'b0;
    end
    else begin
        work_en <= work_en;
    end
end

//baud_cnt计数器逻辑
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        baud_cnt <= 16'd0;
    end
    else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0)) begin
        baud_cnt <= 16'd0;
    end
    else begin
        baud_cnt <= baud_cnt + 1'b1;
    end
end

//bit_flag信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        bit_flag <= 1'b0;
    end
    else if(work_en == 1'b1 && baud_cnt == 16'd0) begin
        bit_flag <= 1'b1;
    end
    else begin
        bit_flag <= 1'b0;
    end
end

//bit_cnt信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        bit_cnt <= 4'd0;
    end
    else if(bit_cnt == 4'd9 && bit_flag == 1'b1) begin
        bit_cnt <= 4'd0;
    end
    else if(bit_flag == 1'b1)begin
        bit_cnt <= bit_cnt + 1'b1;
    end
    else begin
        bit_cnt <= bit_cnt;
    end
end

//tx信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        tx <= 1'b1; //空闲状态下为1
    end
    else if(bit_cnt == 4'd0 && bit_flag == 1'b1) begin
        tx <= 1'b0;
    end
    else if(bit_cnt != 4'd0 && bit_cnt != 4'd9 && bit_flag == 1'b1)begin
        tx <= pi_data_reg[0:0];
    end
    else if(bit_cnt == 4'd9 && bit_flag == 1'b1) begin
        tx <= 1'b1;
    end
    else begin
        tx <= tx;
    end
end

endmodule

仿真代码:

`timescale 1ns/1ns
module tb_uart_tx();

reg sys_clk;
reg sys_rst_n;
wire tx;
reg [7:0] pi_data;
reg       pi_flag;

always #10 sys_clk = ~sys_clk;


initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    #20
    sys_rst_n <= 1'b1;
    
end

initial begin
    #2000
    pi_data <= 8'b1010_1010;
    pi_flag <= 1'b1;
    #20
    pi_flag <= 1'b0;
end

uart_tx
#(
    .UART_BPS('d9600)         , //波特率
    .CLK_FREQ('d50_000_000)     //系统时钟频率缩小100倍
)
uart_tx_inst
(
    .sys_clk        (sys_clk)       ,
    .sys_rst_n      (sys_rst_n)     ,
    .pi_data        (pi_data)       ,
    .pi_flag        (pi_flag)       ,
    .tx             (tx)
);

endmodule

 仿真结果:Verilog学习笔记(串口RS232,基于野火教程)_第13张图片

顶层模块rs32_top

Verilog学习笔记(串口RS232,基于野火教程)_第14张图片

模块verilog代码:

module rs232_top(
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire            rx          ,
    output  wire            tx
);

wire [7:0] data;
wire data_flag;

uart_rx
#(
    .UART_BPS('d9600)         , //波特率
    .CLK_FREQ('d50_000_000)     //系统时钟频率
) 
uart_rx_inst
(
    .sys_clk(sys_clk)     ,
    .sys_rst_n(sys_rst_n)   ,
    .rx(rx)          ,
    
    .po_data(data)     ,
    .po_flag(data_flag)
);

uart_tx
#(
    .UART_BPS('d9600)        , //波特率
    .CLK_FREQ('d50_000_000)     //系统时钟频率
) 
uart_tx_inst
(
    .sys_clk(sys_clk)     ,
    .sys_rst_n(sys_rst_n)   ,
    .pi_data(data)     ,
    .pi_flag(data_flag)     ,
    .tx(tx)
);

endmodule

testbench代码:

module tb_rs232();

reg sys_clk;
reg sys_rst_n;
reg rx;
wire tx;

always #10 sys_clk = ~sys_clk;

task rx_bit(
    input   [7:0]   data
);
    integer i;
    for(i = 0;i < 10;i = i + 1) begin
        case(i)
            0:rx <= 1'b0;
            1:rx <= data[0];
            2:rx <= data[1];
            3:rx <= data[2];
            4:rx <= data[3];
            5:rx <= data[4];
            6:rx <= data[5];
            7:rx <= data[6];
            8:rx <= data[7];
            9:rx <= 1'b1;
        endcase
        #(5208*20);
    end
endtask

initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    #20
    sys_rst_n <= 1'b1;
    rx <= 1'b1;
end

initial begin
    #200
    rx_bit(8'd0);
    rx = 1'b1;
    #200
    rx_bit(8'd1);
    rx = 1'b1;
    #200
    rx_bit(8'd2);
    rx = 1'b1;
    #200
    rx_bit(8'd3);
    rx = 1'b1;
    #200
    rx_bit(8'd4);
    rx = 1'b1;
end

rs232_top rs232_top_inst(
    .sys_clk(sys_clk)     ,
    .sys_rst_n(sys_rst_n)   ,
    .rx(rx)          ,
    .tx(tx)
);
endmodule

仿真结果: 

Verilog学习笔记(串口RS232,基于野火教程)_第15张图片

三、上板验证  

首先要绑定管脚:

Verilog学习笔记(串口RS232,基于野火教程)_第16张图片

我们使用的是usb转串口,引脚分配按下图所示:

原理图上N6所在位置:

Verilog学习笔记(串口RS232,基于野火教程)_第17张图片

E1所在位置:

Verilog学习笔记(串口RS232,基于野火教程)_第18张图片

 M15所在位置:

Verilog学习笔记(串口RS232,基于野火教程)_第19张图片

N5所在位置:

Verilog学习笔记(串口RS232,基于野火教程)_第20张图片

烧写程序:

Verilog学习笔记(串口RS232,基于野火教程)_第21张图片

 点击start即可下载程序:

Verilog学习笔记(串口RS232,基于野火教程)_第22张图片

板子连线如下,要注意的地方就是下图红框内的两个线帽要接正确:

Verilog学习笔记(串口RS232,基于野火教程)_第23张图片

 下载完成后打开野火串口调试助手,按照下图配置: 

Verilog学习笔记(串口RS232,基于野火教程)_第24张图片

打开串口后在下面窗口输入要发送的数据,点击发送后可以看到上面收到的数据无误: 

Verilog学习笔记(串口RS232,基于野火教程)_第25张图片

至此,rs232串口的verilog实现及上板验证结束!

你可能感兴趣的:(FPGA学习,Verilog学习,学习,笔记,fpga开发)