串口通信概念
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
#vlog altera_mf.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
#vlog altera_mf.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发送三个数据文章来源:https://www.toymoban.com/news/detail-595976.html
`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时进行发送和接收文章来源地址https://www.toymoban.com/news/detail-595976.html
到了这里,关于FPGA-串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!