UART通信协议
- 串口通信的信号线只需要两条线就可以完成,TX和RX TX发送端 RX为接收端。
- 起始位,数据线从高变低,低有效为0,数据传输开始。
- 数据位,起始位传输之后便是数据位开始,一般为8位,传输时低位(LSB)在前,高位(MSB)在后。
- 校验位,校验位可以认为是一个特殊的数据位,通常使用的是奇偶校验,使用串口协议时通常取消奇偶校验位。
- 停止位,停止位高有效为1,他表示这一个个字节传输结束。
6.位时间,起始位、数据位、校验位的位宽度是一致的,停止位有0.5位、1位、1.5位格式,一般为1位。
- 空闲位,持续的高电平。
- 帧:从起始位开始到停止位结束的时间间隔称之为一帧。
START |
D0 |
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
D7 |
P |
STOP |
起始位 |
数据位 |
校验位 |
停止位 |
模块编写
首先约定波特率为9600(可变),无校验位
将数据线打两拍,防止亚稳态并捕获其的下降沿信号(起始位0)
检测到下降沿后拉高数据传输标志,意味着数据开始接收;并在传输第九个数据(终止位1)的正中(数据比较稳定)将数据传输标志拉低,意味着数据接收完成
对时钟进行计数,每一个周期(在设定波特率条件下传输一位所需要的时间)对数据线进行采样并将数据寄存,同时记录传输个数(记录当前共接收了多少个数据)
接收到第九个数据(终止位1)后,拉高接收完成标志位。
将8位或者多位数据拆分为一位一位的发送出去的过程称为并转串。将一位一位接收的数据合并为8位或者多位数据的过程称为串转并。
对于串行通信设备来说,发送方都是在执行并转串,接收方都是在执行串转并。UART设备为串行通信设备。
代码
UART_RX
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| module uart_rx( input sys_clk, input sys_rst_n, input uart_rxd, output reg uart_rx_done, output reg [7:0]uart_rx_data );
parameter BPS=9600; parameter SYS_CLK_FRE=50_000_000; localparam BPS_CNT=SYS_CLK_FRE/BPS; reg uart_rx_d0; reg uart_rx_d1; reg [15:0] clk_cnt; reg [3:0] rx_cnt; reg rx_flag; reg [7:0] uart_rx_data_reg; wire neg_uart_rx_data; assign neg_uart_rx_data=uart_rx_d1 & (~uart_rx_d0);
always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin uart_rx_d0<=1'b0; uart_rx_d1<=1'b0; end else begin uart_rx_d0<=uart_rxd; uart_rx_d1<=uart_rx_d0; end end
always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) rx_flag<=1'b0; else begin if(neg_uart_rx_data) rx_flag<=1'b1; else if((rx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2)) rx_flag<=1'b0; else rx_flag<=rx_flag; end end
always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin rx_cnt<=4'd0; clk_cnt<=16'd0; end else if(rx_flag)begin if(clk_cnt<BPS_CNT-1'b1)begin clk_cnt<=clk_cnt+1'b1; rx_cnt<=rx_cnt; end else begin clk_cnt<=16'd0; rx_cnt<=rx_cnt+1'b1; end end else begin rx_cnt<=4'd0; clk_cnt<=16'd0; end end
always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) uart_rx_data_reg<=8'd0; else if(rx_flag) if(clk_cnt==BPS_CNT/2) begin case(rx_cnt) 4'd1:uart_rx_data_reg[0]<=uart_rxd; 4'd2:uart_rx_data_reg[1]<=uart_rxd; 4'd3:uart_rx_data_reg[2]<=uart_rxd; 4'd4:uart_rx_data_reg[3]<=uart_rxd; 4'd5:uart_rx_data_reg[4]<=uart_rxd; 4'd6:uart_rx_data_reg[5]<=uart_rxd; 4'd7:uart_rx_data_reg[6]<=uart_rxd; 4'd8:uart_rx_data_reg[7]<=uart_rxd; default:; endcase end else uart_rx_data_reg<=uart_rx_data_reg; else uart_rx_data_reg<=8'd0; end
always@(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin uart_rx_done<=1'b0; uart_rx_data<=8'd0; end else if(rx_cnt==4'd9)begin uart_rx_done<=1'b1; uart_rx_data<=uart_rx_data_reg; end else begin uart_rx_done<=1'b0; uart_rx_data<=8'd0; end end endmodule
|
UART_TX
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| module uart_tx( input sys_clk, input sys_rst_n, input [7:0] uart_data, input uart_tx_en, output reg uart_txd ); parameter SYS_CLK_FRE=50_000_000; parameter BPS=9_600; localparam BPS_CNT=SYS_CLK_FRE/BPS; reg uart_tx_en_d0; reg uart_tx_en_d1; reg tx_flag; reg [7:0] uart_data_reg; reg [15:0] clk_cnt; reg [3:0] tx_cnt; wire pos_uart_en_txd;
assign pos_uart_en_txd= uart_tx_en_d0 && (~uart_tx_en_d1); always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin uart_tx_en_d0<=1'b0; uart_tx_en_d1<=1'b0; end else begin uart_tx_en_d0<=uart_tx_en; uart_tx_en_d1<=uart_tx_en_d0; end end
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin tx_flag<=1'b0; uart_data_reg<=8'd0; end else if(pos_uart_en_txd)begin uart_data_reg<=uart_data; tx_flag<=1'b1; end else if((tx_cnt==4'd9) && (clk_cnt==BPS_CNT/2))begin tx_flag<=1'b0; uart_data_reg<=8'd0; end else begin uart_data_reg<=uart_data_reg; tx_flag<=tx_flag; end end
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin clk_cnt<=16'd0; tx_cnt <=4'd0; end else if(tx_flag) begin if(clk_cnt<BPS_CNT-1)begin clk_cnt<=clk_cnt+1'b1; tx_cnt <=tx_cnt; end else begin clk_cnt<=16'd0; tx_cnt <=tx_cnt+1'b1; end end else begin clk_cnt<=16'd0; tx_cnt<=4'd0; end end
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) uart_txd<=1'b1; else if(tx_flag) case(tx_cnt) 4'd0: uart_txd<=1'b0; 4'd1: uart_txd<=uart_data_reg[0]; 4'd2: uart_txd<=uart_data_reg[1]; 4'd3: uart_txd<=uart_data_reg[2]; 4'd4: uart_txd<=uart_data_reg[3]; 4'd5: uart_txd<=uart_data_reg[4]; 4'd6: uart_txd<=uart_data_reg[5]; 4'd7: uart_txd<=uart_data_reg[6]; 4'd8: uart_txd<=uart_data_reg[7]; 4'd9: uart_txd<=1'b1; default:; endcase else uart_txd<=1'b1; end endmodule
|
UART_TOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| module uart_top( input sys_clk, input sys_rst_n, input uart_rxd, output uart_txd ); parameter UART_BPS=9600; parameter CLK_FREQ=50_000_000; wire uart_en_w; wire [7:0] uart_data_w;
uart_tx#( .BPS (UART_BPS), .SYS_CLK_FRE (CLK_FREQ)) u_uart_tx( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .uart_tx_en (uart_en_w), .uart_data (uart_data_w), .uart_txd (uart_txd) );
uart_rx #( .BPS (UART_BPS), .SYS_CLK_FRE (CLK_FREQ)) u_uart_rx( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .uart_rxd (uart_rxd), .uart_rx_done (uart_en_w), .uart_rx_data (uart_data_w) ); endmodule
|
TestBench
由于涉及到的模块较少使用VScode进行简单的仿真,需要生成vcd波形文件,添加了额外的代码,一次八位数据为010101012即5516。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| `timescale 1ns/1ns
module uart_top_tb(); reg sys_clk; reg sys_rst_n; reg uart_rxd; wire uart_txd;
uart_top #( .UART_BPS (9600), .CLK_FREQ (50_000_000) ) u_uart_top( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .uart_rxd (uart_rxd), .uart_txd (uart_txd) ); localparam CNT=50_000_000/9600*20; initial begin
sys_clk <=1'b0; sys_rst_n<=1'b0; uart_rxd<=1'b1; #20 sys_rst_n<=1'b1; #(CNT/2) uart_rxd<=1'b0; #CNT uart_rxd<=1'b1; #CNT uart_rxd<=1'b0; #CNT uart_rxd<=1'b1; #CNT uart_rxd<=1'b0; #CNT uart_rxd<=1'b1; #CNT uart_rxd<=1'b0; #CNT uart_rxd<=1'b1; #CNT uart_rxd<=1'b0; #CNT uart_rxd<=1'b1; #20000 $finish; end always begin #10 sys_clk=~sys_clk; end initial begin $dumpfile("wave.vcd"); $dumpvars(0, uart_top_tb); end endmodule
|
图1 仿真结果
全双工通信、半双工通信和单工通信
- 全双工通信是指在同一时刻信息可以进行双向传输。例如:打电话,说的同时也能听。
- 半双工通信是指在同一时刻信息只能单向传输,双方都可以进行发送和接收,但是不能够同时发送和接收。例如:对讲机通信。
- 单工通信是指在通信过程中,只能够设备A发送,设备B接收。例如:听收音机。
UART的通信电平标准
两个设备之间能够互相通信的基础条件为电平标准相同。UART的接口标准有很多,有RS232、RS485等等。台式PC上一般会有一个DB9,接口标准为RS232。
此接口在各个工业板上也有很多。随着技术的发展,PC上的DB9的接口逐渐被淘汰,换成了USB接口。
现如今的开发板上多为USB接口,便于和PC进行通信。
FPGA芯片是无法(较为复杂)发出对应的电平标准,如:RS485、RS232、USB接口电平等。在大多数板卡设计时,都会在FPGA外围添加电平转换器,将FPGA的电平标准转换为通信的电平标准。