Uart 簡介
UART 全稱為 Universal Asynchronous Receiver/Transmitter,譯為通用異步收發傳輸器。它是一種通用串行數據總線,將數據在串行通信與并行通信之間進行轉換,用于異步通信。
Uart 一般有以下幾個特點:
◆ 可完成數據的串并、并串轉換;
◆ 可雙向通信,實現全雙工傳輸和接收;
◆ 協議簡單,最少使用 2 根信號線和一根地線即可完成數據收發,但抗干擾能力差;
◆ 傳輸速率低,一般不到 1Mbps。當傳輸速率超過 3Mbps 時,收發波形不均勻,誤碼率高;
◆ 傳輸距離有限,TTL 電平標準下最長可傳輸 3 米左右,RS232 電平標準最長支持傳輸長度為 15 米左右;
◆ 應用廣泛,常用于數據采集與通信、電路輔助測試、電路控制等。例如通過 USB/網線轉串口模塊,可以時序計算機與串口設備的連接,串口設備可以是打印機、開發板等。
Uart 管腳
Uart 一般使用 9 針的 DB9 接口來完成數據終端設備 (DTE) 與數據通訊設備 (DCE) 之間的數據交換。實際 Uart 的 DB9 接口圖如下所示。
如上圖所示,DB9 公頭從左到右、從上倒下,分別對應的針管腳標號為 1 到 9。各個管腳的功能簡單表述如下表所示。需要注意的是,為了能和公頭相連接,母頭管腳標號從右上角開始。
Uart 協議
Uart 常用的傳輸協議示意圖如下:
◆ 起始位:為低電平,表示傳輸數據的開始。
◆ 數據位:近鄰起始位之后,表示要傳輸的數據。數據長度可以為 5、6、7、8 ,但經常使用的數據長度為 8,用于表示一個 ASCII 字符。數據從低位到高位依次傳輸。
◆ 奇偶校驗位:數據位加上此位數據后,使得 “1” 的位數為偶數 (偶校驗) 或奇數 (奇校驗)。例如,當為奇校驗時,如果 8 位數據中 1 的數量為奇數,則此校驗位為 1;如果 8 位數據中 1 的數量為偶數,則此校驗位為 1。偶校驗同理。校驗位也可以去除不傳輸。
◆ 停止位:一針數據傳輸結束的標志,可以是 1 位、1.5 位、2 位的高電平。停止位個數越多,數據傳輸越穩定,但是數據傳輸速度也越慢。
◆ 空閑位:當總線處于空閑狀態時,所有信號線要保持為高電平,表示沒有數據傳輸。
Uart 接收時大致的工作過程描述如下:
當總線由高電平變為低電平時,表示數據開始傳輸。起始位之后可接收到 8 個數據位。如果設置了奇偶校驗功能,可根據接收的數據位和奇偶校驗位進行檢測。校驗出錯,則數據丟掉。最后,接收到高電平停止位。如果停止位不為高電平,表示此次傳輸出錯,數據也要丟掉。最后總線保持為高電平狀態,表示一次接收完畢。同時,Uart 內部將接收到的串行數據轉換為并行數據。
Uart 發送過程與接收過程類似,這里不再描述。
◆ 波特率
傳輸過程中,1bit 數據的傳輸速率用波特率來描述,單位為 bps (bit per second)。一般經常使用的波特率都為 9600、19200、115200 等,表示 Uart 每秒傳輸多少比特數據。
由于起始位、停止位的存在,實際的傳輸速率并不等于波特率。例如,不考慮校驗位,傳輸 8 位有效數據實際需要傳輸 10 位數據(8 位數據和起始位、停止位),所以實際的數據傳輸速率為波特率的 80%。
◆ 誤碼率
當傳輸 8bit 真實數據時,不考慮校驗位。假如發送數據和接收數據都存在誤差,這種誤差可以累加,那么極限情況就是在接收最后 1bit 數據的結束時發生錯誤。加上已經正確傳輸的 19bit 數據,那么波特率最大誤差為 1/20=5% 。
實際中如果不是連續傳輸,即 Uart 發送和接收中間的延遲時間比較長時,那么要求的波特率誤差將會更小。
Uart 實現
◆ 參數設計
下面使用 Verilog 設計一個 Uart 模塊,參數如下:
波特率:115200
數據位寬度:8
校驗位:無
工作時鐘:50 Mhz
◆ 接收模塊
Uart 接收端口說明如下表所示:
Uart 接收數據狀態示意圖如下所示:
(1) 上電后 Uart 進入空閑狀態 S_IDLE ;
(2) 當輸入端 rx_pin 變低時,表示傳輸開始,進入開始狀態 S_START ;
(3) 等待 1bit 的時間,進入接收數據狀態 S_REC_BYTE ;
(4) 接收完 8bit 數據接收后,進入停止狀態 S_STOP ;
(5) 停止狀態后增加一個 S_DATA 狀態,用于將接收到的數據輸出 ;
(6) 最后回到空閑狀態 S_IDLE,等待下一次接收。
接收代碼描述如下:
module uart_rx
#(
parameter CLK_FRE = 50, //clock frequency(Mhz)
parameter BAUD_RATE = 115200 //serial baud rate
)
(
input clk, //clock input
input rst_n, //asynchronous reset input, low active
output reg[7:0] rx_data, //received serial data
output reg rx_data_valid, //received serial data is valid
input rx_data_ready, //data receiver module ready
input rx_pin //serial data input
);
//calculates the clock cycle for baud rate
localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
//state machine code
localparam S_IDLE = 1;
localparam S_START = 2; //start bit
localparam S_REC_BYTE = 3; //data bits
localparam S_STOP = 4; //stop bit
localparam S_DATA = 5;
reg[2:0] state;
reg[2:0] next_state;
reg rx_d0; //delay 1 clock for rx_pin
reg rx_d1; //delay 1 clock for rx_d0
wire rx_negedge; //negedge of rx_pin
reg[7:0] rx_bits; //temporary storage of received data
reg[15:0] cycle_cnt; //baud counter
reg[2:0] bit_cnt; //bit counter
assign rx_negedge = rx_d1 && ~rx_d0;
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
begin
rx_d0 <= 1'b0;
rx_d1 <= 1'b0;
end
else
begin
rx_d0 <= rx_pin;
rx_d1 <= rx_d0;
end
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
state <= S_IDLE;
else
state <= next_state;
end
always@(*) begin
case(state)
S_IDLE:
if(rx_negedge)
next_state <= S_START;
else
next_state <= S_IDLE;
S_START:
if(cycle_cnt == CYCLE - 1)//one data cycle
next_state <= S_REC_BYTE;
else
next_state <= S_START;
S_REC_BYTE:
if(cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7) //receive 8bit data
next_state <= S_STOP;
else
next_state <= S_REC_BYTE;
S_STOP:
if(cycle_cnt == CYCLE/2 - 1)//half bit cycle,to avoid missing the next byte receiver
next_state <= S_DATA;
else
next_state <= S_STOP;
S_DATA:
if(rx_data_ready) //data receive complete
next_state <= S_IDLE;
else
next_state <= S_DATA;
default:
next_state <= S_IDLE;
endcase
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rx_data_valid <= 1'b0;
else if(state == S_STOP && next_state != state)
rx_data_valid <= 1'b1;
else if(state == S_DATA && rx_data_ready)
rx_data_valid <= 1'b0;
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rx_data <= 8'd0;
else if(state == S_STOP && next_state != state)
rx_data <= rx_bits;//latch received data
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
begin
bit_cnt <= 3'd0;
end
else if(state == S_REC_BYTE)
if(cycle_cnt == CYCLE - 1)
bit_cnt <= bit_cnt + 3'd1;
else
bit_cnt <= bit_cnt;
else
bit_cnt <= 3'd0;
end
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
cycle_cnt <= 16'd0;
else if((state == S_REC_BYTE && cycle_cnt == CYCLE - 1) || next_state != state)
cycle_cnt <= 16'd0;
else
cycle_cnt <= cycle_cnt + 16'd1;
end
//receive serial data bit data
//避免對串行輸入數據的誤采樣,在波特率計數器的中間值時刻進行采樣
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rx_bits <= 8'd0;
else if(state == S_REC_BYTE && cycle_cnt == CYCLE/2 - 1)
rx_bits[bit_cnt] <= rx_pin;
else
rx_bits <= rx_bits;
end
endmodule
◆ 發送模塊
Uart 發送端口說明如下表所示:
Uart 發送數據狀態示意圖如下所示:
(1) 上電后 Uart 進入空閑狀態 S_IDLE ;
(2) 如果有發送請求,進入開始狀態 S_START ;
(3) 等待 1bit 的時間,進入發送數據狀態 S_SEND_BYTE ;
(4) 發送完 8bit 數據接收后,進入停止狀態 S_STOP ;
(5) 最后回到空閑狀態 S_IDLE,等待下一次數據發送。
發送模塊代碼描述如下:
module uart_tx
#(
parameter CLK_FRE = 50, //clock frequency(Mhz)
parameter BAUD_RATE = 115200 //serial baud rate
)
(
input clk, //clock input
input rst_n, //asynchronous reset input, low active
input[7:0] tx_data, //data to send
input tx_data_valid, //data to be sent is valid
output reg tx_data_ready, //send ready
output tx_pin //serial data output
);
//calculates the clock cycle for baud rate
localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
//state machine code
localparam S_IDLE = 1;
localparam S_START = 2;//start bit
localparam S_SEND_BYTE = 3;//data bits
localparam S_STOP = 4;//stop bit
reg[2:0] state;
reg[2:0] next_state;
reg[15:0] cycle_cnt; //baud counter
reg[2:0] bit_cnt;//bit counter
reg[7:0] tx_data_latch; //latch data to send
reg tx_reg; //serial data output
assign tx_pin = tx_reg;
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
state <= S_IDLE;
else
state <= next_state;
end
always@(*) begin
case(state)
S_IDLE:
if(tx_data_valid == 1'b1)
next_state <= S_START;
else
next_state <= S_IDLE;
S_START:
if(cycle_cnt == CYCLE - 1)
next_state <= S_SEND_BYTE;
else
next_state <= S_START;
S_SEND_BYTE:
if(cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7)
next_state <= S_STOP;
else
next_state <= S_SEND_BYTE;
S_STOP:
if(cycle_cnt == CYCLE - 1)
next_state <= S_IDLE;
else
next_state <= S_STOP;
default:
next_state <= S_IDLE;
endcase
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
begin
tx_data_ready <= 1'b0;
end
else if(state == S_IDLE)
if(tx_data_valid == 1'b1)
tx_data_ready <= 1'b0;
else
tx_data_ready <= 1'b1;
else if(state == S_STOP && cycle_cnt == CYCLE - 1)
tx_data_ready <= 1'b1;
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
begin
tx_data_latch <= 8'd0;
end
else if(state == S_IDLE && tx_data_valid == 1'b1)
tx_data_latch <= tx_data;
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
begin
bit_cnt <= 3'd0;
end
else if(state == S_SEND_BYTE)
if(cycle_cnt == CYCLE - 1)
bit_cnt <= bit_cnt + 3'd1;
else
bit_cnt <= bit_cnt;
else
bit_cnt <= 3'd0;
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cycle_cnt <= 16'd0;
else if((state == S_SEND_BYTE && cycle_cnt == CYCLE - 1) || next_state != state)
cycle_cnt <= 16'd0;
else
cycle_cnt <= cycle_cnt + 16'd1;
end
always@(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
tx_reg <= 1'b1;
else
case(state)
S_IDLE,S_STOP:
tx_reg <= 1'b1;
S_START:
tx_reg <= 1'b0;
S_SEND_BYTE:
tx_reg <= tx_data_latch[bit_cnt];
default:
tx_reg <= 1'b1;
endcase
end
endmodule
◆ Uart 模塊整合
將收發模塊進行整合,組成一個完整的 Uart 模塊。
module uart #(
parameter CLK_FRE = 50,
parameter BAUD_RATE = 115200
)
(
input clk,
input rst_n,
input rx,
input rx_ready,
output [7:0] rx_data,
output rx_data_valid,
input [7:0] tx_data,
input tx_data_valid ,
output tx ,
output tx_ready
);
uart_rx #(
.CLK_FRE(CLK_FRE),
.BAUD_RATE(BAUD_RATE))
u_rx(
.clk (clk ),
.rst_n (rst_n ),
.rx_pin (rx ), //input
.rx_data_ready (rx_ready ),
.rx_data (rx_data ), //output
.rx_data_valid (rx_data_valid )
);
uart_tx #(
.CLK_FRE(CLK_FRE),
.BAUD_RATE(BAUD_RATE))
u_tx (
.clk (clk ),
.rst_n (rst_n ),
.tx_data (tx_data ), //input
.tx_data_valid (tx_data_valid ),
.tx_pin (tx ), //output
.tx_data_ready (tx_ready )
);
endmodule
◆ Testbench
Uart 測試時采用自回環的測試方法,即 Uart 發送端接口與接收端接口相連,實現自發自收。此測試方法簡單且有效。
`timescale 1ns/1ns
module test;
//clock and reset
reg clk;
always #10 clk = ~clk ;
reg rstn ;
initial begin
rstn = 0 ;
clk = 0 ;
# 4.5 ;
rstn = 1 ;
end
reg [7:0] tx_data ;
reg tx_data_valid ;
wire [7:0] rx_data ;
wire rx_data_valid ;
wire tx2rx, tx2rx_ready;
//test in loop
uart u_uart1 (
.clk (clk),
.rst_n (rstn),
.rx (tx2rx), //in
.rx_ready (tx2rx_ready),
.rx_data (rx_data[7:0]), //out
.rx_data_valid (rx_data_valid),
.tx_data (tx_data[7:0]), //in,
.tx_data_valid (tx_data_valid),
.tx (tx2rx), //out
.tx_ready (tx2rx_ready)
);
initial begin
tx_data = 0 ;
tx_data_valid = 0 ;
#100;
//send data
wait (tx2rx_ready);
@(negedge clk);
tx_data = 8'h35 ;
tx_data_valid = 1 ;
@(negedge clk);
tx_data_valid = 0 ;
repeat(15) begin
@(negedge clk);
end
wait (tx2rx_ready);
@(negedge clk);
tx_data = 8'h18 ;
tx_data_valid = 1 ;
@(negedge clk);
tx_data_valid = 0 ;
repeat(15) begin
@(negedge clk);
end
wait (tx2rx_ready);
@(negedge clk);
tx_data = 8'ha6 ;
tx_data_valid = 1 ;
@(negedge clk);
tx_data_valid = 0 ;
repeat(15) begin
@(negedge clk);
end
#100 ;
end // initial begin
//receive parallel data
reg [1:0] rx_data_valid_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
rx_data_valid_r <= 1'b0 ;
end
else begin
rx_data_valid_r <= {rx_data_valid_r[0], rx_data_valid} ;
end
end
wire rx_data_valid_pos = rx_data_valid_r == 2'b01 ;
reg [7:0] check_data ;
integer check_num ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
check_data <= 'b0 ;
check_num <= 'b0 ;
end
else if(rx_data_valid_pos === 1'b1) begin
check_data <= rx_data ;
check_num <= check_num + 1'b1 ;
end
end
//check 3 byte data
initial begin
#1 ;
forever begin
@(negedge clk) ;
if (check_num == 1) begin
if (check_data !== 8'h35) begin
$display("---III--- 1st data Failed: %h", check_data);
end
end
else if (check_num == 2) begin
if (check_data !== 8'h18) begin
$display("---III--- 2nd data Failed: %h", check_data);
end
end
else if (check_num == 3) begin
if (check_data !== 8'ha6) begin
$display("---III--- 3rd data Failed: %h", check_data);
$display("---III--- It's a FAILURE!!!");
end
else begin
#1000000 ;
$display("---III--- It's a SUCCESS!!!");
$display("");
$display("");
end
$finish ;
end
end // forever begin
end // initial begin
endmodule
仿真中的自檢驗成功截圖如下所示。
仿真波形圖如下所示。
由圖可知,波特率、首發數據均正確。
評論