当前位置:网站首页>基于FPGA的任意字节数的串口接收(含源码工程)
基于FPGA的任意字节数的串口接收(含源码工程)
2022-08-09 23:33:00 【孤独的单刀】
1、概述
在这篇文章:基于FPGA的任意字节数的串口发送(含源码工程)中实现了基于FPGA的任意字节数的串口发送,那么对应的,这一篇文章将分享给大家如何实现任意字节的FPGA接收方法。
在这篇文章:串口(UART)的FPGA实现(含源码工程),实现了基于FPGA的串口接收驱动。利用接收驱动可以实现 起始位1bit+数据位8bit+停止位1bit 共10bit的单字节接收。
但是在实际应用过程中有时候需要一次性接收多个字节的数据。比如,一次性通过UART接收5个字节的数据,再将其组合成一个位宽为【39:0】的数据。诚然,可以直接更改此文中的串口接收驱动,使其变成 起始位1bit+数据位40bit+停止位1bit 共42bit的多字节传输。这种方法理论上是可行的,因为UART协议并没有规定你一次要发送、接收多个少bit的数据,既然能接收8个bit,那同样能接收40个bit。
但是很不幸,实际上基本行不通,因为通用的绝大部分上位机软件都不支持一次解析40bit的数据位(如果你自己写上位机就当我没说)。
所以只能想点其他办法,比如:写个逻辑,多次调用8bit即单字节的串口接收驱动,那么40bit的数据就调用5次,也就是5个 起始位1bit+数据位8bit+停止位1bit共10bit的单字节 分别接收,再组合成一个40bit的数据就可以了。
2、串口接收驱动
请参考:串口(UART)的FPGA实现(含源码工程),在此文详细介绍了串口的接收驱动。
以下代码可以实现 1bit+数据位8bit+停止位1bit 共10bit的单字节接收,无奇偶校验。
// *******************************************************************************************************
// ** 作者 : 孤独的单刀
// ** 邮箱 : [email protected]
// ** 博客 : https://blog.csdn.net/wuzhikaidetb
// ** 日期 : 2022/08/05
// ** 功能 : 1、基于FPGA的串口接收驱动模块;
// 2、可重新设置波特率BPS、主时钟CLK_FRE;
// 3、起始位1bit,数据位8bit,停止位1bit,无奇偶校验。
// *******************************************************************************************************
module uart_rx
#(
parameter integer BPS = 9_600 , //发送波特率
parameter integer CLK_FRE = 50_000_000 //输入时钟频率
)
(
//系统接口
input sys_clk , //50M系统时钟
input sys_rst_n , //系统复位
//UART接收线
input uart_rxd , //接收数据线
//用户接口
output reg uart_rx_done , //数据接收完成标志,当其为高电平时,代表接收数据有效
output reg [7:0] uart_rx_data //接收到的数据,在uart_rx_done为高电平时有效
);
//param define
localparam integer BPS_CNT = CLK_FRE / BPS; //根据波特率计算传输每个bit需要多个系统时钟
//reg define
reg uart_rx_d1 ; //寄存1拍
reg uart_rx_d2 ; //寄存2拍
reg uart_rx_d3 ; //寄存3拍
reg [31:0] clk_cnt ; //计数器,用于计数发送一个bit数据所需要的时钟数
reg [3:0] bit_cnt ; //bit计数器,标志当前发送了多少个bit
reg rx_en ; //接收标志信号,拉高代表接收过程正在进行
reg [7:0] uart_rx_data_reg; //接收数据寄存
//wire define
wire neg_uart_rxd ; //接收数据线的下降沿
assign neg_uart_rxd = uart_rx_d3 & (~uart_rx_d2); //捕获数据线的下降沿,用来标志数据传输开始
//将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿
[email protected](posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_d1 <= 1'b0;
uart_rx_d2 <= 1'b0;
uart_rx_d3 <= 1'b0;
end
else begin
uart_rx_d1 <= uart_rxd;
uart_rx_d2 <= uart_rx_d1;
uart_rx_d3 <= uart_rx_d2;
end
end
//捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
[email protected](posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_en <= 1'b0;
else begin
if(neg_uart_rxd )
rx_en <= 1'b1;
//接收完第9个数据(终止位)将传输开始标志位拉低,标志传输结束,判断高电平
else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'b1) && (uart_rx_d3 == 1'b1) )
rx_en <= 1'b0;
else
rx_en <= rx_en;
end
end
//当数据传输到终止位时,拉高传输完成标志位,并将数据输出
[email protected](posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_done <= 1'b0;
uart_rx_data <= 8'd0;
end
//结束接收后,将接收到的数据输出
else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'd1) && (uart_rx_d3 == 1'b1))begin
uart_rx_done <= 1'b1; //仅仅拉高一个时钟周期
uart_rx_data <= uart_rx_data_reg;
end
else begin
uart_rx_done <= 1'b0; //仅仅拉高一个时钟周期
uart_rx_data <= uart_rx_data;
end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
[email protected](posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
bit_cnt <= 4'd0;
clk_cnt <= 32'd0;
end
else if(rx_en)begin //在接收状态
if(clk_cnt < BPS_CNT - 1'b1)begin //一个bit数据没有接收完
clk_cnt <= clk_cnt + 1'b1; //时钟计数器+1
bit_cnt <= bit_cnt; //bit计数器不变
end
else begin //一个bit数据接收完了
clk_cnt <= 32'd0; //清空时钟计数器,重新开始计时
bit_cnt <= bit_cnt + 1'b1; //bit计数器+1,表示接收完了一个bit的数据
end
end
else begin //不在接收状态
bit_cnt <= 4'd0; //清零
clk_cnt <= 32'd0; //清零
end
end
//在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器
[email protected](posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_rx_data_reg <= 8'd0; //复位无接收数据
else if(rx_en) //处于接收状态
if(clk_cnt == BPS_CNT >> 1'b1) begin //传输过程正中(数据比较稳定)
case(bit_cnt) //根据位数决定接收的内容是什么
4'd1:uart_rx_data_reg[0] <= uart_rx_d3; //LSB最低位
4'd2:uart_rx_data_reg[1] <= uart_rx_d3; //
4'd3:uart_rx_data_reg[2] <= uart_rx_d3; //
4'd4:uart_rx_data_reg[3] <= uart_rx_d3; //
4'd5:uart_rx_data_reg[4] <= uart_rx_d3; //
4'd6:uart_rx_data_reg[5] <= uart_rx_d3; //
4'd7:uart_rx_data_reg[6] <= uart_rx_d3; //
4'd8:uart_rx_data_reg[7] <= uart_rx_d3; //MSB最高位
default:; //1和9分别是起始位和终止位,不需要接收
endcase
end
else //数据不一定稳定就不接收
uart_rx_data_reg <= uart_rx_data_reg;
else
uart_rx_data_reg <= 8'd0; //不处于接收状态
end
endmodule
3、任意字节接收的实现方法
串口接收驱动模块的对外端口如下:
其中uart_rx_done信号是关键。当一个字节的数据被按约定的串口协议接收成功后,该信号就会拉高一个时钟周期,表示一次接收结束。
可以每次在接收到一个字节后,将接收到的数据通过移位寄存起来,直到所有字节的数据都被成功接收。如下:
所以,多字节数据的接收逻辑:
- 1、用户通过UART数据多次发送单字节数据(比如5个)
- 2、根据uart_rx_done信号判断是否成功接收到一个信号,并用计数器记录此时接收的是第几个字节的数据,同时将其移位寄存起来
- 3、当所有单字节数据均被接收完毕后,拉高uart_bytes_vld,表示一次多字节接收结束,且此时的数据uart_bytes_data是有效数据
完整代码如下:
// *****************************************************************************************************************************
// ** 作者 : 孤独的单刀
// ** 邮箱 : [email protected]
// ** 博客 : https://blog.csdn.net/wuzhikaidetb
// ** 日期 : 2022/08/05
// ** 功能 : 1、基于FPGA的串口多字节接收模块;
// 2、可设置一次接收的字节数、波特率BPS、主时钟CLK_FRE;
// 3、UART协议设置为起始位1bit,数据位8bit,停止位1bit,无奇偶校验(不可在端口更改,只能更改发送驱动源码);
// 4、每接收到1次多字节后拉高指示信号一个周期,指示一次多字节接收结束;
// 5、数据接收顺序,先接收低字节、再接收高字节。如:第1次接收到8’h34,第2次接收到8’h12,则最终接收到的数据为16'h12_34。
// *****************************************************************************************************************************
module uart_bytes_rx
#(
parameter integer BYTES = 4 , //一次接收字节数,单字节8bit
parameter integer BPS = 9600 , //发送波特率
parameter integer CLK_FRE = 50_000_000 //输入时钟频率
)
(
//系统接口
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低电平有效
//用户接口
output [(BYTES * 8 - 1):0] uart_bytes_data , //接收到的多字节数据,在uart_bytes_vld为高电平时有效
output uart_bytes_vld , //成功发送所有字节数据后拉高1个时钟周期,代表此时接收的数据有效
//UART接收
input uart_rxd //UART发送数据线rx
);
//reg define
reg [(BYTES*8-1):0] uart_bytes_data_reg; //寄存接收到的多字节数据,先接收低字节,后接收高字节
reg uart_bytes_vld_reg; //高电平表示此时接收到的数据有效
reg [9:0] byte_cnt; //发送的字节个数计数(因为懒直接用10bit计数,最大可以表示1024BYTE,大概率不会溢出)
//wire define
wire [7:0] uart_sing_data; //接收的单个字节数据
wire uart_sing_done; //单个字节数据接收完毕信号
//对端口赋值
assign uart_bytes_data = uart_bytes_data_reg;
assign uart_bytes_vld = uart_bytes_vld_reg;
//分别接收各个字节的数据
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_bytes_data_reg <= 0;
else if(uart_sing_done)begin //接收到一个单字节则将数据右移8bit,实现最先接收的数据在低字节
if(BYTES == 1) //单字节就直接接收
uart_bytes_data_reg <= uart_sing_data;
else //多字节就移位接收
uart_bytes_data_reg <= {uart_sing_data,uart_bytes_data_reg[(BYTES*8-1)-:(BYTES-1)*8]};
end
else
uart_bytes_data_reg <= uart_bytes_data_reg;
end
//对接收的字节个数进行计数
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
byte_cnt <= 0;
else if(uart_sing_done && byte_cnt == BYTES - 1) //计数到了最大值则清零
byte_cnt <= 0;
else if(uart_sing_done) //发送完一个单字节则计数器+1
byte_cnt <= byte_cnt + 1'b1;
else
byte_cnt <= byte_cnt;
end
//所有数据接收完毕,拉高接收多字节数据有效信号
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_bytes_vld_reg <= 1'b0;
else if(uart_sing_done && byte_cnt == BYTES - 1) //所有单字节数据接收完毕
uart_bytes_vld_reg <= 1'b1;
else
uart_bytes_vld_reg <= 1'b0;
end
//例化串口接收驱动模块
uart_rx #(
.BPS (BPS ),
.CLK_FRE (CLK_FRE )
)
uart_rx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_rx_done (uart_sing_done ),
.uart_rx_data (uart_sing_data ),
.uart_rxd (uart_rxd )
);
endmodule
4、仿真
使用该模块接收数据3次,观测接收结果是否正常。分别使用单字节(8位)、双字节(16位)、5字节(40位)进行测试。
4.1、单字节仿真
模拟上位机按UART协议三次发送随机的单字节数据过来,观察能否正确接收。TB如下:
`timescale 1ns/1ns //定义时间刻度
module tb_uart_bytes_rx();
localparam integer BYTES = 1 ; //一次接收的字节个数
localparam integer BPS = 230400 ; //波特率
localparam integer CLK_FRE = 50_000_000 ; //系统频率50M
localparam integer CNT = 1000_000_000 / BPS ; //计算出传输每个bit所需要的时间,单位:ns
reg sys_clk ; //系统时钟
reg sys_rst_n ; //系统复位,低电平有效
reg uart_rxd ; //UART接收数据线
wire [(BYTES * 8 - 1):0] uart_bytes_data ; //接收到的多字节数据,在uart_bytes_vld为高电平时有效
wire uart_bytes_vld ; //当其为高电平时,代表此时接收到的多字节数据有效
initial begin
sys_clk <=1'b0;
sys_rst_n <=1'b0;
uart_rxd <=1'b1;
#20 //系统开始工作
sys_rst_n <=1'b1;
#3000
repeat(3) begin //重复生成8位随机数
rx_byte({$random} % 256); //
end
#60 $finish();
end
always #10 sys_clk=~sys_clk; //设置主时钟,20ns,50M
//定义任务,每次发送的数据10 位(起始位1+数据位8+停止位1)
task rx_byte(
input [7:0] data
);
integer i; //定义一个常量
//用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1
for(i=0; i<10; i=i+1) begin
case(i)
0: uart_rxd <= 1'b0; //起始位
1: uart_rxd <= data[0]; //LSB
2: uart_rxd <= data[1];
3: uart_rxd <= data[2];
4: uart_rxd <= data[3];
5: uart_rxd <= data[4];
6: uart_rxd <= data[5];
7: uart_rxd <= data[6];
8: uart_rxd <= data[7]; //MSB
9: uart_rxd <= 1'b1; //停止位
endcase
#(CNT+10); //每发送1 位数据延时(加10是为了减小误差)
end
endtask //任务结束
//例化多字节接收模块
uart_bytes_rx #(
.BYTES (BYTES ),
.BPS (BPS ),
.CLK_FRE (CLK_FRE )
)
uart_bytes_rx_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_bytes_data (uart_bytes_data ),
.uart_bytes_vld (uart_bytes_vld ),
.uart_rxd (uart_rxd )
);
endmodule
仿真结果如下:
- 模拟上位机调用了3次,分别发送数据8'h24,8'h81,8'h09
- 串口接收模块接收到了同样的3个数据8'h24,8'h81,8'h09
4.2、双字节仿真
TB基本不用修改,把BYTES这个参数改成2,并把repeat的次数改成6就行。仿真结果如下:
- 上位机分别发送了6个数据8'h24,8'h81,8'h09,8'h63,8'h0d,8'h8d
- 接收到了3个数据16'h8124,16'h6309,16'h8d0d
- 根据先接收低字节后高字节的原则,将6个单字节组合成了3个双字节接收
4.3、5字节仿真
TB基本不用修改,把BYTES这个参数改成5,并把repeat的次数改成10就行。仿真结果如下:
您照着上面的逻辑看看就行,我就不啰嗦了。
5、实测
下板实测,可以通过上位机发送数据到FPGA,同时使用在线逻辑仪signaltap II来观察接收的数据是否与发送一致。
5.1、单字节实测
上位机发送数据8'h55:
FPGA同样接收到数据8'h55:
测试结果与预期一致。
5.2、双字节实测
上位机发送数据8'h55、8'haa:
FPGA接收到数据16'haa55(先接收低字节,后接收高字节):
测试结果与预期一致。
5.3、5字节实测
上位机发送数据8'h9a、8'h78、8'h56、8'h34、8'h12:
FPGA接收到数据40'h123456789a(先接收低字节,后接收高字节):
测试结果与预期一致。
- 博客主页:wuzhikai.blog.csdn.net
- 本文由 孤独的单刀 原创,首发于CSDN平台
- 您有任何问题,都可以在评论区和我交流!
- 创作不易,您的支持是我持续更新的最大动力!如果本文对您有帮助,还请多多点赞、评论和收藏!
边栏推荐
猜你喜欢
随机推荐
共创 Ray 中文社区,Ray Forward Meetup 2022 直播邀你参加!
上交所实时行情文件汇总
Golden Warehouse Database KingbaseGIS User Manual (6.6. Geometric Object Verification Function, 6.7. Spatial Reference System Function)
【「收藏」Oracle 数据库安装】
AppUser object extension based on ABP
游泳馆系统次卡的设置有哪些细节?
防火墙之系统防护
Seq2Seq论文阅读笔记
【云原生】Kubernetes编排工具精讲
【问题解决】训练和验证准确率很高,但测试准确率很低
New window Display Agreement
Kubernetes服务接入Istio
NTP SERVICE TASK 在GWserver配置、启用NTP服务,为当前环境提供时钟同步服务,Client主机可以从该服务器同步时间。
基于ABP的AppUser对象扩展
[SUCTF 2019]CheckIn (.htaccess和.user.ini)
漫谈缺陷管理的自动化实践方案
JSP简介
【渗透工具】浏览器数据导出工具
Digital wallets, red sea ecological rapid introduction of small programs can help capture device entry wisdom
【集训DAY5】快速排序【模拟】【数学】