1、知识点
(1)什么是串口(UART)?
串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
“异步”两个字即意味着在数据传递的两个模块之间使用的不是同步时钟。实际上在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。
(2)串口的组成
UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),如图所示,对于 PC 来说它的 tx 要和对于 FPGA来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果是两个 tx 或者两个 rx 连接那数据就不能正常被发送出去和接收到。
信号的传输由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有RS232、RS422、RS485等,它们定义了接口不同的电气特性,如RS-232是单端输入输 出,而RS-422/485为差分输入输出等。传输距离较短时(不超过15m),RS232是串行通信最常用的接口标准。RS-232标准的串口最常见的接口类型为DB9,样式如图所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点 的台式机都没有串口,它们一般通过USB转串口线来实现与外部设备的串口通信。
(3)RS232
帧结构(10bit):
空闲状态保持高电平
UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。
校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。 奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时, 对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查 1的个数是否为偶数。
UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中 一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认), 1.5或2位。
串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位 /秒),常用的波特率有9600、19200、38400、57600以及115200等。如波特率9600则代表每秒传输9600bit数据,以串口发送1个字节10bit算(起始位1bit+数据8bit+停止位1bit+NO校验位),则传输1个字节需要的时间是1*10/9600秒。
2、设计
(1)模块框图
顶层模块:
子功能模块
串口数据接收模块:
串口数据发送模块:
整体框图:
( 2)功能实现
① 串口数据接收模块:
需要对rx数据进行打一拍,与系统时钟同步,但仍无法直接传送,因为该信号属于异步信号,会引出亚稳态。
亚稳态问题参考:亚稳态问题_发光中请勿扰的博客-CSDN博客
解决方法:对打拍后的数据再次打两拍
波形图:
波形:
data输入
打拍信号
使能信号
计数最大值
bit标志位
该模块支持任意波特率(理论上)的接收,但需要在使用该模块时使用参数将其例化,数据位8位,起始位和停止位各1位,无奇偶校验
串口的传输是以起始位开始的,而起始位是将数据线拉低 ,所以我们需要捕捉数据线的下降沿,将接收数据线打拍两次,捕捉其下降沿。当捕捉到接收数据线的下降沿,拉高接收标志信号,标志模块进入接收过程;当接收完10个bit后,拉低接收标志信号,标志接收过程结束
假设波特率为9600,则传输一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/960;假设系统时钟为50MHz(参数化以便适应不同的系统频率),则其周期为20ns,那么传输一个bit所需要的系统周期数为(1s/960)/ 20ns ≈ 5208(个)。在接收过程中使用一个计数器计数,计数区间为(0,5208-1),这样的区间一共10个(一个字节需要传输10个bit);此外还需一个计数器对接收的bit数计数(每当上一个计数器计数到5207则表示接收完了一个bit),计数区间(0,9)。
在接收过程,根据计数器的值(接收bit计数器),在每个bit计数器的中间接收数据,将其移位寄存(在电平中间数据最稳定)
若接收bit计数器 = 0,则代表是起始位,不需要接收
若接收bit计数器 = 1,则代表此时接收到数据的最低位LSB(数据的传输总是低位在前,高位在后),将其赋值给寄存数据的最低位;
······
若接收bit计数器 = 8,则代表此时接收到数据的最高位MSB,将其赋值给寄存数据的最高位;
若接收bit计数器 = 9,则代表是停止位,不需要接收
代码:
module uart_rx
#(
parameter uart_bps = 'd9600,//波特率
parameter clk_fre = 'd50_000_000//频率
)
(
input sys_clk ,
input sys_rst_n ,
input rx ,
output reg [7:0]po_data ,
output reg po_flag
);
parameter baud_cnt_max = clk_fre/uart_bps; //频率除以波特率
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 ;
//打一拍,同步到系统时钟下
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
//打两拍,消除亚稳态
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1 ;
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2 ;
//开始标志信号
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
start_flag <= 1'b0;
else if (rx_reg2 == 1'b0 && rx_reg3 == 1'b1 && work_en == 1'b0)//引入使能信号是为了避免在数据传输时遇到1变为0,从而检验到下降沿
start_flag <= 1'b1;
else
start_flag <= 1'b0;
//使能信号
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
work_en <= 1'b0;
else if(start_flag == 1'b1)
work_en <= 1'b1;
else if(bit_cnt == 4'd8 && bit_flag == 1'b1)
work_en <= 1'b0;//bit计数器和bit标志信号
else
work_en <= work_en;
//波特率计数器
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
baud_cnt <= 16'd0;
else if((baud_cnt == baud_cnt_max - 1 ) || (work_en == 1'b0))
baud_cnt <= 16'd0;
else
baud_cnt <= baud_cnt + 1'b1;
//bit标志信号
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_flag <= 1'b0;
else if(baud_cnt == baud_cnt_max / 2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit计数器
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_cnt <= 4'd0;
else if(bit_cnt == 4'd8 && bit_flag == 1'b1)
bit_cnt <= 4'd0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
//数据拼接
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_data <= 8'b0;
else if(bit_cnt >= 4'd1 && bit_cnt <= 4'd8 && bit_flag == 1'b1)
rx_data <= {rx_reg3,rx_data[7:1]};
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_flag <= 1'b0;
else if(bit_cnt == 4'd8 && bit_flag == 1'b1)
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
//输出并行信号
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;
//对rx_flag进行打拍,保持与输出数据同步
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
endmodule
`timescale 1ns/1ns //定义时间刻度
//模块、接口定义
module uart_rx_tb();
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
wire [7:0] po_data ;
wire po_flag ;
initial
begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
initial
begin
#200
rx_bit(8'd0);
rx_bit(8'd1);
rx_bit(8'd2);
rx_bit(8'd3);
rx_bit(8'd4);
rx_bit(8'd5);
rx_bit(8'd6);
rx_bit(8'd7);
end
//例化发送模块
uart_rx
#(
.uart_bps (9600),
.clk_fre (50_000_000)
)
uart_rx_inst
(
.sys_clk(sys_clk) ,
.sys_rst_n(sys_rst_n),
.rx(rx) ,
.po_data(po_data) ,
.po_flag(po_flag)
);
task rx_bit (
input [7:0] data
);
integer i; //定义一个常量
//用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1
for(i=0; i<10; i=i+1) begin
case(i)
0: rx <= 1'b0; //起始位
1: rx <= data[0]; //LSB
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]; //MSB
9: rx <= 1'b1; //停止位
endcase
#(5208*20); //每发送 1 位数据延时
end
endtask //任务结束
endmodule
②串口数据接收模块
波形图:
波形:
模拟产生输入
输入标志信号
中间变量
输出
module uart_tx
#(
parameter uart_bps = 'd9600,
parameter clk_fre = 'd50_000_000
)
(
input sys_clk ,
input sys_rst_n ,
input [7:0]pi_data ,
input pi_flag ,
output reg tx
);
localparam baud_cnt_max = clk_fre/uart_bps;
reg work_en ;
reg [15:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
//使能信号
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
work_en <= 1'b0;
else if(pi_flag == 1'b1)
work_en <= 1'b1;
else if(bit_cnt == 4'd09 && bit_flag == 1'b1)
work_en <= 1'b0;
//波特计数器
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
baud_cnt <= 16'd0;
else if(work_en == 1'b0 || baud_cnt == baud_cnt_max - 1)
baud_cnt <= 16'd0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit标志位
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_flag <= 1'b0;
else if(baud_cnt == 16'd1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit计数器
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_cnt <= 4'd0;
else if(bit_cnt == 4'd09 && bit_flag == 1'b1)
bit_cnt <= 4'd0;
else if(work_en == 1'b1 && bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
tx <= 1'b1;
else if(bit_flag == 1'b1)
case(bit_cnt)
0:tx <= 1'b0;
1:tx <= pi_data[0];
2:tx <= pi_data[1];
3:tx <= pi_data[2];
4:tx <= pi_data[3];
5:tx <= pi_data[4];
6:tx <= pi_data[5];
7:tx <= pi_data[6];
8:tx <= pi_data[7];
9:tx <= 1'b1;
default : tx <= 1'b1;
endcase
endmodule
`timescale 1ns/1ns //定义时间刻度
//模块、接口定义
module uart_tx_tb();
reg sys_clk ;
reg sys_rst_n ;
reg [7:0] pi_data ;
reg pi_flag ;
wire tx ;
initial
begin
sys_clk <=1'b0;
sys_rst_n <=1'b0;
#20
sys_rst_n <=1'b1;
end
always #10 sys_clk = ~sys_clk;
initial
begin
pi_data <= 8'd0;
pi_flag <= 1'b0;
#200
//数据0
pi_data <= 8'd0;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据1
pi_data <= 8'd1;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据2
pi_data <= 8'd2;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据3
pi_data <= 8'd3;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据4
pi_data <= 8'd4;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据5
pi_data <= 8'd5;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据6
pi_data <= 8'd6;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据7
pi_data <= 8'd7;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
end
//例化发送模块
uart_tx
#(
.uart_bps (9600),
.clk_fre (50_000_000)
)
uart_tx_inst
(
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.pi_data (pi_data) ,
.pi_flag (pi_flag) ,
.tx (tx)
);
endmodule
③顶层模块:
波形:
rx数据接收(第一个拉高为数据得停止位)
task验证
数据传递
//顶层
module rs232
(
//系统接口
input sys_clk , //50M系统时钟
input sys_rst_n , //系统复位
//UART
input rx, //接收数据线
output tx //UART发送数据线
);
//wire define
wire [7:0] rx_data ; //接收到的一个BYTE数据
wire rx_flag ; //接收有效信号,可用作发送的使能信号
//例化发送模块
uart_tx
#(
.uart_bps (9600),
.clk_fre (50_000_000)
)
uart_tx_inst
(
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.pi_data (rx_data) ,
.pi_flag (rx_flag) ,
.tx (tx)
);
//例化接收模块
uart_rx
#(
.uart_bps (9600),
.clk_fre (50_000_000)
)
uart_rx_inst
(
.sys_clk(sys_clk) ,
.sys_rst_n(sys_rst_n) ,
.rx(rx) ,
.po_data(rx_data) ,
.po_flag(rx_flag)
);
endmodule
`timescale 1ns/1ns //定义时间刻度
module rs232_tb();
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
wire tx ;
initial
begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
initial
begin
#200
rx_byte();
end
task rx_byte();
integer j; //定义一个常量
for(j=0; j<8; j = j +1)
rx_bit(j);
endtask
task rx_bit (
input [7:0] data
);
integer i; //定义一个常量
//用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1
for(i=0; i<10; i=i+1) begin
case(i)
0: rx <= 1'b0; //起始位
1: rx <= data[0]; //LSB
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]; //MSB
9: rx <= 1'b1; //停止位
endcase
#(5208*20); //每发送 1 位数据延时
end
endtask //任务结束
rs232 rs232_inst
(
//系统接口
.sys_clk(sys_clk) , //50M系统时钟
.sys_rst_n(sys_rst_n) , //系统复位
//UART
.rx(rx) , //接收数据线
.tx(tx) //UART发送数据线
);
endmodule