gitlab地址:http://120.55.75.228:23080/zhdyz/ad_dma
本文是阶段性实验,实现了将PL端的模拟AD采集数据通过dma方式搬运到PS端内存中,然后PS端运行的Linux系统可以获取显示采集到的AD数据
之前使用Vitis实现了dma传输:https://www.limfx.pro/ReadArticle/3731/dma-jiang-pl-shu-ju-ban-yun-dao-ps-nei-cun
本文与上篇文章有许多相似之处,可以对比参考
在PL部分模拟产生8个通道16位的AD数据(假的),然后通过DMA传输到PS端,通过Linux驱动实现内存映射等功能,最后通过Linux应用在命令行(以原始形式)显示产生的AD数据
这部分可参考 DMA将PL数据搬运到PS内存 里的Vivado部分,几乎完全一致
就是配置Zynq,配置DMA,处理数据的产生和转换
值得注意的是,这里DMA的一些配置参数和后面的设备树文件紧密相关,如果这里修改了有关参数,后面的设备树文件内的相关参数也要修改成一样的,否则会无法进行DMA传输 (犯过的错误)
整体设计如图所示
生成平台工程,产生需要的三个FSBL文件,在前面文章有多次提过
设备树文件 zynqmp-mzux.dts,下面只显示PL部分,就是这里axi_dma_0的参数注意和刚才Vivado内设置的是否相同
/ {
amba_pl: amba_pl@0 {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges ;
axi_dma_0: dma@80000000 {
#dma-cells = <1>;
clock-names = "m_axi_s2mm_aclk", "s_axi_lite_aclk";
clocks = <&zynqmp_clk 71>, <&zynqmp_clk 71>;
compatible = "xlnx,axi-dma-7.1", "xlnx,axi-dma-1.00.a";
interrupt-names = "s2mm_introut";
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
reg = <0x0 0x80000000 0x0 0x10000>;
xlnx,addrwidth = <0x20>;
xlnx,sg-length-width = <0x17>;
dma-channel@80000030 {
compatible = "xlnx,axi-dma-s2mm-channel";
dma-channels = <0x1>;
interrupts = <0 89 4>;
xlnx,datawidth = <0x80>;
xlnx,device-id = <0x0>;
};
};
gpio_user_rstn: gpio@80010000 {
#gpio-cells = <3>;
clock-names = "s_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a";
gpio-controller ;
reg = <0x0 0x80010000 0x0 0x10000>;
xlnx,all-inputs = <0x0>;
xlnx,all-inputs-2 = <0x0>;
xlnx,all-outputs = <0x0>;
xlnx,all-outputs-2 = <0x0>;
xlnx,dout-default = <0x00000000>;
xlnx,dout-default-2 = <0x00000000>;
xlnx,gpio-width = <0x1>;
xlnx,gpio2-width = <0x20>;
xlnx,interrupt-present = <0x0>;
xlnx,is-dual = <0x0>;
xlnx,tri-default = <0xFFFFFFFF>;
xlnx,tri-default-2 = <0xFFFFFFFF>;
};
gpio_user_start: gpio@80020000 {
#gpio-cells = <3>;
clock-names = "s_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a";
gpio-controller ;
reg = <0x0 0x80020000 0x0 0x10000>;
xlnx,all-inputs = <0x0>;
xlnx,all-inputs-2 = <0x0>;
xlnx,all-outputs = <0x0>;
xlnx,all-outputs-2 = <0x0>;
xlnx,dout-default = <0x00000000>;
xlnx,dout-default-2 = <0x00000000>;
xlnx,gpio-width = <0x1>;
xlnx,gpio2-width = <0x20>;
xlnx,interrupt-present = <0x0>;
xlnx,is-dual = <0x0>;
xlnx,tri-default = <0xFFFFFFFF>;
xlnx,tri-default-2 = <0xFFFFFFFF>;
};
adcdma_fun_0: adcdma_fun0@0 {
compatible = "adcdma_demo";
dmas = <&axi_dma_0 0>;
dma-names = "adc";
num-buf = <3>;
reset-gpios = <&gpio_user_rstn 0 0 GPIO_ACTIVE_HIGH>;
enable-gpios = <&gpio_user_start 0 0 GPIO_ACTIVE_HIGH>;
};
};
};
然后把相关文件传输到Linux开发环境交叉编译,产生BOOT文件放置SD卡
驱动文件dma_drv.c如下,带注释
内存分配有关结构体为adcdma_para
,函数为of_adcdma_data
#define ADC_DMAINIT_S 1
#define ADCDATA_SIZE (1024 * 1024)
/*
struct dma_chan *dma;:DMA 通道指针,用于管理 DMA 操作。
int buf_con;:缓存数,表示 DMA 中缓存的数量,不大于 32。
int mem_switch;:当前 DMA 使用的内存块索引,表示当前写入的内存块。
int mem_flag;:缓存标识,被 DMA 写入后对应位置 1,读取后对应位置 0。
int read_mem;:当前读取的内存块索引。
phys_addr_t adc_phys[32];:DMA 物理内存地址数组,用于存储 32 个内存块的物理地址。
void __iomem *adc_virt[32];:DMA 物理内存对应的映射地址数组,用于存储 32 个内存块的虚拟地址。
struct scatterlist sglis[32];:散射列表,用于描述 DMA 操作中的内存分布情况,包括物理地址和大小等信息。
*/
struct adcdma_para
{
struct dma_chan *dma;
int buf_con;
int mem_switch;
int mem_flag;
int read_mem;
phys_addr_t adc_phys[32];
void __iomem *adc_virt[32];
struct scatterlist sglis[32];
};
/*
struct class *adcdma_class;:指向设备类的指针,用于创建设备文件。
struct device *adcdma_dev;:指向设备的指针,用于表示设备。
wait_queue_head_t read_queue;:用于实现阻塞等待的等待队列头部。
int irq_reprot;:IRQ 报告标志,用于标识是否接收到中断。
int trans_en;:传输使能标志,用于标识 DMA 是否启用。
dev_t t_dev;:设备号,用于标识设备。
struct gpio_desc *reset_gpio;:复位 GPIO 描述符,用于控制设备复位。
struct gpio_desc *enable_gpio;:使能 GPIO 描述符,用于控制设备使能。
struct adcdma_para *dmad;:指向 adcdma_para 结构体的指针,用于管理 DMA 参数。
int used;:设备使用标志,用于标识设备是否被使用。
*/
struct adcdma_fun
{
struct class *adcdma_class;
struct device *adcdma_dev;
wait_queue_head_t read_queue;
int irq_reprot;
int trans_en;
dev_t t_dev;
struct gpio_desc *reset_gpio;
struct gpio_desc *enable_gpio;
struct adcdma_para *dmad;
int used;
};
/*
CMD_GET_DMADATA:用于从设备中获取 ADC 数据的命令,对应的宏 _IO(ADC_IOCTL_MAGIC, 0) 表示没有参数的 IOCTL 命令。
CMD_SET_START:用于启动 ADC 传输的命令,对应的宏 _IO(ADC_IOCTL_MAGIC, 1) 表示没有参数的 IOCTL 命令。
*/
#define ADC_IOCTL_MAGIC 'M'
#define CMD_GET_DMADATA _IO(ADC_IOCTL_MAGIC, 0)
#define CMD_SET_START _IO(ADC_IOCTL_MAGIC, 1)
/*
定义了一个名为 irq_change_mem 的静态函数,用于处理中断并切换 DMA 内存块。下面是对函数的注释和解析:
*/
static void irq_change_mem(struct adcdma_para *dmap, int num)
{
struct adcdma_para *dmad_tmp = dmap; //创建一个临时指针 dmad_tmp,指向传入的 adcdma_para 结构体。
int next;
dmad_tmp->mem_flag |= (1 << dmad_tmp->mem_switch); //将当前内存块的标志位置为已使用,即将对应位置 1
next = (dmad_tmp->mem_switch + 1) % dmad_tmp->buf_con; //计算下一个要使用的内存块的索引
if ((dmad_tmp->mem_flag & (1 << next)) == 0) //检查下一个内存块是否已被使用
{
//如果未被使用,则切换到下一个内存块并调用 adcdma_setup 函数进行设置,并通过 dma_async_issue_pending 函数发出 DMA 异步操作请求。
dmad_tmp->mem_switch = next;
adcdma_setup(dmap, next);
dma_async_issue_pending(dmap->dma);
}
else
{
//如果已被使用,则打印错误信息
printk("mem change err %d\n", next);
}
}
/*
定义了一个名为 adcdma_irq_handler 的静态函数,用于处理 ADC DMA 的中断事件。下面是对函数的注释和解析
*/
static void adcdma_irq_handler(void *data)
{
int num = (int)data; //将 data 转换为整数类型,表示中断号
// printk("irq %d\n", num);
adcdma_data->irq_reprot = 1; //将 IRQ 报告标志位置为 1,表示接收到中断
if (adcdma_data->trans_en) //检查传输使能标志是否为真
{
//如果为真,则唤醒等待队列中的进程,并调用 irq_change_mem 函数切换 DMA 内存块
wake_up_interruptible(&adcdma_data->read_queue);
irq_change_mem(adcdma_data->dmad, num);
}
}
/*
定义了一个名为 adcdma_setup 的函数,用于设置 ADC DMA 的参数并提交 DMA 传输请求。根据 ADC_DMAINIT_S 宏的定义,函数会选择不同的初始化方式
*/
static int adcdma_setup(struct adcdma_para *dmap, int nfb)
{
struct dma_async_tx_descriptor *desc; //DMA 异步传输描述符,用于描述 DMA 传输的参数和状态
struct scatterlist *rx_sg = &dmap->sglis[nfb]; //散射-聚集列表(scatter-gather list,SG),用于描述 DMA 操作中的内存分布情况
struct dma_device *rx_dev = dmap->dma->device; //DMA 设备指针,用于表示 DMA 控制器的设备
enum dma_ctrl_flags flags; //DMA 控制标志,用于设置 DMA 控制参数
int ret = 0;
flags = DMA_PREP_INTERRUPT;
//如果 ADC_DMAINIT_S 宏为真,表示使用 dmaengine_prep_slave_single 函数进行初始化:
#if ADC_DMAINIT_S
desc = dmaengine_prep_slave_single(dmap->dma, dmap->adc_phys[nfb],
ADCDATA_SIZE, DMA_DEV_TO_MEM, flags); //用于准备一个单一的 DMA 传输,将数据从设备传输到内存。
if (!desc)
{
pr_err("Failed to prepare DMA descriptor\n");
return -ENOMEM;
}
else
{
//如果成功准备了 DMA 描述符,则将其提交给 DMA 引擎进行传输,并设置回调函数 adcdma_irq_handler 处理传输完成的中断
ret = dmaengine_submit(desc);
if (ret < 0)
printk("submit err\n");
desc->callback = adcdma_irq_handler;
desc->callback_param = (void *)nfb;
}
//如果 ADC_DMAINIT_S 宏为假,表示使用手动分配和映射内存的方式进行初始化:
#else
dmap->adc_virt[nfb] = kmalloc(ADCDATA_SIZE, GFP_KERNEL);
dma_addr_t adc_dma_rx;
dma_cookie_t rx_cookie;
memset(dmap->adc_virt[nfb], 0, ADCDATA_SIZE);
adc_dma_rx = dma_map_single(rx_dev->dev, dmap->adc_virt[nfb],
ADCDATA_SIZE, DMA_MEM_TO_DEV);
dma_unmap_single(rx_dev->dev, adc_dma_rx,
ADCDATA_SIZE,
DMA_MEM_TO_DEV);
adc_dma_rx = dma_map_single(rx_dev->dev, dmap->adc_virt[nfb],
ADCDATA_SIZE, DMA_DEV_TO_MEM);
sg_init_table(rx_sg, 1);
sg_dma_address(rx_sg) = adc_dma_rx;
sg_dma_len(rx_sg) = ADCDATA_SIZE;
desc = rx_dev->device_prep_slave_sg(dmap->dma, rx_sg, 1,
DMA_DEV_TO_MEM, flags, NULL);
rx_cookie = desc->tx_submit(desc);
desc->callback = adcdma_irq_handler;
desc->callback_param = (void *)nfb;
#endif
return 0;
}
static int data_to_user(struct adcdma_para *dmap, void __user *user_buf)
{
int size;
int ret = 0;
size = ADCDATA_SIZE;
ret = copy_to_user(user_buf, dmap->adc_virt[dmap->read_mem], size) ? -EFAULT : 0;
// if (adcdma_data->trans_en == 1)
// adcdma_setup(dmap,dmap->read_mem);
// dma_async_issue_pending(dmap->dma);
dmap->mem_flag &= ~(1 << dmap->read_mem);
// printk("ioctl free mem %d\n", dmap->read_mem);
dmap->read_mem = (dmap->read_mem + 1) % dmap->buf_con;
return ret;
}
/*
实现了设备驱动程序的 ioctl 函数,用于处理用户空间程序发起的设备控制命令
*/
static long adcdma_func_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
void __user *user_arg = (void __user *)arg; //将 arg 转换为用户空间指针,用于访问用户空间的数据
int flag;
int n;
switch (cmd)
{
//从设备获取 AD 数据
case CMD_GET_DMADATA:
ret = data_to_user(adcdma_data->dmad, user_arg);
break;
//启动 DMA 传输
case CMD_SET_START:
copy_from_user(&flag, user_arg, sizeof(flag));
printk("flag = %d\n", flag);
adcdma_data->dmad->mem_switch = 0;
adcdma_data->dmad->mem_flag = 0;
adcdma_data->dmad->read_mem = 0;
//根据 flag 设置传输使能标志,并根据需要进行 DMA 设置和传输发起
if (flag == 0)
{
adcdma_data->trans_en = 0;
dmaengine_terminate_all(adcdma_data->dmad->dma); //函数终止当前 DMA 通道上的所有传输,确保传输完全停止
}
else
{
adcdma_data->trans_en = 1;
ret = adcdma_setup(adcdma_data->dmad, 0);
dma_async_issue_pending(adcdma_data->dmad->dma); //发出 DMA 异步操作请求,开始数据传输
}
gpiod_set_value_cansleep(adcdma_data->enable_gpio, flag);
break;
default:
ret = -EFAULT;
break;
}
return ret;
}
/*
处理用户空间程序对设备文件的轮询操作
*/
static unsigned int adcdma_func_poll(struct file *file, struct poll_table_struct *wait)
{
int mask = 0; //初始化轮询事件掩码为 0
poll_wait(file, &(adcdma_data->read_queue), wait); //将当前进程添加到设备等待队列中,以便在设备事件发生时唤醒进程
if (adcdma_data->irq_reprot == 1) //如果 IRQ 报告标志为 1,则清除 IRQ 报告标志并设置轮询事件掩码为可读事件(POLLIN | POLLRDNORM)
{
adcdma_data->irq_reprot = 0;
mask |= (POLLIN | POLLRDNORM);
}
return mask;
}
/*
重置 ADC DMA 设备
*/
void adcdma_reset(void)
{
gpiod_set_value_cansleep(adcdma_data->reset_gpio, 0);
gpiod_set_value_cansleep(adcdma_data->enable_gpio, 0);
gpiod_set_value_cansleep(adcdma_data->reset_gpio, 1);
}
/*
打开设备文件
*/
int adcdma_func_open(struct inode *inode, struct file *file)
{
int ret = 0;
if (adcdma_data->used >= 1) //如果设备已被打开(used >= 1),则打印驱动程序忙的消息并返回 -EBUSY 错误码
{
printk("%s driver busy\n", file->f_path.dentry->d_iname);
return -EBUSY;
}
adcdma_data->used = 1;
adcdma_data->trans_en = 1;
// dmaengine_terminate_all(adcdma_data->dmad[i]->dma);
//将 DMA 参数重置,包括内存块切换、内存块标志和读取的内存块索引
adcdma_data->dmad->mem_switch = 0;
adcdma_data->dmad->mem_flag = 0;
adcdma_data->dmad->read_mem = 0;
// adcdma_reset();
return ret;
}
/*
关闭设备文件
*/
int adcdma_func_release(struct inode *inode, struct file *file)
{
int j = 0;
dmaengine_terminate_all(adcdma_data->dmad->dma); //终止当前 DMA 通道上的所有传输
adcdma_data->trans_en = 0;
adcdma_data->used = 0;
#if !ADC_DMAINIT_S
for (j = 0; j < adcdma_data->dmad->buf_con; j++)
kfree(adcdma_data->dmad->adc_virt[j]);
#endif
return 0;
}
const struct file_operations adcdma_fops =
{
.owner = THIS_MODULE,
.open = adcdma_func_open,
.release = adcdma_func_release,
.unlocked_ioctl = adcdma_func_ioctl,
.poll = adcdma_func_poll,
};
/*
初始化 ADC DMA 的字符设备
*/
int adcdma_cdev_init(struct adcdma_fun *pdata)
{
int rc;
struct cdev *c_dev;
rc = alloc_chrdev_region(&pdata->t_dev, 0, 1, "adcdma_fun"); //分配字符设备的主次设备号
if (rc)
goto out_err;
c_dev = cdev_alloc(); //分配一个字符设备结构体
if (!c_dev)
goto out_err;
cdev_init(c_dev, &adcdma_fops); //初始化字符设备结构体
rc = cdev_add(c_dev, pdata->t_dev, 1); //将字符设备添加到系统
if (rc)
goto out_unreg;
pdata->adcdma_class = class_create(THIS_MODULE, "adcdma"); //创建设备类
if (IS_ERR(pdata->adcdma_class))
{
printk("[err]class_create error\n");
rc = -1;
goto out_devdel;
}
pdata->adcdma_dev = device_create(pdata->adcdma_class, NULL, pdata->t_dev, NULL, "adcdma_fun"); //创建设备
if (!pdata->adcdma_dev)
{
rc = -1;
goto class_err;
}
return 0;
class_err:
class_destroy(pdata->adcdma_class);
out_devdel:
cdev_del(c_dev);
out_unreg:
unregister_chrdev_region(pdata->t_dev, 1);
out_err:
return rc;
}
/*
从设备树中读取 ADC DMA 的配置信息并初始化相关数据结构
*/
int of_adcdma_data(struct adcdma_fun *pdata, struct platform_device *pdev)
{
int cnt = 0;
int ret = 0;
int i = 0;
int hsize = 0;
pdata->reset_gpio = devm_gpiod_get_optional(&pdev->dev, "reset", //获取复位 GPIO,如果未找到则返回错误
GPIOD_OUT_LOW);
if (IS_ERR(pdata->reset_gpio))
{
printk("[zgq]get reset gpio err\n");
return PTR_ERR(pdata->reset_gpio);
}
pdata->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", //获取使能 GPIO,如果未找到则返回错误
GPIOD_OUT_LOW);
if (IS_ERR(pdata->enable_gpio))
{
printk("[zgq]get enable gpio err\n");
return PTR_ERR(pdata->enable_gpio);
}
adcdma_reset();
pdata->dmad = devm_kmalloc(&pdev->dev, sizeof(struct adcdma_para), GFP_KERNEL); //分配 DMA 参数结构体的内存
if (pdata->dmad == NULL)
{
printk("kmalloc err\n");
return -1;
}
memset(pdata->dmad, 0, sizeof(struct adcdma_para)); //将 DMA 参数结构体清零
pdata->dmad->dma = dma_request_chan(&pdev->dev, "adc"); //请求 DMA 通道
if (IS_ERR_OR_NULL(pdata->dmad->dma))
{
printk("get dma0 err\n");
return PTR_ERR(pdata->dmad->dma);
}
ret = of_property_read_u32(pdev->dev.of_node, //从设备树中读取缓存数量
"num-buf", &cnt);
if (ret < 0)
{
pr_err("adcdmatest: missing num-frm property\n");
return ret;
}
cnt = cnt > 32 ? 32 : cnt;
pdata->dmad->buf_con = cnt; //设置 DMA 参数结构体中的缓存数量
hsize = ADCDATA_SIZE;
#if ADC_DMAINIT_S
for (i = 0; i < cnt; i++)
{
/*
分配一段连续的内存,用于 DMA 传输,并返回虚拟地址和物理地址
&pdev->dev:设备结构体指针,用于指定分配内存的设备上下文。
PAGE_ALIGN(hsize):将 hsize 向上对齐到页大小的倍数,以保证分配的内存大小是页大小的整数倍。
&pdata->dmad->adc_phys[i]:用于存储分配到的物理地址的指针。
GFP_KERNEL:内存分配标志,表示在内核堆中分配内存,可能会阻塞进程。
*/
pdata->dmad->adc_virt[i] = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(hsize),
&pdata->dmad->adc_phys[i], GFP_KERNEL);
if (pdata->dmad->adc_virt[i] == NULL)
{
printk("can't alloc mem\n");
}
memset(pdata->dmad->adc_virt[i], 0, hsize);
}
#endif
init_waitqueue_head(&pdata->read_queue);
return 0;
}
/*
在设备被检测到时执行初始化操作
*/
static int adcdma_fun_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct adcdma_fun *pdata = dev_get_platdata(dev); //获取设备的平台数据指针
int ret = 0;
if (!pdata) //如果平台数据指针为空,则说明设备没有平台数据,需要进行初始化
{
pdata = devm_kzalloc(dev, sizeof(struct adcdma_fun), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
platform_set_drvdata(pdev, pdata); //将平台数据指针保存在设备结构体中,以便后续使用
}
adcdma_data = pdata;
ret = of_adcdma_data(pdata, pdev); //从设备树中读取配置信息并初始化设备
if (ret < 0)
goto out;
ret = adcdma_cdev_init(pdata); //初始化字符设备
if (ret < 0)
goto out;
out:
return ret;
}
/*
释放 DMA 缓存所占用的内存
*/
static void clean_dma(struct platform_device *pdev, struct adcdma_para *dmap)
{
int size;
int i = 0;
size = ADCDATA_SIZE;
#if ADC_DMAINIT_S
for (i = 0; i < dmap->buf_con; i++)
{
dma_free_coherent(&pdev->dev, PAGE_ALIGN(size),
dmap->adc_virt[i], dmap->adc_phys[i]);
}
#endif
}
/*
在设备被移除时执行清理操作
*/
static int adcdma_fun_remove(struct platform_device *pdev)
{
dma_release_channel(adcdma_data->dmad->dma);
clean_dma(pdev, adcdma_data->dmad);
device_unregister(adcdma_data->adcdma_dev);
unregister_chrdev_region(adcdma_data->t_dev, 1);
class_destroy(adcdma_data->adcdma_class);
return 0;
}
/*
定义了一个名为 adcdma_fun_of_match 的设备树匹配表,用于匹配设备树中的设备节点
*/
static struct of_device_id adcdma_fun_of_match[] = {
{
.compatible = "adcdma_demo",
},
{},
};
/*
定义了一个名为 adcdma_fun_device_driver 的平台驱动程序,用于管理设备的探测和移除
*/
static struct platform_driver adcdma_fun_device_driver = {
.probe = adcdma_fun_probe,
.remove = adcdma_fun_remove,
.driver = {
.name = "adcdma_demo",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(adcdma_fun_of_match),
}
};
static int __init adcdma_fun_init(void)
{
return platform_driver_register(&adcdma_fun_device_driver); //注册平台驱动程序
}
static void __exit adcdma_fun_exit(void)
{
platform_driver_unregister(&adcdma_fun_device_driver); //注销平台驱动程序
}
late_initcall(adcdma_fun_init);
module_exit(adcdma_fun_exit);
// module_platform_driver(adcdma_fun_device_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("uisrc");
应用代码dma_app.c,注意只有打开应用程序才会开始DMA传输,关闭程序会终止DMA传输,然后应用程序将AD数据以16进制形式打印在命令行
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.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;
// 打开 DMA 设备文件
fd = open("/dev/adcdma_fun", O_RDWR);
if (fd < 0)
{
printf("Can't open file fdma_fun\n");
return -1;
}
// 启动 DMA 数据传输
ioctl(fd, CMD_SET_START, &start);
while (1)
{
// 从 DMA 设备中读取数据
ret = ioctl(fd, CMD_GET_DMADATA, data);
if (ret < 0)
{
perror("Get data error\n");
break;
}
// 将数据按照 16 位进行打印
data_tmp = (uint16_t *)data;
for (i = 0; i < ADC_SIZE / sizeof(uint16_t); i += 8)
{
for (j = 0; j < 8; j++)
{
printf("%04X ", data_tmp[i + j]);
}
printf("\n");
}
}
// 停止 DMA 数据传输
start = 0;
ioctl(fd, CMD_SET_START, &start);
// 关闭 DMA 设备文件
close(fd);
return ret;
}
运行dma_app,成功在命令行打印数据,如下图所示
程序运行中可按Ctrl+C终止
本文章使用limfx的vscode插件快速发布