热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于FPGA的任意字节数的串口接收(含源码工程)

原标题:基于FPGA的任意字节数的串口接收(含源码工程)1、概述在这篇文章:基于FPGA的任意字节数的串口发送(含源码工程)中实现了基于FPGA的任意字

原标题:基于FPGA的任意字节数的串口接收(含源码工程)

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的单字节接收,无奇偶校验。


// *******************************************************************************************************
// ** 作者 : 孤独的单刀
// ** 邮箱 : zachary_wu93@163.com
// ** 博客 : 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 文章来源地址14908.html
wire neg_uart_rxd ; //接收数据线的下降沿
assign neg_uart_rxd = uart_rx_d3 & (~uart_rx_d2); //捕获数据线的下降沿,用来标志数据传输开始
//将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿
always@(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个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always@(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
//当数据传输到终止位时,拉高传输完成标志位,并将数据输出
always@(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; //仅仅拉高一个时钟周期
uwww.yii666.comart_rx_data <= uart_rx_data;
end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
always@(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 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 www.yii666.com //不在接收状态
bit_cnt <= 4'd0; //清零
clk_cnt <= 32'd0; //清零
end
end
//在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器
always@(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是有效数据

完整代码如下:


// *****************************************************************************************************************************
// ** 作者 : 孤独的单刀
// ** 邮箱 : zachary_wu93@163.com
// ** 博客 : 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。 文章来源地址14908.html
// *****************************************************************************************************************************
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 , //系统复位,低电平有效
//用户接口
outp文章来源站点https://www.yii666.com/ut [(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'h818'h09,8'h638'h0d,8'h8d

  • 接收到了3个数据16'h812416'h630916'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(先接收低字节,后接收高字节):

测试结果与预期一致。

源码工程点击这里下载(提取码:oc60)




  • ?博客主页:wuzhikai.blog.csdn.net

  • ?本文由孤独的单刀原创,首发于CSDN平台?

  • ?您有任何问题,都可以在评论区和我交流?!

  • ?创作不易,您的支持是我持续更新的最大动力!如果本文对您有帮助,还请多多点赞?、评论?和收藏⭐!



来源于:基于FPGA的任意字节数的串口接收(含源码工程)


推荐阅读
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • PHPMailer邮件类邮件发送功能的使用教学及注意事项
    本文介绍了使用国外开源码PHPMailer邮件类实现邮件发送功能的简单教学,同时提供了一些注意事项。文章涵盖了字符集设置、发送HTML格式邮件、群发邮件以及避免类的重定义等方面的内容。此外,还提供了一些与PHP相关的资源和服务,如传奇手游游戏源码下载、vscode字体调整、数据恢复、Ubuntu实验环境搭建、北京爬虫市场、进阶PHP和SEO人员需注意的内容。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Givenasinglylinkedlist,returnarandomnode'svaluefromthelinkedlist.Eachnodemusthavethe s ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有