串口、UART、RS232、RS485、USB、COM口说明

经常叫串口通信串口通信的,有时候默认串口通信就是指UART,有时候又会叫USB串口,有时候会说串口RS232,串口RS485,有时候又说COM口,傻傻分不清楚,人都搞懵了,今天彻底弄清楚。

串口是一个泛称,代指所有串口时序标准的接口,即每次只传输一位数据,规定了逻辑信号的格式,什么时候该收或发1,什么时候该收或发0,与之对应的是并口。UART、RS232、RS485、TTL都遵循着类似的通信时序协议,因此都被通称为串口。

UART:通用异步收发器(Universal Asynchronous Receiver/Transmitter)。可以说UART不是接口,而是实现串口收发的逻辑电路,这部分可以独立成芯片,也可以作为模块嵌入到其他芯片里,单片机、SOC、PC里都会有UART模块。
COM口:特指台式计算机或一些电子设备上的D-SUB外形(一种连接器结构,VGA接口的连接器也是D-SUB)的串行通信口,应用了串口通信时序和RS232的逻辑电平,注意,RS485并不适用COM口。
USB:通用串行总线,普遍使用的一种接口,和串口完全是两个概念。虽然也是串行方式通信,但由于USB的通信时序和逻辑电平标准都和串口完全不同,因此和串口没有任何关系。USB是高速的通信接口,用于PC连接各种外设,U盘、键鼠、移动硬盘、当然也包括“USB转串口”的模块。(USB转串口模块,就是USB接口的UART模块)
RS232:是电子工业协会(Electronic Industries Association,EIA) 所制定的异步传输标准接口(from 百度),同时对应着电平标准和通信协议(时序),其电平标准+5V~+15V表示0,-15V~-5V表示1。
RS485:也是一种串口接口标准,时序是一样的,不过逻辑电平标准不同。为了长距离传输采用差分方式传输,抗干扰能力很强。电压差大于200mV为1,小于-200mV为0。
 
总的来说串口是串行接口统称,UART是串口收发逻辑电路,RS232和RS485是电平标准,COM口是物理的连接器接口,主要是RS232串口用的,USB串口指的是USB转串口,是因为笔记本主机等设备现在已经没有笨重的COM口了,而USB口一般都是标配,为了方便进行串口调试,因此有使用USB转串口的芯片和电路便于笔记本进行一些设备的串口调试。

来弄清楚一个概念,单工,半双工,全双工的区别。这几个术语是通信网络中的术语,所谓单工就是指通信中数据只能在一个方向上单向传输,要么就是接收端,要么就是发送端,其扮演的角色是固定的,如广播系统,键盘到计算机,传感器网络等;半双工是指在同一时间只能接收或者发送数据,其扮演的角色可以转换,但是同一时间其扮演的角色不能变化,也就是说该设备发送和接受数据的动作不能同时进行,如对讲机;全双工是指可以同时接收和发送数据,这就意味着该设备的数据通道起码有两条,如电话通信、网络通信中的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转串口芯片进行信号转换:

注意 在使用时需将 J2J3 口的 12 脚用跳帽连接起来才能正常使用, 即开发板上的 J2J3 中的 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信号

posted @ 2023-03-16 00:53  million_yh  阅读(2262)  评论(0)    收藏  举报