TCP解帧解码、并发送有效数据到FPGA

TCP解帧解码、并发送有效数据到FPGA

工程的功能:使用TCP协议接收到网络调试助手发来的指令,将指令进行解帧,提取出帧头、有限数据、帧尾;再将有效数据发送到FPGA端的BRAM上,实现信息传递。

参考:正点原子启明星ZYNQ之嵌入式SDK开发指南_V2.0:第三十九章 基于 TCP 协议的远程更新 QSPI Flash 实验 和 第十五章 基于 BRAM 的 PS 和 PL 的数据交互

TCP接收、解帧功能的实现

在正点原子提供的“基于 TCP 协议的远程更新 QSPI Flash 实验”例程中,是使用TCP协议实现远程更新 QSPI 的功能。在本项目中,将其改为接收并且解帧的功能。

  • 如何实现?

先分析一下正点原子的源代码:

在“qspi_remote_update.c”代码中,以下这段代码:

//接收回调函数
static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    struct pbuf *q;

    if (!p)
    {
        tcp_close(tpcb);
        tcp_recv(tpcb, NULL);
        xil_printf("tcp connection closed\r\n");
        return ERR_OK;
    }
    q = p;

    //当接收到的数据长度为 6 且内容为“update”时,发送完数据且准备更新 QSPI
    if (q->tot_len == 6 && !(memcmp("update", p->payload, 6)))
    {
        start_update_flag = 1;
        sent_msg("\r\nStart QSPI Update\r\n");//向网络调试助手发送数据
    }
    //当接收到的数据长度为 5 且内容为“clear”时,清除先前发送的数据
    else if (q->tot_len == 5 && !(memcmp("clear", p->payload, 5)))
    {
        start_update_flag = 0;
        total_bytes = 0;
        sent_msg("Clear received data\r\n");
        xil_printf("Clear received data\r\n");
    }
    //对于除此之外接收到的信息,将写入到 rxbuffer中,rxbuffer是一个大小为 MAX_FLASH_LEN 的数组,用于存放发送方发送的 BOOT.bin 文件数据
    else {
    	xil_printf("tot_len=%d    len=%d\r\n",q->tot_len,q->len);

        while (q->tot_len != q->len)
        {
        	//tot_len:表示客户端发送数据的总字节数
        	//len:表示服务器端接收客户端发过来的有效字节数
        	//字符串复制函数。从q->payload中复制q->len个字节到&  rxbuffer[total_bytes]中
            memcpy(&rxbuffer[total_bytes], q->payload, q->len);
            total_bytes += q->len;//更新总字节数
            q = q->next;

        }

        memcpy(&rxbuffer[total_bytes], q->payload, q->len);
        total_bytes += q->len;

    }
    tcp_recved(tpcb, p->tot_len);//当程序处理完数据后一定要调用这个函数,通知内核更新接收窗口
    pbuf_free(p);


    return ERR_OK;
}

代码中我加的注释很详细,这里再简单分析一下。在这之前的代码中,ZYNQ将接收到的信息(BOOT.bin文件)写入到了QSPI中。

上面的这段代码是一个判断:

  • 当接收数据长度为 6 且内容为“update”时,发送完数据且准备更新 QSPI;

  • 当接收到的数据长度为 5 且内容为“clear”时,清除先前发送的数据;

  • 除此之接收的信息,一律全写入rxbuffer中。这个rxbuffer就是用于存储BOOT.bin文件的。

所以,我们可以这样改:前面的两个判断全部不要了,所有来的信息我全部都接收,存入rxbuffer中,之后再来判断。

//接收回调函数
static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    struct pbuf *q;

    if (!p)
    {
        tcp_close(tpcb);
        tcp_recv(tpcb, NULL);
        xil_printf("tcp connection closed\r\n");
        return ERR_OK;
    }
    q = p;
    
   	receive_flag = 1;	//receive_flag是接收数据的标志位
    //接收到的信息,将写入到 rxbuffer 中,rxbuffer是一个大小为 MAX_FLASH_LEN 的数组
    while(q->tot_len != q->len)	//tot_len == len 时,表明已经传输到最后一个了。
    {
    	xil_printf("tot_len=%d    len=%d\r\n",q->tot_len,q->len);
    	//tot_len:表示客户端发送数据的总字节数
    	//len:表示服务器端接收客户端发过来的有效字节数
    	//memcpy:字符串复制函数。从q->payload中复制q->len个字节到&  rxbuffer[total_bytes]中
    	memcpy(&rxbuffer[total_bytes], q->payload, q->len);
    	total_bytes += q->len;//更新总字节数
    	q = q->next;
    }
    memcpy(&rxbuffer[total_bytes], q->payload, q->len);  //对最后一个进行接收
    total_bytes += q->len;
    
    tcp_recved(tpcb, p->tot_len);//当程序处理完数据后一定要调用这个函数,通知内核更新接收窗口
    pbuf_free(p);

    return ERR_OK;
}

改为以上代码,这样,不管网络调试助手发送什么数据,我能全部接收,并存入rxbuffer中,方便后面的解析。

如何解帧?

关于帧头、帧尾、有效数据的概念,这里不再赘述。总之,我们要实现的功能是:只有按照特定的帧头、帧尾发送数据,这个数据才是有效的,才能被我使用;按照其他格式发送的数据一律无效

假设我的帧头是:AAAA5555

帧尾是:5555AAAA

需要发送的有效数据是8个字节、32位(如 12345678)

    if(receive_flag == 1)
    {
    	sent_msg("TCP recv\r\n");
    	receive_flag = 0;
    	//帧头
    	frame_header = (rxbuffer[0]<<24)+(rxbuffer[1]<<16)+(rxbuffer[2]<<8)+rxbuffer[3];

    	xil_printf("rxbuffer[0] = %x\r\n",rxbuffer[0]);
    	xil_printf("rxbuffer[1] = %x\r\n",rxbuffer[1]);
    	xil_printf("rxbuffer[2] = %x\r\n",rxbuffer[2]);
    	xil_printf("rxbuffer[3] = %x\r\n",rxbuffer[3]);
    	xil_printf("rxbuffer[4] = %x\r\n",rxbuffer[4]);
    	xil_printf("rxbuffer[5] = %x\r\n",rxbuffer[5]);
    	xil_printf("rxbuffer[6] = %x\r\n",rxbuffer[6]);
    	xil_printf("rxbuffer[7] = %x\r\n",rxbuffer[7]);
    	xil_printf("rxbuffer[8] = %x\r\n",rxbuffer[8]);
    	xil_printf("rxbuffer[9] = %x\r\n",rxbuffer[9]);
    	xil_printf("rxbuffer[10] = %x\r\n",rxbuffer[10]);
    	xil_printf("rxbuffer[11] = %x\r\n",rxbuffer[11]);

    	xil_printf("frame_header = %x\r\n",frame_header);
    	if(frame_header == 0xAAAA5555)
    	{
    		//帧尾
    		frame_end = (rxbuffer[8]<<24)+(rxbuffer[9]<<16)+(rxbuffer[10]<<8)+rxbuffer[11];
    		xil_printf("frame_end = %x\r\n",frame_end);
    		if(frame_end == 0x5555AAAA)
    		{
    			//有效数据
    			vaild_data = (rxbuffer[4]<<24)+(rxbuffer[5]<<16)+(rxbuffer[6]<<8)+rxbuffer[7];
    			xil_printf("vaild_data = %x\r\n",vaild_data);
                total_bytes = 0;	//如果帧头,帧尾都正确,指针清零
    		}
    		else
    		{
    			xil_printf("frame_end detection is error!\r\n");
    			total_bytes = 0;	//如果帧尾不正确,指针清零
    		}
    	}
    	else
    	{
    		xil_printf("frame_header detection is error!\r\n");
    		total_bytes = 0;	//如果帧头不正确,指针清零
    	}
    }

代码解释:在代码最前面,定义了 u32 frame_header = 0; u32 frame_end = 0; u32 vaild_data = 0;

用来存储帧头、帧尾和有效数据。

frame_header = (rxbuffer[0]<<24)+(rxbuffer[1]<<16)+(rxbuffer[2]<<8)+rxbuffer[3];

解释:假如我发送的信息是“AAAA5555123456785555AAAA”那么,rxbuffer[0]是“AA”、rxbuffer[1]是“AA”、rxbuffer[2]是“55”依此类推。rxbuffer[i]的数据是8位的,需要将它们拼成32位的帧头、帧尾、有效数据。这行代码就实现了这个功能。

中间的xil_printf函数可以帮助理解代码。

并且如果帧头或帧尾不正确,则将total_bytes清零,即把rxbuffer清零,这组数据无效。

PS_PL传输数据

按照正点原子 第十五章 基于 BRAM 的 PS 和 PL 的数据交互 实验,硬件工作不再赘述。

将软件的代码移植到qspi_remote_update.c文件中即可。

#define PL_BRAM_START			PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET	//RAM读开始寄存器地址
#define PL_BRAM_START_ADDR		PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET	//RAM起始寄存器地址
#define PL_BRAM_LEN				PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET	//PL读 RAM的深度
#define	PL_BRAM_BASE			XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR	//PL_RAM_RD基地址

#define START_ADDR			0	//RAM起始地址范围:0~1023
#define BRAM_DATA_BYTE		4	//BRAM数据字节个数

char ch_data[1024];		//写入BRAM的字符数组
int  ch_data_len;		//写入BRAM的字符个数

......省略中间的代码......
    
//接收回调函数
static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    struct pbuf *q;
    u16 i;
    u16 j;
	int wr_cnt = 0;
	int read_data = 0;
	int ch_data_len = 8;

    if (!p)
    {
        tcp_close(tpcb);
        tcp_recv(tpcb, NULL);
        xil_printf("tcp connection closed\r\n");
        return ERR_OK;
    }
    q = p;

    receive_flag = 1;

    //接收到的信息,将写入到 rxbuffer中,rxbuffer是一个大小为 MAX_FLASH_LEN 的数组
    while(q->tot_len != q->len)	//tot_len == len 时,表明已经传输到最后一个了。
    {
    	xil_printf("tot_len=%d    len=%d\r\n",q->tot_len,q->len);
    	//tot_len:表示客户端发送数据的总字节数
    	//len:表示服务器端接收客户端发过来的有效字节数
    	//memcpy:字符串复制函数。从q->payload中复制q->len个字节到&  rxbuffer[total_bytes]中
    	memcpy(&rxbuffer[total_bytes], q->payload, q->len);
    	total_bytes += q->len;//更新总字节数
    	q = q->next;
    }
    memcpy(&rxbuffer[total_bytes], q->payload, q->len);  //对最后一个进行接收
    total_bytes += q->len;
    for(j=0;j<30;j++);
    Xil_Out32(XPAR_BRAM_0_BASEADDR,0);//clear

    if(receive_flag == 1)
    {
    	sent_msg("TCP recv\r\n");
    	receive_flag = 0;
    	//帧头
    	frame_header = (rxbuffer[0]<<24)+(rxbuffer[1]<<16)+(rxbuffer[2]<<8)+rxbuffer[3];
    	xil_printf("frame_header = %x\r\n",frame_header);
    	if(frame_header == 0xAAAA5555)
    	{
    		//帧尾
    		frame_end = (rxbuffer[8]<<24)+(rxbuffer[9]<<16)+(rxbuffer[10]<<8)+rxbuffer[11];
    		xil_printf("frame_end = %x\r\n",frame_end);
    		if(frame_end == 0x5555AAAA)
    		{
    			//有效数据
    			vaild_data = (rxbuffer[4]<<24)+(rxbuffer[5]<<16)+(rxbuffer[6]<<8)+rxbuffer[7];
    			xil_printf("vaild_data = %x\r\n",vaild_data);

    			//将有效数据写入BRAM,每次循环向 BRAM中写入 1 个字符;vaild_data的长度是4个字节
    			for(i = 0; i<(START_ADDR + ch_data_len)*BRAM_DATA_BYTE; i+=BRAM_DATA_BYTE)
    			{
    				XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,vaild_data[wr_cnt]);
    				wr_cnt++;
    			}
    			//配置PL_BRAM_RD起始地址
    			PL_BRAM_RD_mWriteReg(PL_BRAM_BASE,PL_BRAM_START_ADDR,START_ADDR*BRAM_DATA_BYTE);
    			//配置PL_BRAM_RD长度
    			PL_BRAM_RD_mWriteReg(PL_BRAM_BASE,PL_BRAM_LEN,ch_data_len*BRAM_DATA_BYTE);
    			//配置PL_BRAM_RD开始读信号,产生一个上升沿
    			PL_BRAM_RD_mWriteReg(PL_BRAM_BASE,PL_BRAM_START,1);
    			PL_BRAM_RD_mWriteReg(PL_BRAM_BASE,PL_BRAM_START,0);
                
                total_bytes = 0;	//如果帧头,帧尾都正确,指针清零
    		}
    		else
    		{
    			xil_printf("frame_end detection is error!\r\n");
    			total_bytes = 0;	//如果帧尾不正确,指针清零
    		}
    	}
    	else
    	{
    		xil_printf("frame_header detection is error!\r\n");
    		total_bytes = 0;	//如果帧头不正确,指针清零
    	}
    }
    tcp_recved(tpcb, p->tot_len);//当程序处理完数据后一定要调用这个函数,通知内核更新接收窗口
    pbuf_free(p);

    return ERR_OK;
}

......省略后面的代码......
    

实验结果

在这里插入图片描述

上面是UART串口打印出的数据,上面显示了帧头:AAAA5555;帧尾:5555AAAA;有效数据:12345678

在这里插入图片描述

上面是FPGA ILA抓取的波形。可以看到,有效数据12345678被存入到了BRAM中。

以上代码只是一个雏形/模板,根据具体情况要更改很多的地方。如果这篇文章和正点原子的两个例程学明白了,那就自然会变通了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/205980.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用canvas实现代码雨高级升阶版【附带源码和使用方法】

文章目录 前言基本绿色的彩色版本飘散雪花状后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;前端面试 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&…

ProgrammingError: nan can not be used with MySQL

该错误怎么发生的&#xff1f; 我们先在本地创建测试表&#xff1a; CREATE TABLE users_test (id int NOT NULL AUTO_INCREMENT COMMENT 主键,trade_account varchar(50) DEFAULT NULL COMMENT 交易账号,username varchar(50) DEFAULT NULL,email varchar(100) DEFAULT NULL…

哪个软件消除笔好用?我来告诉你

全民自媒体时代&#xff0c;人人都是自媒体人&#xff0c;越来越多的人接触到修图&#xff0c;剪辑&#xff0c;制作&#xff0c;常常在社交媒体上分享美食制作教程&#xff0c;居家好物&#xff0c;影视混剪&#xff0c;小说解说等各种各样的精彩照片,但是在网上找的图片素材往…

数据收集与处理(爬虫技术)

文章目录 1 前言2 网络爬虫2.1 构造自己的Scrapy爬虫2.1.1 items.py2.1.2 spiders子目录2.1.3 pipelines.py 2.2 构造可接受参数的Scrapy爬虫2.3 运行Scrapy爬虫2.3.1 在命令行运行2.3.2 在程序中调用 2.4 运行Scrapy的一些要点 3 大规模非结构化数据的存储与分析4 全部代码 1 …

建文工程项目管理软件 SQL 注入漏洞复现

0x01 产品简介 建文工程管理软件是一个适用于工程投资领域的综合型的多方协作平台。 0x02 漏洞概述 建文工程项目管理软件BusinessManger.ashx、Desktop.ashx等接口处存在SQL注入漏洞&#xff0c;攻击者可通过该漏洞获取数据库中的信息&#xff08;例如&#xff0c;管理员后台…

TypeScript 5.3

导入属性 TypeScript 5.3支持导入属性提案的最新更新。 导入属性的一个用例是向运行库提供有关模块预期格式的信息。 // We only want this to be interpreted as JSON, // not a runnable/malicious JavaScript file with a .json extension. import obj from "./somet…

【综述+自动流量分析A】New Directions in Automated Traffic Analysis

文章目录 论文简介摘要存在的问题论文贡献1. 整体架构2. nPrint3. nPrintML4. 任务 总结论文内容工具数据集可读的引用文献笔记参考文献 论文简介 原文题目&#xff1a;New Directions in Automated Traffic Analysis 中文题目&#xff1a;自动流量分析的新方向 发表会议&#…

Java多线程-第20章

Java多线程-第20章 1.创建线程 Java是一种支持多线程编程的编程语言。多线程是指在同一程序中同时执行多个独立任务的能力。在Java中&#xff0c;线程是一种轻量级的子进程&#xff0c;它是程序中的最小执行单元。Java的多线程编程可以通过两种方式实现&#xff1a;继承Threa…

windows配置使用supervisor

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、使用步骤1.安装supervisor-win2.配置supervisord3.配置program4.启动supervisord.exe5.supervisorctl.exe管控 二、后台启动总结 前言 windows使用supervi…

PromptRank:使用Prompt进行无监督关键词提取

论文题目&#xff1a;PromptRank: Unsupervised Keyphrase Extraction Using Prompt   论文日期&#xff1a;2023/05/15(ACL 2023)   论文地址&#xff1a;https://arxiv.org/abs/2305.04490   GitHub地址&#xff1a;https://github.com/HLT-NLP/PromptRank 文章目录 Ab…

虽然在不同设备上会出现同样的原神错误代码9907,但解决办法因设备而异

你是不是很享受在原神(Genshin Impact)中的神奇旅程,但错误代码9907出现了?与PS4控制台中全面讨论PS4的错误CE-34878-0不同,本文关注的是原神错误本身。本文不仅讨论了这个错误背后的原因,还讨论了每种类型设备的具体解决方案。 在Microsoft Windows/PC上修复错误代码99…

NSSCTF第14页(2)

[UUCTF 2022 新生赛]ezpop 提示说看看反序列化字符串逃逸 PHP反序列化字符串逃逸_php反序列化逃逸-CSDN博客 php反序列化字符逃逸_php反序列化逃逸_Leekos的博客-CSDN博客 buuctf刷题9 (反序列化逃逸&shtml-SSI远程命令执行&idna与utf-8编码漏洞)_extract($_post);…

Linux 基本语句_12_信号

用途&#xff1a; 信号可以直接进行用户进程与内核进程之间的交互 特性&#xff1a; 对于一个进程&#xff0c;其可以注册或者不注册信号&#xff0c;不注册的信号&#xff0c;进程接受后会按默认功能处理&#xff0c;对于注册后的信号&#xff0c;进程会按自定义处理 自定义…

vue3 keep-alive页面切换报错:parentComponent.ctx.deactivate is not a function

问题&#xff1a; <router-view v-slot"{ Component }"><keep-alive ><component :is"Component" v-if"$route.meta.keepAlive" /></keep-alive><component :is"Component" v-if"!$route.meta.keepA…

FFmpeg之将视频转为16:9(横屏)或9:16(竖屏)(一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

全网最牛最全面的Jmeter接口测试:jmeter_逻辑控制器_事务控制器

事务&#xff1a; 性能测试中&#xff0c;事务指的是从端到端&#xff0c;一个完整的操作过程&#xff0c;比如一次登录、一次 筛选条件查询&#xff0c;一次支付等&#xff1b;技术上讲&#xff1a;事务就是由1个或多个请求组成的 事务控制器 事务控制器类似简单控制器&…

Selenium——isDisplayed()、isEnabled()、isSelected()

判断页面是否存在某元素 Selenium没有直接提供判断是否存在的方法&#xff0c;可以使用findElements返回的数量判断&#xff1b;或者判断findElement是否抛出异常 webDriver.findElements(By.xpath("(//div[classel-button-group]//button)[1]")).size()isDisplaye…

分享72个简历竞聘PPT,总有一款适合您

分享72个简历竞聘PPT&#xff0c;总有一款适合您 72个简历竞聘PPT下载链接&#xff1a;https://pan.baidu.com/s/1EGqu8ufs8nh45NliNniWuQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整…

BUUCTF john-in-the-middle 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 注意&#xff1a;得到的 flag 请包上 flag{} 提交 密文&#xff1a; 下载附件&#xff0c;解压得到john-in-the-middle.pcap文件。 解题思路&#xff1a; 1、双击文件&#xff0c;打开wireshark。 看到很多http流…

Go 语言输出文本函数详解

Go语言拥有三个用于输出文本的函数&#xff1a; Print()Println()Printf() Print() 函数以其默认格式打印其参数。 示例 打印 i 和 j 的值&#xff1a; package mainimport "fmt"func main() {var i, j string "Hello", "World"fmt.Print(…