FPGA-串口通信
串口通信概念
UART通信原理
UART (universal asynchronous receiver-transmitter)是一种采用异步串行通信方式的通用异步收发传输器;它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。UART串口通信需要两根信号线来实现,一根用于发送,另外一根接收(表明是异步全双工通信)。
①协议层:通信协议(包括数据格式、传输速率等)。
②物理层:接口类型、电平标准等。
协议层:数据格式,一帧数据由4部分组成(用代码设计串口用到的就是协议层):
·起始位( 1bit)
·数据位(6/7/8bit)
·奇偶校验位(1bit)
·停止位(1bit/1.5bit/2bit)
起始位,数据位,停止位是必要的。
数据位的数据高位在后,低位在前,从左只有从低到高,即bit[0]至bit[n].
协议层:传输速率
串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,
单位是bit/s (位/秒),简称bps;
常用的波特率有9600、19200、 38400、 57606以 及115200等。
物理层:接口类型、电平标准串口电平标准:
.TTL电平的串口(3.3V)
·RS232电平的串口( 5~ 12V为低电平,-12~-5V为高电平)
串口按电气标准分包括:
·RS-232-C:TXD/RXD/GND、15米/9600bps
·RS-422:TX /TX-/RX /RX-/GND
.RS485:A/B/G 、1200米/9600bps
数据传输(串口回环)整体框架:
数据传输时,串行数据经过接收端进行串转并的处理变为并行数据,再经过发送端并转串处理传输串行数据。
起始位检测
首先找到起始位才能开始数据接收(检测下降沿,进行同步打拍;检测下降沿时为r0低电平,r1高电平)
ps:为什么要做同步?因为传输过程中是没有时钟信号的,首先需要将其与时钟边沿同步,然后打拍检测边沿
起始位传输占据1bit,求波特率为9600传输1bit需要的时钟周期,即起始位所占用的时间,计算方法为:链接(https://blog.csdn.net/weixin_45388202/article/details/116465712),需要计数大约5208次。
数据位传输
传输一帧数据的数据位如图所示:
从低位到高位传输,所以数据位数据为11111001.
接收端RX
rx_data接收数据进行中间采样(中间信号稳定),如果接收的时候检测到了下降沿但采样的起始位为1(接收数据包含了起始位),说明rxd可能产生了抖动过后又回到高电平,因此需要进行判断其是否为起始位,采样到1说明产生了抖动,此时将bit计数器清零,rx_data不是有效数据不能作为输出。
输入串行数据rxd,进入FPGA后,一帧数据里包含停止位和起始位,但只有数据位这8位是需要的有效数据。
接收端设计文件:
-
输入串行数据rxd,输出8位数据rx_data,加入标志信号rx_data_vld判断输出的rx_data是否为8位数据位,是则有效。
-
对rxd进行同步打拍,边沿检测下降沿(起始位为一帧数据的开始)
-
定义参数及信号,flag为接收一帧数据的标志信号,作为开启bps计数器的标志,即在一帧数据到来时,起始位检测下降沿bps计数器开启计数flag拉高,记完1bit后end_cnt_bps拉高结束这一比特计时,此处flag则拉低。除开end_cnt_bps的其他时刻均保持flag拉高,知道最后1bit数据传输完毕。
-
对于end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]),它必须满足计数完第十个bit或者起始位data_in[0]为1,此时才是bit结束计数标志,两钟情况满足任一即可。
-
进行数据采样时最好是选择之间的稳定信号,为什么加入data_in信号?我的理解是此信号是经过串行信号通过FPGA进行并行处理,但我们只需要数据位的8位有效信号,因此需要对有效信号进行提取做准备
-
将有效数据位数据赋予rx_data,并进行是否是有效数据的判断,判断条件结束bit计数和存在起始位必须同时满足,这样才能有一帧完整的10bit数据,然后去除的data_in[8:1]中间的8位数据才是数据位信息。
-
module uart_rx (
-
input clk ,
-
input rst_n ,
-
input rxd ,//输入串行数据
-
-
output reg [7:0] rx_data ,
-
output reg rx_data_vld//判断标志信号,判断rx_data的数据是否有效
-
);
-
//参数定义
-
parameter BPS_9600 = 5208 ;
-
-
//信号定义
-
reg rx_r0 ;
-
reg rx_r1 ;
-
-
wire nedge ;//检测起始位
-
-
reg [12:0] cnt_bps ;//计数1bit时钟周期
-
wire add_cnt_bps ;
-
wire end_cnt_bps ;
-
reg flag ;//接收标志
-
-
reg [3:0] cnt_bit ;//计数bit数
-
wire add_cnt_bit ;
-
wire end_cnt_bit ;
-
-
reg [9:0] data_in ;//一帧数据,起始位(1bit),数据位(8bit),停止位(1bit)
-
//进行串并转换
-
-
//同步,打拍,边沿检测
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
rx_r0 <= 1'b1 ;
-
rx_r1 <= 1'b1 ;
-
end
-
else begin
-
rx_r0 <= rxd ;//同步数据
-
rx_r1 <= rx_r0 ;//打拍
-
end
-
end
-
-
assign nedge = rx_r1 & ~rx_r0 ;//下降沿检测
-
-
//flag
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
flag <= 1'b0 ;
-
end
-
else if(nedge) begin
-
flag <= 1'b1 ;
-
end
-
else if(end_cnt_bit) begin
-
flag <= 1'b0;
-
end
-
end
-
-
//cnt_bps
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
cnt_bps <= 'd0;
-
end
-
else if (add_cnt_bps) begin
-
if (end_cnt_bps) begin
-
cnt_bps <= 'd0 ;
-
end
-
else begin
-
cnt_bps <= cnt_bps 1'b1 ;
-
end
-
end
-
end
-
-
assign add_cnt_bps = flag ;
-
assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;
-
-
-
-
//cnt_bit
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
cnt_bit <= 'd0;
-
end
-
else if (add_cnt_bit) begin
-
if (end_cnt_bit) begin
-
cnt_bit <= 'd0 ;
-
end
-
else begin
-
cnt_bit <= cnt_bit 1'b1 ;
-
end
-
end
-
end
-
-
assign add_cnt_bit = end_cnt_bps ;
-
assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]) ;//data_in[0]是存的起始位
-
-
//data_in采样数据(包含起始位和停止位)
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
data_in <= 0;
-
end
-
else if (cnt_bps == BPS_9600 >> 1) begin//为了稳定采样中间的值
-
data_in[cnt_bit] <= rx_r1 ;
-
end
-
end
-
-
//取数据位rx_data
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
rx_data <= 0 ;
-
end
-
else if (end_cnt_bit) begin
-
rx_data <= data_in[8:1] ;//只需要给数据位
-
end
-
else begin
-
rx_data <= 0 ;
-
end
-
end
-
-
//判断rx_data是否有效
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
rx_data_vld <= 0 ;
-
end
-
else begin
-
rx_data_vld <= end_cnt_bit && (data_in[0] == 0);
-
end
-
end
-
-
-
endmodule
接收端测试文件:
定义系统时钟周期,例化模块,产生时钟,上电复位并赋予初值,然后分配起始位,数据位,停止位并进行延时处理。
-
`timescale 1ns/1ps
-
module tb_uart_rx ();
-
reg clk ;
-
reg rst_n ;
-
reg rxd ;
-
-
wire [7:0] rx_data ;
-
wire rx_data_vld ;
-
-
//参数定义
-
parameter CYCLE = 20 ;
-
-
//例化
-
uart_rx u_uart_rx(
-
.clk (clk ),
-
.rst_n (rst_n ),
-
.rxd (rxd ),
-
.rx_data (rx_data ),
-
.rx_data_vld (rx_data_vld)
-
);
-
-
//时钟
-
initial begin
-
clk = 1'b1 ;
-
forever begin
-
#(CYCLE/2);
-
clk = ~clk ;
-
end
-
end
-
-
integer i;//数据个数
-
-
initial begin
-
rst_n = 1'b1 ;
-
#(CYCLE);
-
rst_n = 1'b0 ;
-
rxd = 1'b1 ;
-
#22;
-
rst_n = 1'b1 ;
-
-
#(CYCLE*200);
-
-
//模拟一帧数据的格式
-
rxd = 1'b0 ;//起始位
-
#(CYCLE*5208);
-
-
//数据位
-
for (i=0;i<8;i=i 1) begin
-
case (i)//赋值rxd = 1111_1001
-
1: rxd = 1'b0 ;
-
2: rxd = 1'b0 ;
-
default: rxd = 1'b1 ;//只有第一位和第二位为0
-
endcase
-
#(CYCLE*5208);
-
end
-
-
rxd = 1'b1 ;//停止位
-
#(CYCLE*5208);
-
-
#(CYCLE*200);
-
$stop;
-
end
-
endmodule
仿真
test.do文件:
-
vlib work
-
vmap work work
-
-
#编译testbench文件
-
vlog tb_uart_rx.v
-
#编译 设计文件
-
vlog ../rtl/uart_rx.v
-
-
-
-
-
-
#指定仿真顶层
-
vsim -novopt work.tb_uart_rx
-
#添加信号到波形窗
-
add wave -position insertpoint sim:/tb_uart_rx//*
-
-
run -all
观测到最后8位数据位为1111_1001符合要求。
疑问
此处没有理解透彻5802为啥右移一位就变成中间部分的数据了?将打拍的数据赋予data_in内部为什么应bit计数器表示?
答:右移一个一个地位相当于除以2,符合在中间值采样原则
发送端TX
发送端设计文件:
和发送端类似,由上设计框图
-
输入并行信号tx_data,将rx_data的数据传输给它,tx_data_vld为传输8位数据位有效信号标志,输出串行数据txd
-
必须保持波特率一致,定义参数
-
flag在这里作为发送数据标志,同时也是计时器bps的开启条件,其在数据有效时拉高进行发送,所有bit的数据发送完后拉低
-
同样用data_out寄存一帧10bit的数据(并行数据),通过发送端转化为串行数据,在数据发送有效时,添加起始位和停止位赋值于它进行寄存
-
输出串行数据txd,仅在发送数据标志flag拉高时发送数据,数据来自于寄存于data_out的10bit并行数据。
-
module uart_tx (
-
input clk ,
-
input rst_n ,
-
input [7:0] tx_data ,
-
input tx_data_vld ,//发送有效信号标志
-
-
output reg txd
-
);
-
//参数定义
-
parameter BPS_9600 = 5208 ;
-
-
//信号定义
-
-
reg [12:0] cnt_bps ;//计数1bit时钟周期
-
wire add_cnt_bps ;
-
wire end_cnt_bps ;
-
reg flag ;//发送数据标志,同时也是bps计数的开启条件
-
-
reg [3:0] cnt_bit ;//计数bit数
-
wire add_cnt_bit ;
-
wire end_cnt_bit ;
-
-
reg [9:0] data_out ;
-
-
//flag
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
flag <= 1'b0 ;
-
end
-
else if(tx_data_vld) begin //在发送有效信号时,flag作为标志拉高
-
flag <= 1'b1 ;
-
end
-
else if(end_cnt_bit) begin//bit计数结束时,flag拉低
-
flag <= 1'b0;
-
end
-
end
-
-
//cnt_bps
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
cnt_bps <= 'd0;
-
end
-
else if (add_cnt_bps) begin
-
if (end_cnt_bps) begin
-
cnt_bps <= 'd0 ;
-
end
-
else begin
-
cnt_bps <= cnt_bps 1'b1 ;
-
end
-
end
-
end
-
-
assign add_cnt_bps = flag ;
-
assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;
-
-
-
-
//cnt_bit
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
cnt_bit <= 'd0;
-
end
-
else if (add_cnt_bit) begin
-
if (end_cnt_bit) begin
-
cnt_bit <= 'd0 ;
-
end
-
else begin
-
cnt_bit <= cnt_bit 1'b1 ;
-
end
-
end
-
end
-
-
assign add_cnt_bit = end_cnt_bps ;
-
assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9) ;
-
-
//寄存数据至data_out(包含起始位和停止位)
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
data_out <= 0;
-
end
-
else if (tx_data_vld) begin
-
data_out <= {1'b1,tx_data,1'b0} ;
-
end
-
end
-
-
//输出txd
-
always @(posedge clk or negedge rst_n) begin
-
if (!rst_n) begin
-
txd <= 1'b1 ;
-
end
-
else if (flag) begin
-
txd <= data_out[cnt_bit] ;
-
end
-
end
-
-
endmodule
发送端测试文件:
只需要加上接收端的例化模块即可,中间信号端口互通,在最后再增加10bit的周期延迟。
-
`timescale 1ns/1ps
-
module tb_uart_rx ();
-
reg clk ;
-
reg rst_n ;
-
reg rxd ;
-
-
wire [7:0] rx_data ;
-
wire rx_data_vld ;
-
-
//参数定义
-
parameter CYCLE = 20 ;
-
-
//例化
-
uart_rx u_uart_rx(
-
.clk (clk ),
-
.rst_n (rst_n ),
-
.rxd (rxd ),
-
.rx_data (rx_data ),
-
.rx_data_vld (rx_data_vld)
-
);
-
-
uart_tx u_uart_tx(
-
.clk (clk ) ,
-
.rst_n (rst_n ) ,
-
.tx_data (rx_data ) ,
-
.tx_data_vld (rx_data_vld) ,
-
-
.txd (txd )
-
);
-
-
//时钟
-
initial begin
-
clk = 1'b1 ;
-
forever begin
-
#(CYCLE/2);
-
clk = ~clk ;
-
end
-
end
-
-
integer i;//数据个数
-
-
initial begin
-
rst_n = 1'b1 ;
-
#(CYCLE);
-
rst_n = 1'b0 ;
-
rxd = 1'b1 ;
-
#22;
-
rst_n = 1'b1 ;
-
-
#(CYCLE*200);//增加间隔
-
-
//模拟一帧数据的格式
-
rxd = 1'b0 ;//起始位
-
#(CYCLE*5208);
-
-
//数据位
-
for (i=0;i<8;i=i 1) begin
-
case (i)//赋值rxd = 1111_1001
-
1: rxd = 1'b0 ;
-
2: rxd = 1'b0 ;
-
default: rxd = 1'b1 ;//只有第一位和第二位为0
-
endcase
-
#(CYCLE*5208);
-
end
-
-
rxd = 1'b1 ;//停止位
-
#(CYCLE*5208);
-
-
#(CYCLE*200);//增加间隔
-
-
#(CYCLE*10*5208);//10bit时钟周期延迟
-
-
-
$stop;
-
end
-
endmodule
仿真
test.do文件
-
vlib work
-
vmap work work
-
-
#编译testbench文件
-
vlog tb_uart_rx.v
-
#编译 设计文件
-
vlog ../rtl/uart_rx.v
-
vlog ../rtl/uart_tx.v
-
-
-
-
-
-
#指定仿真顶层
-
vsim -novopt work.tb_uart_rx
-
#添加信号到波形窗
-
add wave -position insertpoint sim:/tb_uart_rx//*
-
-
run -all
顶层仿真
只需要改动例化模块即可
-
`timescale 1ns/1ps
-
module tb_uart ();
-
reg clk ;
-
reg rst_n ;
-
reg rxd ;
-
-
wire txd ;
-
-
//参数定义
-
parameter CYCLE = 20 ;
-
-
//例化
-
uart u_uart(
-
.clk (clk ),
-
.rst_n (rst_n),
-
.rxd (rxd ),
-
-
.txd (txd )
-
);
-
-
//时钟
-
initial begin
-
clk = 1'b1 ;
-
forever begin
-
#(CYCLE/2);
-
clk = ~clk ;
-
end
-
end
-
-
integer i;//数据个数
-
-
initial begin
-
rst_n = 1'b1 ;
-
#(CYCLE);
-
rst_n = 1'b0 ;
-
rxd = 1'b1 ;
-
#22;
-
rst_n = 1'b1 ;
-
-
#(CYCLE*200);//增加间隔
-
-
//模拟一帧数据的格式
-
rxd = 1'b0 ;//起始位
-
#(CYCLE*5208);
-
-
//数据位
-
for (i=0;i<8;i=i 1) begin
-
case (i)//赋值rxd = 1111_1001
-
1: rxd = 1'b0 ;
-
2: rxd = 1'b0 ;
-
default: rxd = 1'b1 ;//只有第一位和第二位为0
-
endcase
-
#(CYCLE*5208);
-
end
-
-
rxd = 1'b1 ;//停止位
-
#(CYCLE*5208);
-
-
#(CYCLE*200);//增加间隔
-
-
#(CYCLE*10*5208);//10bit时钟周期延迟
-
-
-
$stop;
-
end
-
endmodule
仿真图
另外一种测试方法:
用FPGA的tx端去模拟PC端的tx端发送数据,然后FPGA接收端rx进行接收
代码:
直接建立task发送三个数据
-
`timescale 1ns/1ps
-
module tb_uart1 ();
-
reg clk ;
-
reg rst_n ;
-
reg [7:0] data ;
-
reg data_vld ;
-
-
wire dout ; //中间连线
-
wire txd ;//tx输出
-
-
//参数定义
-
parameter CYCLE = 20 ;
-
-
//例化
-
uart_tx u_uart_tx(
-
.clk (clk ) ,
-
.rst_n (rst_n ) ,
-
.tx_data (data ) ,
-
.tx_data_vld (data_vld) ,
-
-
.txd (dout )
-
);
-
-
uart u_uart(
-
.clk (clk ),
-
.rst_n (rst_n ),
-
.rxd (dout ),
-
-
.txd (txd )
-
);
-
-
//时钟
-
initial begin
-
clk = 1'b1 ;
-
forever begin
-
#(CYCLE/2);
-
clk = ~clk ;
-
end
-
end
-
-
-
initial begin
-
rst_n = 1'b1 ;
-
#(CYCLE);
-
rst_n = 1'b0 ;
-
data = 0 ;
-
data_vld = 1'b0 ;
-
#22;
-
rst_n = 1'b1 ;
-
#(CYCLE*200);//增加间隔
-
-
//发送几个数据
-
Send(8'hf9);
-
Send(8'ha0);
-
Send(8'hff);
-
-
#(CYCLE*10*5208);//10bit时钟周期延迟
-
#(CYCLE*200);
-
$stop;
-
end
-
-
task Send;
-
input [7:0] send_data ;
-
begin
-
data = send_data ;
-
data_vld = 1'b1 ;
-
#(CYCLE);
-
data_vld = 1'b0 ;
-
#(10*5208*CYCLE);
-
end
-
endtask
-
-
-
-
endmodule
上板验证
能够在波特率为9600时进行发送和接收
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgfahie
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01