赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
本项目使用FPGA-ZYNQ-7020采集8通道ADC数据,通过以太网上传到上位机显示波形
PL部分包括数据的获取与处理,数据从PL传递到PS
PS部分包括以太网的发送,由PL到PS的中断触发
本文章不详细讲解背后原理 但保证每一步足够详细,跟着就能实现。
本人FPGA平台 大磊FPGA7020开发板 vivado2020.2 vitis2020.2 匿名上位机公开版
该部分主要有 ① PL端的block design;② PL端IP-------axi_lite自定义 用于PL-PS端数据交互
打开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),
如图所示
第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等,我不会,还没学。
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部分的配置
首先配置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
配置完成后 点击OK 等待vitis设置生效
(在此感谢GZ哥,)
接着修改makefile文件,因为vitis这个版本的bug,有自定义IP的需要修改makefile文件才可以使用。该图片来源正点原子
到此,vitis的基本配置结束,接下来我们编写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); //执行完毕,打开中断 /**/ }
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); //申请的空间释放 }
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_ */
到此,代码部分全部结束,来看看我们写的对不对
下载程序,打开串口,
可见以太网连接成功,主程序中断程序运行正常。
接着查看匿名上位机,这里发现了一个问题,数据不太对,经检查后,发现是axi的寄存器地址用错了 因为我添加了两个axi_ip,连接到他们的数据是不一样的 ,但是从PL→PS→以太网的数据读取是没问题的
下图是上位机的波形显示,我这边是用电阻串联分压简单做了一个电压测试的,可以看到比例是一二三四的关系。
下图是以太网发送的数据,可以看到前导码,8个数据,校验码
本文完成了fpga采集多路adc数据并通过以太网发送到上位机显示波形,文中有部分遗漏,比如数据从PS→PL未做,qspi固化程序也未做,等我有空下次更新,先去打原神了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。