接口/总线/驱动
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
仿真中的自检验成功截图如下所示。
仿真波形图如下所示。
由图可知,波特率、首发数据均正确。
全部0条评论
快来发表一下你的评论吧 !