UART接口协议是一种比较简单、非常常用的一种接口协议,使用它的场景很常见,是我们学习FPGA一定要会的接口协议。
通用异步收发器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种串行、异步、全双工的通信协议,在嵌入式领域应用的非常广泛。其数据通信格式如下图:
UART 数据传输格式
LSB:least significant bit 表示二进制数据的最低位。
MSB:most significant bit 表示二进制数据的最高位。
起始位:
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。
数据位:
起始位之后就是我们所要传输的数据,数据位可以是5、6、7、8,9位等,构成一个字符(一般都是8位)。先发送最低位,最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
奇偶校验位:
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。
停止位:
它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备之间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢。
空闲位:
UART协议规定,当总线处于空闲状态时信号线的状态为 ‘1’ 即高电平,表示当前线路上没有数据传输。
为了更加简单的描述串口协议,我们这里使用常用的1位起始位,1位停止位,8位数据位,无奇偶校验位,波特率9600(本次波特率以参数形式提供,如需测试其他波特率,计算后更改即可)。
分频器:
`timescale 1ns / 1ps
//波特率发生器,即分频器
module baud_gen #(
parameter max_baud = 326 //时钟周期/(波特率*N),N选取16
)
(
input clk,
input rst_n,
output reg bclk
);
reg [$clog2(max_baud):0] cnt;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
cnt <= 0;
bclk <= 0;
end
else if(cnt == max_baud>> 1) //大约50%占空比
begin
cnt <= cnt + 1'b1;
bclk <= 1'b1;
end
else if(cnt == max_baud-1'b1)
begin
cnt <= 0;
bclk <= 0;
end
else cnt <= cnt +1'b1;
end
endmodule
发送模块:
`timescale 1ns / 1ps
module uart_tx(
input bclk , //分频时钟
input rst_n , //系统复位
input [7:0] uart_tx_data, //待发送数据
input uart_tx_en, //发送使能
output reg uart_txd //发送数据
);
parameter idle = 'd0, start = 3'd1, shift = 3'd2, stop = 3'd3;
reg [2:0] state,next_state;
reg [3:0] bclk_cnt;
reg [2:0] dcnt;
reg [7:0] tx_data_reg;
reg tx_state;
// 当发送使能信号到达时,将待发送数据暂存
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n)
tx_data_reg <= 8'd0;
else if(uart_tx_en)
tx_data_reg <= uart_tx_data;
else
tx_data_reg <= tx_data_reg ;
end
// 当发送使能信号到达时,将发送状态标识置为1,发送结束后置为0,
// 该标识可表示发送数据线是否处于繁忙状态
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n)
tx_state <= 0;
else if(uart_tx_en)
tx_state <= 1'b1;
else if(state == stop && bclk_cnt == 4'd15)
tx_state <= 0;
else
tx_state <= tx_state;
end
// 使用状态机进行数据发送
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n) begin
state <= idle;
// uart_txd <= 1'b1;
// next_state <= idle;
end
else
state <= next_state;
end
// always @(bclk_cnt or uart_tx_en or dcnt or state)
always @(*)
begin
case(state)
idle:
begin
next_state = (tx_state && bclk_cnt == 4'd15 )? start : idle;
uart_txd = 1;
end
start: //数据发送起始位
begin
next_state = (bclk_cnt==4'd15) ? shift:start;
uart_txd = 0;
end
shift://发送数据
begin
next_state = (bclk_cnt == 4'd15)&&(dcnt == 3'd7 ) ? stop:shift;
uart_txd = tx_data_reg[dcnt];
end
stop://数据结束位
begin
next_state = tx_state?stop:idle;
uart_txd = 'b1;
end
default :
begin
next_state = idle ;
uart_txd = 'b1;
end
endcase
end
// 进入发送过程后,启动时钟计数器
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n) begin
bclk_cnt <= 0;
end
else if(tx_state)
bclk_cnt <= bclk_cnt + 'b1;
else bclk_cnt <= bclk_cnt;
end
// 发送数据bit位计数
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n) begin
dcnt <= 0;
end
else if(tx_state && bclk_cnt == 'd15 && state == shift)
dcnt <= dcnt + 'b1;
else dcnt <= dcnt;
end
endmodule
分频器:同上
接收模块:
`timescale 1ns / 1ps
module uart_rx
(
input bclk ,
input rst_n ,
input uart_rxd,
output reg [7:0] rx_data
);
reg rx_data_reg1, rx_data_reg2, rx_data_reg3;
reg uart_rx_cmd;
reg [3:0] bit_cnt;
reg [3:0] bclk_cnt;
//将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n) begin
rx_data_reg1 <= 'b0;
rx_data_reg2 <= 'b0;
rx_data_reg3 <= 'b0;
end
else begin
rx_data_reg1 <= uart_rxd ;
rx_data_reg2 <= rx_data_reg1;
rx_data_reg3 <= rx_data_reg2;
end
end
//捕获到数据下降沿(起始位0)后,拉高传输开始标志位,
//并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n) begin
uart_rx_cmd <= 0;
end
else if(rx_data_reg3 && !rx_data_reg2) begin
uart_rx_cmd <= 1;
end
else if((bit_cnt == 4'd9) && (bclk_cnt == 4'd7) && (rx_data_reg3 == 1'b1))
begin
uart_rx_cmd <= 0;
end
else uart_rx_cmd <= uart_rx_cmd;
end
//时钟计数
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n)
bclk_cnt <= 0;
else if(uart_rx_cmd)
begin
if(bclk_cnt <= 4'd15)
bclk_cnt <= bclk_cnt +1;
else
bclk_cnt <= 0;
end
else bclk_cnt <= 0;
end
//bit计数
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n)
bit_cnt <= 0;
else if(uart_rx_cmd)
begin
if(bclk_cnt == 4'd7)
bit_cnt <= bit_cnt + 1;
else
bit_cnt <= bit_cnt;
end
else bit_cnt <= 0;
end
// 数据接收
always @(posedge bclk or negedge rst_n)
begin
if(!rst_n) rx_data <= 0;
else if(uart_rx_cmd && bit_cnt!=0)
if(bclk_cnt == 4'd7)
rx_data[bit_cnt-1'b1] <= rx_data_reg3;
// case(bit_cnt)
// 4'd1: rx_data[0] <= rx_data_reg3;
// 4'd2: rx_data[1] <= rx_data_reg3;
// 4'd3: rx_data[2] <= rx_data_reg3;
// 4'd4: rx_data[3] <= rx_data_reg3;
// 4'd5: rx_data[4] <= rx_data_reg3;
// 4'd6: rx_data[5] <= rx_data_reg3;
// 4'd7: rx_data[6] <= rx_data_reg3;
// 4'd8: rx_data[7] <= rx_data_reg3;
// default: rx_data<= 0;
// endcase
else rx_data <= rx_data;
else
rx_data <= 0;
end
endmodule
Testbench:
`timescale 1ns / 1ps
module uart_top_tb(
);
reg clk1;
reg clk2;
reg rst_n;
reg [7:0] uart_tx_data;
reg uart_en;
wire [7:0] rx_data;
wire uart_txd ;
initial begin
clk1 = 0;
clk2 = 0;
rst_n = 0;
uart_en = 0;
#10 rst_n = 1;
#20 uart_en = 1;
uart_tx_data = 8'b0101_0010;
#3250 uart_en = 0;
end
always begin
#10 clk1 = ~clk1;
end
always begin
#5 clk2 = ~clk2;
end
uart_top uart_top(
clk1 ,
clk2 ,
rst_n,
uart_tx_data,
uart_en,
rx_data,
uart_txd
);
endmodule
(1)UART存在的问题
电气接口不统一:对于UART来说,它只是对信号的时序进行了定义,并没有定义接口的电气特性;UART通信一般使用的都是处理器的电平,也就是TTL电平,但是由于不同处理器之间的电平存在差异,所以不同的处理器之间的UART不能直接相连;
抗干扰能力差:采用TTL电平的高低代表0和1,在数据传输过程中很容易出错。并且,由于抗干扰能力很差,所以通信距离也很短,一般只能用在一个板子上不同的芯片的通信;
(2)改进
针对于电气接口不统一的问题,于是产生了RS232协议。
RS232协议: 在串行通讯时,要求通讯双方都采用一个标准接口,使不同的设备可以方便地连接起来进行通讯。规定逻辑“1”的电平为-5V~-15 V,逻辑“0”的电平为+5 V~+15 V。选用该电气标准的目的在于提高抗干扰能力,增大通信距离。RS -232的噪声容限为2V,接收器将能识别高至+3V的信号作为逻辑“0”,将低到-3 V的信号作为逻辑“1”。由于RS -232采用串行传送方式,并且将微机的TTL电平转换为RS-232C电平,其传送距离一般可达30 m。
那么对于RS232来说,又存在一些问题:
首先,由于接口芯片电平较高,容易损坏接口电路的芯片,又因为和TTL电平不兼容,所以需要电平转换芯片才可以和TTL电路连接;通信速度地,通信距离短,抗干扰能力较弱,易产生共模干扰;并且这种接口只可以实现点对点的通信方式,不能实现联网功能。针对这些问题,就产生了如下的RS485;
RS485协议:RS485标准规定采用差分信号进行数据传输,两线间的电压差+2~+6v表示逻辑‘1’,两线间-2v到-6v表示逻辑0;使用差分信号可以有效地减少噪声信号的干扰,延长通信距离,可达到1500m;并且RS485接口信号的电平相比于RS232降低了,所以不宜损坏接口芯片,并且该电平和TTL兼容,可以方便的和TTL电路连接;但是由于RS485采用两线制,所以数据的发送和接收都要使用这对差分信号线,所以是半双工的工作方式,在编程时需要加以处理。同时具有多站能力,可以很方便的建立起一个设备网络,最多可以有32个节点。