RS232 波特率时钟产生方法?
目录
上篇博文介绍了:RS232接口是如何工作的?
讲到了该接口的传输速率,也就是波特率可以为:
- 1200 bauds.
- 9600 bauds.
- 38400 bauds.
- 115200 bauds (usually the fastest you can go).
在这里,我们希望以最大速度使用串行链路,即115200波特(较慢的速度也很容易生成)。 FPGA通常以MHz速度运行,远高于115200Hz(按照今天的标准,RS-232相当慢)。 我们需要找到一种方法来生成(从FPGA时钟)一个“tick”,尽可能接近每秒115200次。
产生波特率的方法可以分为两种,原理近似:
方法一
传统上,RS-232芯片使用1.8432MHz时钟,因为这样可以非常容易地生成标准波特频率...... 1.8432MHz除以16得到115200Hz。
可以以下面的方法来分频得到分频时钟(波特率时钟):
-
// let's assume the FPGA clock signal runs at 1.8432MHz
-
// we create a 4-bit counter
-
reg [3:0] BaudDivCnt;
-
always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1; // count forever from 0 to 15
-
-
// and a tick signal that is asserted once every 16 clocks (so 115200 times a second)
-
wire BaudTick = (BaudDivCnt==15);
那很简单。 但是,如果不是1.8432MHz,你有2MHz的时钟怎么办? 要从2MHz时钟产生115200Hz,我们仍可以采用分频的方法,但是这里的分频会产生误差,尽管如此,这是FPGA所容许的。
2000_000/115200=17.361111111111111111111111111111
可以采用17分频的方法来产生这样的波特率。
采用Verilog描述:
-
module baud_gen_n17d(
-
-
input clk,
-
input enable,
-
output reg BaudTick = 0
-
-
);
-
-
reg [4:0] baud_count = 0;
-
-
/* always@(posedge clk) begin
-
if(enable && baud_count < 16) begin
-
baud_count <= baud_count + 1;
-
BaudTick <= 0;
-
end
-
else if(enable && baud_count == 16) begin
-
baud_count <= 0;
-
BaudTick <= 1;
-
end
-
else ;
-
-
end */
-
-
always@(posedge clk) begin
-
if(enable) begin
-
if(baud_count < 16) begin
-
baud_count <= baud_count + 1;
-
BaudTick <= 0;
-
end
-
else if(baud_count == 16) begin
-
baud_count <= 0;
-
BaudTick <= 1;
-
end
-
else ;
-
-
end
-
else ;
-
-
end
-
-
-
endmodule
Testbench文件为:
-
module baud_gen_f17_tb(
-
-
);
-
-
reg enable;
-
reg clk;
-
wire Baud_Tick;
-
-
initial begin
-
clk = 0;
-
forever
-
# 250 clk = ~clk;
-
-
end
-
initial enable = 1;
-
-
baud_gen_n17d u0(
-
.clk(clk),
-
.enable(1'b1),
-
.BaudTick(Baud_Tick)
-
);
-
-
-
endmodule
仿真波形为:
从仿真波形中可以看出,传输一位数据需要8.75us,那么传输10bit数据需要87.5us;
如果波特率为标准的115200的话,那么传输1bit数据需要8.68us,那么传输10bit数据需要86.8us;
二者误差相差0.7us;
如果采用标准波特率的话,一位停止位就占了8.68us,这点误差算的了什么呢?况且RS232接收数据采用过采样,所以,不必担心采用分频时钟生成的波特率不准确的问题。
下面是请教前辈的聊天记录:
有这样的前辈,耐心的教我这个菜鸟,还真是很感动呢!
方法二
前面方法一是一种常规意义上的分频而已,没有那么多的花里胡哨,也很好理解,但是在我们的主要参考链接中给出了另外一种方法,链接:Baud generator
还以2MHz的系统时钟为例,要产生115200的波特率时钟。
前面我们算过二者的比率为:2000000/115200=17.361111111111111111111111111111;
文中的思路是:
期望2000000是2的幂。 显然2000000不是。 所以我们改变比率...而不是“2000000/115200”,让我们使用“1024/59”= 17.356。 这非常接近我们的理想比率,并且实现了高效的FPGA实现:我们使用增量为59的10位累加器,每次累加器溢出时都会标记一个Tick。
这是个什么原理呢?
通过比率相似,我们可以取一个计数器,位数恰好能计数到1024,那么选一个10位的计数器(实际上是11位,后面解释),累加器每次累加59,会发现计数到第17个59时,累加器溢出,溢出一个时钟,这样我们可以取溢出位为Tick,这样就实现了计数17,Tick就有效一次,和17分频原理一致呀。
这种方法的要点在于,我们需要找到一个2的幂次的分子,来凑出相似比率。
当然,如果你忘记了这种方法,还可以直接用方法一,也即是直接分频的方法。
下面对这种方法进行仿真:
以FPGA系统时钟为2MHz为例,
Verilog描述代码:
-
module BaudGen(
-
-
input clk,
-
input enable,
-
output BaudTick
-
-
);
-
-
-
// let's assume the FPGA clock signal runs at 2.0000MHz
-
// we use a 10-bit accumulator plus an extra bit for the accumulator carry-out
-
reg [10:0] acc = 0; // 11 bits total!
-
-
// add 59 to the accumulator at each clock
-
always @(posedge clk) begin
-
if(enable)
-
acc <= acc[9:0] + 59; // use 10 bits from the previous accumulator result, but save the full 11 bits result
-
else
-
acc <= 59;
-
-
end
-
-
assign BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out
-
-
-
-
endmodule
testbench文件:
-
module BaudGen_tb(
-
-
);
-
-
reg clk;
-
wire BaudTick;
-
-
initial begin
-
clk = 0;
-
forever
-
#250 clk = ~clk;
-
-
end
-
-
-
-
BaudGen u_BaudGen(
-
.clk(clk),
-
.enable(1'b1),
-
.BaudTick(BaudTick)
-
);
-
-
-
endmodule
仿真波形为:
看细节:
计数值超过1024后,最高位变为1,同时BaudTick变为高电平,持续时间为一个时钟周期。
通过下图计算下传输1bit数据需要多久:
经过简单计算,需要8.5us传输1bit,那么1s中传输多少位呢?1000000/8.5=117647;
然后我们算算误差多大:(117647 - 115200)/115200 = 0.02;可见误差很小。
前面方法一也说了,这种误差我们完全可以接受。
波特率参数化产生方法
由于前面已经铺垫过了,所以这种参数化完全是套用下参数而已,直接引用链接:https://www.fpga4fun.com/SerialInterface2.html
参数化FPGA波特率发生器
之前的设计使用的是10位累加器,但随着时钟频率的增加,需要更多的位。
这是一个25MHz时钟和16位累加器的设计。 设计参数化,易于定制。
parameter ClkFrequency = 25000000; // 25MHz parameter Baud = 115200; parameter BaudGeneratorAccWidth = 16; parameter BaudGeneratorInc = (Baud<<BaudGeneratorAccWidth)/ClkFrequency; reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc; always @(posedge clk) BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc; wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth]; |
最后一个实现问题:“BaudGeneratorInc”计算错误,因为Verilog使用32位中间结果,计算超出了这个。 更改以下行以获得解决方法。
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4); |
此行还具有对结果进行舍入而不是截断的附加优势。
现在我们拥有足够精确的波特率发生器,我们可以继续使用RS-232发送器和接收器模块。
最后一个参数化还是会让人产生疑惑的,算了还是直接用自己的方法吧,花里胡哨的:
-
module BaudGen_param(
-
input clk,
-
input enable,
-
output BaudTick
-
-
);
-
-
parameter ClkFrequency = 25000000; // 25MHz
-
parameter Baud = 115200;
-
parameter Ratio = ClkFrequency/Baud;
-
parameter BaudGeneratorAccWidth = 16;
-
parameter BaudGeneratorInc = (1<<BaudGeneratorAccWidth)/Ratio;
-
-
reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc = 0;
-
always @(posedge clk)
-
if(enable)
-
BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;
-
else
-
BaudGeneratorAcc <= BaudGeneratorInc;
-
assign BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth];
-
-
endmodule
测试文件为:
-
module BaudGen_Param_tb;
-
-
reg clk;
-
wire BaudTick;
-
-
-
initial begin
-
clk = 0;
-
forever
-
#20 clk = ~clk;
-
end
-
-
-
BaudGen_param u0(
-
.clk(clk),
-
.enable(1'b1),
-
.BaudTick(BaudTick)
-
);
-
-
endmodule
行为仿真波形图:
可见,波特周期是8.68us,与115200的波特率对应的波特周期差不多。
文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。
原文链接:reborn.blog.csdn.net/article/details/90524485
- 点赞
- 收藏
- 关注作者
评论(0)