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”时,发送完数据且准备更新 QSPIif (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);//clearif(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中。

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode(40)同构字符串【哈希表】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 同构字符串 1.题目 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0…

在idea中写sql语句,向数据库添加数据时,添加的字符串却显示???,解决方法

这是字符编码的问题 如何解决&#xff1a; 在idea的配置数据库的地方修改下边&#xff1a;mysql8版本和5版本差距不大。 在URL后加?useUnicodetrue&characterEncodingUTF8 例如 原来&#xff1a;String url “jdbc:mysql://localhost:3306/stu”; 改变后&#xff1a;St…

ssh连接docker容器处理备忘

1、查看容器ip&#xff0c;记下来之后要用 docker inspect elastic | grep IPAddress 2、使用root进入docker容器 docker exec -it -u root elastic /bin/bash 3、安装openssh #更新apt apt-get update#安装ssh client apt-get install openssh-client#安装ssh server apt-…

ubuntu配置ssh

本教程中的涉及路径的所有命令都是在root用户下的&#xff0c;读者可将路径中的/root更改为/home/用户名 1、重置密码 新安装的系统需要在服务器控制台点击“重置密码”&#xff0c;为root用户设置一个密码 ————————————————————————————————…

分享:大数据方向学生学徒参与条件

学生学徒制的实施旨在解决当前新技术企业招聘技能人才难和青年就业难的结构性矛盾&#xff0c;通过生态链链主企业携手院校共同解决毕业年度学生就业问题&#xff0c;按照学生个人意愿&#xff0c;建立以就业导向的学生学徒制关系&#xff0c;签订学徒培养协议确定学生就业岗位…

每日一练2023.11.30——谁先倒【PTA】

题目链接&#xff1a;谁先倒 题目要求&#xff1a; 划拳是古老中国酒文化的一个有趣的组成部分。酒桌上两人划拳的方法为&#xff1a;每人口中喊出一个数字&#xff0c;同时用手比划出一个数字。如果谁比划出的数字正好等于两人喊出的数字之和&#xff0c;谁就输了&#xff0…

快速排序算法的代码及算法思想

快速排序&#xff08;Quick Sort&#xff09;是一种常用的排序算法&#xff0c;他的时间复杂度为O(nlogn) 算法思想: 通过一趟排序将待排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另一部分的所有数据小&#xff0c;然后再对这两部分数据分别进行快速排…

换抵挡装置(按位运算符的运用)

给出两个长度分别为n1&#xff0c;n2&#xff08;n1 n2 <32)且每列高度只为1或2的长条&#xff08;保证高度为1的地方水平上一致&#xff09;。需要将它们放入一个高度为3的容器长度&#xff0c;问能够容纳它们的最短容器长度 用手画的 本来是n1&#xff0c;n2 < 100的…

[操作系统] 文件管理

文章目录 5.1 磁盘调度算法1. 先来先服务算法( First Come First Served, FCFS) 算法2. 最短寻道时间优先算法( Shortest Seek Time First, SSTF) 算法3. 扫描算法( SCAN ) 算法4. 循环扫描算法( Circular Scan, CSCAN ) 算法5. LOOK 与 CLOOK 算法 5.2 进程写文件时&#xff0…

全汉电源SN生产日期解读

新买了一个全汉的电脑电源&#xff0c;SN&#xff1a;WZ3191900030&#xff0c;看了几次没想明白&#xff0c;最后估计SN是2023年19周这样来记录日期的。问了一下京东全汉客服&#xff0c;果然就是这样的。那大家如果在闲鱼上看到全汉电源&#xff0c;就知道它的生产日期了。

键入网址到网页显示,期间发生了什么?(计算机网络)

浏览器首先会对URL进行解析 下面以http://www.server.com/dir1/file1.html为例 当没有路径名时&#xff0c;就代表访问根目录下事先设置的默认文件&#xff0c;也就是 /index.html 或者 /default.html 对URL进行解析之后&#xff0c;浏览器确定了 Web 服务器和文件名&#x…

C语言错误处理之 “<errno.h>与<error.h>”

目录 前言 错误号处理方式 errno.h头文件 error.h头文件 参数解释&#xff1a; 关于的”__attribute__“解释&#xff1a; 关于“属性”的解释&#xff1a; 实例一&#xff1a; 实例二&#xff1a; error.h与errno.h的区别 补充内容&#xff1a; 前言 在开始学习…