FPGA-UART-双工通信
US(A)RT,The Universal Synchronous and Asynchronous serial Receiver and Transmitter,通用同步(异步)串行接受和发送器,简称为串口
Ø USART是最普遍的一种串行通信协议
文章目录
提示:本文仅位个人学习记录
一、概述
1、目的及意义:
了解串口通信的背景知识后,编程实现串口通信(FPGA-FPGA)。了解UART(通用异步收发传输器)的基本原理。
2、主要功能:
实现两个FPGA通过拨码开关传输数据,通过七段数码管显示数据,并使用LED展示通信状态。
二、原理及步骤
1、原理框图:
2、工作原理
UART即通用异步收发器,是一种串行通信方式。数据在传输过程中是通过一位一位地进行传输来实现通信的,串行通信方式具有传输线少,成本底等优点,缺点是速度慢。串行通信分为两种类型:同步通信方式和异步通信方式。但一般多用异步通信方式,主要因为接受和发送的时钟是可以独立的这样有利于增加发送与接收的灵活性。异步通信是一个字符接着一个字符传输,一个字符的信息由起始位、数据位、奇偶校验位和停止位组成。
每一个字符的传输靠起始位来同步,字符的前面一位是起始位,用下降沿通知收方开始传输,紧接着起始位之后的是数据位,传输时低位在前高位在后,字符本身由5~8位数据位组成。数据位后面是奇偶校验位,最后是停止位,停止位是用高电平来标记一个字符的结束,并为下一个字符的传输做准备。停止位后面是不同长度的空闲位。停止位和空闲位都规定为高电平,这样可以保证起始位有一个下降沿。UART的帧格式如图所示。
3、功能模块简介
数据发送模块:发送模块实现数据由并行输入到串行输出
数据接收模块:接收模块实现数据由串行输入到并行输出
波特率发生器:波特率发生器模块控制产生UART时钟频率
串口有三根线,分别如下:
TXD:发送;
RXD:接收;
GND:接地
串口通信是异步通讯,端口能够在一根线上发送数据同时在另一根线上接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
4、实验步骤
1、首先导入uart_top文件并导入他的3个附属文件,修改实际发送值,运行程序,进行引脚配置,然后用串口监视器进行初步演示,了解数据输出方式。
2、设计实验,编程实现思路,配置引脚如图:
3、通过数码管和串口监视器进行调试,(检查数据从数码管输入到被接收再被显示的过程中有无出错的地方)最后发现对uart_tx(读数据模块)中的data_i直接进行数码管显示时会出错(初步估计是数据更新速度过快数码管无法直接显示),于是采用用寄存器(data_s)对数据进行暂存并显示 并根据receive_ack信号对数据进行更新。
4、调试完成之后连接好两块FPGA并将输入输出和GND用邦杜线进行连接,然后烧写程序进行验证。
三、程序设计及描述
代码:
module uart_top
(
output txd,
output [6:0] hex0,hex1, //两个七段数码管对数据进行显示
output reg tx,rx,re, //三个LED分别显示通信状态
input rxd,
input clk,
input start, //控制数据传输的开始结束
input receive, //控制是否接受数据
input [7:0]data_tx //实际传输数据
);
wire clk_9600;
wire receive_ack;
wire [7:0] data;
always @(*)
begin
begin
if (start == 1)//控制LED9显示是否发送数据
begin
tx<=1;
end
else
begin
tx<=0;
end
end
begin
if(receive_ack) //控制LED0显示是否接收到数据
begin
rx<=1;
end
else
begin
rx<=0;
end
end
begin
if(receive) //控制LED9显示是否允许接收数据
begin
re<=1;
end
else
begin
re<=0;
end
end
end
//发送模块
uart_tx uart_tx
(
.clk(clk_9600),
.txd (txd),
.data_o(data_tx), //实际发送值
.send (start) //允许发送怎么填写
);
//接受模块
uart_rx uart_rx
(
.clk(clk_9600),
.rxd (rxd),
.receive (receive),
.data_i (data),
.data_s (num), //将data_s 寄存器的值赋给num
.receive_ack(receive_ack)
);
//时钟模块
clk_div clk_div
(
.clk (clk),
.clk_out (clk_9600)
);
//数码管显示 num进行显示
hex_7seg seg0 (.hex((num)%10),.sseg(hex0));
hex_7seg seg1 (.hex((num)/10%10),.sseg(hex1));
endmodule
module uart_tx(
input [7:0]data_o,
output reg txd,
input clk,
input send
);
//发送状态机分为四个状态:等待,发送起始位,发送数据,发送结束
localparam IDLE=0,
SEND_START=1,
SEND_DATA=2,
SEND_END=3;
reg [3:0]cur_st=0,nxt_st=0;
reg [4:0]count=0;
reg [7:0]data_o_tmp=0;
always@(posedge clk)
cur_st<=nxt_st;
always@(*)
begin
nxt_st=cur_st;
case(cur_st)
IDLE: if(send) nxt_st=SEND_START; //接收完成时开始发送数据
SEND_START: nxt_st=SEND_DATA; //发送起始位
SEND_DATA: if(count==7) nxt_st=SEND_END;//发送八位数据
SEND_END: nxt_st=SEND_START; //发送结束
default: nxt_st=IDLE;
endcase
end
always@(posedge clk)
if(cur_st==SEND_DATA)
count<=count+1;
else if(cur_st==IDLE | cur_st==SEND_END)
count<=0;
//发送低位到高位
always@(posedge clk)
if(cur_st==SEND_START)
data_o_tmp<=data_o; //将发送数据导入变量
else if(cur_st==SEND_DATA)
data_o_tmp[6:0]<=data_o_tmp[7:1]; //每发送以为数据后data_o_tmp右移一位,便于下一位数据的发送
always@(posedge clk)
if(cur_st==SEND_START)
txd<=0;
else if(cur_st==SEND_DATA)
txd<=data_o_tmp[0];//由于每次发送后右移,所以每次最低位
else if(cur_st==SEND_END)
txd<=1;
endmodule
module uart_rx(
input rxd,
input clk,
input receive,
output receive_ack,
output reg [7:0]data_i,
output reg [7:0]data_s
);
//串口接收状态机分为三个状态:等待,接收,接收完成
localparam IDLE=0,
RECEIVE=1,
RECEIVE_END=2;
reg [3:0]cur_st=0,nxt_st=0; //状态机变量
reg [4:0]count=0;
always@(posedge clk)
cur_st<=nxt_st;
always@(*)
begin
begin
data_s<=(receive_ack)?data_i:data_s;//仿照原有语句通过receive_ack的信号判断数据是否传输完。
end
nxt_st=cur_st;
case(cur_st)
IDLE: if(!rxd & receive ) nxt_st=RECEIVE; //接受到开始信号,开始接受数据
RECEIVE: if(count==7) nxt_st=RECEIVE_END; //八位数据接受计数
RECEIVE_END: nxt_st=IDLE; //接收完成
default: nxt_st=IDLE;
endcase
end
always@(posedge clk)
if(cur_st==RECEIVE)
count<=count+1; //接受数据计数
else if(cur_st==IDLE | cur_st==RECEIVE_END)
count<=0;
always@(posedge clk)
if(cur_st==RECEIVE) //从高到低发送数据
begin
data_i[6:0]<=data_i[7:1];
data_i[7]<=rxd;
end
assign receive_ack=(cur_st==RECEIVE_END)?1:0; //接受完成时间回复信号
endmodule
module clk_div(
input clk,
output reg clk_out
);
localparam Baud_Rate=9600; //波特率
localparam div_num='d50_000_000/Baud_Rate; //分频数位时钟速率除以波特率
reg [15:0]num=0;
always@(posedge clk)
if(num==div_num)
begin
num<=0;
clk_out<=1;
end
else
begin
num<=num+1;
clk_out<=0;
end
endmodule
四、仿真与综合测试
仿真代码
`timescale 1 n在这里插入图片描述
s/ 1 ps
module uart_top_vlg_tst();
// constants
// general purpose registers
reg eachvec;
// test vector input registers
reg clk;
reg [7:0] data_tx;
reg receive;
reg rxd;
reg start;
// wires
wire [6:0] hex0;
wire [6:0] hex1;
wire re;
wire rx;
wire tx;
wire txd;
// assign statements (if any)
uart_top i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.data_tx(data_tx),
.hex0(hex0),
.hex1(hex1),
.re(re),
.receive(receive),
.rx(rx),
.rxd(rxd),
.start(start),
.tx(tx),
.txd(txd)
);
initial
begin
clk=1;
start=1;
receive=1;
data_tx=00001101;
forever
begin
#1 rxd=1;
#1 rxd=0;
end
//#1000 $stop;
$display("Running testbench");
end
always
// optional sensitivity list
// @(event1 or event2 or .... eventn)
begin
#1 clk=~clk;// 每10ns翻转一次,周期即20ns,50Mz
end
endmodule
1、 仿真图
2、 实物图
五、总结
1、串口通信的实质是串转并的结果,本质和用状态机检测脉冲序列并无太大区别。
2、当遇见输出不对的时候,要学会利用检查短路的思路一小段一小段地检查,并用数码管观察输出,用串口助手输入信号检测接收模块的功能。