目录
一、串口简介
二、设计与实现
串口数据回环顶层模块设计
串口接收模块uart_rx
串口发送模块uart_tx
顶层模块rs32_top
三、上板验证
其中SPI和I2C为同步通信接口,双方时钟频率相同。而UART属于异步通信接口,没有统一时钟,靠起始位和终止位来接收数据。
上图为 串口的通信方式,可以同时收发(全双工通信)。其中rx负责接收,tx负责发送,每次发送10bit数据(起始位+8bit数据+停止位),从最低位开始发送。
波特率为每秒钟传输的码元数量,单位为Bps。而比特率为每秒传输的bit个数,单位为bps。比特率=波特率x单个调制状态对应的二进制数。在串口中比特率=比特率x1。
亚稳态,与建立时间和保持时间有关(可以参考这篇博客数字电路中的亚稳态产生原因和处理方法_IamSarah的博客-CSDN博客):
可以使用多级寄存器来减小亚稳态的危害(多延迟几拍)。
串口传输的波特率为9600,系统的时钟频率为50MHz,那么可以知道传输一位的时间为5208个时钟周期:
接收模块时序图设计:
接口模块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
仿真结果:
发送模块时序图设计:
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代码:
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
仿真结果:
首先要绑定管脚:
我们使用的是usb转串口,引脚分配按下图所示:
原理图上N6所在位置:
E1所在位置:
M15所在位置:
N5所在位置:
烧写程序:
点击start即可下载程序:
板子连线如下,要注意的地方就是下图红框内的两个线帽要接正确:
下载完成后打开野火串口调试助手,按照下图配置:
打开串口后在下面窗口输入要发送的数据,点击发送后可以看到上面收到的数据无误:
至此,rs232串口的verilog实现及上板验证结束!