一.UART介绍
UART:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种异步收发传输器。
异步:通过uart通信协议进行数据交互的双方没有时钟线来进行同步,想要正确的进行数据交互只能按照事先约定的数据传输速率进行传输。对于这个问题我想到了大多数通信方式,无论是异步还是同步在进行通信时都需要一种机制去保证数据传输是正确的,对于同步通信来说,通信双方有着时钟线来保证收发一致,根据时钟信号来决定数据发送和采样时刻,对于异步通信来说,可以像串口一样事先规定波特率,或者把数据通过特定的方式进行编码后再传输,接收方接收数据后再进行解码,这样传输的编码既包含了码元信息又包含了时钟信息。例如,曼彻斯特编码和差分曼彻斯特编码,如图。这样在没有时钟的情况下也可以正确的进行数据传输。
曼彻斯特编码的编码规则是,电平信号由高跳变到低表示逻辑‘1’,电平信号由低跳变到高表示逻辑‘0’;,按照这样的规则,数据接收方可以通过检测电平的跳变来知道传输的码元个数和码元的值。差分曼彻斯特编码的规则是,在每个时钟周期的开始出电平和上一时刻一致则表示逻辑‘1’,否则表示逻辑‘0’;不难发现的是无论是曼彻斯特编码还是差分曼彻斯特编码,编码后的信号都会在时钟周期的中间时刻变化一次。
物理连接线:通过uart通信的双发一般只需要TXD,RXD,GND三根线即可通信。
通信方式:全双工,通信双方可以同时收发数据,因为uart的数据发送和接收是通过不同的数据线实现的,并且在通信双方的RXD和TXD连接是相反的。
传输速率:uart的传输速率也叫波特率,常用的波特率有9600bps,115200bps,38400bps等。
数据格式:uart的数据传输格式为一位起始位,8位数据位(有其他位数),可选的一位校验位,一位停止位。
二.串口回环设计
(一).框图设计
该设计包括一个按键消抖模块,一个串口接收模块,一个串口发送模块,一个fifoip核,一个数码管驱动模块,主要功能是,通过pc端的串口调试助手向开发板发送数据,接收模块把串行数据转成并行数据存进fifo,接收的数据会在数码管上进行显示来观察接收模块数据是否正确,再通过按键一次性把fifo中暂存的数据连续读出直至fifo为空,读出的数据通过串口发送模块返回到串口调试助手,此外可以通过开发板上的按键调整串口收发模块的波特率。
RTL视图
(二).代码实现
串口接收模块代码:通过状态机实现
module uart_rx #(parameter DATA_WIDE = 8 )(
input clk , //system clock 50MHz
input rst_n , //reset, low valid
input key , //波特率调节按键
input port_rx , //数据接收端口
input fifo_full , //fifo满信号
output recive_vld , //数据接收有效信号
output reg [DATA_WIDE - 1:00] data_rx //接收的数据,送给fifo
);
//Parameter Declarations
localparam //状态机独热码参数
IDLE = 4'b0001 , //空闲状态
START = 4'b0010 , //起始位
DATA = 4'b0100 , //数据位
STOP = 4'b1000 ; //停止位
localparam SYS_CLK = 50_000_000 ;//系统时钟
localparam BPS9600 = 9600 ,//9600波特率参数
BPS115200 = 115200 ;//115200波特率参数
//Internal wire/reg Declarations
reg [03:00] state_c ,state_n ;//状态机现态,次态
reg [23:00] bps_param ;//波特率变量
reg [15:00] cnt_base ;//计时一个bit所需时间
wire add_cnt_base ;
wire end_cnt_base ;
wire [15:00] end_cnt_base_para;//cnt_base计数最大值
reg [03:00] cnt_bit ;//比特计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg port_rx_r ;
wire neg_rx ;//下降沿检测
reg cnt_key ;//按键计数器
wire idle2start ;//初始状态跳转到起始位状态条件
wire start2data ;//起始位状态跳转到数据接收状态条件
wire data2stop ;//数据接收状态跳转到停止位状态条件
wire stop2idle ;//停止位状态跳转到空闲状态条件
//Logic Description
//三段式状态机//
/状态转移描述//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_c <= IDLE ; //空闲状态
end
else begin
state_c <= state_n ;//下一状态
end
end
//状态转移条件描述/
always @(*) begin
case (state_c)
IDLE :begin
if(idle2start)begin
state_n = START ;
end
else begin
state_n = IDLE ;
end
end
START:begin
if(start2data)begin
state_n = DATA ;
end
else begin
state_n = START ;
end
end
DATA :begin
if(data2stop)begin
state_n = STOP ;
end
else begin
state_n = DATA ;
end
end
STOP :begin
if(stop2idle)begin
state_n = IDLE ;
end
else begin
state_n = STOP ;
end
end
default: begin
if(idle2start)begin
state_n = START ;
end
else begin
state_n = IDLE ;
end
end
endcase
end
assign idle2start = state_c == IDLE && neg_rx ; //数据检测到下降沿
assign start2data = state_c == START && cnt_bit == 1 ;//发送一位起始位后跳转到数据发送状态
assign data2stop = state_c == DATA && cnt_bit == 8 && end_cnt_base ;//数据位最后一位发送完成跳转到停止位状态
assign stop2idle = state_c == STOP && end_cnt_bit ;//停止位发送完毕回到空闲状态
///输出信号控制///
///data_rx/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_rx <= 8'b0;
end
else begin
case (state_c)
DATA : if((cnt_bit != 0 && cnt_bit != 9 && cnt_base == end_cnt_base_para/2 -1 ))begin
data_rx[cnt_bit - 1 ] <= port_rx ;
end
else begin
data_rx <= data_rx ;
end
default: data_rx <= 8'b0 ;
endcase
end
end
///其他信号产生
/cnt_key,计数按键按下次数来改变波特率/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_key <= 1'b0 ;
end
else if( key == 1 )begin
cnt_key <= ~cnt_key ;
end
else begin
cnt_key <= cnt_key ;
end
end
bps_param;波特率选择/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
bps_param <= BPS9600 ;
end
else if(cnt_key)begin
bps_param <= BPS115200 ;
end
else begin
bps_param <= BPS9600 ;
end
end
//end_cnt_base_para///
assign end_cnt_base_para = SYS_CLK / bps_param ;
//cnt_base
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_base <= 0;
end
else if(add_cnt_base)begin
if(end_cnt_base)begin
cnt_base <= 0;
end
else begin
cnt_base <= cnt_base + 1;
end
end
end
assign add_cnt_base = state_c != IDLE ;
assign end_cnt_base = add_cnt_base && cnt_base == end_cnt_base_para - 1'b1 ;
//cnt_bit//
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = state_c != IDLE && end_cnt_base ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10 - 1'b1 ;
assign recive_vld = (add_cnt_base && cnt_base == end_cnt_base_para/2 - 1)&& cnt_bit == 8 && fifo_full == 0;
port_rx_r
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
port_rx_r <= 1'b0 ;
end
else begin
port_rx_r <= port_rx ;
end
end
assign neg_rx = (~port_rx) && port_rx_r ;//下降沿检测
endmodule
串口发送模块代码:通过状态机实现
module uart_tx #(parameter DATA_WIDE = 8 )(
input clk , //system clock 50MHz
input rst_n , //reset, low valid
input [01:00] key , //波特率调节按键key[0],发送数据按键key[1]
input [DATA_WIDE - 1:00] data_tx , //要发送的数据,来源是fifo
input [07:00] usedw , //fifo数据余量
input fifo_empty , //fifo空信号
output [23:00] bps , //波特率输出
output send_vld , //发送有效信号
output rd_req , //fifo读请求
output led , //led指示空信号
output reg sclr , //fifo同步复位信号
output reg port_tx //数据发送端口
);
//Parameter Declarations
parameter //状态机独热码参数
IDLE = 4'b0001 , //空闲状态
START = 4'b0010 , //起始位
DATA = 4'b0100 , //数据位
STOP = 4'b1000 ; //停止位
parameter SYS_CLK = 50_000_000 ;//系统时钟
parameter BPS9600 = 9600 ,//9600波特率参数
BPS115200 = 115200 ;//115200波特率参数
//Internal wire/reg Declarations
reg [03:00] state_c ,state_n ;//状态机现态,次态
reg [23:00] bps_param ;//波特率变量
reg [15:00] cnt_base ;//计时一个bit所需时间
wire add_cnt_base ;
wire end_cnt_base ;
wire [15:00] end_cnt_base_para;//cnt_base计数最大值
reg [03:00] cnt_bit ;//比特计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [31:00] cnt_byte ;//字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg tx_en ;//发送使能
reg tx_en_r ;//发送使能打拍;寻找发送使能下降沿。当下降沿到来时说明数据发送完毕,复位fifoa
reg cnt_key ;//按键计数器
wire idle2start ;//初始状态跳转到起始位状态条件
wire start2data ;//起始位状态跳转到数据发送状态条件
wire data2stop ;//数据发送状态跳转到停止位状态条件
wire stop2idle ;//停止位状态跳转到空闲状态条件
//Logic Description
//三段式状态机//
/状态转移描述//
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_c <= IDLE ; //空闲状态
end
else begin
state_c <= state_n ;//下一状态
end
end
//状态转移条件描述/
always @(*) begin
case (state_c)
IDLE :begin
if(idle2start)begin
state_n = START ;
end
else begin
state_n = IDLE ;
end
end
START:begin
if(start2data)begin
state_n = DATA ;
end
else begin
state_n = START ;
end
end
DATA :begin
if(data2stop)begin
state_n = STOP ;
end
else begin
state_n = DATA ;
end
end
STOP :begin
if(stop2idle)begin
state_n = IDLE ;
end
else begin
state_n = STOP ;
end
end
default: begin
if(idle2start)begin
state_n = START ;
end
else begin
state_n = IDLE ;
end
end
endcase
end
assign idle2start = state_c == IDLE && tx_en ; //数据发送使能有效
assign start2data = state_c == START && cnt_bit == 1 ;//发送一位起始位后跳转到数据发送状态
assign data2stop = state_c == DATA && cnt_bit == 8 && end_cnt_base ;//数据位最后一位发送完成跳转到停止位状态
assign stop2idle = state_c == STOP && end_cnt_bit ;//停止位发送完毕回到空闲状态
///输出信号控制///
///port_tx///
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
port_tx <= 1'b1 ;
end
else begin
case (state_c)
IDLE :begin
port_tx <= 1'b1 ;
end
START:begin
port_tx <= 1'b0 ;
end
DATA :begin
port_tx <= data_tx[cnt_bit -1] ;
end
STOP :begin
port_tx <= 1'b1 ;
end
default: port_tx <= 1'b1 ;
endcase
end
end
///其他信号产生
/cnt_key,计数按键按下次数来改变波特率/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_key <= 1'b0 ;
end
else if( key[0] == 1 )begin
cnt_key <= ~cnt_key ;
end
else begin
cnt_key <= cnt_key ;
end
end
bps_param;波特率选择/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
bps_param <= BPS9600 ;
end
else if(cnt_key)begin
bps_param <= BPS115200 ;
end
else begin
bps_param <= BPS9600 ;
end
end
//end_cnt_base_para///
assign end_cnt_base_para = SYS_CLK / bps_param ;
//cnt_base
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_base <= 0;
end
else if(add_cnt_base)begin
if(end_cnt_base)begin
cnt_base <= 0;
end
else begin
cnt_base <= cnt_base + 1;
end
end
end
assign add_cnt_base = state_c != IDLE ;
assign end_cnt_base = add_cnt_base && cnt_base == end_cnt_base_para - 1'b1 ;
//cnt_bit//
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = state_c != IDLE && end_cnt_base ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10 - 1'b1 ;
assign bps = bps_param ;
/cnt_byte
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if( end_cnt_byte )begin
cnt_byte <= 0;
end
else if( add_cnt_byte )begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 1;
end
end
end
assign add_cnt_byte = end_cnt_bit ;
assign end_cnt_byte = add_cnt_byte && cnt_byte == 8 - 1;
///tx_en/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_en <= 1'b0 ;
end
else if( fifo_empty == 1 )begin
tx_en <= 1'b0 ;
end
else if(usedw >= 8 && tx_en == 0)begin
tx_en <= 1'b1 ;
end
else if(add_cnt_byte && usedw == 0 )begin
tx_en <= 1'b0 ;
end
else begin
tx_en <= tx_en ;
end
end
//tx_en_r///
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_en_r <= 1'b0;
end
else begin
tx_en_r <= tx_en ;
end
end
send_vld/
assign send_vld = end_cnt_bit ;
assign rd_req = tx_en && start2data && fifo_empty == 0;
///sclr///
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sclr <= 1'b0 ;
end
/* else if( tx_en == 0 && tx_en_r != 0 )begin
sclr <= 1'b0 ;
end */
else begin
sclr <= 1'b1 ;
end
end
assign led = fifo_empty ;
endmodule
(三).仿真
仿真代码:
`timescale 1ns/1ps
module tb_UART_top();
/* input */ reg clk ;//system clock 50MHz
/* input */ reg rst_n ;//reset, low valid
/* input */ reg [03:00] key_in ;//四位按键
/* input */ reg uart_rx ;//uart数据接收端口
/* output */ wire uart_tx ;//uart数据发送端口
/* output */ wire [05:00] sel ;//数码管位选
/* output */ wire [07:00] seg ;//数码管段选
defparam UART_top_inist.key_bit_state_inist.delay20ms = 20 ,
UART_top_inist.seg_drive_inist.delay2ms = 2 ,
UART_top_inist.uart_tx_inist.SYS_CLK = 5000 ,
UART_top_inist.uart_tx_inist.BPS9600 = 100 ,
UART_top_inist.uart_tx_inist.BPS115200 = 1000 ,
UART_top_inist.uart_rx_inist.SYS_CLK = 5000 ,
UART_top_inist.uart_rx_inist.BPS9600 = 100 ,
UART_top_inist.uart_rx_inist.BPS115200 = 1000 ;
parameter clk_peroid = 20 ;
UART_top UART_top_inist(
/* input */ . clk (clk ), //system clock 50MHz
/* input */ . rst_n (rst_n ), //reset, low valid
/* input [03:00] */ . key_in (key_in ), //四位按键
/* input */ . uart_rx (uart_rx), //uart数据接收端口
/* output */ . uart_tx (uart_tx), //uart数据发送端口
/* output [05:00] */ . sel (sel ), //数码管位选
/* output [07:00] */ . seg (seg ) //数码管段选
);
initial clk = 0;
always #10 clk = ~clk ;
initial begin
key_in = 4'b1111;
rst_n = 1'b0 ;
#(3 * clk_peroid + 3);
rst_n = 1'b1 ;
#(2000*clk_peroid);
/* 接收8帧数据 */
recive();//第一个数据
#(2000*clk_peroid);
recive();//第2个数据
#(2000*clk_peroid);
recive();//第3个数据
#(2000*clk_peroid);
recive();//第4个数据
#(2000*clk_peroid);
recive();//第5个数据
#(2000*clk_peroid);
recive();//第6个数据
#(2000*clk_peroid);
recive();//第7个数据
#(2000*clk_peroid);
recive();//第8个数据
#(1000_0*clk_peroid);//7
repeat(1)begin
@(UART_top_inist.uart_tx_inist.cnt_byte == 7 && UART_top_inist.uart_tx_inist.end_cnt_bit )
#29;
end
/* 改变波特率 */
key_in = 4'b1110 ;
#(300*clk_peroid);
key_in = 4'b1111 ;
/* 接收8帧数据 */
recive();//第一个数据
#(2000*clk_peroid);
recive();//第2个数据
#(2000*clk_peroid);
recive();//第3个数据
#(2000*clk_peroid);
recive();//第4个数据
#(2000*clk_peroid);
recive();//第5个数据
#(2000*clk_peroid);
recive();//第6个数据
#(2000*clk_peroid);
recive();//第7个数据
#(2000*clk_peroid);
recive();//第8个数据
#(1000_000*clk_peroid);//7
$stop;
end
task recive();
begin
uart_rx = 1'b1 ;
#( (UART_top_inist.uart_rx_inist.SYS_CLK /UART_top_inist.uart_rx_inist.bps_param)*clk_peroid + 3);
uart_rx = 1'b0 ;
repeat(7)begin
#( (UART_top_inist.uart_rx_inist.SYS_CLK /UART_top_inist.uart_rx_inist.bps_param)*clk_peroid + 3);
uart_rx = {$random}%2 ;
end
#( (UART_top_inist.uart_rx_inist.SYS_CLK /UART_top_inist.uart_rx_inist.bps_param)*clk_peroid + 3);
uart_rx = 1'b0;
#( (UART_top_inist.uart_rx_inist.SYS_CLK /UART_top_inist.uart_rx_inist.bps_param)*clk_peroid + 3);
uart_rx = 1'b1;
#(1000*clk_peroid);
end
endtask //
endmodule
串口接收模块仿真
串口接收模块时序
fifo里暂存的数据
发送模块仿真