从零开始的FPGA学习13-串口RS232详解
原理
RS232 通信协议
- RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线,tx 是发送数据的线。
- 串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有10bit。在不发送或者不接收数据的情况下,rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。
工程整体框图
串口数据接收模块
波形图:
第一部分:
首先画出三个输入信号,必不可少的两个输入信号是时钟和复位,另一个是串行输入数据 rx,如图 24-12 所示,我们发现 rx 串行数据一开始直接打了两拍,就是经过了两级寄存器,理论上我们应该按照串口接收数据的时序要求找到 rx 的下降沿,然后开始接收起始位的数据,但为什么先将数据打了两拍呢?那就要先从跨时钟域会导致亚稳态的问题上说起。
如果 FPGA 的系统时钟刚好采集到 rx 信号上升沿或下降沿的中间位置附近(按照概率来讲,如果数据传输量足够大或传输速度足够快时一定会产生这种情况,即 FPGA 在接收 rx 数据时不满足内部寄存器的建立时间 Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间),此时 FPGA 的第一级寄存器的输出端在时钟沿到来之后比较长的一段时间内都处于不确定的状态,在 0 和 1 之间处于振荡状态,而不是等于串口输入的确定的 rx 值。
如图 24-13 所示为产生亚稳态的波形示意图,rx 信号经过 FPGA 中的第一级寄存器后输出的 rx_reg1 信号在时钟上升沿 Tco 时间后会有 Tmet(决断时间)的振荡时段,当第一个寄存器发生亚稳态后,经过 Tmet 的振荡稳定后,第二级寄存器就能采集到一个相对稳定的值。但由于振荡时间 Tmet 是受到很多因素影响的,所以 Tmet 时间有长有短。
如图24-14 所示,当 Tmet1 时间长到大于一个采样周期后,那第二级寄存器就会采集到亚稳态,但是从第二级寄存器输出的信号就是相对稳定的了。当然会人会问到第二级寄存器的Tmet2 的持续时间会不会继续延长到大于一个采样周期?这种情况虽然会存在,但是其概率是极小的,寄存器本身就有减小 Tmet 时间让数据快速稳定的作用。
代码
module uart_rx
#(
// parameter UART_BPS = 'd9600, //串口波特率
// parameter CLK_FREQ = 'd50_000_000 //时钟频率
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据
output reg [7:0] po_data , //串转并后的 8bit 数据
output reg po_flag //串转并后的数据有效标志信号
);
reg rx_reg1=0;
reg rx_reg2=0;
reg rx_reg3=0;
//rx下降信号
reg start_nedge=0;
//工作使能
reg work_en=0;
//取数标志信号
reg [13:0]baud_cnt=0;
reg bit_flag=0;
reg [3:0]bit_cnt=0;
reg [7:0]rx_data=0;
reg rx_flag=0;
//rx缓存 打两拍
always @ (posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n==0)
begin
rx_reg1<=1;
rx_reg2<=1;
rx_reg3<=1;
end
else
begin
rx_reg1<=rx;
rx_reg2<=rx_reg1;
rx_reg3<=rx_reg2;
end
end
//rx下降沿检测
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0)
start_nedge<=0;
else if(work_en==1)
start_nedge<=0;
else if(rx_reg3==1 && rx_reg2==0) begin
start_nedge<=1;
end
else
start_nedge<=0;
end
//work_en信号
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0)
work_en<=0;
else if(bit_flag==1 && bit_cnt=='d8)
work_en<=0;
else if(start_nedge==1) begin
work_en<=1;
end
else
work_en<=work_en;
end
//baud_cnt计数
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0)
baud_cnt<=0;
else if(baud_cnt=='d5207)
baud_cnt<=0;
else if(work_en==1) begin
baud_cnt<=baud_cnt+1;
end
else
baud_cnt<=0;
end
//bit_flag
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0)
bit_flag<=0;
else if(baud_cnt=='d2603) begin
bit_flag<=1;
end
else
bit_flag<=0;
end
//bit_cnt
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0)
bit_cnt<=0;
else if(bit_cnt=='d9)
bit_cnt<=0;
else if(bit_flag==1) begin
bit_cnt<=bit_cnt+1;
end
else
bit_cnt<=bit_cnt;
end
//rx_data
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0)
rx_data<=0;
else if(bit_cnt==0)
rx_data<=rx_data;
else if(bit_flag=='d1)
rx_data<={rx,rx_data[7:1]};
else
rx_data<=rx_data;
end
//rx_flag
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0)
rx_flag<=0;
else if(bit_cnt=='d8 && bit_flag==1)
rx_flag<=1;
else
rx_flag<=0;
end
//输出
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0) begin
po_data<=0;
end else if(rx_flag==1)
po_data<=rx_data;
else
po_data<=0;
end
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n==0) begin
po_flag<=0;
end else if(rx_flag==1)
po_flag<=1;
else
po_flag<=0;
end
endmodule
仿真
`timescale 1ns/1ns
module tb_uart_rx();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg sys_clk;
reg sys_rst_n;
reg rx;
//wire define
wire [7:0] po_data;
wire po_flag;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20;
sys_rst_n <= 1'b1;
end
//模拟发送8次数据,分别为0~7
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
//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;
//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit(
//传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
input [7:0] data
);
integer i; //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=i++的形式
for(i=0; i<10; i=i+1) begin
case(i)
0: rx <= 1'b0;
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'b1;
endcase
#(5208*20); //每发送1位数据延时5208个时钟周期
end
endtask //任务以endtask结束
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------uart_rx_inst------------------------
uart_rx uart_rx_inst(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.rx (rx ), //input rx
.po_data (po_data ), //output [7:0] po_data
.po_flag (po_flag ) //output po_flag
);
endmodule