一、uart的基本知识(参考正点原子视频)
UART是一种采用异步串行通信方式的通用异步收发传输器。功能:在发送数据时将并行数据转换为串行数据来传输,在接收数据时将接收的串行数据转换成并行数据。
异步通信和同步通信最大的区别就是时候带有同步时钟信号
奇偶检验位是检验数据时候真实可靠,如果是奇数校验的话根据前面的有效数据1的个数在校验位补上0或者1,来保证1的个数是奇数;偶校验一样的原理,保证1的个数为偶数。有效数据的个数可以为5、6、7、8,常用的是8位。停止位可以是1、1.5、2个时钟周期的高电平
二、uart实验【参考:链接】
1. 接收模块代码
uart串口通信是一种异步串行的全双工通信方式,tx端用于数据发送,rx端用于数据的接收。信号线空闲时为高电平。由于是异步通信,数据发送会包装成数据帧的形式发送,帧格式为:1个起始位(0)、8个数据位(用户数据)、1个奇偶校验位(用于简单纠错以保证传送的可靠性)、1或者2两个停止位(1个),其中奇偶校验位不是必须的。
- 检测数据的发送:由图可知数据帧格式第一个bit是低电平,当FPGA的rx端检测到信号线上有下降沿产生时,表示有数据传送过来,根据预先设置的波特率对数据接收,由于数据是串行从低位到高位传输,接收到的数据暂时存储到寄存器中,等到接收完1个字节的数据,通过串并转换保存接收到的数据
- 发送时通过tx信号线按照设置好的波特率将数据发送出去,数据按照数据帧的格式发送,即先发送1bit的低电平,再从低位到高位发送数据。
module uart_rx(
input sys_clk, //50M系统时钟
input sys_rst_n, //系统复位
input uart_rxd, //接收数据线
output reg uart_rx_done, //数据接收完成标志
output reg [7:0]uart_rx_data //接收到的数据
);
//常量化波特率,可更改
parameter BPS=9600; //波特率9600bps,可更改
parameter SYS_CLK_FRE=50_000_000; //50M系统时钟
localparam BPS_CNT=SYS_CLK_FRE/BPS; //传输一位数据所需要的时钟个数
reg uart_rx_d0; //寄存1拍
reg uart_rx_d1; //寄存2拍
reg [15:0] clk_cnt; //时钟计数器
reg [3:0] rx_cnt; //接收计数器
reg rx_flag; //接收标志位
reg [7:0] uart_rx_data_reg; //数据寄存
wire neg_uart_rx_data; //数据的下降沿
assign neg_uart_rx_data=uart_rx_d1 & (~uart_rx_d0); //捕获数据线的下降沿,用来标志数据传输开始
//将数据线打两拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:用以捕获下降沿
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_d0<=1'b0;
uart_rx_d1<=1'b0;
end
else begin
uart_rx_d0<=uart_rxd;
uart_rx_d1<=uart_rx_d0;
end
end
//捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_flag<=1'b0;
else begin
if(neg_uart_rx_data)
rx_flag<=1'b1;
else if((rx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2))//在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
rx_flag<=1'b0;
else
rx_flag<=rx_flag;
end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
rx_cnt<=4'd0;
clk_cnt<=16'd0;
end
else if(rx_flag)begin
if(clk_cnt<BPS_CNT-1'b1)begin
clk_cnt<=clk_cnt+1'b1;
rx_cnt<=rx_cnt;
end
else begin
clk_cnt<=16'd0;
rx_cnt<=rx_cnt+1'b1;
end
end
else begin
rx_cnt<=4'd0;
clk_cnt<=16'd0;
end
end
//在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_rx_data_reg<=8'd0;
else if(rx_flag)
if(clk_cnt==BPS_CNT/2) begin
case(rx_cnt)
4'd1:uart_rx_data_reg[0]<=uart_rxd;
4'd2:uart_rx_data_reg[1]<=uart_rxd;
4'd3:uart_rx_data_reg[2]<=uart_rxd;
4'd4:uart_rx_data_reg[3]<=uart_rxd;
4'd5:uart_rx_data_reg[4]<=uart_rxd;
4'd6:uart_rx_data_reg[5]<=uart_rxd;
4'd7:uart_rx_data_reg[6]<=uart_rxd;
4'd8:uart_rx_data_reg[7]<=uart_rxd;
default:;
endcase
end
else
uart_rx_data_reg<=uart_rx_data_reg;
else
uart_rx_data_reg<=8'd0;
end
//当数据传输到终止位时,拉高传输完成标志位,并将数据输出
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_done<=1'b0;
uart_rx_data<=8'd0;
end
else if(rx_cnt==4'd9)begin
uart_rx_done<=1'b1;
uart_rx_data<=uart_rx_data_reg;
end
else begin
uart_rx_done<=1'b0;
uart_rx_data<=8'd0;
end
end
endmodule
2.接收模块测试代码
`timescale 1ns/1ns //定义时间刻度
//模块、接口定义
module uart_rx_tb();
reg sys_clk;
reg sys_rst_n;
reg uart_rxd;
wire uart_rx_done;
wire uart_rx_data;
//例化被测试的接收模块
uart_rx #(
.BPS (9600), //波特率9600
.SYS_CLK_FRE (50_000_000)//时钟频率50M
)
u_uart_rx(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_rx_done (uart_rx_done),
.uart_rx_data (uart_rx_data)
);
localparam CNT=50_000_000/9600*20; //计算出传输每个时钟所需要的时间
initial begin //传输8位数据 8'b01010101
//初始时刻定义
sys_clk <=1'b0;
sys_rst_n<=1'b0;
uart_rxd<=1'b1;//空闲时需要拉高
#20 //系统开始工作
sys_rst_n<=1'b1;
#(CNT/2)
uart_rxd<=1'b0;//开始传输起始位
#CNT
uart_rxd<=1'b1;//传输最低位,第1位
#CNT
uart_rxd<=1'b1;//传输第2位
#CNT
uart_rxd<=1'b1;//传输第3位
#CNT
uart_rxd<=1'b0; //传输第4位
#CNT
uart_rxd<=1'b1;//传输第5位
#CNT
uart_rxd<=1'b0;//传输第6位
#CNT
uart_rxd<=1'b1;//传输第7位
#CNT
uart_rxd<=1'b0; //传输最高位,第8位
#CNT
uart_rxd<=1'b1; //传输终止位
end
always begin
#10 sys_clk=~sys_clk; //时钟20ns,50M
end
endmodule
3.接收模块仿真图
4.发送模块代码
module uart_tx(
input sys_clk, //50M系统时钟
input sys_rst_n, //系统复位
input [7:0] uart_data, //发送的8位置数据
input uart_tx_en, //发送使能信号
output reg uart_txd //串口发送数据线
);
parameter SYS_CLK_FRE=50_000_000; //50M系统时钟
parameter BPS=9_600; //波特率9600bps,可更改
localparam BPS_CNT=SYS_CLK_FRE/BPS; //传输一位数据所需要的时钟个数
reg uart_tx_en_d0; //寄存1拍
reg uart_tx_en_d1; //寄存2拍
reg tx_flag; //发送标志位
reg [7:0] uart_data_reg; //发送数据寄存器
reg [15:0] clk_cnt; //时钟计数器
reg [3:0] tx_cnt; //发送个数计数器
wire pos_uart_en_txd; //使能信号的上升沿
//捕捉使能端的上升沿信号,用来标志输出开始传输
assign pos_uart_en_txd= uart_tx_en_d0 && (~uart_tx_en_d1);
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_tx_en_d0<=1'b0;
uart_tx_en_d1<=1'b0;
end
else begin
uart_tx_en_d0<=uart_tx_en;
uart_tx_en_d1<=uart_tx_en_d0;
end
end
//捕获到使能端的上升沿信号,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
tx_flag<=1'b0;
uart_data_reg<=8'd0;
end
else if(pos_uart_en_txd)begin
uart_data_reg<=uart_data;
tx_flag<=1'b1;
end
else if((tx_cnt==4'd9) && (clk_cnt==BPS_CNT/2))begin//在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
tx_flag<=1'b0;
uart_data_reg<=8'd0;
end
else begin
uart_data_reg<=uart_data_reg;
tx_flag<=tx_flag;
end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
clk_cnt<=16'd0;
tx_cnt <=4'd0;
end
else if(tx_flag) begin
if(clk_cnt<BPS_CNT-1)begin
clk_cnt<=clk_cnt+1'b1;
tx_cnt <=tx_cnt;
end
else begin
clk_cnt<=16'd0;
tx_cnt <=tx_cnt+1'b1;
end
end
else begin
clk_cnt<=16'd0;
tx_cnt<=4'd0;
end
end
//在每个数据的传输过程正中(数据比较稳定)将数据寄存器的数据赋值给数据线
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_txd<=1'b1;
else if(tx_flag)
case(tx_cnt)
4'd0: uart_txd<=1'b0;
4'd1: uart_txd<=uart_data_reg[0];
4'd2: uart_txd<=uart_data_reg[1];
4'd3: uart_txd<=uart_data_reg[2];
4'd4: uart_txd<=uart_data_reg[3];
4'd5: uart_txd<=uart_data_reg[4];
4'd6: uart_txd<=uart_data_reg[5];
4'd7: uart_txd<=uart_data_reg[6];
4'd8: uart_txd<=uart_data_reg[7];
4'd9: uart_txd<=1'b1;
default:;
endcase
else
uart_txd<=1'b1;
end
endmodule
5.发送模块测试代码
`timescale 1ns/1ns //定义时间刻度
//模块、接口定义
module uart_tx_tb();
reg sys_clk;
reg sys_rst_n;
reg [7:0] uart_data;
reg uart_tx_en;
wire uart_txd;
//例化被测试的接收模块
uart_tx #(
.BPS (9600), //波特率9600
.SYS_CLK_FRE (50_000_000)//时钟频率50M
)
u_uart_tx(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_data (uart_data),
.uart_tx_en (uart_tx_en),
.uart_txd (uart_txd)
);
localparam CNT=50_000_000/9600*20; //计算出传输每个时钟所需要的时间
initial begin //传输8位数据 8'b01010101
//初始时刻定义
sys_clk <=1'b0;
sys_rst_n <=1'b0;
uart_tx_en <=1'b0;
uart_data <=8'b01010101;//发送数据 01010101
#20 //系统开始工作
sys_rst_n <=1'b1;
#(CNT/2)
uart_tx_en <=1'b1;
#20
uart_tx_en <=1'b0;
end
always begin
#10 sys_clk=~sys_clk; //时钟20ns,50M
end
endmodule
6.发送模块仿真图
7.顶层文件代码
module uart_top(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位
input uart_rxd, //接收端口
output uart_txd //发送端口
);
parameter UART_BPS=9600; //波特率
parameter CLK_FREQ=50_000_000; //系统频率50M
wire uart_en_w;
wire [7:0] uart_data_w;
//例化发送模块
uart_tx#(
.BPS (UART_BPS),
.SYS_CLK_FRE (CLK_FREQ))
u_uart_tx(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_tx_en (uart_en_w),
.uart_data (uart_data_w),
.uart_txd (uart_txd)
);
//例化接收模块
uart_rx #(
.BPS (UART_BPS),
.SYS_CLK_FRE (CLK_FREQ))
u_uart_rx(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_rx_done (uart_en_w),
.uart_rx_data (uart_data_w)
);
endmodule
8.顶层文件测试代码
`timescale 1ns/1ns //定义时间刻度
//模块、接口定义
module uart_top_tb();
reg sys_clk;
reg sys_rst_n;
reg uart_rxd;
wire uart_txd;
//例化被测试的接收模块
uart_top #(
.UART_BPS (9600), //波特率9600
.CLK_FREQ (50_000_000)//时钟频率50M
)
u_uart_top(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_txd (uart_txd)
);
localparam CNT=50_000_000/9600*20; //计算出传输每个时钟所需要的时间
initial begin //传输8位数据 8'b01010101
//初始时刻定义
sys_clk <=1'b0;
sys_rst_n<=1'b0;
uart_rxd<=1'b1;
#20 //系统开始工作
sys_rst_n<=1'b1;
#(CNT/2)
uart_rxd<=1'b0;//开始传输起始位
#CNT
uart_rxd<=1'b1;//传输最低位,第1位
#CNT
uart_rxd<=1'b0;//传输第2位
#CNT
uart_rxd<=1'b1;//传输第3位
#CNT
uart_rxd<=1'b0; //传输第4位
#CNT
uart_rxd<=1'b1;//传输第5位
#CNT
uart_rxd<=1'b0;//传输第6位
#CNT
uart_rxd<=1'b1;//传输第7位
#CNT
uart_rxd<=1'b0; //传输最高位,第8位
#CNT
uart_rxd<=1'b1; //传输终止位
end
always begin
#10 sys_clk=~sys_clk; //时钟20ns,50M
end
endmodule
9.顶层模块仿真代码
整个系统的框图
---晓凡 2022年7月于桂林书