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