ZYNQ初识10(zynq_7010)UART通信实验
基于bi站正点原子讲解视频:
以下,是串口接收端的波形图,系统时钟和波特率时钟不同,为异步时钟,,需要先延时两拍,将时钟同步过来,取到start_flag信号,由start_flag信号结合clk_cnt、bps_cnt两个计数器取到rx_flag信号,随后在rx_flag高电平时计算clk_cnt以及bps_cnt两个信号。最后两个信号uart_done、uart_data则在串口发送模块有所体现。
实际上,uart_done在串口发送模块中也就是uart_en信号,而uart_data也就是发送模块中的uart_din信号。
`timescale 1ns / 1ps
// Create Date: 2025/01/06 09:38:08
// Design Name:
// Module Name: uart_recv
module uart_recv(
input sys_clk , //50Mhz系统时钟
input sys_rst_n ,
input uart_rxd , //接收到的数据
output reg [7:0] uart_data , //输出的并行数据
output reg uart_done //一帧信号接收完成
);
parameter sys_freq = 50_000_000;
parameter uart_bps = 115_200;
parameter bps_cnt = sys_freq/uart_bps - 1;//从0开始计算
reg uart_rxd_d0;
reg uart_rxd_d1;
wire start_flag;
reg rx_flag;
reg [15:0] clk_cnt;
reg [3:0] rx_cnt;
reg [7:0] rx_data;//中间变量存储提取到的每个位的数据来实现串口端的串并转换
//由高电平向低电平的跳变(下降沿),相当于d1延时2个时钟周期;d0延时1个时钟周期
//因此判断d1是否为高且d0是否为低即可。
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);
//异步时钟同步化处理
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
uart_rxd_d0 <= 1'b1;
uart_rxd_d1 <= 1'b1;
end
else
begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
end
end
//rx_flag
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
rx_flag <= 1'b0;
else if(start_flag == 1'b1)
rx_flag <= 1'b1;
else if((rx_cnt == 4'd9) && (clk_cnt == bps_cnt/2 - 1'b1))
//为监测到下一帧数据的起始位留半个周期的时间
rx_flag <= 1'b0;
else
rx_flag <= 1'b1;
end
//clk_cnt
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
clk_cnt <= 16'd0;
else if(rx_flag == 1'b1)
// begin //效果相同否?
// if(clk_cnt == uart_bps)
// clk_cnt <= 16'd0;
// else
// clk_cnt <= clk_cnt + 16'd1;
// end
begin
if(clk_cnt < uart_bps - 16'd1)
clk_cnt <= clk_cnt + 16'd1;
else
clk_cnt <= 16'd0;
end
else
clk_cnt <= 16'd0;
end
//rx_cnt
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
rx_cnt <= 4'd0;
else if(rx_flag == 1'b1)
begin
if(clk_cnt == uart_bps - 16'd1)
rx_cnt <= rx_cnt + 4'd1;
else
rx_cnt <= rx_cnt;
end
else
rx_cnt <= 4'd0;
end
//rx_data
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
rx_data <= 8'd0;
else if((rx_flag == 1'b1)&&(clk_cnt == uart_bps/2))
begin
case(rx_cnt)//数据的串转并_uart_rxd是异步信号
//所以要需要两拍之后的信号uart_rxd_d1。
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;
endcase
end
end
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
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
以下:如何在程序中捕获信号的高低电平:(可实现异步时钟的同步处理以及信号的边沿检测)
d0 <= 1'b1;
d1 <= 1'b1;
d0 <= uart_rxd;
d1 <= d0;
也就是说,在初始状态时将d0、d1拉高,通过两次打拍将d0延时1个时钟周期,d1延时2个时钟周期,从而根据d0和d1的状态,设置wire型的标志位flag来捕获需要的高低电平。
以下,是是串口发送端的波形图,整体过程和接收端基本类似,只需要进行某些变量名称的修改即可,但需要注意,接收到的数据实际上是串口接收端的发送的数据(uart_data);uart_done信号也在发送端检测边沿电平过程中以uart_en的形式完成了两次打拍延迟。
`timescale 1ns / 1ps
// Create Date: 2025/01/06 14:50:27
// Design Name:
// Module Name: uart_send
module uart_send(
input sys_clk , //50Mhz系统时钟
input sys_rst_n ,
output reg uart_txd , //准备发送的数据
input uart_en ,
input [7:0] uart_din //从发送模块接收到的数据
);
parameter sys_freq = 50_000_000;
parameter uart_bps = 115_200;
parameter bps_cnt = sys_freq/uart_bps - 1;//从0开始计算
reg uart_en_d0;
reg uart_en_d1;
reg tx_flag;
reg [15:0] clk_cnt;
reg [3:0] tx_cnt;
reg [7:0] tx_data;//中间变量存储提取到的每个位的数据来实现串口端的串并转换
wire en_flag;
//由高电平向低电平的跳变(下降沿),相当于d1延时2个时钟周期;d0延时1个时钟周期
//因此判断d1是否为高且d0是否为低即可。
assign en_flag = (~uart_en_d1) & uart_en_d0;
//边沿信号检测,检测上升沿
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else
begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
//tx_data
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
tx_data <= 8'd0;
else if(en_flag == 1'b1)
tx_data <= uart_din;
else
tx_data <= tx_data;
end
//tx_flag
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
tx_flag <= 1'b0;
else if(en_flag == 1'b1)
tx_flag <= 1'b1;
else if((tx_cnt == 4'd9) && (clk_cnt == bps_cnt/2 - 1))
tx_flag <= 1'b0;
else
//tx_flag <= tx_flag; //这两句话在这里作用相同否?
tx_flag <= 1'b1;
end
//clk_cnt
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
clk_cnt <= 16'd0;
else if(tx_flag == 1'b1)
// begin
// if(clk_cnt == uart_bps)
// clk_cnt <= 16'd0;
// else
// clk_cnt <= clk_cnt + 16'd1;
// end
begin
if(clk_cnt < uart_bps - 16'd1)
clk_cnt <= clk_cnt + 16'd1;
else
clk_cnt <= 16'd0;
end
else
clk_cnt <= 16'd0;
end
//tx_cnt
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
tx_cnt <= 4'd0;
else if(tx_flag == 1'b1)
begin
if(clk_cnt == uart_bps - 16'd1)
tx_cnt <= tx_cnt + 4'd1;
else
tx_cnt <= tx_cnt;
end
else
rx_cnt <= 4'd0;
end
//uart_txd
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
uart_txd <= 1'b1;
else if((tx_flag == 1'b1)&&(clk_cnt == 16'd0))
begin
case(tx_cnt)//数据并转串,首先判断起始位,赋值低电平;
//随后将tx_data一次赋值给输出端口uart_txd;
//最后注意一帧数据的停止位,赋值高电平。
4'd0: uart_txd <= 1'b0;
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'b1;
endcase
end
end
endmodule
以下是顶层文件,分别将串口发送端和接收端两部分程序例化。
`timescale 1ns / 1ps
// Create Date: 2025/01/06 15:49:15
// Design Name:
// Module Name: top_uart
module top_uart(
input sys_clk , //50Mhz系统
input sys_rst_n ,
input uart_rxd , //接收到的数据
output uart_txd //准备发送的数据
);
wire [7:0] uart_data;
wire uart_done;
//串口接收模块的例化:
uart_recv uart_recv_u(
.sys_clk (sys_clk ) , //50Mhz系统时钟
.sys_rst_n (sys_rst_n ) ,
.uart_rxd (uart_rxd ) , //接收到的数据
.uart_data (uart_data ) , //输出的并行数据
.uart_done (uart_done ) //一帧信号接收完成
);
// wire uart_en ;
// wire [7:0] uart_din;
//串口发送模块的例化;
uart_send uart_send_u(
.sys_clk (sys_clk ) , //50Mhz系统时钟
.sys_rst_n (sys_rst_n) ,
.uart_txd (uart_txd ) , //准备发送的数据
.uart_en (uart_done ) ,
.uart_din (uart_data ) //从发送模块接收到的数据
);
endmodule
以下是对应程序的RTL视图:
另注意:
打两拍的异步时钟的同步处理,打一拍是为了将异步时钟转为同步时钟,如上本例子是将波特率时钟(115200bps)转换为系统时钟50Mhz,打两拍则是为了消除亚稳态(0 1之间的状态)。
参考连接:【Chips】跨时钟域的亚稳态处理、为什么要打两拍不是打一拍、为什么打两拍能有效?_跨时钟域为什么打两拍-CSDN博客