首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

基于FPGA的串口设计

基于FPGA的串口设计

过采样到0开始传输,紧跟8位数据,然后上拉1结束。让我们来看看0x55是如何传输的:
0x55的二进制表示为:01010101。
但是由于先发送的是最低有效位,所以发送序列是这样的: 1-0-1-0-1-0-1-0.
电缆上的信号使用正负电压的机制:

"1" 用 -10V 的电压表示(或者在 -5V 与 -15V之间的电压).
"0" 用 +10V 的电压表示(或者在 5V 与 15V之间的电压).
所以没有数据传输的电缆上的电压应该为-10V或-5到-10之间的某个电压。

波特率发生器
这里我们使用串行连接的最大速度115200波特,其他较慢的波特也很容易由此产生。
FPGA通常运行在远高于115200Hz的时钟频率上(对于今天的标准的来说RS-232真是太慢了),这就意味着我们需要用一个较高的时钟来分频产生尽量接近于115200Hz的时钟信号。
从1.8432MHz的时钟产生
通常RS-232芯片使用1.8432MHz的时钟,以为这个时钟很容易产生标准的波特率,所以我们假设已经拥有了一个这样的时钟源。

只需要将 1.8432MHz 16分频便可得到 115200Hz的时钟,多方便啊!
reg [3:0] BaudDivCnt;
always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1;
wire BaudTick = (BaudDivCnt==15);

所以 "BaudTick" 每16个时钟周期需要置位一次,从而从1.8432MHz的时钟得到115200Hz的时钟。
从任意频率产生
早期的发生器假设使用1.8432MHz的时钟。但如果我们使用2MHz的时钟怎么办呢?要从2MHz的时钟得到 115200Hz,需要将时钟 "17.361111111..." 分频,并不是一个整数。我的解决办法是有时候17分频,有时候18分频,使得整体的分频比保持在 "17.361111111"。这是很容易做到的。

下面是实现这个想法的C语言代码:
while(1) // 死循环
{
acc += 115200;
if(acc >=2000000) printf("*"); else printf(" ");
acc %= 2000000;
}

这段代码会精确的以平均每 "17.361111111..." 个时钟间隔打印出一个"*"。
为了从FPGA得到同样的效果,考虑到串行接口可以容忍一定的波特率误差,所以即使我们使用17.3或者17.4这样的分频比也是没有关系的。
FPGA波特率发生器
我们希望2000000是2的整数幂,但很可惜,它不是。所以我们改变分频比,"2000000/115200" 约等于 "1024/59" = 17.356. 这跟我们要求的分频比很接近,并且使得在FPGA上实现起来相当有效。

//10 位的累加器 ([9:0]), 1位进位输出 ([10])
reg [10:0] acc; //一共11位!
always @(posedge clk)
acc <= acc[9:0] + 59; //我们使用上一次结果的低10位,但是保留11位结果
wire BaudTick = acc[10]; //第11位作为进位输出

使用 2MHz 时钟, "BaudTick" 为 115234 波特, 跟理想的115200波特存在 0.03% 的误差。
参数化的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 位的默认结果, 但实际计算过程中的某些数据超过了32位,所以改变一种计算方法。
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);
这行程序也使得结果成为整数,从而避免截断。
这就是整个的设计方法了。
现在我们已经得到了足够精确的波特率,可以继续设计串行接收和发送模块了。

RS-232发送模块
下面是我们所想要实现的:

它应该能像这样工作:
发送器接收8位的数据,并将其串行输出。("TxD_start"置位后开始传输).
当有数传输的时候,使"busy"信号有效,此时“TxD_start”信号被忽略.
RS-232模块的参数是固定的: 8位数据, 2个停止位, 无奇偶校验.
数据串行化
假设我们已经有了一个115200波特的"BaudTick"信号.

我们需要产生开始位、8位数据以及停止位。
用状态机来实现看起来比较合适。
继承事业,薪火相传
返回列表