前记:师夷长技以自强。
1.基本概念
UART:Universal Asynchronous Receiver/Transmitter,通用异步收发传输器,它是一种电子器件,可以在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接收到的串行数据转换成并行数据。UART是异步串行通信的总称,包含了RS232,RS449,RS423,RS422和RS485等协议标准。
RS-232:美国电子工业联盟(EIA)制定的串行数据通信的接口标准,被广泛用于计算机串行接口外设连接。
2.UART设计
2.1关键参数
我们已经知道UART是一种串行传输协议,那么一次传多少个数据呢?应该传多快呢?怎么区分一次传输?还有既然是通信,难免会有干扰,如何降低数据传输错误率呢?这些都是一个通信设计者应该考虑的事情。
数据位:定义单个UART数据传输从开始到停止期间发送的数据位数。可以为5,6,7,8(默认)。
波特率:每秒可以通信的数据比特个数,典型波特率有300,1200,2400,9600,19200,115200。
奇偶校验类型:可选参数,分为奇校验(各数位和校验位中“1”的个数为奇数)和偶校验(各数位和校验位中“1”的个数为偶数)。
停止位:每次传输完成后就要发送停止位,可选1,1.5,2位。
2.2时序
需要注意的是,UART作为一种古老的传输协议,是先传低位数据的。
2.3电路设计
早起采用的方案是RS232转TTL
但由于DB9的RS232接口占用PCB太大,多数系统采用USB转TTL
在windows下装CH340G的驱动程序就能够仿真标准串口了。
3.UART模块设计
3.1模块框图
为了启动UART发送数据,需要一个Send_En信号;发送完成应该输出Tx_Done信号;待发送的数据data_byte[7:0];波特率设置baud_Set[2:0];输出的信号有RS232_Tx;还有基本的时序信号Clk和Rst_n,uart_state。
3.2 代码文件
module UART(
input Clk,
input Rst_n,
input Send_En,
input [7:0]data_byte,
input [2:0]baud_set,
output uart_state,
output reg Tx_done,
output reg Rs232_Tx
);
reg en;
reg [7:0]r_data_byte;
reg [12:0]Clk_CNT;
reg [3:0]BPS_CNT;
reg [12:0]Clk_CNT_SHR;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
en <= 0;
else if(Send_En)
en <= 1;
else if(Tx_done)
en <= 0;
else
en <= en;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
r_data_byte <= 0;
else if(Send_En)
r_data_byte <= data_byte;
else
r_data_byte <= r_data_byte;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Clk_CNT_SHR <= 0;
else if(Send_En)begin
case(baud_set)
0:
Clk_CNT_SHR <= 5208; //9600
1:
Clk_CNT_SHR <= 3472; //14400
2:
Clk_CNT_SHR <= 2604; //19200
3:
Clk_CNT_SHR <= 1736; //28800
4:
Clk_CNT_SHR <= 1302; //38400
5:
Clk_CNT_SHR <= 892; //56000
6:
Clk_CNT_SHR <= 868; //57600
7:
Clk_CNT_SHR <= 434; //115200
default:;
endcase
end
else
Clk_CNT_SHR <= Clk_CNT_SHR;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Clk_CNT <= 0;
else if(en)
if(Clk_CNT == Clk_CNT_SHR)
Clk_CNT <= 0;
else
Clk_CNT <= Clk_CNT + 13'b1;
else
Clk_CNT <= 0;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
BPS_CNT <= 0;
else if(en&&Clk_CNT == Clk_CNT_SHR)begin
if(BPS_CNT == 10)
BPS_CNT <= 0;
else
BPS_CNT <= BPS_CNT + 4'b1;
end
else if(Tx_done)
BPS_CNT <= 0;
else
BPS_CNT <= BPS_CNT;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Rs232_Tx <= 1;
else if(en)
case(BPS_CNT)
0:
Rs232_Tx <= 0;
1:
Rs232_Tx <= r_data_byte[0];
2:
Rs232_Tx <= r_data_byte[1];
3:
Rs232_Tx <= r_data_byte[2];
4:
Rs232_Tx <= r_data_byte[3];
5:
Rs232_Tx <= r_data_byte[4];
6:
Rs232_Tx <= r_data_byte[5];
7:
Rs232_Tx <= r_data_byte[6];
8:
Rs232_Tx <= r_data_byte[7];
9:
Rs232_Tx <= 1;
endcase
else
Rs232_Tx <= 1;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Tx_done <= 1;
else if(en&&BPS_CNT==10)
Tx_done <= 1;
else
Tx_done <= 0;
assign uart_state = en;
endmodule
3.3 仿真
3.3.1 仿真文件
为了测试UART的时序是否正确,需要进行RTL仿真,代码如下
`timescale 1ns/1ns
`define clock_period 20module UART_tb();
reg Clk;
reg Rst_n;
reg Send_En;
reg [7:0]data_byte;
reg [2:0]baud_set;
wire uart_state;
wire Tx_done;
wire Rs232_Tx;
UART UART1(
.Clk(Clk),
.Rst_n(Rst_n),
.Send_En(Send_En),
.data_byte(data_byte),
.baud_set(baud_set),
.uart_state(uart_state),
.Tx_done(Tx_done),
.Rs232_Tx(Rs232_Tx)
);
initial Clk = 0;
always #10 Clk = ~Clk;
initial begin
Rst_n = 0;
Send_En = 0;
#`clock_period;
Rst_n = 1;
data_byte = 8'h53;
baud_set = 0;
Send_En = 1;
#`clock_period;
Send_En =0;
wait(Tx_done);
#20000;
data_byte = 8'h73;
Send_En = 1;
#`clock_period;
Send_En =0;
wait(Tx_done);
#20000;
$stop;
end
endmodule
仿真的结果为
3.3.2 板上测试
为了在开发板上能跑起来,需要在写一个UART_top模块,主要的测试思路是固定波特率为9600,要发送的数据是0x54,复位后就不断的发送数据,添加的代码文件如下
module UART_top(
input Clk,
input Rst_n,
output Rs232_Tx
);
reg Send_En;
wire [7:0]data_byte;
wire [2:0]baud_set;
wire Tx_done;
UART UART1(
.Clk(Clk),
.Rst_n(Rst_n),
.Send_En(Send_En),
.data_byte(data_byte),
.baud_set(baud_set),
.uart_state(),
.Tx_done(Tx_done),
.Rs232_Tx(Rs232_Tx)
);
assign data_byte = 8'h54;
assign baud_set = 8'b0;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Send_En <= 0;
else if(Tx_done)
Send_En <= 1;
else
Send_En <= 0;
endmodule
为了确保设计的时序没问题,可以先在软件上仿真,所以需要添加的测试文件UART_top_tb如下
`timescale 1ns/1ns
module UART_top_tb();
reg Clk;
reg Rst_n;
wire Rs232_Tx;
UART_top UART_top1(
.Clk(Clk),
.Rst_n(Rst_n),
.Rs232_Tx(Rs232_Tx)
);
initial Clk = 0;
always #10 Clk = ~Clk;
initial begin
Rst_n = 0;
#20;
Rst_n = 1;
#10000000;
end
endmodule
仿真后可以得到如下的波形
下载到开发板上,用在PC端用串口调试助手接收也可以看到收到的数据