一、uart概念
uart是一种异步收发传输器,在发送数据通过将并行数据转换成串行数据进行传输,在接收数据时将串行数据转换成并行数据。
串行通信分为同步串行通信和异步串行通信。同步串行通信即需要时钟的参与,通信双方需要在同一时钟的控制下,同步传输数据;异步串行通信则不需要时钟的干预,通信双方使用各种的时钟来控制数据的发送和接收。
uart属于异步串行通信,即没有时钟信号来同步或验证从发送器发送并由接收器接收的数据,这就要求发送器和接收器必须事先就时序参数达成一致。
特点:
(1)串口通信的信号线只需要两条线就可以完成,TX和RX ,TX为发送端 RX为接收端。
(2) 传输速率较低,传输距离短;
二、物理层
1、接口标准:目前使用的RS232标准。
三、uart协议层
b.起始位,数据线从高变低,低有效为0,数据传输开始。
c.数据位,起始位传输之后便是数据位开始,一般为8位,传输时低位(LSB) 在前,高位(MSB)在后。
d.校验位,是用来验证数据的正确性,在偶校验中,因为奇偶校验位会被相应的置 1 或 0(一般是最高位或最低位),所以数据会被改变以使得所有传送的数位(含字符的各数位和校验位)中“1”的个数为偶数;在奇校验中,所有传送的数位(含字符的各数位和校验位)中“1”的个数为奇数。奇偶校验可以用于接受方检查传输是否发送生错误,如果某一字节中“1”的个数发生了错误,那么这个字节在传输中一定有错误发生。如果奇偶校验是正确的,那么要么没有发生错误, 要么发生了偶数个的错误。如果用户选择数据长度为 8 位,则因为没有多余的比特可被用来作为奇偶校验位,因此就叫做“无奇偶校验(Non)
e.停止位,停止位高有效为1,他表示这一个个字节传输结束。
帧:从起始位开始到停止位结束的时间间隔称之为一帧。因为UART是一个异步协议,每一帧的开头可以用跳变沿来同 步,但是停止位只能通过波特率来计算相对位置,如果在停止位 的位置识别到一个低电平,则会产生帧错误。在通讯过程中,为了减少波特率的误差导致的问题,可以设置不同的停止位长度来适配。
四、系统设计
系统框架图
说明:
在设计本项目时,将模块划分为:顶层模块、数据发送模块、数据接收模块、控制模块。
顶层模块:
module uart_top#(
parameter CHECK_BIT = "None" , //"None" 无校验,"Odd" 奇校验,"Even" 偶校验
parameter BPS = 115200 , //UART波特率
parameter CLK = 50000000 //工作时钟
)(
input uart_clk , //50m
input rst_n ,
input uart_rxd ,
output uart_txd
);
wire [7:0] rx_data ;
wire rx_data_vld ;
wire [7:0] tx_data ;
wire tx_data_vld ;
wire tx_ready ;
uart_tx #(
.CHECK_BIT ("None" ), //"None" 偶校验,"Odd" 奇校验,"Even" 偶校验
.BPS (115200 ), //UART波特率
.CLK (50000000 ) //工作时钟
)uart_tx_inst(
/* input */.clk (uart_clk ),
/* input */.rst_n (rst_n ),
/* input [7:0] */.tx_din (rx_data ),
/* input */.tx_din_vld (1'b1 ),
/* output */.ready (tx_ready ),
/* output reg */.tx (uart_txd )
);
uart_rx #(
.CHECK_BIT ("None" ), //"None" 无校验,"Odd" 奇校验,"Even" 偶校验
.BPS (115200 ), //UART波特率
.CLK (50000000 ) //工作时钟
)uart_rx_inst(
/* input */.clk (uart_clk ),
/* input */.rst_n (rst_n ),
/* output [7:0] */.rx_din (rx_data ),
/* output */.rx_din_vld (rx_data_vld ),
/* output */.ready ( ),
/* input */.rx (uart_rxd )
);
endmodule
数据发送模块:
module uart_tx#(
parameter CHECK_BIT = "None" , //"None" 无校验,"Odd" 奇校验,"Even" 偶校验
parameter BPS = 115200 , //UART波特率
parameter CLK = 50000000 //工作时钟
)(
input clk ,
input rst_n ,
input [7:0] tx_din ,
input tx_din_vld ,
output ready ,
output reg tx
);
localparam IDLE = 0,
START = 1,
DATA = 2,
CHECK = 3,
STOP = 4;
// parameter CHECK_BIT = "NONE";//“NONE”无校验
// parameter BPS = 115200 ,
// CLK = 50000000 ,
parameter BPS_MAX = CLK/BPS ;
reg [12:0] cnt_1bit ;
wire add_cnt_1bit ;
wire end_cnt_1bit ;
reg [3:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;
reg [2:0] state_c ;
reg [2:0] state_n ;
reg [7:0] tx_din_r ; //输入寄存
wire idle2start ;
wire start2data ;
wire data2stop ;
wire data2check ;
wire check2stop ;
wire stop2idle ;
//****************************************************************
//-- 状态机
//****************************************************************
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 : if(idle2start)
state_n = START;
else
state_n = state_c;
START : if(start2data)
state_n = DATA ;
else
state_n = state_c;
DATA : if(data2stop)
state_n = STOP;
else if(data2check)
state_n = CHECK;
else
state_n = state_c;
CHECK : if(check2stop)
state_n = STOP;
else
state_n = state_c;
STOP : if(stop2idle)
state_n = IDLE;
else
state_n = state_c;
default : ;
endcase
end
assign idle2start = tx_din_vld && (state_c == IDLE );
assign start2data = end_cnt_bit && (state_c == START);
assign data2stop = end_cnt_bit && (state_c == DATA) && CHECK_BIT == "NONE";
assign data2check = end_cnt_bit && (state_c == DATA);
assign check2stop = end_cnt_bit && (state_c == CHECK);
assign stop2idle = end_cnt_bit && (state_c == STOP);
//****************************************************************
//-- 计数器
//****************************************************************
//波特率计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1bit <= 'd0;
end
else if(add_cnt_1bit)begin
if(end_cnt_1bit)begin
cnt_1bit <= 'd0;
end
else begin
cnt_1bit <= cnt_1bit + 1'b1;
end
end
end
assign add_cnt_1bit = state_c != IDLE;
assign end_cnt_1bit = add_cnt_1bit && cnt_1bit == BPS_MAX - 1;
//比特计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'b1;
end
end
end
assign add_cnt_bit = end_cnt_1bit;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;
always @(*)begin
case (state_c)
IDLE : bit_max = 1;
START : bit_max = 1;
DATA : bit_max = 8;
CHECK : bit_max = 1;
STOP : bit_max = 1;
default : bit_max = 1;
endcase
end
//****************************************************************
//-- 数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_din_r <= 'd0;
end
else if(tx_din_vld)begin
tx_din_r <= tx_din;
end
end
wire check_vld;
// assign check_vld = ~^tx_din_r;//奇校验,缩位同或
// assign check_vld = ^tx_din_r ;//偶校验,缩位异或
assign check_vld = (CHECK_BIT == "Odd") ? ~^tx_din_r : ^tx_din_r;
//****************************************************************
//-- 串口时序
//****************************************************************
always @(*)begin
case (state_c)
IDLE : tx = 1;
START : tx = 0;
DATA : if(tx_din_r[cnt_bit])
tx = 1;
else
tx = 0;
CHECK : tx = check_vld;
STOP : tx = 1;
default : tx = 1;
endcase
end
assign ready = state_c == IDLE;
endmodule
数据接收模块:
module uart_rx#(
parameter CHECK_BIT = "None" , //"None" 无校验,"Odd" 奇校验,"Even" 偶校验
parameter BPS = 115200 , //UART波特率
parameter CLK = 50000000 //工作时钟
)(
input clk ,
input rst_n ,
input rx ,
output [7:0] rx_din ,
output rx_din_vld ,
output ready
);
localparam IDLE = 0,
START = 1,
DATA = 2,
CHECK = 3,
STOP = 4;
//parameter CHECK_BIT = "NONE";
// parameter BPS = 115200 ,
// CLK = 50000000 ,
parameter BPS_MAX = CLK/BPS ;
reg [12:0] cnt_1bit ;
wire add_cnt_1bit ;
wire end_cnt_1bit ;
reg [3:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;
reg [2:0] state_c ;
reg [2:0] state_n ;
// reg [7:0] tx_din_r ;
reg [7:0] rx_temp;//数据寄存
wire idle2start ;
wire start2data ;
wire data2stop ;
wire data2check ;
wire check2stop ;
wire stop2idle ;
//****************************************************************
//-- 下降沿
//****************************************************************
reg rx_1 ;
reg rx_2 ;
wire rx_nege ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_1 <= 1;
rx_2 <= 1;
end
else begin
rx_1 <= rx;
rx_2 <= rx_1;//同步,打拍
end
end
assign rx_nege = !rx_1 && rx_2;
//****************************************************************
//-- 状态机
//****************************************************************
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 : if(idle2start)
state_n = START;
else
state_n = state_c;
START : if(start2data)
state_n = DATA ;
else
state_n = state_c;
DATA : if(data2stop)
state_n = STOP;//没有校验就直接跳转到STOP状态
else if(data2check)
state_n = CHECK;
else
state_n = state_c;
CHECK : if(check2stop)
state_n = STOP;
else
state_n = state_c;
STOP : if(stop2idle)
state_n = IDLE;
else
state_n = state_c;
default : ;
endcase
end
assign idle2start = rx_nege && (state_c == IDLE );
assign start2data = end_cnt_bit && (state_c == START);
assign data2stop = end_cnt_bit && (state_c == DATA) && CHECK_BIT == "NONE";
assign data2check = end_cnt_bit && (state_c == DATA);
assign check2stop = end_cnt_bit && (state_c == CHECK);
assign stop2idle = end_cnt_bit && (state_c == STOP);
//****************************************************************
//-- 计数器
//****************************************************************
//波特率计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1bit <= 'd0;
end
else if(add_cnt_1bit)begin
if(end_cnt_1bit)begin
cnt_1bit <= 'd0;
end
else begin
cnt_1bit <= cnt_1bit + 1'b1;
end
end
end
assign add_cnt_1bit = state_c != IDLE;
assign end_cnt_1bit = add_cnt_1bit && cnt_1bit == BPS_MAX - 1;
//比特计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'b1;
end
end
end
assign add_cnt_bit = end_cnt_1bit;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;
always @(*)begin
case (state_c)
IDLE : bit_max = 1;
START : bit_max = 1;
DATA : bit_max = 8;
CHECK : bit_max = 1;
STOP : bit_max = 1;
default : bit_max = 1;
endcase
end
wire check_vld ;//接受到的校验值
reg check_rev ;//计算出的校验值
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
check_rev <= 'd0;
end
else if(state_c == CHECK && (cnt_1bit == BPS_MAX >> 1))begin
check_rev <= rx_1;
end
end
assign check_vld = (CHECK_BIT == "Odd") ? ~^rx_din : ^rx_din;
//****************************************************************
//-- 串口时序
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_temp <= 'd0;
end
else if(state_c == DATA && (cnt_1bit == BPS_MAX >> 1))begin //(波特率计数器)中间时刻采样一次,因为最稳定,边沿采样不稳定因为还在变化
rx_temp[cnt_bit] <= rx_1; //移位寄存器,传输顺序LSB
end
else begin
rx_temp = rx_temp;
end
end
// assign rx_din_vld = data2stop;//停止位和空闲位都可以将有效信号拉高
assign rx_din = rx_temp;
assign rx_din_vld = (CHECK_BIT == "NONE") ? data2stop //无校验方式输出数据有效信号
: (check2stop && (check_vld == check_rev)) ? 1//有校验方式时,当计算的校验值与接受的校验值相同时,则输出数据有效信号,不同则不输出数据有效信号
: 0;
assign ready = state_c == IDLE;
endmodule
测试文件:
module uart_tx_tb();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
//输出信号定义
reg [7:0] tx_din ;
reg tx_din_vld ;
reg [7:0] rx_din ;
reg rx_din_vld ;
wire ready ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
uart_tx #(
.CHECK_BIT("Odd" ),
.BPS (115200 ),
.CLK (50000000 )
)
u_uart_tx(
/*input */.clk (tb_clk ),
/*input */.rst_n (tb_rst_n ),
/*input [7:0] */.tx_din (tx_din ),
/*input */.tx_din_vld (tx_din_vld ),
/*output */.ready (ready ),
/*output reg */.tx (rx )
);
uart_rx #(
.CHECK_BIT("Odd" ),
.BPS (115200 ),
.CLK (50000000 )
)
u_uart_rx(
/*input */.clk (tb_clk ),
/*input */.rst_n (tb_rst_n ),
/*output [7:0] */.rx_din ( ),
/*output */.rx_din_vld ( ),
/*output */.ready ( ),
/*output */.rx (rx )
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
//产生激励
initial begin
tb_rst_n = 1'b1;
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*2);
tb_rst_n = 1'b1;
end
initial begin
tx_din = 0;
tx_din_vld = 0;
#(CLOCK_CYCLE*5);
repeat(8) begin
tx_din_vld = 1;
tx_din = {$random};
#20;
tx_din_vld = 0;
wait(ready == 1);
#20;
end
end
endmodule