ZYNQ DMA Linux驱动实验
ZYNQ DMA Linux驱动实验
简介
在PL中调用AXI-DMA向PS的内存中写入数据,数据源为自己造的一个递增数,在PS中可通过驱动控制DMA的传输。
搭建Vivado工程
主要调用了自定义的数据发生器模块、FIFO转AXI-Stream模块、AXI-DMA模块和ZYNQ PS模块,数据流向为数据发生器->FIFO->FIFO转AXI-Stream->AXI-DMA->PS。PS需要配置FIFO转AXI-Stream的数据包长度和启动发送2个寄存器。需要使用到的寄存器如下:
PL复位寄存器
PL内所有模块的复位信号采用PS输出的FCLK_RESET0来控制,需要使用到的寄存器如下:
| 物理地址 | 寄存器 | 读写 | 默认值 | 说明 |
|---|---|---|---|---|
| 0xF8000008 | SLCR_UNLOCK | wo | 0x00000000 | SCLR解锁寄存器,向该寄存器写入0xDF0D解锁SCLR寄存器的写保护 |
| 0xF800000C | SLCR_LOCKSTA | ro | 0x00000001 | SLCR锁定状态寄存器,读为0代表SLCR寄存器为可写状态,读为1代表SLCR为写保护状态 |
| 0x00000240 | FPGA_RST_CTRL | rw | 0x01F33F0F | 低4位分别为FCLK_RESET0~3的复位控制,写1为复位,写0为解复位 |
FIFO转AXI-Stream模块寄存器定义
基地址:0x60000000
| 偏移地址 | 寄存器 | 读写 | 默认值 | 说明 |
|---|---|---|---|---|
| 0x0 | 数据包长度寄存器 | R/W | 0x00000000 | 每一个AXI-Stream数据包的长度 |
| 0x8 | 数据包数量寄存器 | R/W | 0x00000000 | 每一次启动发送后产生的AXI-Stream数据包数量 |
| 0x4 | 启动寄存器 | R/W | 0x00000000 | 寄存器bit0从0变为1后启动一次发送 |
AXI-DMA寄存器定义
见Xilinx IP手册PG021

编写Linux驱动
总的ADC采集数据这个功能的驱动可以分为2个部分来完成,一个是AXI-DMA的驱动,通过AXI-DMA驱动可以向Linux注册一个DMA设备。另一个才是我们想实现的ADC数据采集驱动,在这个驱动中调用AXI-DMA驱动实现PL到PS的数据传输,再使用字符设备的驱动框架实现其他接口,比如控制采集长度、开始采集等功能,
AXI-DMA驱动
AX-DMA使用Linux内核自带的驱动,我们只需要提供设备树给驱动获取具体的硬件信息就行了。
- 编辑设备树文件,添加以下内容
//从Vitis生成的AXI DMA设备树,与Linxu内核中现成的AXI-DMA驱动匹配
amba_pl: amba_pl {
ranges;
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
axi_dma_1: axi_dma@40400000 {
#dma-cells = <1>;
xlnx,dlytmr-resolution = <125>;
xlnx,num-s2mm-channels = <1>;
xlnx,rable = <0>;
xlnx,sg-length-width = <14>;
xlnx,ip-name = "axi_dma";
reg = <0x40400000 0x10000>;
xlnx,sg-use-stsapp-length = <0>;
xlnx,s2mm-burst-size = <256>;
xlnx,c-dlytmr-resolution = <125>;
xlnx,c-num-s2mm-channels = <1>;
xlnx,enable-multi-channel = <0>;
xlnx,num-mm2s-channels = <1>;
xlnx,c-sg-length-width = <14>;
interrupt-names = "s2mm_introut";
compatible = "xlnx,axi-dma-7.1" , "xlnx,axi-dma-1.00.a";
xlnx,c-m-axis-mm2s-tdata-width = <32>;
xlnx,c-sg-use-stsapp-length = <0>;
xlnx,c-s2mm-burst-size = <256>;
xlnx,mm2s-burst-size = <16>;
xlnx,c-enable-multi-channel = <0>;
xlnx,c-num-mm2s-channels = <1>;
interrupt-parent = <&intc>;
xlnx,include-s2mm-dre = <0>;
xlnx,c-mm2s-burst-size = <16>;
status = "okay";
xlnx,c-include-s2mm-dre = <0>;
xlnx,include-mm2s-dre = <0>;
xlnx,name = "axi_dma_0";
interrupts = < 0 29 4 >;
xlnx,c-include-sg = <0>;
xlnx,c-m-axi-s2mm-data-width = <32>;
xlnx,include-s2mm-sf = <1>;
xlnx,c-include-mm2s-dre = <0>;
xlnx,include-s2mm = <1>;
clocks = <&clkc 15>, <&clkc 15>;
xlnx,addrwidth = <0x20>;
xlnx,c-m-axi-mm2s-data-width = <32>;
xlnx,edk-iptype = "PERIPHERAL";
xlnx,c-include-s2mm-sf = <1>;
xlnx,include-mm2s-sf = <1>;
clock-names = "m_axi_s2mm_aclk" , "s_axi_lite_aclk";
xlnx,c-include-s2mm = <1>;
xlnx,c-addr-width = <32>;
xlnx,c-single-interface = <0>;
xlnx,include-mm2s = <0>;
xlnx,c-s-axis-s2mm-tdata-width = <16>;
xlnx,s2mm-data-width = <0x20>;
xlnx,c-include-mm2s-sf = <1>;
xlnx,prmry-is-aclk-async = <0>;
xlnx,c-include-mm2s = <0>;
xlnx,increase-throughput = <0>;
xlnx,micro-dma = <0>;
xlnx,mm2s-data-width = <0x20>;
xlnx,c-prmry-is-aclk-async = <0>;
xlnx,c-sg-include-stscntrl-strm = <0>;
xlnx,c-increase-throughput = <0>;
xlnx,c-micro-dma = <0>;
dma_channel_40400030: dma-channel@40400030 {
interrupts = <0 29 4>;
xlnx,datawidth = <0x10>;
xlnx,device-id = <0x0>;
compatible = "xlnx,axi-dma-s2mm-channel";
dma-channels = <0x1>;
};
};
};
AXI-DMA部分的设备树采用的是Vitis根据xsa文件生成的,具体生成步骤这儿不详细记录,可以网上查找相关步骤。
-
配置Linux驱动,使能XILINX_DMA驱动编译为模块
![image-20240824230450387]()
-
编译Linux源码工程,生成设备树 xxx.dtb、Linux内核镜像zImage和驱动模块xilinx_dma.ko
make -j32 -
将设备树、Linux内核和驱动模块拷贝到ZYNQ板子上,上电启动后使用insmod命令插入编译好的axi-dma驱动模块,查看/sys/class/dma目录下的DMA设备,可以看到新增的AXI-DMA通道dma1chan0
![image-20240824230858130]()
再往目录里面看到of_node文件夹内,可以看到dma通道对于的基地址就是0x40400000,与设备树新增的设备基地址是一样的,证明dma1chan0就是对应设备树新增的axi-dma的s2mm通道。
![image-20240824231015739]()
查看/proc/interrupts中使能的中断设备,可以看到设备树中指定的AXI-DMA设备的中断已经注册到Linux中断系统了。
![image-20240826000350827]()
-
到这一步只是把AXI-DMA设备添加到Linux内核的DMA子系统的驱动框架中了,后面就可以使用DMA子系统提供的标准接口函数控制DMA通道接收数据。根据Vivado搭建的工程可以看到,除了DMA需要使用DMA子系统来控制之外,还需要配置一下其他的自定义寄存器才能完整实现数据采集功能。因此还需要写一个把整个PL的逻辑当成一个设备的驱动,在驱动中实现PL逻辑复位、配置初始化寄存器和使用DMA采集数据的功能。
编写ADC数据采集设备驱动
-
设备树增加自定义设备,指定了AXI基地址和使用的DMA通道
//自定义的ADC数据设备,后面会用到 //指定了AXI基地址0x60000000 //指定了使用axi_dma_1 DMA设备 adc_axi_dma_0: adc_axi_dma@1 { #address-cells = <1>; #size-cells = <1>; status = "okay"; compatible ="adc_axi_dma"; dmas = <&axi_dma_1 1>; dma-names = "axidma0"; reg = <0x60000000 0x20>; } ; -
新建驱动文件
- 在driver/dma/xilinx/目录下新建一个驱动源文件,命名为adc_axi_dma_demo.c,文件内容如下:
#include <linux/module.h> #include <linux/dma-mapping.h> #include <linux/dmaengine.h> #include <linux/platform_device.h> #include <linux/dmaengine.h> #include <linux/slab.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/errno.h> #include <asm/mach/map.h> #include <asm/io.h> #include <linux/cdev.h> #include <linux/platform_device.h> #include <linux/ioport.h> #include <linux/of_address.h> #define DEV_NAME "my_adc_axi_dma" #define DEV_NUM 1 #define CLASS_NAME "ADC_AXI_DMA" #define DMA_CHANNEL "axidma0" #define IOCTL_START_DMA _IO('d', 1) // 控制寄存器基地址和寄存器偏移 #define FIFO2AXIS_BASE 0X60000000 #define PACKAGE_LENGTH 0x0 #define PACKAGE_NUMBER 0X8 #define AXIS_START 0X4 // PL复位信号控制寄存器 #define ZYNQ_FCLK_RESET_BASE 0XF8000240 //储存控制寄存器和复位寄存器映射的虚拟地址 struct adc_axi_dma_reg { void __iomem *package_length; void __iomem *package_number; void __iomem *axis_start; void __iomem *fclk_reset; }; // 自定义的设备结构体,包含DMA的信息和设备相关的结构体 struct adc_axi_dma_dev { struct dma_chan *dma_chan; struct device *dev; struct platform_device *pdev; dma_addr_t dma_src; dma_addr_t dma_dst; void *src_buf; void *dst_buf; size_t buf_size; struct adc_axi_dma_reg reg; int major; int minor; struct class *class; struct cdev cdev; dev_t devid; }; // 映射控制寄存器和复位寄存器到虚拟地址,存储到设备结构体中,用于注册驱动时获取虚拟地址,方便后续函数操作寄存器 static void adc_axi_dma_remap(struct adc_axi_dma_dev *dev) { dev->reg.fclk_reset = ioremap(ZYNQ_FCLK_RESET_BASE, 4); if (!dev->reg.fclk_reset) { printk(KERN_ERR "Failed to remap fclk_reset\n"); // Handle error if needed } dev->reg.package_length = ioremap(FIFO2AXIS_BASE + PACKAGE_LENGTH, 4); if (!dev->reg.package_length) { printk(KERN_ERR "Failed to remap package_length\n"); // Handle error if needed } dev->reg.package_number = ioremap(FIFO2AXIS_BASE + PACKAGE_NUMBER, 4); if (!dev->reg.package_number) { printk(KERN_ERR "Failed to remap package_number\n"); // Handle error if needed } dev->reg.axis_start = ioremap(FIFO2AXIS_BASE + AXIS_START, 4); if (!dev->reg.axis_start) { printk(KERN_ERR "Failed to remap axis_start\n"); // Handle error if needed } } // 取消控制寄存器和复位寄存器的地址映射,用于注销驱动时清除映射 static void adc_axi_dma_unmap(struct adc_axi_dma_dev *dev) { if (dev->reg.fclk_reset) iounmap(dev->reg.fclk_reset); if (dev->reg.package_length) iounmap(dev->reg.package_length); if (dev->reg.package_number) iounmap(dev->reg.package_number); if (dev->reg.axis_start) iounmap(dev->reg.axis_start); } // 复位PL逻辑 static void adc_axi_dma_reset(struct adc_axi_dma_dev *dev) { writel(0xf, dev->reg.fclk_reset); udelay(10); writel(0x0, dev->reg.fclk_reset); } // 字符设备的标准open函数 static int simple_dma_open(struct inode *inode, struct file *file) { struct adc_axi_dma_dev *dev; dev = container_of(inode->i_cdev, struct adc_axi_dma_dev, cdev); file->private_data = dev; // 将设备结构体指针存储到文件私有数据中 return 0; } // 字符设备的release函数 static int simple_dma_release(struct inode *inode, struct file *file) { return 0; } // 字符设备的read函数,主要功能是当DMA传输完成后,DMA写到DDR的内存地址存储在dma_dev->dst_buf指针中,使用read函数从dst_buf中获取传输的数据,传给应用程序 static ssize_t adc_axi_dma_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct adc_axi_dma_dev *dma_dev = file->private_data; size_t bytes_to_copy; pr_info("adc_axi_dma read\n"); pr_info("%lx\n", (unsigned long)dma_dev->dst_buf); pr_info("%d\n", *(unsigned int *)(dma_dev->dst_buf)); if (*ppos >= dma_dev->buf_size) { return 0; // 已经读取完所有数据 } bytes_to_copy = min(count, dma_dev->buf_size - *ppos); if (copy_to_user(buf, dma_dev->dst_buf + *ppos, bytes_to_copy)) { return -EFAULT; } *ppos += bytes_to_copy; // 读完buf中的数据后释放空间,方便下次申请 if (*ppos == dma_dev->buf_size) { dma_free_coherent(dma_dev->dev, dma_dev->buf_size, dma_dev->dst_buf, dma_dev->dma_dst); } return bytes_to_copy; } // DMA完成中断回调函数 static void adc_axi_dma_callback(void *data) { struct adc_axi_dma_dev *dma_dev = (struct adc_axi_dma_dev *)data; pr_info("DMA transfer completed callback\n"); } struct dma_ioctl_args { unsigned int rx_buf_size; // DMA buffer size unsigned int package_length; unsigned int package_number; unsigned long dst_addr; // Address to store the DMA destination buffer address }; // 字符设备的IOCTL函数,实现了一个启动传输的命令,应用程序需要传递一个dma_ioctl_args类型结构体指针,设置AXI-STREAM数据包的长度、接收BUF的大小 static long simple_dma_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct dma_async_tx_descriptor *tx; struct adc_axi_dma_dev *dma_dev = file->private_data; dma_cookie_t cookie; enum dma_status status; struct dma_ioctl_args args; if (cmd == IOCTL_START_DMA) { // 获取传递的dma_ioctl_args类型参数 pr_info("adc_axi_dma ioctl\n"); if (copy_from_user(&args, (void __user *)arg, sizeof(args))) { pr_err("Failed to copy arguments from user space\n"); return -EFAULT; } // 给控制寄存器写入长度参数 writel(args.package_length, dma_dev->reg.package_length); writel(args.package_number, dma_dev->reg.package_number); // 请求名为axidma0的DMA通道,这个名字和设备树是对应的 dma_dev->dma_chan = dma_request_chan(&(dma_dev->pdev->dev), "axidma0"); if (IS_ERR(dma_dev->dma_chan)) { pr_err("Failed to request DMA channel\n"); return PTR_ERR(dma_dev->dma_chan); } // 申请一个接收buffer的指针给dma_dev->dst_buf,接收到的数据存储在这个指针指向的内存中 // Prepare the destination buffer only dma_dev->buf_size = args.rx_buf_size; dma_dev->dst_buf = dma_alloc_coherent(dma_dev->dev, dma_dev->buf_size, &dma_dev->dma_dst, GFP_KERNEL); if (!dma_dev->dst_buf) { pr_err("Failed to allocate DMA destination buffer\n"); return -ENOMEM; } // Prepare the DMA transaction without specifying a source address tx = dmaengine_prep_slave_single( dma_dev->dma_chan, dma_dev->dma_dst, dma_dev->buf_size, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!tx) { pr_err("Failed to prepare DMA transaction\n"); return -ENOMEM; } // 绑定DMA完成中断回调函数 tx->callback = adc_axi_dma_callback; tx->callback_param = dma_dev; // 提交DMA传输任务 cookie = dmaengine_submit(tx); dma_async_issue_pending(dma_dev->dma_chan); // 配置控制寄存器开始从AXI-Stream发送数据到AXI-DMA writel(0x1, dma_dev->reg.axis_start); writel(0x0, dma_dev->reg.axis_start); // 轮询等待DMA传输完成 do { status = dma_async_is_tx_complete(dma_dev->dma_chan, cookie, NULL, NULL); } while (status == DMA_IN_PROGRESS); if (status == DMA_COMPLETE) { pr_info("DMA transfer completed successfully\n"); } else { pr_err("DMA transaction failed with status: %d\n", status); return -EIO; } pr_info("DMA transfer completed successfully\n"); // Copy the DMA destination buffer address back to user space // 这个返回去好像用不了,应用程序中没用这个传回去的地址 args.dst_addr = (unsigned long)dma_dev->dst_buf; if (copy_to_user((void __user *)arg, &args, sizeof(args))) { pr_err("Failed to copy DMA destination buffer address to user space\n"); return -EFAULT; } // Clean up dma_release_channel(dma_dev->dma_chan); } return 0; } // 绑定字符设备的标准函数 static struct file_operations fops = { .open = simple_dma_open, .read = adc_axi_dma_read, .release = simple_dma_release, .unlocked_ioctl = simple_dma_ioctl, }; // 平台驱动的probe函数,当设备树与驱动的compatible匹配时该函数被执行,驱动编译为模块的话则在insmod时若发现匹配的设备就执行该函数 static int adc_axi_dma_probe(struct platform_device *pdev) { int ret = 0; struct resource *res; struct adc_axi_dma_dev *dma_dev; // 申请一个adc_axi_dma_dev结构体的空间 dma_dev = kzalloc(sizeof(*dma_dev), GFP_KERNEL); if (!dma_dev) { pr_err("Failed to allocate memory for device structure\n"); return -ENOMEM; } dma_dev->pdev = pdev; // 获取设备树中指定的寄存器地址资源,向内核申请资源 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { pr_err("Failed to get platform resource\n"); return -ENODEV; } if (!request_mem_region(res->start, resource_size(res), pdev->name)) { pr_err("Failed to request memory region\n"); return -EBUSY; } // 映射寄存器地址到虚拟地址 adc_axi_dma_remap(dma_dev); adc_axi_dma_reset(dma_dev); // 创建字符设备标准操作,申请主设备号、创建字符设备 if (dma_dev->major) { dma_dev->devid = MKDEV(dma_dev->major, 0); ret = register_chrdev_region(dma_dev->devid, DEV_NUM, DEV_NAME); if (ret) { printk(KERN_ERR "Failed to register chardev region\n"); goto err_chrdev_region; } } else { ret = alloc_chrdev_region(&dma_dev->devid, 0, DEV_NUM, DEV_NAME); if (ret) { printk(KERN_ERR "Failed to allocate chardev region\n"); goto err_chrdev_alloc; } dma_dev->major = MAJOR(dma_dev->devid); dma_dev->minor = MINOR(dma_dev->devid); } // 在linux的/dev目录下创建设备 dma_dev->class = class_create(DEV_NAME); if (IS_ERR(dma_dev->class)) { ret = PTR_ERR(dma_dev->class); printk(KERN_ERR "Failed to create device class\n"); goto err_class_create; } // 将驱动实现的open、write等函数绑定到设备,函数在led_fops结构体中 cdev_init(&dma_dev->cdev, &fops); ret = cdev_add(&dma_dev->cdev, dma_dev->devid, 1); if (ret < 0) { printk(KERN_ERR "Failed to add cdev\n"); goto err_cdev_add; } dma_dev->dev = device_create(dma_dev->class, NULL, dma_dev->devid, NULL, DEV_NAME); if (IS_ERR(dma_dev->dev)) { ret = PTR_ERR(dma_dev->dev); printk(KERN_ERR "Failed to create device\n"); goto err_device_create; } platform_set_drvdata(pdev, dma_dev); pr_info("ADC AXI-DMA device probed\n"); return 0; // 错误处理,主要是之前初始化步骤失败后释放相关资源 err_device_create: cdev_del(&dma_dev->cdev); err_cdev_add: class_destroy(dma_dev->class); err_class_create: unregister_chrdev_region(dma_dev->devid, 1); err_chrdev_alloc: err_chrdev_region: adc_axi_dma_unmap(dma_dev); err_ioremap: return ret; } // 平台驱动的remove函数,在rmmod时被执行,释放使用的资源 static int adc_axi_dma_remove(struct platform_device *pdev) { struct resource *res; struct adc_axi_dma_dev *dma_dev = platform_get_drvdata(pdev); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res) { adc_axi_dma_unmap(dma_dev); release_mem_region(res->start, resource_size(res)); // 释放内存区域 } device_destroy(dma_dev->class, dma_dev->devid); class_unregister(dma_dev->class); class_destroy(dma_dev->class); unregister_chrdev(dma_dev->major, DEV_NAME); kfree(dma_dev); pr_info("ADC AXI-DMA device exited\n"); return 0; } // 驱动匹配表,与设备树中设备节点的compatible属性匹配 static const struct of_device_id adc_axi_dma_of_match[] = { { .compatible = "adc_axi_dma" }, {} }; // 平台驱动结构体 static struct platform_driver adc_axi_dma_driver = { .driver = { .name = "adc_axi_dma_driver", .of_match_table = adc_axi_dma_of_match }, .probe = adc_axi_dma_probe, .remove = adc_axi_dma_remove, }; // 注册一个平台驱动 module_platform_driver(adc_axi_dma_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple DMA device driver"); MODULE_VERSION("0.1");-
修改Makefile和Kconfig
修改/driver/dma/xilinx/Makefile文件,新增一行内容
obj-$(CONFIG_ADC_AXI_DMA_DEMO) += adc_axi_dma_demo.o修改/driver/dma/Kconfig文件,新增以下内容
config ADC_AXI_DMA_DEMO tristate "MY ADC TO AXI-DMA DEMO" depends on HAS_IOMEM select DMA_ENGINE help ADC data trsanmited to ps ddr by axi-dma -
使能内核编译驱动为模块
![image-20240826003352354]()
-
编译驱动
make modules -j32编译完后生成adc_axi_dma_demo.ko驱动模块
-
加载驱动
将adc_axi_dma_demo.ko复制到ZYNQ板子Linux系统内,通过insmod命令插入模块,插入成功后在/dev目录下可以看到新增的设备my_adc_axi_dma
![image-20240826003656497]()
编写应用程序
-
在虚拟机中新建一个应用程序源文件demo.c
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define IOCTL_START_DMA _IO('d', 1) struct dma_ioctl_args { unsigned int rx_buf_size; // DMA buffer size unsigned int package_length; unsigned int package_number; unsigned long dst_addr; // Address to store the DMA destination buffer address }; int main() { int fd = open("/dev/my_adc_axi_dma", O_RDWR); struct dma_ioctl_args args; unsigned char *user_buffer; args.rx_buf_size = 1024 * 1024; args.package_length = 200; args.package_number = 1; args.dst_addr = 0; if (fd < 0) { perror("Failed to open device"); return EXIT_FAILURE; } if (ioctl(fd, IOCTL_START_DMA, &args) < 0) { perror("Failed to start DMA"); close(fd); return EXIT_FAILURE; } // 分配用户空间缓冲区 user_buffer = (unsigned char *)malloc(args.rx_buf_size); if (!user_buffer) { perror("Failed to allocate user buffer"); close(fd); return -1; } // 使用read函数从内核空间复制数据到用户空间 if (read(fd, user_buffer, args.rx_buf_size) != args.rx_buf_size) { perror("Failed to read data from device"); free(user_buffer); close(fd); return -1; } printf("DMA transfer completed successfully\n"); printf("rx data in 0x%x\n", (unsigned short)user_buffer); int i; for (i = 0; i < args.package_length * 2; i = i + 2) { printf("addr:0x%lx,\tdata:%d\n", (unsigned long)(user_buffer + i), *(unsigned short *)(user_buffer + i)); } close(fd); return EXIT_SUCCESS; } -
编译为可执行文件
arm-linux-gnueabihf-gcc demo.c生成a.out文件
-
运行应用程序
将a.out拷贝到ZYNQ Linux系统,添加可执行权限并运行
sudo chmod +x a.out sudo ./a.out![image-20240826004610354]()
根据打印信息可以看到程序调用了驱动中的ioctl函数并完成了传输,传输完成后进入了自定义的中断回调函数,最终通过驱动的read函数将接收端DDR的数据打印了出来,接收数据为一个递增数,与Vivado端设计的数据发生器是对应的。

在Vivado生成比特流之前我把AXI-Stream接口抓了下信号,当PS给PL加载bin文件后也可以连接仿真器参看debug的信号,这儿是执行了4次测试应用程序抓取的波形,可以看到PL中也发生了4次AXI-DMA的传输过程,AXI-Stream总线上的数据与应用程序打印的数据也是对应的。








浙公网安备 33010602011771号