一.理论知识
1.在最初的应用中, RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称“猫” )之间的通讯,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由)和数据通讯设备 DCE(调制调解器)。 我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。
2.在旧式的台式计算机中一般会有 RS-232 标准的 COM 口(也称 DB9 接口),见下图。
3.其中接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机中一般引出公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连接起来。通讯时,串口线中传输的信号使用 RS-232 标准调制。 在各种应用场合下, DB9 接口中的公头及母头的各个引脚的标准信号线接法见下图,下图中的RXD和TXD信号线是最重要的信号线,RXD是接收信号线,TXD是发送信号线。
二.RS-232通信协议
1.S232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线, tx 是发送数据的线。
2.rx 位宽为 1bit, PC 机通过串口调试助手往 FPGA 发 8bit 数据时, FPGA 通过串口线rx 一位一位地接收,从最低位到最高位依次接收,最后在 FPGA 里面位拼接成 8 比特数据。
3.tx 位宽为 1bit, FPGA 通过串口往 PC 机发 8bit 数据时, FPGA 把 8bit 数据通过 tx线一位一位的传给 PC 机,从最低位到最高位依次发送,最后上位机通过串口助手按照RS232 协议把这一位一位的数据位拼接成 8bit 数据。
4.串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有10bit。在不发送或者不接收数据的情况下, rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。如图 30-7 所示为一个最基本的 RS232 帧结构。
5、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、 9600、 115200 等。
6、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。如果使用的是 9600 的波特率,其串口的比特率为: 9600Bps *1bit= 9600bps。
7、由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/9600 秒,如果用50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = (1s * 10^9)ns /9600bit)ns / 20ns ≈ 5208 个系统时钟周期,即每个 bit 数据之间的间隔要在 50MHz 的时钟频率下计数 5208 次。
8、上位机通过串口发 8bit 数据时,会自动在发 8 位有效数据前发一个波特时间的起始位,也会自动在发完 8 位有效数据后发一个停止位。同理,串口助手接收上位机发送的数据前,必须检测到一个波特时间的起始位才能开始接收数据,接收完 8bit 的数据后,再接收一个波特时间的停止位。
三.亚稳态
1.当你使用示波器把一个矩形脉冲的上升沿或下降沿放大后会发现其上升沿和下降沿并不是瞬间被拉高或拉低的,而是有一个倾斜变化的过程,这在运放中被称为“压摆率”,如果 FPGA 的系统时钟刚好采集到 rx 信号上升沿或下降沿的中间位置附近(按照概率来讲,如果数据传输量足够大或传输速度足够快时一定会产生这种情况),即 FPGA 在接收 rx 数据时不满足内部寄存器的建立时间 Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间),此时 FPGA 的第一级寄存器的输出端在时钟沿到来之后比较长的一段时间内都处于不确定的状态,在 0 和 1 之间处于振荡状态,而不是等于串口输入的确定的 rx 值。
2…单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态。第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为 70%~80%左右,第二级寄存器可以稳定输出的概率为 99%左右,后面再多加寄存器的级数改善效果就不明显了,所以数据进来后一般选择打两拍即可。
3.另外单比特信号从快速时钟域同步到慢速时钟域还仅仅使用打两拍的方式会漏采数据,所以往往使用脉冲同步法或的握手信号法;而多比特信号跨时钟域需要进行格雷码编码(多比特顺序数才可以)后才能进行打两拍的处理,或者通过使用 FIFO、 RAM 来处理数据与时钟同步的问题。
4.亚稳态振荡时间 Tmet 关系到后级寄存器的采集稳定问题, Tmet 影响因素包括:器件的生产工艺、温度、环境以及寄存器采集到亚稳态里稳定态的时刻等。甚至某些特定条件,如干扰、辐射等都会造成 Tmet 增长。
四.接收信号模块时序图
五.接收信号模块代码
module uart_rx
# (
parameter SYS_FR = 'd50_000_000,
parameter UART_FR = 'd9600
)
(
input wire sys_clk,
input wire sys_rst_n,
input wire rx,
output reg [7:0] po_data,
output reg po_flag
);
localparam BPS_MAX = SYS_FR / UART_FR;
reg rx_reg1;
reg rx_reg2;
reg start_flag;
reg [12:0] cnt_bps;
reg [3:0] cnt_bit;
reg middle_flag;
reg rx_en;
reg [7:0] rx_data;
reg rx_data_flag;
// rx_reg1和rx_reg2信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
rx_reg1 <= 1;
rx_reg2 <= 1;
end
else begin
rx_reg1 <= rx;
rx_reg2 <= rx_reg1;
end
end
// start_flag信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
start_flag <= 0;
else if(rx_reg1 == 0 && rx_reg2 == 1)
start_flag <= 1;
else
start_flag <= 0;
end
// cnt_bps信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_bps <= 0;
else if(start_flag == 1 || rx_en == 1)
cnt_bps <= cnt_bps + 1;
else if(cnt_bps == BPS_MAX - 1)
cnt_bps <= 0;
else
cnt_bps <= 0;
end
// cnt_bit信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_bit <= 0;
else if(rx_en == 1 && cnt_bps == BPS_MAX - 1)
cnt_bit <= cnt_bit + 1;
else if(rx_en == 0)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit;
end
// middle_flag信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
middle_flag <= 0;
else if(cnt_bit >= 1 && cnt_bps == BPS_MAX / 2)
middle_flag <= 1;
else
middle_flag <= 0;
end
// rx_en信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_en <= 0;
else if(start_flag == 1)
rx_en <= 1;
else if(cnt_bit == 8 && middle_flag == 1)
rx_en <= 0;
end
// rx_data信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_data <= 8'b0;
else if(middle_flag == 1)
rx_data <= {rx,rx_data[7:1]};
end
// rx_data_flag信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_data_flag <= 0;
else if(cnt_bit == 8 && middle_flag == 1)
rx_data_flag <= 1;
else
rx_data_flag <= 0;
end
// po_data信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
po_data <= 8'b0;
else if(rx_data_flag == 1)
po_data <= rx_data;
end
// po_flag信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
po_flag <= 0;
else
po_flag <= rx_data_flag;
end
endmodule
六.接收信号模块仿真代码
`timescale 1ns/1ns
module tb_uart_rx();
reg sys_clk;
reg sys_rst_n;
reg rx;
wire [7:0] po_data;
wire po_flag;
initial begin
sys_clk <= 1;
sys_rst_n <= 0;
rx <= 1;
#50
sys_rst_n <= 1;
end
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
always #10 sys_clk = ~sys_clk;
task rx_bit(
input [7:0] data
);
integer i;
for(i=0; i<10; i=i+1) begin
case(i)
0: rx <= 0;
1: rx <= data[0];
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];
9: rx <= 1;
endcase
#(5208*20);
end
endtask
uart_rx uart_rx_unst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.po_data (po_data ),
.po_flag (po_flag )
);
endmodule
七.发送信号模块时序图
八.发送信号模块代码
module uart_tx
# (
parameter SYS_FR = 'd50_000_000,
parameter UART_FR = 'd256000
)
(
input wire sys_clk,
input wire sys_rst_n,
input wire pi_flag,
input wire [7:0] pi_data,
output reg tx
);
localparam BPS_MAX = SYS_FR / UART_FR;
reg tx_en;
reg [12:0] cnt_bps;
reg [3:0] cnt_bit;
reg bit_flag;
// tx_en信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tx_en <= 0;
else if(pi_flag == 1)
tx_en <= 1;
else if(cnt_bit == 9 && bit_flag == 1)
tx_en <= 0;
else
tx_en <= tx_en;
end
// cnt_bps信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_bps <= 0;
else if(cnt_bps == BPS_MAX - 1)
cnt_bps <= 0;
else if(tx_en == 1)
cnt_bps <= cnt_bps + 1;
else
cnt_bps <= 0;
end
// cnt_bit信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_bit <= 0;
else if(pi_flag == 1)
cnt_bit <= 0;
else if(cnt_bps == BPS_MAX - 1)
cnt_bit <= cnt_bit + 1;
else
cnt_bit <= cnt_bit;
end
// bit_flag信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
bit_flag <= 0;
else if(pi_flag == 1)
bit_flag <= 1;
else if(cnt_bps == BPS_MAX - 1)
bit_flag <= 1;
else
bit_flag <= 0;
end
// tx信号
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tx <= 1;
else if(bit_flag == 1)
case(cnt_bit)
0: tx <= 0;
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;
default: tx <= 1;
endcase
end
endmodule
九.发送信号模块仿真代码
`timescale 1ns/1ns
module tb_uart_tx();
reg sys_clk;
reg sys_rst_n;
reg pi_flag;
reg [7:0] pi_data;
wire tx;
initial begin
sys_clk <= 1;
sys_rst_n <= 0;
pi_flag <= 0;
pi_data <= 8'b0;
#50
sys_rst_n <= 1;
end
initial begin
#200
pi_flag <= 1;
pi_data <= 8'd0;
#20
pi_flag <= 0;
#(5208*20*10)
pi_flag <= 1;
pi_data <= 8'd1;
#20
pi_flag <= 0;
#(5208*20*10)
pi_flag <= 1;
pi_data <= 8'd2;
#20
pi_flag <= 0;
#(5208*20*10)
pi_flag <= 1;
pi_data <= 8'd3;
#20
pi_flag <= 0;
#(5208*20*10)
pi_flag <= 1;
pi_data <= 8'd4;
#20
pi_flag <= 0;
#(5208*20*10)
pi_flag <= 1;
pi_data <= 8'd5;
#20
pi_flag <= 0;
#(5208*20*10)
pi_flag <= 1;
pi_data <= 8'd6;
#20
pi_flag <= 0;
#(5208*20*10)
pi_flag <= 1;
pi_data <= 8'd7;
#20
pi_flag <= 0;
end
always #10 sys_clk = ~sys_clk;
uart_tx uart_tx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.pi_flag (pi_flag ),
.pi_data (pi_data ),
.tx (tx )
);
endmodule
十.顶层模块架构图
十一.顶层模块代码
module rs232
(
input wire sys_clk,
input wire sys_rst_n,
input wire rx,
output wire tx
);
wire [7:0] byte_data;
wire byte_flag;
uart_rx uart_rx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.po_data (byte_data ),
.po_flag (byte_flag )
);
uart_tx uart_tx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.pi_flag (byte_flag ),
.pi_data (byte_data),
.tx (tx )
);
endmodule