当前位置:   article > 正文

FPGA_adc采样并通过以太网发送到上位机_ps端发数据给上位机

ps端发数据给上位机

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本项目使用FPGA-ZYNQ-7020采集8通道ADC数据,通过以太网上传到上位机显示波形
PL部分包括数据的获取与处理,数据从PL传递到PS
PS部分包括以太网的发送,由PL到PS的中断触发
本文章不详细讲解背后原理 但保证每一步足够详细,跟着就能实现。
本人FPGA平台 大磊FPGA7020开发板 vivado2020.2 vitis2020.2 匿名上位机公开版


一、PL部分

该部分主要有 ① PL端的block design;② PL端IP-------axi_lite自定义 用于PL-PS端数据交互

1.PL端AXI_Lite IP自定义

打开vivado,新建工程 注意,此时无需勾选该选项选择工程类型
选择芯片XC7Z020CLG_400并进入主界面,读者可根据自己开发板自行选择芯片。
开始新建IP 如图所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第7步:我将寄存器配置为14个,包括有,8个PL→PS的adc数据,3个PS→PL的PID数据,3个PID数据的检查(PL→PS),因为不知道实际读取到了没有,需要检查一下。

在这里插入图片描述
第9步,选择 edit-ip 即编辑IP。我们需要对IP进行编辑才可以使用。
点击finish后会自动打开我们刚创建好的AXI_Lite这个IP的vivado工程。如图
在这里插入图片描述
第11步,打开ip_axi_lite_v1_0.v文件,准备在如图所示位置添加数据接口
在这里插入图片描述
根据前文所说,有8+3个 PL→PS的数据,3个PS→PL的数据,
所以,添加接口代码如下

在这里插入图片描述
第11步结束
第12步 还是在ip_axi_lite_v1_0.v文件中,在文件的下边 找到ip_axi_lite_v1_0_S00_AXI的例化(红色箭头),并准备在如图所示蓝色箭头位置添加接口代码
在这里插入图片描述
接口代码

   .A1_data0(A1_data0),   
   .A1_data1(A1_data1),
   .A3_data0(A3_data0),
   .A3_data1(A3_data1),
   .B1_data0(B1_data0),
   .B1_data1(B1_data1),
   .B3_data0(B3_data0),
   .B3_data1(B3_data1),
   .PL_PPPPP(PL_PPPPP),
   .PL_IIIII(PL_IIIII),
   .PL_DDDDD(PL_DDDDD),
   .PS_PPPPP(PS_PPPPP),
   .PS_IIIII(PS_IIIII),
   .PS_DDDDD(PS_DDDDD),
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如图所示
在这里插入图片描述
第12步结束
第13步:打开ip_axi_lite_v1_0_S00_AXI.v文件 在如图所示位置copy第11步中代码,最后如图所示
在这里插入图片描述
第13步结束
第14步,打开ip_axi_lite_v1_0_S00_AXI.v文件,在文件下方约四百多行,修改为如图所示
可以理解为,11个数据在这里从PL流到PS
在这里插入图片描述
在文件下方约三百多行位置 修改为如图所示
可以理解为,3个数据在这里从PS流到PL
在这里插入图片描述第14步结束,AXI_Lite IP的代码部分修改完成。
第15步,完成IP的封装 如图所示
在这里插入图片描述
可以在图中看到11个input 与 3 个 output,点击review and package
在这里插入图片描述
第15步结束,IP封装完成。PL与PS数据交互的桥梁已经打通。当然,这只是一条乡间小路,想要高速公路可以使用AXI_DMA等,我不会,还没学。

2.PL端block_design

1:打开vivado,新建工程,勾选可拓展,选择芯片平台,并creat a block design。
注意在这里插入图片描述
block已经新建完成
在这里插入图片描述
2 向block中添加自己使用到的ip(注意添加IP路径),我用到了
①adc驱动(all_ip_0 自编)
②数据滤波(adc_filter_0自编/adc_data_handle_0自编)
③axi_lite (ip_axi_lite_0自编/axi_transfer_0自编)
④clk
⑤ PS (ZYNQ7)
注意:clk按自己需求配置即可,我配置为50M ,是PLL,locked是我当成复位来使用的。
PS端的配置为①:以太网配置(电压1.8v,开启MDIO,引脚速度为fast,参考正点原子即可)②:uart0配置(参考正点原子即可) ③ 中断配置(如图)④ DDR配置(参考正点原子或自身开发板的说明)⑤ QSPI配置(参考原子)
在这里插入图片描述
在这里插入图片描述
3:按自己的信号走向连接线,我的如图
在这里插入图片描述
注意,ip_concat是将两个中断连接到了PS的中断上,如果只有一个中断信号来源,比如如果我图中只有一个data_valid, 那么直接将data_valid连接到IRQ_F2P即可。
3 如图所示
在这里插入图片描述
在这里插入图片描述
完成后如图所示
在这里插入图片描述
4 此时,Platform setup依然有问题,点击
在这里插入图片描述
在这里插入图片描述
5 查看分配的AXI地址,点击address editor ,如图
在这里插入图片描述
到此,PL端的Block design设计暂时结束,有关PID的6个引脚暂时没有用到,就先放着,等我写到下边再回来用这几个。
6 如图所示,步骤三和步骤四需要时间,请内耐心等待,结束后如下图所示在这里插入图片描述
在这里插入图片描述
7:自行配置引脚,并generate bitstream。
8:准备导出XSA文件到vitis
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
修改文件位置,我一般在vivado工程下新建一个vitis文件夹内放置xsa文件 next完之后直接finish
9:打开刚才的vitis文件夹,看xsa文件是否生成成功。
在这里插入图片描述
10,加载vitis并选择刚才的vitis文件夹作为workspace
在这里插入图片描述
在这里插入图片描述
11:新建vitis工程
create application project →next→create a new platform hardware(xsa)→browse(选择刚才生成的xsa文件)→next→给工程命名(adc_pl_ps_eth)→ next → next → 选择 Empty Application → Finish
新建完成如图所示
在这里插入图片描述

二、PS部分

前言;硬件平台的搭建已经结束,接下来进行PS部分的配置

1.基本配置

首先配置BSP
在这里插入图片描述
在这里插入图片描述

 PARAMETER mem_size = 524288
 PARAMETER memp_n_pbuf = 2048
 PARAMETER memp_n_udp_pcb = 1024
 PARAMETER n_rx_descriptors = 256
 PARAMETER n_tx_descriptors = 256
 PARAMETER pbuf_pool_size = 4096
 PARAMETER use_axieth_on_zynq = 0
 PARAMETER use_emaclite_on_zynq = 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

配置完成后 点击OK 等待vitis设置生效
(在此感谢GZ哥,)
在这里插入图片描述
接着修改makefile文件,因为vitis这个版本的bug,有自定义IP的需要修改makefile文件才可以使用。该图片来源正点原子
在这里插入图片描述
到此,vitis的基本配置结束,接下来我们编写ARM端的代码

2.arm核代码编写

首先我们新建几个文件 ①main.c ②user_udp.c ③user_udp.h
开始编写main中代码,主要包括有

代码如下(示例):

#include "xparameters.h"
#include "stdio.h"
#include "string.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "xplatform_info.h"
#include "xil_printf.h"
#include "sleep.h"
#include "user_udp.h"
#include "xtime_l.h"
#include "sleep.h"
#include "math.h"


/*
 * XPAR_AXI_TRANSFER_0_S00_AXI_BASEADDR和XPAR_IP_AXI_LITE_0_S00_AXI_BASEADDR
 * 是在block中添加的2个axi的寄存器,相关信息可以在xparameters.h找到
 * 中断号从61U开始,可以尝试单独新建一个工程去测试中断的使用,有一个问题需要注意的是
 * 你只写一个高低电平去测试中断,那么在xparameters.h中你可以找到官方生成的中断号宏定义
 * 就像我下边写的这样  但我添加了自定义IP更改makefile文件后,是没有这个宏定义的
 * 所以如果定义的中断比较多,就需要自己对一下中断号,从61U开始,具体可以查看xlinx官方文件
 */
#define SCUGIC_ID         XPAR_SCUGIC_0_DEVICE_ID                //中断控制器
#define TIME_INTERRUPT_ID 61U                                    //pl端中断号
//#define TIME_INTERRUPT_ID 62U                                  //pl端中断号
#define TRANS_ADDR        XPAR_AXI_TRANSFER_0_S00_AXI_BASEADDR   //pl端adc数据写入AXI寄存器地址
//#define TRANS_ADDR XPAR_IP_AXI_LITE_0_S00_AXI_BASEADDR         //pl端adc数据写入AXI寄存器地址


static void intr_handler(void *CallBackRef);                     //pl端中断函数定义
void        udp_send_myself();

XScuGic        intc;                                      //通用中断控制器驱动实例
XScuGic_Config * intc_cfg;                                //通用中断控制器配置信息

int   data0,data1,data2,data3,data4,data5,data6,data7= 0;
float Data0,Data1,Data2,Data3,Data4,Data5,Data6,Data7= 0;
int udp_send_flag = 0;                                    //触发中断 写1,开始以太网发送,发送结束后,写0
u8 check[38] = {0xAB,0xFD,0xFE,0xF1,0x20,0x00};           //该数组用于匿名上位机校验码计算

/*  开发板MAC地址  */
unsigned char mac_ethernet_address[] = {0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};

int main(void)
{

	/****************intr配置开始****************/
	intc_cfg = XScuGic_LookupConfig(SCUGIC_ID);
	XScuGic_CfgInitialize(&intc,intc_cfg,intc_cfg->CpuBaseAddress);
	//0x03 配置为高电平触发,请注意
	XScuGic_SetPriorityTriggerType(&intc,TIME_INTERRUPT_ID,0xA0,0x03);
	//将TIME_INTERRUPT_ID中断触发与intr_handler中断函数相连
	XScuGic_Connect(&intc,TIME_INTERRUPT_ID,intr_handler,0);
	//打开中断
	XScuGic_Enable(&intc,TIME_INTERRUPT_ID);

	Xil_ExceptionInit();
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
					(Xil_ExceptionHandler)XScuGic_InterruptHandler,&intc);
	Xil_ExceptionEnable();
	/****************intr配置结束****************/

	/****************udp配置开始****************/
	//请注意,勿将中断代码配置放在UDP配置之前,会配置失败
	netif = &server_netif;
	IP4_ADDR(&ipaddr,  192, 168,   0, 3);
	IP4_ADDR(&netmask, 255, 255, 255, 0);
	IP4_ADDR(&gw,      192, 168,   0, 1);
	lwip_init();                                       //初始化lwIP库
	xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR);
	netif_set_default(netif);
	netif_set_up(netif);                               //启动网络
	udp_tx_init();                                     //初始化UDP*/
	/****************udp配置结束****************/

	/****************匿名上位机协议前导码配置****************/
	Xil_Out8(tx_base_addr      , 0xAB ); 			//帧头
	Xil_Out8(tx_base_addr +  1 , 0xFD ); 			//源地址:暂定
	Xil_Out8(tx_base_addr +  2 , 0xFE ); 			//目地址:暂定
	Xil_Out8(tx_base_addr +  3 , 0xF1 ); 			//功能码:自定义
	Xil_Out8(tx_base_addr +  4 , 0x20 ); 			//数据长度:每个数据4字节 共八个数据 数据长度为32(十六进制为0x20)
	Xil_Out8(tx_base_addr +  5 , 0x00 ); 			//数据长度:小端在前
	/****************匿名上位机协议前导码配置结束****************/

	while(1)
	{
		printf("main_program\n");
		if(udp_send_flag == 1)
		{
			udp_send_myself();
		}
	}
    return 0;
}

void intr_handler(void *CallBackRef)
{
	XScuGic_Disable(&intc,TIME_INTERRUPT_ID);//进入中断后关闭中断防止重复触发
	printf("intr_program\n");
	udp_send_flag = 1;
}

void udp_send_myself()
{
	printf("udp\n");

	/*adc数据从axi寄存器中读出*/
	data0 = *(volatile unsigned int *)(TRANS_ADDR + 0*4);
	data1 = *(volatile unsigned int *)(TRANS_ADDR + 1*4);
	data2 = *(volatile unsigned int *)(TRANS_ADDR + 2*4);
	data3 = *(volatile unsigned int *)(TRANS_ADDR + 3*4);
	data4 = *(volatile unsigned int *)(TRANS_ADDR + 4*4);
	data5 = *(volatile unsigned int *)(TRANS_ADDR + 5*4);
	data6 = *(volatile unsigned int *)(TRANS_ADDR + 6*4);
	data7 = *(volatile unsigned int *)(TRANS_ADDR + 7*4);
	/**/

	/*我的ADC是一个24位的,直接传数据太大了,放小一点波形方便看*/
	Data0 = data0/100;
	Data1 = data1/100;
	Data2 = data2/100;
	Data3 = data3/100;
	Data4 = data4/100;
	Data5 = data5/100;
	Data6 = data6/100;
	Data7 = data7/100;
	/**/

	/*adc数据向ddr中写入*/
	Xil_Out32(tx_base_addr +  6,  Data0); //第1个数据
	Xil_Out32(tx_base_addr +  10, Data1); //第2个数据
	Xil_Out32(tx_base_addr +  14, Data2); //第3个数据
	Xil_Out32(tx_base_addr +  18, Data3); //第4个数据
	Xil_Out32(tx_base_addr +  22, Data4); //第5个数据
	Xil_Out32(tx_base_addr +  26, Data5); //第6个数据
	Xil_Out32(tx_base_addr +  30, Data6); //第7个数据
	Xil_Out32(tx_base_addr +  34, Data7); //第8个数据
	/**/

	/*校验数据从ddr中读出*/
	for ( u16 i=6; i<38; i++ )
    {
    	check[i] = Xil_In8(tx_base_addr + i); //写入check数组用于计算校验码
    }
	/**/

	/*校验数据,参考匿名上位机公开版使用说明*/
    u8 sumcheck = 0;
    u8 addcheck = 0;
    u16 flen = check[4] + check[5]*256;
    for ( u16 i=0; i<(flen+6); i++ )
    {
    	sumcheck += check[i];
    	addcheck += sumcheck;
    }
    Xil_Out8(tx_base_addr +  38, sumcheck); //和校验
    Xil_Out8(tx_base_addr +  39, addcheck); //附加校验
    /**/

	/*udp数据发送*/
	xemacif_input(netif);
	udp_tx(0);
	udp_send_flag = 0;
	XScuGic_Enable(&intc,TIME_INTERRUPT_ID); //执行完毕,打开中断
	/**/
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168

udp.c文件

#include "user_udp.h"
#include "xil_cache.h"
#include "xtime_l.h"

struct udp_pcb *pcb = NULL;
struct pbuf *pbuf_to_be_sent = NULL;

//---------------------------------------------------------
// UDP连接初始化函数
//---------------------------------------------------------
int udp_tx_init(void)
{
	err_t err;

	/*  创建UDP控制块   */
	pcb = udp_new();
	if (!pcb) {
		xil_printf("Error Creating PCB.\r\n");
		return -1;
	}
	/*  绑定本地端口   */
	err = udp_bind(pcb, IP_ADDR_ANY, (u16)local_port);
	if (err != ERR_OK) {
		xil_printf("Unable to bind to port %d\r\n", (u16)local_port);
		return -2;
	}
	/*  设置远程地址   */
	IP4_ADDR(&ipaddr, 192, 168, 0, 4);

	return 0;
}

//---------------------------------------------------------
//                   UDP发送数据函数
//---------------------------------------------------------
void udp_tx(int pkg_cnt)
{
	err_t err;
	int tx_addr;
	tx_addr = tx_base_addr+pkg_size*pkg_cnt;
	//我的数据大小为 6个字节的前导码,8个4字节的float 2个字节的校验 是40
	pbuf_to_be_sent = pbuf_alloc(PBUF_TRANSPORT, 40, PBUF_POOL);  //申请一个40的空间
	memcpy(pbuf_to_be_sent->payload, (u8 *)tx_addr, pkg_size);
	err = udp_sendto(pcb, pbuf_to_be_sent, &ipaddr, (u16)remote_port);
	if (err != ERR_OK) {
		xil_printf("Error on udp send : %d\r\n", err);
		return;
	}
	pbuf_free(pbuf_to_be_sent); //申请的空间释放
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

udp.h文件

#ifndef _USER_UDP_H_
#define _USER_UDP_H_

#include "lwip/udp.h"
#include "lwip/init.h"
#include "lwipopts.h"
#include "lwip/err.h"
#include "lwipopts.h"
#include "netif/xadapter.h"
#include "xil_printf.h"
#include "sleep.h"

#define local_port    5000  //本地端口
#define remote_port   8080  //远程端口
#define tx_base_addr   0x01000000
#define pkg_size   40

struct netif *netif, server_netif;

ip_addr_t ipaddr, netmask, gw;

int udp_tx_num;
int pkg_cnt ;

int  udp_tx_init(void);
void udp_tx(int pkg_cnt);

#endif /* _USER_UDP_H_ */

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

到此,代码部分全部结束,来看看我们写的对不对
下载程序,打开串口,
在这里插入图片描述
可见以太网连接成功,主程序中断程序运行正常。
接着查看匿名上位机,这里发现了一个问题,数据不太对,经检查后,发现是axi的寄存器地址用错了 因为我添加了两个axi_ip,连接到他们的数据是不一样的 ,但是从PL→PS→以太网的数据读取是没问题的
下图是上位机的波形显示,我这边是用电阻串联分压简单做了一个电压测试的,可以看到比例是一二三四的关系。
在这里插入图片描述
下图是以太网发送的数据,可以看到前导码,8个数据,校验码
在这里插入图片描述

总结

本文完成了fpga采集多路adc数据并通过以太网发送到上位机显示波形,文中有部分遗漏,比如数据从PS→PL未做,qspi固化程序也未做,等我有空下次更新,先去打原神了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/301823
推荐阅读
  

闽ICP备14008679号