目录
- 前言
- 一丶通信方式
-
- 1.串行通信
- 2.并行通信
- 二丶UART 串口通信
- 三丶模块设计
- 四丶发送模块
-
- 1.代码
- 2.仿真
- 五丶接收模块
-
- 1.代码
- 2.仿真
- 六丶顶层模块
-
- 1.代码
- 2.模块原理图
- 3.仿真
- 七丶上板验证
- 八丶源码
前言
嵌入式电子产品是一种互连电路(处理器或其他集成电路)以创建共生系统。这一章我们需要理解串行通信,并行通信两种方式。并理清UART串口通信的整个模块设计,然后依次编写发送模块,接收模块
为了使这些单独的电路交换其信息,它们必须共享公共通信协议
。
已经定义了数百种通信协议来实现这种数据交换,并且通信的方式主要可以分为两类:并行或串行
。
一丶通信方式 1.串行通信 定义:串行通信是指利用一条传输线将数据一位位地顺序传送。(也就是说串行通信传输的数据是1比特1比特的传送的)
串行通信又称为点对点通信,对于点对点之间的通信
根据数据的同步方式,又分为 异步通信 和 同步通信 两种方式
根据数据的传输方向与时间关系,又可分为:①单工通信、②半双工通信及③全双工通信三种方式。
文章图片
1.同步通信
同步通信一般有一个同步时钟,如下图,一根数据线,一根时钟线。一个时钟传输一个Bit位。
文章图片
2.异步通信
异步通信中,在异步通信中有两个比较重要的指标:字符帧格式和波特率。
数据通常以字符或者字节为单位组成字符帧传送,是通过双方约定好的波特率进行数据传输。
假如双方波特率不一致,则接收到数据就是乱码。
文章图片
单工:
只允许数据按照一个固定的方向传送,在任何时刻都只能进行一个方向的通信,一个设备固定为发送设备,一个设备固定为接收设备。
半双工:
两个设备之间可以收发数据,但是不能在同一时刻进行,每次只能有一个设备发送,另一个站接收。
全双工:
在同一时刻,两个设备之间可以同时进行发送和接收数据。
文章图片
2.并行通信 并行是指多比特数据同时通过并行线进行传送,这样数据传送速度大大提高。
但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错。
并行接口同时传输多个位。它们通常需要数据总线(八、十六或更多线路),以1和0的波形传输数据。
如下图:使用9线的并行通信,由时钟控制的8位数据总线,每个时钟脉冲发送一个字节。
文章图片
二丶UART 串口通信 它是
全双工
的异步通信
。串口通信的信号线需要两条线完成,
TX和RX TX发送端 RX为接收端
它的协议主要由四部分组成
- 起始位(1 bit)
- 数据位(6/7/8 bit)
- 奇偶校验位(1 bit)
- 停止位(1 bit)
文章图片
注意:此原理图既作为接收的数据帧,也作为发送的数据帧。因为串行通信中,在数据线上传输的数据是一位一位的,所以在收发数据的时候要进行串并之间的转换收发流程:
- 发送:首先是
空闲状态
,线路处于高电位
;当收到发送数据指令后,拉低线路一个数据位的时间T1,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位
和停止位
(停止位为高电位),一帧数据发送结束。 - 接收:首先是
空闲状态
,线路处于高电位
;当检测到线路的下降沿
(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验
位是否正确,如果正确则通知后续设备准备接收数据或存入缓存,最后接收到停止位
(停止位为高电位),一帧数据接收结束。
文章图片
- rx为接收端口
- tx为发送端口
上图所示分为主机和从机
我们把FPGA当作从机,来接收上位机(也就是我们的PC)发送过来的数据,再把数据发送给上位机,内部就需要两个模块 uart_rx和uart_tx
串口接收模块从 rx 接收到数据后,在内部实现接收方法,接收完毕后将数据进行串转并,然后发送给控制模块
uart_tx:
串口发送模块接收到uart_rx模块传来的数据,将数据进行
并转串
,通过tx端口发送出去四丶发送模块 1.代码 uart_tx.v
module uart_tx (
inputwireclk,
inputwirerst_n,
inputwire [7:0]din,
inputwiredin_vld,
output regtx
);
//定义一个寄存器来锁存 din_vld 时的din
reg [9:0]data;
//波特率计数器
reg [8:0]cnt_bps;
wireadd_cnt_bps;
wireend_cnt_bps;
//比特计数器
reg [4:0]cnt_bit;
wireadd_cnt_bit;
wireend_cnt_bit;
regflag;
//计数器开启标志位parameterBPS_115200=434;
//发送一bit数据需要的周期数//data
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data<=0;
end
else if(din_vld) begin
data<={1'b1,din,1'b0};
//拼接起始位和停止位
end
else
data<=data;
end//发送数据 tx
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx<=1'b1;
end
else if(cnt_bps==1) begin
tx<=data[cnt_bit];
end
end//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag<=0;
end
else if(din_vld) begin
flag<=1;
end
else if(end_cnt_bit) begin//发送完成关闭计数器
flag<=0;
end
else
flag<=flag;
end//cnt_bps
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bps<=0;
end
else if(add_cnt_bps) begin
if (end_cnt_bps) begin
cnt_bps<=0;
end
else
cnt_bps<=cnt_bps+1;
end
endassign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;
//cnt_bit
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bit<=0;
end
else if(add_cnt_bit) begin
if (end_cnt_bit) begin
cnt_bit<=0;
end
else
cnt_bit<=cnt_bit+1;
end
endassign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==9;
endmodule //uart_tx
2.仿真 testbench
`timescale 1ns/1ns
module tb_uart_tx ();
regclk;
regrst_n;
reg [7:0]din;
regdin_vld;
wiretx;
parameterCYCLE=20;
uart_tx uart_tx(
.clk(clk),
.rst_n(rst_n),
.din(din),
.din_vld(din_vld),
.tx(tx)
);
always #(CYCLE/2)clk=~clk;
initial begin
clk=1;
rst_n=1;
#(CYCLE*10);
rst_n=0;
din_vld=0;
#(CYCLE*10);
rst_n=1;
din=8'b1001_0101;
din_vld=1;
#CYCLE;
din_vld=0;
#(CYCLE*434*12);
$stop;
endendmodule //tb_uart_tx
如下图所示:
一开始我们进行
复位之后
,用仿真模拟了uart_rx接收模块,把din
(也就是本应该在uart_tx模块进行串转并)和din_vld
信号给uart_tx模块,检测到din_vld拉高
,data锁存
住din,同时开启波特率计数器,从起始位开始发送,直到停止位文章图片
因为我们仿真文件给din赋值为8’b1001_0101,在uart_tx模块中用data
拼接
起始位和停止位,拼接之后的data为10位的1_1001_0101_0仿真时序图中可以看到
tx端口为0101010011
,按顺序输出文章图片
五丶接收模块 1.代码 uart_rx:
module uart_rx (
inputwireclk,
inputwirerst_n,
inputwirerx,
output reg[7:0]dout,//由接收到的数据进行并转串输出给uart_tx模块
output regdout_vld //接收完成标志位
);
regflag;
//rx下降沿来临flag拉高
reg [9:0]dout_r;
//锁存rx接收到的数据(从起始位 到数据为 到停止位)regrx_r0;
//同步
regrx_r1;
//打拍
wirenedge;
//下降沿检测//波特率计数器
reg [8:0]cnt_bps;
wireadd_cnt_bps;
wireend_cnt_bps;
//bit计数器
reg [3:0]cnt_bit;
wireadd_cnt_bit;
wireend_cnt_bit;
parameterBPS_115200 = 434;
//同步
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_r0<=1;
end
else
rx_r0<=rx;
end
//打拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_r1<=1;
end
else
rx_r1<=rx_r0;
endassign nedeg=~rx_r0 & rx_r1;
//下降沿检测,起始位为低电平//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag<=0;
end
else if(nedeg) begin
flag<=1;
end
else if(end_cnt_bit) begin
flag<=0;
end
end//cnt_bps
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bps<=0;
end
else if(add_cnt_bps) begin
if (end_cnt_bps) begin
cnt_bps<=0;
end
else
cnt_bps<=cnt_bps+1;
end
endassign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;
//cnt_bit
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bit<=0;
end
else if(add_cnt_bit) begin
if (end_cnt_bit) begin
cnt_bit<=0;
end
else
cnt_bit<=cnt_bit+1;
end
endassign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==10 -1;
//dout_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout_r<=0;
end
else if(cnt_bps==(BPS_115200>>1)) begin
dout_r[cnt_bit]<=rx_r0;
end
end//dout
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout<=0;
end
else if(end_cnt_bit) begin
dout<=dout_r[8:1];
//只用把数据位传给uart_tx模块,所以取中间的8位
end
end//dout_vld
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout_vld<=0;
end
else if(end_cnt_bit) begin
dout_vld<=1;
//给发送模块的数据有效信号
end
else
dout_vld<=0;
end
endmodule //uart_rx
2.仿真 testbench
`timescale 1ns/1ns
module tb_uart_rx ();
regclk;
regrst_n;
regrx;
wire[7:0]dout;
wiredout_vld;
integer i;
parameterCYCLE=20;
uart_rx uart_rx(
.clk(clk),
.rst_n(rst_n),
.dout(dout),
.dout_vld(dout_vld),
.rx(rx)
);
always #(CYCLE/2)clk=~clk;
initial begin
clk=1;
rst_n=1;
#(CYCLE*10);
rst_n=0;
rx=1;
#(CYCLE*10);
rst_n=1;
rx=0;
//起始位
#(CYCLE*434);
for (i = 0;
i<8;
i=i+1) begin
#(CYCLE*434);
rx={$random}%2;
//产生0~1的随机数
endrx=1;
//停止位#(CYCLE*1000);
$stop;
endendmodule
结果如下图所示:
我们在接收模块需要把接收到的串行数据转换成
并行数据
再传给uart_tx模块空闲状态的时候,rx处于高电平状态,我们在仿真文件中模拟rx拉低,也就是接收到了起始位,波特率计数器(add_cnt_bps)开启
文章图片
rx接收到
停止位
,也就是一共接收了10bit(1bit起始位+8bit数据位+1bit停止位),此时bit计数器拉低
(end_cnt_bit),并行数据dout锁存
,拉高一个周期的数据有效信号
(dout_vld)文章图片
六丶顶层模块 1.代码 uart.v
module uart (
inputwireclk,
inputwirerst_n,
inputwirerx,//接收
output wiretx//发送
);
wire [7:0]dout;
wiredout_vld;
uart_txu_uart_tx(//将发送的并行数据转换成串行数据
.clk(clk),
.rst_n(rst_n),
.tx(tx),//串行数据
.din(dout),//并行数据
.din_vld(dout_vld)
);
uart_rxu_uarr_rx(//将接收到的串行数据转换成并行数据
.clk(clk),
.rst_n(rst_n),
.rx(rx),//串行数据
.dout(dout),//并行数据
.dout_vld(dout_vld)
);
endmodule //uart
2.模块原理图
文章图片
3.仿真 对于顶层的仿真,我们需要模拟出一个上位机来给我们的FPGA发送数据,
所以在例化模块的时候,我们首先例化一个uart_tx当作上位机,来给我们的从机发送数据,然后例化一个uart模块当作从机,也就是例化顶层模块
思路清晰了,进行testbench的编写
`timescale 1ns/1ns
module tb_uart ();
regclk;
regrst_n;
reg [7:0]din;
regdin_vld;
wiretx_r;
//用来连接上位机的tx和从机的rx
wirerx;
parameterCYCLE=20;
//例化从机(顶层模块,包含了一个uart_rx和一个uart_tx)
uart uart(
.clk(clk),
.rst_n(rst_n),
.rx(tx_r),//接收
.tx(tx)//发送
);
//例化上位机(用来给从机发送数据)
uart_tx uart_tx(
.clk(clk),
.rst_n(rst_n),
.din(din),
.din_vld(din_vld),
.tx(tx_r)
);
always #(CYCLE/2)clk=~clk;
initial begin
clk=1;
rst_n=1;
#(CYCLE*10);
rst_n=0;
din_vld=0;
#(CYCLE*10);
rst_n=1;
din=8'b1001_0101;
din_vld=1;
#CYCLE;
din_vld=0;
#(CYCLE*434*22);
$stop;
endendmodule //tb_uart_tx
首先,我们模拟上位机发送数据,在仿真文件中
拉高dout_vld
,并给dout
一个8位的数据1001_0101,经过拼接起始位和停止位,data锁存这一帧的数据:1_1001_0101_0文章图片
放大观察细节,在时钟上升沿检测到cnt_bps为1,则把data的第0位给tx发送
文章图片
对照uart_tx的这一部分代码来理解
文章图片
发送了起始位之后,从机(FPGA)收到起始位
文章图片
下图 第3步 从机的接收模块接收完成之后,给自己的发送模块 传送并行数据
dout
以及数据有效信号dout_vld
文章图片
发送模块接收到数据有效信号dout_vld拉高,将数据缓存,并开启计数器(意味着开始传数据,!!!从起始位开始,这里缓存数据也要拼接起始位和停止位)
文章图片
之后就跟上位机发送数据一模一样了
七丶上板验证 【FPGA|【FPGA】UART串口通信】管脚绑定
文章图片
本次实验的结果:上位机(PC)给我们的从机(FPGA)发送数据之后,从机收到数据并将数据发送给上位机,如图显示的一样
下一章,我们将使用FIFO作为数据缓存单元,完成数据回环
文章图片
八丶源码 https://github.com/xuranww/uart.git
参考文章:
1.https://blog.csdn.net/Rocher_22/article/details/116590629
2.https://blog.csdn.net/ybhuangfugui/article/details/109465401
- 本文采用的比特率是115200bps,也就是1s传输115200bit的数据,
且我们使用的时钟是20MHz
计算传输一次需要的时间:
计数时间 = 1_000_000_000ns/115200 = 8680ns
50MHz的时钟周期为20ns,所以计数传输一个比特的次数为8680 / 20 = 434 ??
推荐阅读
- FPGA自学|FPGA的I2C的原理及应用(含有源码)
- 基于FPGA的串口指令帧接收与解析的verilog代码
- FPGA|【FPGA】UART串口通信---基于FIFO
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Edgecapture
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Dualedge
- HDLbits|Circuits--Sequential Logic--Shift Registers--Lfsr32
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Exams/2014 q4a
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Edgedetect2
- FPGA学习笔记|FPGA学习笔记——知识点总结