首先看UART发送时序图:
要发送一个完整字节,需要“1位起始位+ 8位数据位+1位停止位”,图上的第11位,是确认一个字节发送完的一位。
重点是每一位之间的发送时间需要保持一致,也就是bps_clk的每个高脉冲之间的间隔相等稳定,bps_clk的频率就是波特率。例如波特率为9600,就是1秒内有9600个脉冲,可以发送9600位。
因此,发送速率(波特率)需要严格控制,以便稳定发送。
发送模块简化图、RTL电路设计图 :
UART发送单字节模块,除去必要的clk系统时钟、rst_n系统复位,还需要三个输入分别是
波特率选择bps_set,
发送数据输入data_byte,
发送开始标志单脉冲信号tx_en。
三个输出分别是
发送输出端口rs232_tx,
发送停止标志单脉冲信号tx_down,
串口工作状态uart_state。
RTL电路图看起来比较复杂,分模块理解就比较容易了。
首先为了发送稳定,需要特定的波特率,就需要一个计数器给系统时钟clk分频生成bps_clk;
波特率肯定不知能只有一种,所以需要一个查找表,查找波特率对应的计数值;
要对生成的bps_clk从1~11计脉冲数以便知道将要发送一个字节的第几位;
通过一个比较器比较,当计数到11时,清零重新计数;
data_byte+start_bit+stop_bit通过一个10选1的多路器,计数到哪一位时就选哪一位的数据进行发送;
最后加一个描述uart_state状态的标志信号监视UART的工作状态。
RTL程序:
module uart_byte_tx(clk50M,rst_n,data_byte,tx_en,bps_set,rs232_tx,tx_down,uart_state);
input clk50M;
input rst_n;
input [7:0] data_byte; //输入想要发送的8位数据
input [2:0] bps_set; //波特率选择查找表的输入
input tx_en; //发送开始的标志单脉冲信号
output reg rs232_tx; //输出发送数据端
output reg tx_down; //发送停止的标志单脉冲信号
output reg uart_state; //UART工作状态信号,1为发送,0为空闲
reg [15:0] counter; //分频器计数寄存器
reg [15:0] bps_DR; //选定的波特率的最大计数值
reg [3:0] bps_cnt; //1~11的位计数
reg [7:0] r_data_byte; //data_byte进来 先寄存一次,以便后续稳定发送
reg bps_clk; //波特率时钟
localparam start_bit = 1'b0; //定义起始位为0低电平
localparam stop_bit = 1'b1; 定义停止位为1高电平
/*************波特率对应的分频计数值查找表**************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
bps_DR<=16'd5207;
else begin
case(bps_set)
3'd0:bps_DR<=16'd5207;//9600
3'd1:bps_DR<=16'd2603;//19200
3'd2:bps_DR<=16'd1301;//38400
3'd3:bps_DR<=16'd867;//57600
3'd4:bps_DR<=16'd433;//115200
default:bps_DR<=16'd5207;
endcase
end
end
/****************************************************/
/********************分频计数器生成********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
counter<=16'd0;
else if(uart_state) begin
if(counter==bps_DR)
counter<=16'd0;
else
counter<=counter+1'd1;
end
else
counter<=16'd0;
end
/****************************************************/
/********************分频时钟生成*********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
bps_clk<=1'b0;
else if(counter==16'd1)
bps_clk<=1'b1;
else
bps_clk<=1'b0;
end
/****************************************************/
/****************bps_clk计数(最大11)****************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
bps_cnt<=4'd0;
else if(bps_cnt==4'd11)
bps_cnt<=4'd0;
else if(bps_clk)
bps_cnt<=bps_cnt+1'd1;
else
bps_cnt<=bps_cnt;
end
/****************************************************/
/************tx_down发送停止单脉冲标志信号*************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
tx_down<=1'b0;
else if(bps_cnt==4'd11)
tx_down<=1'b1;
else
tx_down<=1'b0;
end
/****************************************************/
/******************寄存一下data_byte******************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
r_data_byte<=8'd0;
else if(tx_en)
r_data_byte<=data_byte;
else
r_data_byte<=r_data_byte;
end
/****************************************************/
/**********************发送模块***********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
rs232_tx<=1'b1;
else begin
case(bps_cnt)
0:rs232_tx<=1'b1;
1:rs232_tx<=start_bit;
2:rs232_tx<=r_data_byte[0];
3:rs232_tx<=r_data_byte[1];
4:rs232_tx<=r_data_byte[2];
5:rs232_tx<=r_data_byte[3];
6:rs232_tx<=r_data_byte[4];
7:rs232_tx<=r_data_byte[5];
8:rs232_tx<=r_data_byte[6];
9:rs232_tx<=r_data_byte[7];
10:rs232_tx<=stop_bit;
default:rs232_tx<=1'b1;
endcase
end
end
/****************************************************/
/********************状态标志信号*********************/
always @ (posedge clk50M or negedge rst_n)begin
if(!rst_n)
uart_state<=1'b0;
else if(tx_en)
uart_state<=1'b1;
else if(bps_cnt==4'd11)
uart_state<=1'b0;
else
uart_state<=uart_state;
end
/****************************************************/
endmodule
testbench文件:
`timescale 1ns/1ns
`define clock_period 20
module uart_byte_tx_tb;
reg clk;
reg rst_n;
reg [7:0]data_byte;
reg tx_en;
reg [2:0]bps_set;
wire rs232_tx;
wire tx_down;
wire uart_state;
uart_byte_tx uart_byte_tx(
.clk50M(clk),
.rst_n(rst_n),
.data_byte(data_byte),
.tx_en(tx_en),
.bps_set(bps_set),
.rs232_tx(rs232_tx),
.tx_down(tx_down),
.uart_state(uart_state)
);
initial clk=1;
always #(`clock_period/2) clk=~clk;
initial begin
//首先激励初始化,波特率查找表默认选4,也就是115200.然后复位结束,系统工作
rst_n=0;
data_byte=0;
tx_en=0;
bps_set=4'd4;
#(`clock_period*20);
rst_n=1;
#(`clock_period*50);
//发送0xaa,等待tx_down到来时延时五千个系统周期
tx_en=1;
data_byte=8'haa;
#(`clock_period);
tx_en=0;
@(posedge tx_down)
#(`clock_period*5000);
//发送0x37,等待tx_down到来时延时五千个系统周期。然后停止仿真
tx_en=1;
data_byte=8'h37;
#(`clock_period);
tx_en=0;
@(posedge tx_down)
#(`clock_period*5000);
$stop;
end
endmodule
波形图:
可以看到先要发送的data_byte,和已经发送的rs232_tx一致。而且1~11是从 data_byte的低位到高位进行选取数据发送。每产生一个bps_cnt就发送一位。