基于Verilog实现uart串口回环
一、uart串口通信
1、基本概念
UART,全称Universal Asynchronous Receiver/Transmitter,通用异步收发传输器。是电脑硬件的一部分,将资料由串行通信与并行通信间作传输转换。具体实物表现为独立的模块化芯片,或作为集成于微处理器中的周边设备。一般和RS-232C规格的,类似Maxim的MAX232之类的标准信号幅度变换芯片进行搭配,作为连接外部设备的接口。
- 实现数据的串并转换;
- 全双工(可以同时进行数据的双向传输)的异步通信;
- 发送数据时,将并行数据转换为串行数据进行传输;
- 接收数据时,将串行数据转换为并行数据。
- UART串口收发的原理与Verilog实现
2、串口回环
流程图
如图,串口回环流程。
上位机发送数据 ,由串口接收端接收,传到控制模块,而后在传给发送模块,发送模块发送数据后,传给上位机,以此循环,从而实现串口数据收发的回环操作。
3、时序图
串口接收模块
串口发送模块
4、FIFO简述
First Input First Output的缩写,先入先出队列,这是一种传统的按序执行方法,先进入的指令先完成并引退,跟着才执行第二条指令。
FIFO阈值设置及深度计算原理
5、相关端口信号说明
端口信号 | 说明 |
---|---|
clk | 系统时钟(50Mhz) |
rst_n | 复位信号 |
uart_rxd | uart接收端口 |
uart_txd | uart发送端口 |
rx_data | 接收端口接收数据 |
rx_data_vld | 接收端口接收数据有效 |
din | 控制模块从接收端的数据输入 |
ack | 应答信号 |
dout | 数据输出 |
tx_req | 发送数据请求 |
tx_dout | 发送端口输出数据 |
二、工程及实现
1、工程创建
可参考上篇博客,有关创建描述的很清楚,这里不再赘述。
数码管实现秒表计数
2、代码实现
1、串口接收模块
uart_rx.v
`include "cfg.v"
module uart_rx
(
input clk ,
input rst_n ,
input uart_rxd ,//接收端接收数据
output [7:0] rx_data ,//接收到的数据
output rx_data_vld //接收数据有效
);
//信号定义
reg [12:0] cnt_baud ;//波特率计数器 50_000_000/9600
wire add_cnt_baud ;
wire end_cnt_baud ;
reg [3:0] cnt_bit ;//bit计数器 记录传输数据bit
wire add_cnt_bit ;
wire end_cnt_bit ;
reg rx_flag ;//数据接收标志信号
reg [9:0] rx_din ;//数据采样寄存器 由上位机发送,从机接收到的数据
/*
reg rx_r0 ;//同步
reg rx_r1 ;//打拍
*/
reg [1:0] rxd_r ;//同步 打拍
wire nedge ;//下降沿
//计数器
//cnt_baud
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_baud <= 0;
end
else if(add_cnt_baud)begin
if(end_cnt_baud)begin
cnt_baud <= 0;
end
else begin
cnt_baud <= cnt_baud + 1;
end
end
end
assign add_cnt_baud = rx_flag;//接收数据标志信号开始时,波特率计数器开始计数
assign end_cnt_baud = add_cnt_baud && cnt_baud == `BAUD/`CLK_FREQ;
//cnt_bit
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = end_cnt_baud;
assign end_cnt_bit = add_cnt_bit && (cnt_bit == 10-1 || rx_din[0] == 1'b1);//数据接收完成或者控制模块开始采样数据(数据位 8 加起始位 停止位)
//同步、打拍 rx_r0/r1
/*
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_r0 <= 1'b1;
rx_r1 <= 1'b1;
end
else begin
rx_r0 <= uart_rxd;//同步
rx_r1 <= rx_r0;//打拍
end
end
assign nedge = ~rx_r0 & rx_r1;//检测下降沿
*/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rxd_r <= 1'b1;
end
else begin
rxd_r <= {
rxd_r[0],uart_rxd};
end
end
assign nedge = ~rxd_r[0] & rxd_r[1];//检测下降沿
//rx_flag
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_flag <= 1'b0;
end
else if(nedge)begin
rx_flag <= 1'b1;//检测到下降沿 接收数据开始
end
else if(end_cnt_bit)begin
rx_flag <= 1'b0;//bit计数器计时结束 表明数据传输完成 拉低flag
end
end
//接收数据 rx_din
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_din <= 0;
end
else if(add_cnt_baud && cnt_baud == ((`CLK_FREQ>>1) / `BAUD))begin
// rx_din[cnt_bit] <= rx_r0;//从当前时钟开始采样数据
rx_din[cnt_bit] <= rxd_r[0];
end
end
assign rx_data = rx_din[8:1];
assign rx_data_vld = end_cnt_bit && rx_din[0] == 1'b0;
endmodule
2、串口发送模块
uart_tx.v
`include "cfg.v"
module uart_tx
(
input clk ,
input rst_n ,
input tx_req ,//数据发送请求
input [7:0] tx_din ,//发送数据输入
output tx_rdy ,//准备好数据发送
output tx_dout //数据输出
);
//信号定义
reg [12:0] cnt_baud ;//波特率计数器
wire add_cnt_baud ;
wire end_cnt_baud ;
reg [3:0] cnt_bit ;//bit计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg tx_flag ;//数据发送标志
reg [9:0] tx_data ;//传输数据寄存
reg tx_bit ;//传输bit
//计数器
//cnt_baud
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_baud <= 0;
end
else if(add_cnt_baud)begin
if(end_cnt_baud)begin
cnt_baud <= 0;
end
else begin
cnt_baud <= cnt_baud + 1;
end
end
end
assign add_cnt_baud = tx_flag;
assign end_cnt_baud = add_cnt_baud && cnt_baud == `CLK_FREQ/`BAUD;
//cnt_bit
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = end_cnt_baud;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10-1;
//tx_flag
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_flag <= 1'b0;
end
else if(tx_req)begin
tx_flag <= 1'b1;//接收到数据发送请求 开始发送数据
end
else if(end_cnt_bit)begin
tx_flag <= 1'b0;//bit计数结束 拉低flag
end
end
//tx_data
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data <= 0;
end
else if(tx_req)begin//收到请求 开始发送数据
tx_data <= {1'b1,tx_din,1'b0};//数据拼接 1'b1 停止位 1'b0 起始位
end
end
//tx_bit
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_bit <= 1'b1;
end
else if(add_cnt_baud && cnt_baud == 1)begin
tx_bit <= tx_data[cnt_bit];//将寄存的数据以bit形式传输 循环左移
end
end
//tx_rdy
assign tx_rdy = ~tx_flag;//发送模块处于非工作状态,可以进行数据发送
//tx_dout
assign tx_dout = tx_bit;//输出等于传输的bit数据
endmodule
3、串口控制模块
uart_ctrl.v
module uart_ctrl(
input clk ,
input rst_n ,
input [7:0] din ,//接收模块输入数据
input ack ,//发送数据应答信号
input din_vld ,//接收模块输入数据有效
output [7:0] dout ,//发送模块数据输出
output dout_vld //发送模块输出数据有效
);
//信号定义
reg rd_req ;
wire wr_req ;
wire empty ;
wire full ;
wire [7:0] q_out ;
wire [4:0] usedw ;
reg rd_fifo ;//读fifo标志信号
//rd_fifo
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rd_fifo <= 1'b0;
end
else if(usedw > 4)begin
rd_fifo <= 1'b1;
end
else if(empty)begin
rd_fifo <= 1'b0;
end
end
//rd_req
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rd_req <= 1'b0;
end
else if(rd_fifo && ack)begin
rd_req <= 1'b1;
end
else begin
rd_req <= 1'b0;
end
end
//dout
assign dout = q_out;
//dout_vld
assign dout_vld = rd_req;
//fifo例化
uart_fifo u_uart_fifo(
.aclr (~rst_n ),
.clock (clk ),
.data (din ),
.rdreq (rd_req ),
.wrreq (wr_req ),
.empty (empty ),
.full (full ),
.q (q_out ),
.usedw (usedw )
);
assign wr_req = full == 1'b0 && din_vld;
endmodule
4、顶层模块
uart.v
module uart(
input clk ,//系统时钟
input rst_n ,//复位
input uart_rxd ,//串口接收端数据
output uart_txd //串口发送端数据
);
//中间信号定义
wire [7:0] rx_byte ;
wire rx_byte_vld ;
wire [7:0] tx_byte ;
wire tx_byte_vld ;
wire tx_rdy ;
//串口接收模块 uart_rx
uart_rx u_uart_rx
(
.clk (clk ),
.rst_n (rst_n ),
.uart_rxd (uart_rxd ),//串口接收模块接收端数据
.rx_data (rx_byte ),//接收到的数据 接收的是由上位机发送的数据
.rx_data_vld (rx_byte_vld ) //接收数据有效
);
//串口控制模块 uart_ctrl
uart_ctrl u_uart_ctrl
(
.clk (clk ),
.rst_n (rst_n ),
.din (rx_byte ),//串口控制模块的输入 接收模块接收的数据
.ack (tx_rdy ),//发送数据应答信号 发送数据准备
.din_vld (rx_byte_vld ),
.dout (tx_byte ),//数据输出 控制模块控制发送模块发送数据
.dout_vld (tx_byte_vld )
);
//串口发送模块 uart_tx
uart_tx u_uart_tx
(
.clk (clk ),
.rst_n (rst_n ),
.tx_req (tx_byte_vld ),//数据发送请求 发送数据有效
.tx_din (tx_byte ),
.tx_rdy (tx_rdy ),//准备好数据发送
.tx_dout (uart_txd )
);
endmodule
5、波特率定义
cfg.v
//波特率定义
//`define BAUD_RATE_9600
//`define BAUD_RATE_19200
//`define BAUD_RATE_38400
//`define BAUD_RATE_57600
`define BAUD_RATE_115200
//时钟频率
`define CLK_FREQ 50_000_000
`ifdef BAUD_RATE_9600
`define BAUD 9600
`elsif BAUD_RATE_19200
`define BAUD 19200
`elsif BAUD_RATE_38400
`define BAUD 38400
`elsif BAUD_RATE_57600
`define BAUD 57600
`elsif BAUD_RATE_115200
`define BAUD 115200
`endif
3、引脚脚本
#时钟复位
set_location_assignment PIN_E1 -to clk
set_location_assignment PIN_E15 -to rst_n
#UART
set_location_assignment PIN_M2 -to uart_rxd
set_location_assignment PIN_G1 -to uart_txd
4、相关说明
FIFO例化
本文是调用quartus里面的FIFO,需要自己去配置,利用FIFO先进先出的特点,例化到控制模块。
两种同步打拍方式
时钟频率 与波特率关系
三、效果展示
将工程全编译而后烧录到开发板
打开串口调试助手
在串口输入要发送的数据,打开串口
点击发送,查看效果
也可以设置定时发送