DMA加NVMe

gitlab地址:http://120.55.75.228:23080/zhdyz/nvme_dma

总体概述

这一步就是将之前DMA实验和NVMe实验两个组合到一起,共同运行在Zynq UltraScale+ 的Linux系统上

主要难点在于Verilog部分,DMA实验的IP核和NVMe实验的IP核都使用了AXI来控制,所以开始使习惯使用自动连接导致出错,得自己用AXI Interconnect将两部分的IP核分别与Zynq IP核相连接

3

还有就是两部分加在一起Vivado综合所需要的时间较长,Vivado这个软件还有莫名其妙的bug(没有错但是硬报错,再运行一遍就好了),遇到这种情况直接重新综合,不行再退出程序重新打开再综合,所以感觉很简单的任务也拖了老半天

文件修改

流程跑通后重写了dma应用程序的功能,原先是将数据打印在控制台,如今是以文本文件形式写入挂载在NVMe硬盘的目录中

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>

#define ADC_IOCTL_MAGIC 'M'
#define CMD_GET_DMADATA _IO(ADC_IOCTL_MAGIC, 0)
#define CMD_SET_START _IO(ADC_IOCTL_MAGIC, 1)

#define ADC_SIZE (1024 * 1024)

int main()
{
    int fd;             // 文件描述符
    int ret = 0;        // 返回值
    char data[ADC_SIZE];    // 用于存储 DMA 数据的缓冲区
    uint16_t *data_tmp; // 临时指针,用于按照 16 位读取数据
    int start = 1;      // 控制 DMA 数据传输的标志
    int i, j;
    FILE *file = NULL;
    char filename[256];

    // 打开 DMA 设备文件
    fd = open("/dev/adcdma_fun", O_RDWR);
    if (fd < 0)
    {
        printf("Can't open file fdma_fun\n");
        return -1;
    }
    printf("open file adcdma_fun\n");
    // 启动 DMA 数据传输
    ioctl(fd, CMD_SET_START, &start);

    while (1)
    {
        // 从 DMA 设备中读取数据
        ret = ioctl(fd, CMD_GET_DMADATA, data);
        //printf("ioctl\n");
        if (ret < 0)
        {
            perror("Get data error\n");
            break;
        }

        // 将数据按照 16 位进行写入文件
        if (!file || ftell(file) >= ADC_SIZE)
        {
            if (file)
            {
                fclose(file);
            }
            time_t t = time(NULL);
            struct tm tm = *localtime(&t);
            sprintf(filename, "/nvme/data_%d_%d_%d_%d_%d.txt", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min);
            //printf("before open\n");
            file = fopen(filename, "w");
            //printf("after open\n");
            if (!file)
            {
                perror("Failed to open file for writing\n");
                break;
            }
        }
        data_tmp = (uint16_t *)data; // 将 data 转换为 uint16_t 指针
        for (i = 0; i < ADC_SIZE / sizeof(uint16_t); i += 8)
        {
            for (j = 0; j < 8; j++)
            {
                fprintf(file, "%04X ", data_tmp[i + j]);
            }
            fprintf(file, "\n");
        }
    }

    // 停止 DMA 数据传输
    start = 0;
    ioctl(fd, CMD_SET_START, &start);

    // 关闭 DMA 设备文件
    close(fd);

    if (file)
    {
        fclose(file);
    }

    return ret;
}

运行测试

如图所示,运行程序dma_app_file后,查看/nvme目录,可以看到有写入的文件,文件名的格式为data_年_月_日_时_分,不过获得的是UTC时间,就是北京时间-8个小时

查看文本文件内容的话就是采集到的原始数据

1

然后再查看磁盘使用情况,可以看到/nvme是挂载在/dev/nvme0n1下的,已经使用了73MB了,说明DMA采集到的数据是能写入NVMe硬盘的

2

不过以文本形式写入的速度可能不是很快,如何提高写入速度是下一步要研究的问题

后续研究

报错

#define ADC_SIZE (1024 * 1024)
 char data[ADC_SIZE];    // 用于存储 DMA 数据的缓冲区

发现当 ADC_SIZE 定义为 8 * 1024 * 1024(即 8 MB)时,程序会触发 Segmentation fault 错误。而当 ADC_SIZE 设置为小于 8 MB 时,程序可以正常运行

解决方法:改为动态分配内存

char* data = (char*) malloc(ADC_SIZE);

本文章使用limfx的vscode插件快速发布