UART串口实验
一、实验任务
本节实验任务是上位机通过串口调试助手发送数据给 Zynq,Zynq PL 端通过 RS232 串口接收数据并将
接收到的数据发送给上位机,完成串口数据环回。
二、程序设计
- 串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
- UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receivertransmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换
成并行数据。 - 串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是 bps(位/秒),常用的波特率有 9600、19200、38400、57600 以及 115200 等。
- UART 负责完成数据的串并转换
设计文件
//顶层模块
module uart_loopback_top (
input sys_clk,
input sys_rst_n,
input recv_data_c ,
output send_data_c
);
//parameter define
parameter CLK_FREQ = 50000000; //定义系统时钟频率
parameter BPS = 115200; //定义串口波特率
wire [7:0] uart_send_data;
wire [7:0] uart_recv_data;
wire uart_done;
wire tx_busy;
wire uart_send_en;
uart_recv #(
.CLK_FREQ(CLK_FREQ),
.BPS (BPS )
) u_uart_recv (
.sys_clk (sys_clk ),
.sys_rst_n(sys_rst_n ),
.uart_rxd (recv_data_c),
.uart_data(uart_recv_data ),
.uart_done(uart_done )
);
uart_loop u_uart_loop (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_recv_data(uart_recv_data ),
.uart_recv_done(uart_done ),
.tx_busy (tx_busy ),
.uart_send_en (uart_send_en),
.uart_send_din (uart_send_data )
);
uart_send #(
.CLK_FREQ(CLK_FREQ),
.BPS (BPS )
) u_uart_send (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_en (uart_send_en),
.uart_din (uart_send_data ),
.uart_txd (send_data_c ),
.uart_tx_busy(tx_busy )
);
endmodule
//从上位机接收数据模块
module uart_recv (
input sys_clk,
input sys_rst_n,
input uart_rxd , //数据通道
output reg [7:0] uart_data,
output reg uart_done
);
parameter CLK_FREQ = 50000000; //时钟频率50MHZ
parameter BPS = 115200;
parameter BPS_CNT = CLK_FREQ / BPS; //传输一位需要的周期数
//localparameter 本地参数不可传递 parameter 可以在模块之间传递参数
reg rx_flag;
reg [15:0] clk_cnt;
reg [7:0] rx_data; //先将数据保存到寄存器中,当接收完成后再output
reg [3:0] rx_cnt;
wire start_flag;
reg uart_rxd_d0;
reg uart_rxd_d1;
//抓取数据通道的下降沿
assign start_flag = ~uart_rxd_d0 && uart_rxd_d1 ;
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
uart_rxd_d0 <= 1'd0;
uart_rxd_d1 <= 1'd0;
end
else begin
uart_rxd_d0 <= uart_rxd ;
uart_rxd_d1 <= uart_rxd_d0;
end
end
//开始接收信号
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
rx_flag <= 1'd0;
end
else begin
if (start_flag)
rx_flag <= 1'd1;
//停止位并不需要完整的一个BPS_CNT,所以取其一半作为判断条件
else if (rx_cnt == 4'd9 && (clk_cnt == BPS_CNT / 2)) begin
rx_flag <= 1'd0;
end
else
rx_flag <= rx_flag;
end
end
//时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
clk_cnt <= 16'd0;
end
else if (rx_flag) begin
if (clk_cnt == BPS_CNT-1) begin
clk_cnt <= 16'd0; //每数434个时钟周期接收一个bit
end
else
clk_cnt <= clk_cnt + 16'd1;
end
else begin
clk_cnt <= 16'd0;
end
end
//接收数据计数器
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
rx_cnt <= 4'd0;
end
else if (rx_flag) begin
if (clk_cnt == BPS_CNT-1)
rx_cnt <= rx_cnt + 1'd1;
else
rx_cnt <= rx_cnt;
end
else begin
rx_cnt <= 4'd0;
end
end
//接收数据
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
rx_data <= 8'd0;
end
else if (rx_flag) begin
if (clk_cnt == BPS_CNT/2) begin
//避免出现亚稳态,就等到稳定之后,可以选择捕获后的第二个寄存器来表示
case(rx_cnt)
4'd1 : rx_data[0] <= uart_rxd_d1;
4'd2 : rx_data[1] <= uart_rxd_d1;
4'd3 : rx_data[2] <= uart_rxd_d1;
4'd4 : rx_data[3] <= uart_rxd_d1;
4'd5 : rx_data[4] <= uart_rxd_d1;
4'd6 : rx_data[5] <= uart_rxd_d1;
4'd7 : rx_data[6] <= uart_rxd_d1;
4'd8 : rx_data[7] <= uart_rxd_d1;
default : ;
endcase
end
else
rx_data <= rx_data;
end
else begin
rx_data <= 8'd0;
end
end
//数据接收完,进行output并给出done信号
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
uart_data <= 8'd0;
uart_done <= 1'd0;
end
else if (rx_cnt == 4'd9) begin
uart_data <= rx_data;
uart_done <= 1'd1;
end
else begin
uart_data <= 8'd0;
uart_done <= 1'd0;
end
end
endmodule
//环回模块,对数据进行转发
module uart_loop (
input sys_clk,
input sys_rst_n,
input [7:0] uart_recv_data,
input uart_recv_done,
input tx_busy,
output reg uart_send_en,
output reg [7:0] uart_send_din
);
reg tx_ready;
wire en_flag;
reg uart_recv_done_d0;
reg uart_recv_done_d1;
//抓取发送使能信号的上升沿
assign en_flag = uart_recv_done_d0 && ~uart_recv_done_d1 ;
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
uart_recv_done_d0 <= 1'd0;
uart_recv_done_d1 <= 1'd0;
end
else begin
uart_recv_done_d0 <= uart_recv_done ;
uart_recv_done_d1 <= uart_recv_done_d0;
end
end
//传给发送模块
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
uart_send_en <= 1'd0;
uart_send_din <= 8'd0;
tx_ready <= 1'd0;
end
else begin
//这个捕获到的上升沿信号en_flag是一瞬间的
if (en_flag) begin
uart_send_en <= 1'd0;
tx_ready <= 1'd1; //这个信号很有必要,接收完进入准备状态
uart_send_din <= uart_recv_data;
end
//当发送模块不处于发送状态且环回模块准备好发送时,才可以发送
else if (~tx_busy && tx_ready) begin
tx_ready <= 1'd0; //只发送一次即可,已经发送了就需要复位准备下次发送
uart_send_en <= 1'd1;
end
end
end
endmodule
//从开发板将数据发送给上位机
//注意这里的发送模块是将数据发送给上位机
//发送的数据是来自环回模块传递来的数据
module uart_send (
input sys_clk,
input sys_rst_n,
input uart_en, //发送使能信号
input [7:0] uart_din, //并行的数据
output reg uart_txd, //将串行数据传给上位机
output uart_tx_busy
);
parameter CLK_FREQ = 50000000; //时钟频率50MHZ
parameter BPS = 115200;
parameter BPS_CNT = CLK_FREQ / BPS; //传输一位需要的周期数
//localparameter 本地参数不可传递 parameter 可以在模块之间传递参数
reg tx_flag;
reg [15:0] clk_cnt;
reg [ 7:0] tx_data; //先将数据保存到寄存器中
reg [ 3:0] tx_cnt;
wire en_flag;
reg uart_en_d0;
reg uart_en_d1;
//抓取发送使能信号的上升沿
assign en_flag = uart_en_d0 && ~uart_en_d1 ;
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
uart_en_d0 <= 1'd0;
uart_en_d1 <= 1'd0;
end
else begin
uart_en_d0 <= uart_en ;
uart_en_d1 <= uart_en_d0;
end
end
//开始发送信号
assign uart_tx_busy = tx_flag;
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
tx_flag <= 1'd0;
tx_data <= 8'd0;
end
else begin
if (en_flag) begin
tx_flag <= 1'd1;
tx_data <= uart_din;
end
//停止位并不需要完整的一个BPS_CNT
//发送完一次数据,清零
else if (tx_cnt == 4'd9 && (clk_cnt == (BPS_CNT - BPS_CNT/16) ) ) begin
tx_flag <= 1'd0;
tx_data <= 8'd0;
end
//没发完,即中间过程就继续保存不变
else begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
end
//时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
clk_cnt <= 16'd0;
end
else if (tx_flag) begin
if (clk_cnt == BPS_CNT-1) begin
clk_cnt <= 16'd0; //每数434个时钟周期接收一个bit
end
else
clk_cnt <= clk_cnt + 16'd1;
end
else begin
clk_cnt <= 16'd0;
end
end
//接收数据计数器
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
tx_cnt <= 4'd0;
end
else if (tx_flag) begin
if (clk_cnt == BPS_CNT-1)
tx_cnt <= tx_cnt + 1'd1;
else
tx_cnt <= tx_cnt;
end
else begin
tx_cnt <= 4'd0;
end
end
//发送数据,并转串
always @(posedge sys_clk or posedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
uart_txd <= 1'd1; //当开始接收前信号处于高电平
end
else if (tx_flag) begin
case(tx_cnt)
4'd0 : uart_txd <= 1'd0 ; //第一个低电平,开始接收
4'd1 : uart_txd <= tx_data[0] ;
4'd2 : uart_txd <= tx_data[1] ;
4'd3 : uart_txd <= tx_data[2] ;
4'd4 : uart_txd <= tx_data[3] ;
4'd5 : uart_txd <= tx_data[4] ;
4'd6 : uart_txd <= tx_data[5] ;
4'd7 : uart_txd <= tx_data[6] ;
4'd8 : uart_txd <= tx_data[7] ;
4'd9 : uart_txd <= 1'd1 ; //最后接收完,拉高
default : ;
endcase
end
else begin
uart_txd <= 1'd1;
end
end
endmodule
tb文件
module tb_uart_loopback_top ();
//激励信号一般定义为 reg型
reg sys_clk ;
reg sys_rst_n ;
reg recv_data_c;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#100
sys_rst_n = 1'b1;
#8680 recv_data_c = 1'b0;
#8680 recv_data_c = 1'b1;
#8680 recv_data_c = 1'b1;
#8680 recv_data_c = 1'b0;
#8680 recv_data_c = 1'b0;
#8680 recv_data_c = 1'b1;
#8680 recv_data_c = 1'b0;
#8680 recv_data_c = 1'b1;
#8680 recv_data_c = 1'b0;
#8680 recv_data_c = 1'b1;
end
always #10 sys_clk = ~sys_clk;
uart_loopback_top u_uart_loopback_top (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.recv_data_c (recv_data_c ),
.send_data_c (send_data_c )
);
endmodule