热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于FPGA的串口通讯设计

这个小项目是在2013年初学FPGA时所做的,现把当时的设计笔记贴出来。RS232C电气规定EIA-RS-232C对电气特定、逻辑电平和各信号线功能都做了相关规定。在此部分,只简单

这个小项目是在2013年初学FPGA时所做的,现把当时的设计笔记贴出来。


RS232C电气规定

EIA-RS-232C对电气特定、逻辑电平和各信号线功能都做了相关规定。在此部分,只简单介绍相关的电气特性。

《基于FPGA的串口通讯设计》

对于RS232C标准对逻辑电平的定义,在数据部分,逻辑1的电平低于-3V;逻辑0的电平高于3V。在控制信号部分,信号有效的电平高于+3V;信号无效的电平低于-3V。在介于-3V~3V之间,高于15V,低于-15V的电压没有意义。

所以在实际的工作中,需要完成RS232电平与TTL电平的转换,以保证MCU,FPGA等能匹配RS232电平。最常用的芯片MAX232芯片可以完成TTL到RS232电平的转换。


串行通信数据格式

一帧异步通信的数据格式主要包含以下几个部分:

起始位:以一位低电平开始,表示发送端开始发送一帧数据

数据位:即要传输的数据信息,低位在前,高位在后。数据位长度一般为5-10bit,常使用8bit.

奇偶校验位:用于校验数据的正确性,可以选择为奇校验或者偶校验

停止位:用于向接收端表示一帧数据已经发送完成,长度一般为1~2位。

空闲状态:一般用高电平表示,用于通知接收端等待数据传输。

《基于FPGA的串口通讯设计》

RS232C的串行总线在空闲的时候总保持为逻辑1状态,即串行数据连接线上的电平为-3v~-15v。当需要传输一个字符时,首先会发送一个逻辑0为起始位,表示开始发送数据;之后就逐个发送数据位、奇偶校验位和停止位(逻辑1),每一次传输1个字符(通常为8bit).由于两次传输中的时间间隔是可变的,所以这种传输被称之为异步传输。


RS232C规定了一系列的波特率标准,常用的有9600b/s、115200b/s等。在设置波特率的时候,必须通知通信双方。


具体设计

本设计在KC705的Demo板上,实现了波特率为9600b/s,停止位为1bit,不带奇偶校验位,具有异步复位功能的串口通讯控制器。最终要求和PC上的超级终端完成双向通信。为了测试串口通信是否正常,在顶层设计的时候添加了可供测试验证的部分:当在开发板上按下button时,发送数据每次加1,并且当通过PC发送数据的时候,点亮对应的LED灯。


总体设计方案

RS232串口通信主要由波特率发生模块,串口数据发送模块,串口数据接收模块构成。在顶层设计中还加入了外部按键相应模块和LED驱动模块。总体设计框图如下:

《基于FPGA的串口通讯设计》

在本设计中采用自底向上的设计方法。


波特率产生模块

板级整体输入时钟为50M,为了要产生9600b/s的波特率,那么必须使用分频器。一般来讲,为了提高系统的容错性处理,经常会要求波特率发生器的输出时钟为实际串口数据波特率的N倍,N一般取值为8、16、32等。由于串口通讯的速率较慢,取N倍后的时钟频率也不是很高,所以能保证设计完成。在本设计中,取N为16,因此波特率发生器的输出信号频率应该为9600×16=153.6kb/s.在波特率倍频后,可以不按照时钟信号的占空比为50%开设计,在本设计中使用占空比为1:325.设计的波特率发生器的verilog代码如下:

module baud_gen(
input rst,
input clk50m,

output reg bclk,
);
reg [8:0] cnt;
always @ (posedge clk50m or posedge rst)
begin
if(rst)
begin
cnt <= 9'd0;
end
else
begin
//50_000_000 / 153600 = 325.5
if(cnt > 9'd324)
begin
cnt <= 9'd0;
end
else
begin
cnt <= cnt + 1'b1;
end
end
end
always @ (posedge clk50m or posedge rst)
begin
if(rst)
begin
bclk <= 1'b0;
end
else
begin
if(cnt > 9'd324)
bclk <= 1'b1;
else
bclk <= 1'b0;
end
end
endmodule

经仿真所得的仿真波形如下:

《基于FPGA的串口通讯设计》

由此可以得到一个153.6KHz的工作时钟。


发送模块

由于波特率发生器产生的时钟信号bclk为9600b/s的16倍,因此在发送器的设计中,应该每隔16拍发送一个bit.发送数据格式应该按照串口数据帧格式来传输。首先应该发送起始位,即发送端口txd从逻辑1变化到逻辑0,并持续1/9600s.其次是8个有效数据bit,低位在前,高位在后,最后是一位停止位。注意在本设计中没有校验位。


整个发送模块的状态转移图如下:

《基于FPGA的串口通讯设计》

状态转换说明:

s_idle为空闲状态,当复位信号有效或发送任务完成时,发送模块就处于s_idle状态,等待下一个发送指令(tx_cmd)到来。在s_idle中,发送完成指示tx_ready为高电平,表示随时可以接受外部的发送指令。tx_cmd信号高有效,且持续一个bclk周期。当tx_cmd有效时,发送模块的下一个状态为s_start.

s_start为发送模块的起始状态,拉底tx_ready信号,表明发送模块正在发送中,并拉底发送线txd,给出起始位,然后跳转到s_wait状态。需要注意的是,s_start状态仅持续一个bclk周期,完成起始位的发送后,转入s_wait状态。

s_wait为发送模块的等待状态,保持所有信号值不变,当发送模块处于这一状态的时候,等待计数满16个bclk后,判断8个有效数据位是否发送完成,如果发送完成,则跳转到s_stop准备发送停止位。否则,跳转到s+shift状态,发送下一个有效数据位。

s_shift为数据移位状态,发送模块在这一状态将下一个要发送的数据移动到发送端口上,然后直接跳转到s_wait状态。

s_stop状态完成停止位的发送,当有效数据发送完成以后,发送模块进入该状态,发送一个停止位后,自动进入s_idle状态,并且将tx_ready信号拉高。

verilog代码设计如下:

module uart_tx (
input bclk,
input rst,

input tx_cmd,
input [7:0] tx_din,

output reg tx_ready,
output txd
);
parameter S_IDLE = 3'b000;
parameter S_START = 3'b001;
parameter S_WAIT = 3'b010;
parameter S_SHIFT = 3'b011;
parameter S_STOP = 3'b100;
parameter LFRAME = 8;
reg txd_t;
reg [3:0] cnt;
reg [3:0] dcnt;
assign txd = txd_t;
reg [2:0] st_current;
reg [2:0] st_next;
always @ (posedge bclk or posedge rst)
begin
if(rst)
st_current <= S_IDLE;
else
st_current <= st_next;
end
always @ *
begin
case(st_current)
S_IDLE :
begin
if(tx_cmd == 1'b1)
st_next = S_START;
else
st_next = S_IDLE;
end

S_START :
begin
st_next = S_WAIT;
end

S_WAIT :
begin
if(cnt >= 4'b1110)
begin
if(dcnt == LFRAME)
st_next = S_STOP;
else
st_next = S_SHIFT;
end
else
begin
st_next = S_WAIT;
end
end

S_SHIFT :
begin
st_next = S_WAIT;
end

S_STOP:
begin
if(cnt >= 4'b1110)
st_next = S_IDLE;
else
st_next = S_STOP;
end

default : ;

endcase
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
cnt <= 4'h0;
else if(st_current == S_WAIT)
begin
if(cnt >= 4'b1110)
cnt <= 4'h0;
else
cnt <= cnt + 1'b1;
end
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
dcnt <= 4'h0;
else
begin
if(st_current == S_SHIFT)
begin
if(dcnt == LFRAME)
dcnt <= 4'h0;
else
dcnt <= dcnt + 1'b1;
end
end
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
tx_ready <= 1'b0;
else
begin
if(st_current == S_IDLE)
tx_ready <= 1'b1;
else if((st_current == S_STOP) && (cnt >= 4'b1110))
tx_ready <= 1'b1;
else
tx_ready <= 1'b0;
end
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
txd_t <= 1'b1;
else
begin
if(st_current == S_IDLE)
txd_t <= 1'b1;
else if(st_current == S_START)
txd_t <= 1'b0;
else if(st_current == S_WAIT)
begin
if((cnt == 4'b1110) && (dcnt == LFRAME))
txd_t <= 1'b1;
end
else if(st_current == S_SHIFT)
txd_t <= tx_din[dcnt];
end
end

endmodule

对上面逻辑代码编写的testbench如下:

module uart_tx_tb ();
reg bclk;
reg rst;
reg [7:0] tx_din;
reg tx_cmd;

wire tx_ready;
wire txd;

parameter RST_PERIOD = 100;
parameter CLK_PERIOD = 10;

initial
begin
rst = 1;
bclk = 0;
tx_din = 0;
tx_cmd = 0;
#RST_PERIOD;
rst = 0;
#20;
tx_din = 10;
tx_cmd = 1;
#20;
tx_cmd = 0;
end

always
bclk = #(CLK_PERIOD/2.0) !bclk;

uart_tx uut(
.bclk (bclk),
.rst (rst),

.tx_cmd (tx_cmd),
.tx_din (tx_din),

.tx_ready (tx_ready),
.txd (txd)
);

endmodule

需要发送的数据为0x0A,仿真模型如下:

《基于FPGA的串口通讯设计》

经功能仿真后测试正确。

接收模块
在涉及到双方通信的系统,接收机的复杂程度总是高于发送机的。对于串口通信也是如此。在接受系统中,起始状态和数据都需要依靠接收端检测得到,为了避免毛刺影响,能够得到正确的其实信号和有效数据,需要完成一个简单的最大似然判断。其方法如下:由于bclk的频率为9600的16倍,则对于每个数据都会有16个采样值,最终的采样结果为出现次数超过8次以上的电平值。这样就能最大限度地保证接收到的数据有最大的概率是正确的。
整个接收模块的状态机包括三个部分。状态转移图如下:

《基于FPGA的串口通讯设计》

s_idle状态为空闲状态,在这个状态下,接收机循环接收发送机所发送的起始信号。当接收到有从1->0的起始信号以后,状态机转换到采样状态。在此状态下,tx_ready的值为1.
s_sample状态为采样状态。接口模块连续采样数据,并对每16个采样数据进行最大似然判决,得到相应的逻辑值,这一过程在每次接收到数据的时候都需要重复。然后依次完成串并转换。在接收完8个数据位后,直接进入s_stop状态。在这一状态下,rx_ready信号值为0.
s_stop状态用于检测停止位,在接收到停止为后,直接转入到s_idle状态。如果设计者需要添加校验位的话,还需要在此部分先做数据校验。另外需要注意的是,在第八个数据位后一定是停止位,再因为我所选择的时钟为9600的16倍,所以需要在停止位后有时间补偿。
逻辑设计代码如下:

module uart_rx(
input bclk,
input rst,
input rxd,

output [7:0] rx_dout,
output reg rx_ready
);
parameter LFRAME = 8;
parameter S_IDLE = 3'b001;
parameter S_SAMPLE = 3'b010;
parameter S_STOP = 3'b100;
reg [2:0] st_current;
reg [2:0] st_next;
reg [3:0] cnt;
reg [3:0] dcnt;
reg [3:0] num;
reg [7:0] rx_dout_t;
always @ (posedge bclk or posedge rst)
begin
if(rst)
st_current <= S_IDLE;
else
st_current <= st_next;
end
always @ *
begin
case(st_current)
S_IDLE :
begin
if((cnt == 4'b1111) && (num > 7))
st_next = S_SAMPLE;
else
st_next = S_IDLE;
end

S_SAMPLE :
begin
if(dcnt == LFRAME)
st_next = S_STOP;
else
st_next = S_SAMPLE;
end

S_STOP :
begin
if(cnt == 4'b1111)
st_next = S_STOP;
end

default : ;
endcase
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
cnt <= 4'b0000;
else
cnt <= cnt + 1'b1;
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
dcnt <= 4'b0000;
else
begin
if(st_current == S_IDLE)
dcnt <= 4'h0;
else if((st_current == S_SAMPLE) && (cnt == 4'b1111))
dcnt <= dcnt + 1'b1;
end
end
// 最大似然
always @ (posedge bclk or posedge rst)
begin
if(rst)
num <= 4'h0;
else
begin
if((st_current == S_IDLE) && (rxd == 1'b0))
begin
if(cnt == 4'b1111)
num <= 4'h0;
else
num <= num + 1'b1;
end
else if((st_current == S_SAMPLE) && (rxd == 1'b1))
begin
if(cnt == 4'b1111)
num <= 4'h0;
else
num <= num + 1'b1;
end
end
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
rx_ready <= 1'b0;
else
begin
if(st_current == S_SAMPLE)
rx_ready <= 1'b0;
else
rx_ready <= 1'b1;
end
end
always @ (posedge bclk or posedge rst)
begin
if(rst)
rx_dout_t <= 7'h00;
else
begin
if(st_current == S_SAMPLE)
begin
if(num > 7)
rx_dout_t[dcnt] <= 1'b1;
else
rx_dout_t[dcnt] <= 1'b0;
end
end
end
assign rx_dout = rx_dout_t;
endmodule

对上逻辑设计编写testbench如下:

module uart_rx_tb();
reg bclk;
reg rst;
reg rxd;

wire rx_ready;
wire [7:0] rx_dout;

uart_rx
uut (
.bclk (bclk),
.rst (rst),
.rxd (rxd),

.rx_dout (rx_dout),
.rx_ready (rx_ready)
);
initial
begin
bclk = 0;
rst = 1;
rxd = 1;
#100;
rst = 0;
rxd = 0;
#320;
rxd = 1;
end

always bclk = #5 !bclk;
endmodule

在testbench中,通过txd发送一个0xFE的反向bit序列(低位在前,高位在后),仿真波形如下:

《基于FPGA的串口通讯设计》


顶层模块
由以上各个子模块的描述,顶层模块的功能只是完成各个子模块的例化,以及完成与其他外部接口的通讯。在本设计中,通过上位机接收数据,当接收到的数据为0x47的时候,点亮对应的LED灯。
本部分逻辑设计代码略。


推荐阅读
  • 本题探讨了在一个有向图中,如何根据特定规则将城市划分为若干个区域,使得每个区域内的城市之间能够相互到达,并且划分的区域数量最少。题目提供了时间限制和内存限制,要求在给定的城市和道路信息下,计算出最少需要划分的区域数量。 ... [详细]
  • 本题探讨如何通过最大流算法解决农场排水系统的设计问题。题目要求计算从水源点到汇合点的最大水流速率,使用经典的EK(Edmonds-Karp)和Dinic算法进行求解。 ... [详细]
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 本教程涵盖OpenGL基础操作及直线光栅化技术,包括点的绘制、简单图形绘制、直线绘制以及DDA和中点画线算法。通过逐步实践,帮助读者掌握OpenGL的基本使用方法。 ... [详细]
  • 毕业设计:基于机器学习与深度学习的垃圾邮件(短信)分类算法实现
    本文详细介绍了如何使用机器学习和深度学习技术对垃圾邮件和短信进行分类。内容涵盖从数据集介绍、预处理、特征提取到模型训练与评估的完整流程,并提供了具体的代码示例和实验结果。 ... [详细]
  • 配置多VLAN环境下的透明SQUID代理
    本文介绍如何在包含多个VLAN的网络环境中配置SQUID作为透明网关。网络拓扑包括Cisco 3750交换机、PANABIT防火墙和SQUID服务器,所有设备均部署在ESXi虚拟化平台上。 ... [详细]
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
  • 反向投影技术主要用于在大型输入图像中定位特定的小型模板图像。通过直方图对比,它能够识别出最匹配的区域或点,从而确定模板图像在输入图像中的位置。 ... [详细]
  • 本题探讨了在大数据结构背景下,如何通过整体二分和CDQ分治等高级算法优化处理复杂的时间序列问题。题目设定包括节点数量、查询次数和权重限制,并详细分析了解决方案中的关键步骤。 ... [详细]
  • 本文详细介绍了Java中org.w3c.dom.Text类的splitText()方法,通过多个代码示例展示了其实际应用。该方法用于将文本节点在指定位置拆分为两个节点,并保持在文档树中。 ... [详细]
  • 机器学习中的相似度度量与模型优化
    本文探讨了机器学习中常见的相似度度量方法,包括余弦相似度、欧氏距离和马氏距离,并详细介绍了如何通过选择合适的模型复杂度和正则化来提高模型的泛化能力。此外,文章还涵盖了模型评估的各种方法和指标,以及不同分类器的工作原理和应用场景。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文详细介绍了Java中的访问器(getter)和修改器(setter),探讨了它们在保护数据完整性、增强代码可维护性方面的重要作用。通过具体示例,展示了如何正确使用这些方法来控制类属性的访问和更新。 ... [详细]
author-avatar
旧情人旧往事_322
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有