文章目录
- 前言
- 一、特征值自适应原理
- 二、整体结构框图
- 三、接收模块
- 四、发送模块
- 五、编写仿真测试文件以及结果分析
- 六、上板测试
- 参考
前言
在上一篇文章《详解UART通信协议以及FPGA实现》我们实现了UART的通信,我们知道UART通信需要双方约定好波特率,如果想要更换波特率,则需要重新更改代码,重新编译,十分的不方便。所以有没有一种方案就能让通信双方不约定波特率,主机按照主机的波特率发送数据,从机自适应调整自己的波特率跟主机通信。在实际中可以采用特征值匹配的方法实现自适应波特率发生器。
一、特征值自适应原理
在双方开始通信前,主机通过串行接口以自己设定的通信波特率向从机发送 1 个字节的波特率校准信号0X55,从机测量每一个高与低电平的宽度,不算起始位和停止位,一共测量8个码元的宽度值W,最后再除以8得到一个码元的宽度bitwidth,再用测量的码元宽度与典型波特率的码元宽度进行比较,误差1%范围内就算匹配成功。
b i t w i d t h = W 8 bitwidth = \frac{W}{8} bitwidth=8W
二、整体结构框图
接收模块在rx线拉低时候,开始接收0X55的校准信号,同时计算每个高低电平的宽度,计算完成后,拉高auto_done信号和正确的波特率码元宽度值给tx模块,tx模块根据新的波特率进行发送。
三、接收模块
- 判断模块:由于典型波特率大部分都成倍数关系,因此码元宽度也成倍数关系,防止给计算模块造成误导,因此保险起见,记录上每个高低电平的宽度。否则如果只计算一个码元的宽度,很容易算错,例如以下情况:
设定系统时钟频率50M,如果主机以115200波特率发送(0X66)数据,如果只判断一个码元的长度,那么计算的值将会是868(波特率为57600),不符合设计要求。
因此我们设定一个bit宽度计数器,每隔一个沿开始,清空此计数器,如果次计数器超过了最大码元宽度值,表示此数据不是特征值0X55,调整失败。否者就一直累加8次,最后判断一个码元宽度是否在典型波特率的1%误差范围内。
典型波特率码元宽度值 | |||||
波特率 | 码元宽度 | ||||
1200 | CLK_FREQ/1200 | ||||
2400 | CLK_FREQ/2400 | ||||
4800 | CLK_FREQ/4800 | ||||
9600 | CLK_FREQ/9600 | ||||
19200 | CLK_FREQ/19200 | ||||
38400 | CLK_FREQ/38400 | ||||
57600 | CLK_FREQ/57600 | ||||
115200 | CLK_FREQ/115200 |
- 接收模块:与上文的rx模块基本上一致,只是接受数据前判断auto_done信号,如果信号拉高了,表示波特率更新完成,可以正常接收数据,然后调整自己的波特率计算值,准备接收数据。否者不会接收数据。
四、发送模块
与上文的rx模块基本上一致,只是接受数据前判断auto_done信号,如果信号拉高了,表示波特率更新完成,可以正常发送数据,然后调整自己的波特率计算值,准备发送数据。否者不会发送数据。代码如下:
`timescale 1ns / 1ps
module uart_tx
(
input clk ,
input rst_n ,
input [23:0] baud_cnt_max ,
input auto_done ,
input [7:0] tx_data , //发送数据
input tx_data_en , //发送数据有效信号
output reg tx
);
reg [7:0] tx_data_reg ;
reg [23:0] baud_cnt ;
reg [3:0] bit_cnt ;
reg bit_flag;
reg tx_flag ;
//将待发送的数据缓存起来
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
tx_data_reg <= 'd0;
else if(tx_data_en == 1'b1)
tx_data_reg <= tx_data;
else
tx_data_reg <= tx_data_reg;
end
//tx_flag拉高时刻
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
tx_flag <= 1'b0;
else if(tx_data_en == 1'b1 && auto_done == 1'b1)
tx_flag <= 1'b1;
else if(bit_cnt == 'd9 && bit_flag == 1'b1)
tx_flag <= 1'b0;
else
tx_flag <= tx_flag;
end
//baud_cnt 计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
baud_cnt <= 'd0;
else if(baud_cnt == baud_cnt_max)
baud_cnt <= 'd0;
else if(tx_flag == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 'd0;
end
//bit_flag拉高时刻
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == 'd1 && auto_done == 1'b1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
//bit_cnt 计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
bit_cnt <= 'd0;
else if(bit_cnt == 'd9 && bit_flag == 1'b1)
bit_cnt <= 'd0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
//移位data,由低到高发送至tx
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
tx <= 1'b1;
else if(bit_flag == 1'b1)
case(bit_cnt)
0: tx <= 1'b0;
1: tx <= tx_data_reg[0];
2: tx <= tx_data_reg[1];
3: tx <= tx_data_reg[2];
4: tx <= tx_data_reg[3];
5: tx <= tx_data_reg[4];
6: tx <= tx_data_reg[5];
7: tx <= tx_data_reg[6];
8: tx <= tx_data_reg[7];
9: tx <= 1'b1;
default:tx <= 1'b1;
endcase
end
endmodule
五、编写仿真测试文件以及结果分析
第一次仿真文件测试,使用上一篇文章的测试文件,此时发送的数据中没有特征值0X55,模拟波特率38400发送。
`timescale 1ns / 1ps
module tb_uart_top();
reg clk ;
reg rst_n ;
reg rx ;
wire tx ;
initial begin
clk = 0;
rst_n = 0;
rx = 1;
#100
rst_n = 1;
end
//调用task函数,连续发送八个数据,不含特征值0X55
initial begin
#300
tx_data(8'h6);
tx_data(8'h6);
tx_data(8'h1);
tx_data(8'hA);
tx_data(8'hB);
tx_data(8'hC);
tx_data(8'hD);
tx_data(8'hE);
tx_data(8'hF);
end
//定义task函数
task tx_data(
input [7:0] tx_data
);
integer i;
for (i=0; i<10; i=i+1 ) begin
case(i)
0: rx <= 1'b0;
1: rx <= tx_data[0];
2: rx <= tx_data[1];
3: rx <= tx_data[2];
4: rx <= tx_data[3];
5: rx <= tx_data[4];
6: rx <= tx_data[5];
7: rx <= tx_data[6];
8: rx <= tx_data[7];
9: rx <= 1'b1;
endcase
#(1302*20);//每发送完一个bit数据后,延迟一个码元的时间
end
endtask
always #10 clk =~clk;
uart_auto_bps u_uart_auto_bps(
.clk ( clk ),
.rst_n ( rst_n ),
.rx ( rx ),
.tx ( tx )
);
endmodule
由于没有发现特征值0X55,因此波特率调整失败,auto_done一直拉低,rx_data以及valid信号也都为0;
我们修改一下仿真文件,在发送数据前,发送特征值0X55。代码如下:
`timescale 1ns / 1ps
module tb_uart_top();
reg clk ;
reg rst_n ;
reg rx ;
wire tx ;
initial begin
clk = 0;
rst_n = 0;
rx = 1;
#100
rst_n = 1;
end
//调用task函数,连续发送八个数据,第一个含特征值0X55
initial begin
#300
tx_data(8'h55);
tx_data(8'h6);
tx_data(8'h6);
tx_data(8'h1);
tx_data(8'hA);
tx_data(8'hB);
tx_data(8'hC);
tx_data(8'hD);
tx_data(8'hE);
tx_data(8'hF);
end
//定义task函数
task tx_data(
input [7:0] tx_data
);
integer i;
for (i=0; i<10; i=i+1 ) begin
case(i)
0: rx <= 1'b0;
1: rx <= tx_data[0];
2: rx <= tx_data[1];
3: rx <= tx_data[2];
4: rx <= tx_data[3];
5: rx <= tx_data[4];
6: rx <= tx_data[5];
7: rx <= tx_data[6];
8: rx <= tx_data[7];
9: rx <= 1'b1;
endcase
#(1302*20);//每发送完一个bit数据后,延迟一个码元的时间
end
endtask
always #10 clk =~clk;
uart_auto_bps u_uart_auto_bps(
.clk ( clk ),
.rst_n ( rst_n ),
.rx ( rx ),
.tx ( tx )
);
endmodule
因为发送数据前发送了特征值,所以判断模块输出了auto_done以及波特率码元值1302(50000000/1302=)38402波特率,与主机设定的一致。后续接收到了主机后面发送的八个数据06、06、01、0a、0b、0c、0d、0e、0f
发送给tx模块,tx模块也正确的发送了这八个数据。
六、上板测试
将程序下载到板卡上,用上位机发送数据,用不同波特率发送数据测试一下。
从上位机可以看出,以115200波特率,如果没有发送特征值,接受区域没有接收到任何数据。我们发送特征0X55,然后再发送一些数据看看结果。
在波特率115200情况下发送特征值0x55后,发送一系列数据都收到了。表示波特率调整完成。我们修改一下波特率,再做同样的实验看看结果怎样。
我们将波特率调整到9600,发送一些列数据(不包括特征值),接受区域依然空白。我们把特征值加上去后,再看看结果。
结果依然正确,换成其它典型波特率结果一致。
参考
《基于FPGA的UART自适应波特率发生器的实现》