串口、UART、RS232、RS485、USB、COM口说明
经常叫串口通信串口通信的,有时候默认串口通信就是指UART,有时候又会叫USB串口,有时候会说串口RS232,串口RS485,有时候又说COM口,傻傻分不清楚,人都搞懵了,今天彻底弄清楚。
串口是一个泛称,代指所有串口时序标准的接口,即每次只传输一位数据,规定了逻辑信号的格式,什么时候该收或发1,什么时候该收或发0,与之对应的是并口。UART、RS232、RS485、TTL都遵循着类似的通信时序协议,因此都被通称为串口。
来弄清楚一个概念,单工,半双工,全双工的区别。这几个术语是通信网络中的术语,所谓单工就是指通信中数据只能在一个方向上单向传输,要么就是接收端,要么就是发送端,其扮演的角色是固定的,如广播系统,键盘到计算机,传感器网络等;半双工是指在同一时间只能接收或者发送数据,其扮演的角色可以转换,但是同一时间其扮演的角色不能变化,也就是说该设备发送和接受数据的动作不能同时进行,如对讲机;全双工是指可以同时接收和发送数据,这就意味着该设备的数据通道起码有两条,如电话通信、网络通信中的TCP连接等。
串口,异步串行通信口,也叫通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),发送数据时将并行数据转换成串行数据来传输,在接收数据时将收到的串行数据转换成并行数据。
串口属于异步通信接口,接收方不知道数据什么时候会到达,所以收发端双方都要有各自的时钟。在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接收方是在数据的起始位和停止位的帮助下实现信息同步的。
串口通信只有两根信号线,一根是发送数据端口线叫tx(Transmitter),一根是接收数据端口线叫rx(Receiver),对于PC来说它的tx要和对于FPGA来说的rx连接,同样PC的rx要和FPGA的tx连接,rx和tx都是相对自身主体来讲的。串口可以实现全双工:同时进行发送数据和接收数据。

loopback(回环测试)是指发送端发送什么数据,接收端就接收什么数据,说明从数据发送端到数据接收端之间的数据链路是正常的,以此来验证数据链路的畅通。

串口RS232标准COM(DB9)接口:

目前通常只使用RXD、TXD以及GND三条信号线来直接传输数据信号。
RS232是UART的一种:
1.rx是接收数据的线,位宽为1bit, PC机通过串口调试助手往FPGA发送8bits数据时,FPGA通过串口线rx一位一位地接收,从最低位到最高位依次接收,最后在FPGA内部拼成8bits数据。
2.tx是发送数据的线,位宽为1bit,FPGA通过串口往PC机发8bit数据时,FPGA把8bit数据通过tx数据线一位一位的传给PC机,从最低位到最高位依次发送,最后上位机通过串口助手按照RS232协议把这一位一位的数据位拼接成8bit数据。
3.串口数据的发送与接收是基于帧结构的,每一帧开头的起始位固定为0,停止位为1,数据位为8bit,基本的帧结构有10bit。空闲状态时tx和rx都保持高电平。

4.波特率:在信息传输通道中,携带数据信息的信号单元叫码元(常见为1bit),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常见的波特率有4800、9600、115200。比特率概念类似。
5.由计算得串口发送或者接收1bit数据的时间为一个波特,即1/9600,如果用50MHz(周期为20ns)的系统时钟来计数,需要计数的个数cnt=109*(1/9600)/20 = 5208个系统时钟周期,即每个bit数据之间的间隔要在50MHz的时钟频率下计数5208次。
6.上位机通过串口发8bit数据时,会自动在发8位有效数据前发一个波特时间的起始位,也会自动在发完8位有效数据后发一个停止位。同理,串口助手接收上位机发送的数据前,必须检测到一个波特时间的起始位才能开始接收数据,接收完8bit的数据后,再接收一个波特时间的停止位。
由于FPGA串口输入输出的引脚为TTL电平,高电平为3.3V,低电平为0;而RS232引脚高电平为-15 ~ -5V,低电平为 5 ~ 15V,S所以需要电平转换芯片将RS232电平标准信号转换成TTL电平信号:

为了方便用USB线进行串口调试,还可以通过USB转串口芯片进行信号转换:

注意 在使用时需将 J2、 J3 口的 1、 2 脚用跳帽连接起来才能正常使用, 即开发板上的 J2、 J3 中的 TXD 与 RX 短接、 RXD 与 TX 短接 ,即FPGA 的TX与PC的RX连接,FPGA的RX与PC的TX连接。
同样的使用 RS232 串口也要选择相应的连接 , 使用跳帽将 J6 口的 5 脚和 7 脚, 6 脚和 8 脚连接起来才能正常使用, 即开发板上 J6 中的 TX 与 T1INT 短接、 RX 与 R1OUT 短接 .7、8脚T1NT接到了MAX3232CSE串口转换芯片的T1INT、R1OUT,只有短接时,才是使用了RS232。


先设计串口接收模块,该模块的功能是接收通过 PC 机上的串口调试助手发送的固定波特率的数据,串口接收模块按照串口的协议准确接收串行数据,解析提取有用数据后需将其转化为并行数据,因为并行数据在 FPGA 内部传输的效率更高,转化为并行数据
后同时产生一个数据有效信号标志信号伴随着并行的有效数据一同输出。 为什么还需要输出一个伴随并行数据有效的标志信号,这是因为后级模块或系统使用该并行数据的时候可能无法知道该时刻采样的数据是不是稳定有效的,而数据有效标志信号的到来就说明数据才该时刻是稳定有效的,起到一个指示作用。当数据有效标志信号为高时,该并行数据就可以被后级模块或系统使用了 。
PC 机通过串口调试助手发过来的信号没有时钟,所以 FPGA 在接收数据的时候要约定好一个固定的波特率,一个比特一个比特地接收数据


RX:

1.RX是来自于另外一个系统的TX,存在跨时钟域的问题,对单bit数据打两拍可以减小亚稳态问题的概率,所以有rx_reg1和rx_reg2.
2.UART协议,bit接收的开始标志位是0,从1到0,可以判断下降沿,判断下降沿也应该使用稳定的信号,因此再打一拍 rx_reg3,根据rx_reg2和rx_reg3得到下降沿——开始标志位start_edge
3.判断下降沿的意图是获取串口帧开始的标志位,但是一帧数据10bit当中也许存在着多个下降沿,但是开始标志位只有第一个,因此,需要引入一个新的信号work_en来对开始标志位后直到接收结束的这段时间区域进行锁定,过滤掉其他下降沿
4.根据UART协议,对应波特率下,每个bit的传输具有严格的时间即 BAUD_CNT_MAX = CLK_FREQ / UART_BPS,因此需要对时钟周期进行计数baud_cnt,每bit数据需要持续这么久的时间,计满表示该bit数据传输完成,该传下一bit数据了
5.对每bit数据的中心时间位置 进行采样是最准确的,因此,baud_cnt计数到最大维持时间一半时就可以进行采样了,因此产生一个可以接收该bit的标志位bit_flag.
6.需要对每1bit数据进行计数bit_cnt,当8bit数据接收完时进行结束位的设定,完成本串口帧的传输。
7.每次接收到1bit数据,需要将其组合到8bit并行数据中,而根据协议,先接收的是低位,要实现最后左高右低的排序,得进行右移位寄存,rx_data[7:0],根据bit_cnt和bit_flag顺序移位寄存,在最后判断bit_cnt的值和bit_flag生成rx_data完成标志位rx_flag.
8.rx_flag有效时8位rx_data也已经组装完成,可以根据rx_flag,在时钟上升沿将rx_data赋值给po_data,送到下一个模块了,由于rx_data打了一拍得到po_data,因此将rx_flag打一拍得到po_flag,与po_data同步。
9.这里只判断了一帧串口帧里的前9位数据,1位开始位+8位数据位,并没有管最后1位结束位,这是因为默认了接收到的是一帧准确的完整的数据,只需要判断开始位,正确接收到8位数据位并组合成8bit并行数据即可,结束位不用再判断。
module uart_rx
#(
parameter UART_BPS = 'd9600,//BAUD rate
parameter CLK_FREQ = 'd50_000_000//System clock frequency
)
(
input sys_clk ,//system clock
input sys_rst_n ,//system nagetive reset
input rx ,//rx data from another system tx part
output reg [7:0] po_data ,//paranell 8 bit data
output reg po_flag //effective flag
);
parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;// (1/9600) / (1/50_000_000)
reg rx_reg1 ;//1'latency
reg rx_reg2 ;//2'latency
reg rx_reg3 ;//3'latency
reg start_edge ;//rx_reg3 && !rx_reg2
reg work_en ;//start frame effective area,from start edge to bit8
reg [12:0] baud_cnt ;//receive 1 bit count clock period number
reg bit_flag ;//bit ready to get
reg [3:0] bit_cnt ;//bit count to trans to paranell data
reg [7:0] rx_data ;//paranell shift data
reg rx_flag ;// rx_data shift finish
//rx_reg1
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_reg1 <= 1'b0;
else
rx_reg1 <= rx;
end
//rx_reg2
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_reg2 <= 1'b0;
else
rx_reg2 <= rx_reg1;
end
//rx_reg3
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_reg3 <= 1'b0;
else
rx_reg3 <= rx_reg2;
end
//start_edge
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
start_edge <= 1'b0;
else if(!rx_reg2 && rx_reg3)
start_edge <= 1'b1;
else
start_edge <= 1'b0;
end
//work_en
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
work_en <= 1'b0;
else if(start_edge)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && bit_flag)//bit count to 8 and bit8 receive finish && bit_flag is important
work_en <= 1'b0;
end
//baud_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
baud_cnt <= 13'd0;
else if((baud_cnt == BAUD_CNT_MAX - 1'b1) || !work_en)//arive max count,1 bit receive finish or 8 bit receive finish
baud_cnt <= 13'd0;
else
baud_cnt <= baud_cnt + 1'b1;
end
//bit_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX[12:1])//center count position
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
//bit_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
bit_cnt <= 4'd0;
else if((bit_cnt == 4'd8) && bit_flag)// 8 bit receive finish, && bit_flag is important
bit_cnt <= 4'd0;
else if(bit_flag)// per bit receive
bit_cnt <= bit_cnt + 1'b1;
end
//rx_data
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1 && bit_cnt <= 4'd8) && bit_flag)
rx_data <= {rx_reg3,rx_data[7:1]};//put per bit on msb,right shift
else
rx_data <= rx_data;
end
//rx_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && bit_flag)
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
end
//po_data
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
po_data <= 8'b0;
else if(rx_flag)
po_data <= rx_data;
else
po_data <= po_data;
end
//po_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
po_flag <= 1'b0;
else
po_flag <= rx_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'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20 sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
//simulate to send 8 times data, per data is 1 byte
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
task rx_bit(
input [7:0] data
);
integer i;
for (i=0;i<=9;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
#(5028 * 20);//send 1 bit delay 5028 * 20ns,20ns is clock period, simulate receive 1bit need 5028 clock period
end
endtask
uart_rx uart_rx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.po_data (po_data ),
.po_flag (po_flag )
);
endmodule

TX:

module uart_tx
#(
parameter UART_BPS = 'd9600,
parameter CLK_FREQ = 'd50_000_000
)
(
input sys_clk ,
input sys_rst_n ,
input [7:0] pi_data ,
input pi_flag ,
output reg tx ,
output reg work_en //rs485 USB转换IC需要Work_en做使能信号 RS232不需要
);
parameter BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
// reg work_en ; rs232
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [4:0] bit_cnt ;
//work_en
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
work_en <= 1'b0;
else if(pi_flag)
work_en <= 1'b1;
else if(bit_cnt == 4'd9 && bit_flag)
work_en <= 1'b0;
end
//baud_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
baud_cnt <= 13'd0;
else if(baud_cnt == BAUD_CNT_MAX - 1'b1 || !work_en)
baud_cnt <= 13'd0;
else
baud_cnt <= baud_cnt + 1'b1;
end
//bit_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
bit_flag <= 1'b0;
else if(baud_cnt == 13'd1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
//bit_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
bit_cnt <= 4'd0;
else if(bit_cnt == 4'd9 && bit_flag)
bit_cnt <= 4'd0;
else if(bit_flag)
bit_cnt <= bit_cnt + 1'b1;
end
//tx
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tx <= 1'b1;
else if(bit_flag)
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
end
endmodule
module rs232 (
input sys_clk ,
input sys_rst_n ,
input rx ,
output tx ,
output work_en //RS232不需要,rs485需要
);
parameter UART_BPS = 14'd9600; //比特率
parameter CLK_FREQ = 26'd50_000_000; //时钟频率
wire [7:0] po_data ;
wire po_flag ;
uart_rx uart_rx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.rx (rx ),
.po_data (po_data ),
.po_flag (po_flag )
);
uart_tx uart_tx_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.pi_data (po_data ),
.pi_flag (po_flag ),
.tx (tx ),
.work_en (work_en ) //RS232不需要 RS485需要
);
endmodule
RS232 和 RS485基本差不多,RX部分完全一样,TX多输出一个work_en信号
浙公网安备 33010602011771号