网易首页 > 网易号 > 正文 申请入驻

嵌入式使用DMA实现内存中数据传递

0
分享至

想学习单片机的同学可以关注、私信我或者在评论区回复我要入门。这一期我们主要介绍在内核中简单使用DMA实现内存数据传递。因为文章中没有介绍与框架相关的程序,只是使用字符设备来操作DMA,同时也没有抽象的层次,因此本文中代码分析部分会比较简单。但我还是会将文章分为两部分,第一部分我将介绍与DMA相关的知识。而第二部分讲解在内核中如何通过代码实现DMA的数据传递。

第一部分:DMA相关知识

计算机系统中各种常用的数据输入/输出方法有查询方式和中断方式,这些方式常见于CPU与慢速及中速外设之间的数据交换。但当高速外设要与系统内存或者要在系统内存的不同区域之间进行大量数据的快速传送时,就在一定程度上限制了数据传送的速率。直接存储器存取(DMA)就是为了解决这个问题而生的。

采用DMA方式,在一定时间段内,由DMA控制器取代CPU,获得总线控制权,来实现内存与外设或者内存的不同区域之间大量数据的快速传送。同时很重要的一点是当DMA传输数据时,并不占用CPU资源,在这个时候CPU可以空出手来做其他的事情。这样我们既可以做大量数据的高速传输又可以让CPU有时间和资源去做其他的事情。

在S3C2440A中,已经有了集成了DMA模块,可以用来传递高速传输数据。数据传输有三个要素:源,目的,长度。

在2440中我们的源与目的地选择有四种情况:

·1.源头和目的都在系统总线上

·2.源在系统总线,而目的在外部总线

·3.源在外部总线,而目的在系统总线

·4.源头和目的都在外部总线

2440中源与目的实际上是通信的双方,而这双方是通过请求DMA传递信息的,所以我们将上面这些向DMA发送请求的(不管是源还是目的)称为请求源。他们请求DMA来传输数据。而在2440中有四条通道来设置不同的请求源。

DMA模式介绍:

DMA service mode:single service&Whole service。前一模式下,一次DMA请求完成一项原子操作,并且transfer count的值减1。后一模式下,一次DMA请求完成一批原子操作,直到transfer count等于0表示完成一次整体服务。

DMA DREQ/DACK PROTOCOL:DMA请求和应答的协议有两种,Demond mode和 Handshake mode。

两者对Request和Ack的时序定义有所不同:

·在Demond模式下,如果DMA完成一次请求后如果Request仍然有效,那么DMA就认为这是下一次DMA请求,马上就会开始下一次的传输;

·在Handshake模式下,DMA完成一次请求后等待Request信号无效,如果Request无效,DMA会无效ACK两个时钟周期,再等待下一次Request。

下面我们来介绍DMA中数据传输的格式,注意,这里说的是传输格式,而不是传输的大小。在DMA中有两种传输格式,单元传输和burst4传输,相对于单元传输的每次读写一个单元,burst4可以一次完成四个单元的读写。单元代表的就是数据的大小有:字节,半字,字。

了解完上面的知识我们就可以了解总的数据的传输大小了:DSZ x TSZ x TC,其中DSZ就是上面说的数据的大小,TSZ是传输的格式,而TC是传输的次数。他们的乘积就是整个数据的大小了。

在我们的S3C2440中他是使用三态的有限状态机(FSM)来实现数据的传输的。虽然是使用的有限状态机,但2440中还有两种传输模式:单服务模式和全服务模式。

状态1,DMA等待DMA请求,此时DMA ACK和INT REQ为 0。当DMA收到DMA请求时,他跳转到状态2。

状态2,DMA ACK为1,INT REQ还为0,此时CURR_TC从DCON[19:0]从加载计数值。在他们完成后他跳到状态3。

状态3,引入子状态机用来处理一次原子操作,即完成一次数据从源读出然后写到目的中。而在这时我们就要分单服务模式和全服务模式来讨论了。在单服务模式中,子有限状态机完成一次原子操作后CURR_TC-1,主有限状态机将DMA ACK设为0,然后调回到状态1,然后等待下一次的DMA请求。而在全服务模式时,子有限状态机将一直运行直到CURR_TC为0,然后他再将INT REQ设为1而将DMA ACK设为0,然后调回到状态1,然后等待下一次的DMA请求。

我们下面就要讲一下在2440中DMA的配置步骤和要点了(主要针对寄存器):

1.数据从哪里来,到哪里去?

使用DMA首先我们要知道数据的流向,DISRCx寄存器是DMA初始源寄存器,存放了数据的源地址。DIDSTx是DMA的初始目的寄存器,存放数据的目的地址。

2.数据走的什么总线?地址是否是固定的?

我们还要知道源与目的数据存储设备在什么总线上(AHB系统总线,一般是高速的比如内存,APB外围总线,低速的比如SD,UART;具体走什么总线可以在datasheet上查到);以及数据传输结束以后起始地址还原到发送前的起始地址呢,还是在现在的末尾+1作为新的起始地址。这些设置在DISRCCx与DIDSTCx两个寄存器里面配置。

3.数据以什么方式传输?源于什么目的是什么呢?要不要自动重载?

需要确定数据的传输方式有请求还是握手,根据上面的总线确定与什么时钟同步(HCLK,PCLK),是单元传输还是突发传输,是以字节传输还是字传输,是否重载。是单服务(只发送一次)还是多服务(不停循环发送),以及数据的传送大小。选择源与目的设备。最后还要确定中断是不是传输结束发生(CURR_TC记数是不是0)。这些都在DCONx中设置。

4.怎么开始传输DMA和停止DMA,这些在DMASKTRIG中设置。

内核学习网站:

Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂ke.qq.com/course/4032547?flowToken=1040236

第二部分:在内核中如何通过代码实现DMA数据传递。

通过上面对DMA的介绍,我对DMA有了一定的认识。那么我将在这一部分用代码实现对DMA的简单控制。现在我写一下我在代码中要实现的功能:对比于使用和不使用DMA来传递内存中的数据,来确定DMA对于解放CPU的重要性。

我们将借助于字符设备驱动在他的操作函数中实现对DMA的控制。

所以我们可以大体总结出这个程序的写作步骤:

·1.确定主设备号

·2.写file_operations结构体,并在其操作函数中写出对DMA的控制

·3.在入口/出口函数中注册/注销这个字符驱动

·4.使用;udev机制在内核中创建字符设备

下面是dma_alloc_writecombine函数的介绍:

/*该函数只禁止cache缓冲,保持写缓冲区,也就是对注册的物理区写入数据,也会更新到对应的虚拟缓存区上*/void*dma_alloc_writecombine(structdevice*dev,size_tsize,dma_addr_t*handle,gfp_tgfp);//分配DMA缓存区//返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败,需要释放,避免内存泄漏//参数如下://*dev:指针,这里填0,表示这个申请的缓冲区里没有内容//size:分配的地址大小(字节单位)//*handle:申请到的物理起始地址//gfp:分配出来的内存参数,标志定义在,常用标志如下://GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存.从不睡眠.//GFP_KERNEL 内核内存的正常分配.可能睡眠.//GFP_USER 用来为用户空间页来分配内存;它可能睡眠.

好了,有了上面的知识我们就可以上代码了。我们还是按着老师的步骤来讲,我们先看入口函数中做了什么:

staticints3c_dma_init(void){

/*申请DMA内存 */

if(request_irq(IRQ_DMA3,s3c_dma_irq,0,"s3c_dma",1)){

free_irq(IRQ_DMA3,1);

printk("can't request_irq for DMA \n");

return-ENOMEM;

/*为源分配内存对应的缓存区 */

src=dma_alloc_writecombine(NULL,BUF_SIZE,&src_phys,GFP_KERNEL);

if(NULL==src){

printk("can't alloc buffer for src \n");

return-ENOMEM;

/*为目的分配内存对应的缓存区 */

dst=dma_alloc_writecombine(NULL,BUF_SIZE,&dst_phys,GFP_KERNEL);

if(NULL==dst){

printk("can't alloc buffer for dst \n");

dma_free_writecombine(NULL,BUF_SIZE,src,src_phys);

return-ENOMEM;

/*对DMA寄存器做重映射 */

dma_regs=ioremap(DMA3_BASE_ADDR,sizeof(structs3c_dma_regs));

/*注册字符设备 */

auto_major=register_chrdev(auto_major,"s3c_dma",&dma_fops);

/*创建设备节点 */

cls=class_create(THIS_MODULE,"s3c_dma");

class_device_create(cls,NULL,MKDEV(auto_major,0),NULL,"s3c_dma");

return0;}

从上面看我们做了:

·1.申请DMA内存

·2.为源分配内存对应的缓存区

·3.为目的分配内存对应的缓存区

·4.对DMA寄存器做重映射

·5.注册字符设备

·6.创建设备节点

在上面我们分配为源和目的分配内存对应的缓冲区,并对2440中DMA相关的寄存器做了重映射,这是为下面对DMA的操作做准备,而与DMA相关的寄存器我们将他们放到了一个结构体中,这样方便我们的调用:

#define DMA0_BASE_ADDR 0x4b000000 /* DMA0寄存器的基地址 */#define DMA1_BASE_ADDR 0x4b000040 /* DMA1寄存器的基地址 */#define DMA2_BASE_ADDR 0x4b000080 /* DMA2寄存器的基地址 */#define DMA3_BASE_ADDR 0x4b0000c0 /* DMA3寄存器的基地址 */structs3c_dma_regs{

unsignedlongdisrc;

unsignedlongdisrcc;

unsignedlongdidst;

unsignedlongdidstc;

unsignedlongdcon;

unsignedlongdstat;

unsignedlongdcsrc;

unsignedlongdcdst;

unsignedlongdmasktrig;};

staticvolatilestructs3c_dma_regs*dma_regs;

上面就是我们定义的DMA寄存器的基地址和寄存器结构体。我们会根据DMA请求源的不同而调用不同的DMA基地址。从而操作不同DMA的寄存器,在本实例中我们使用DMA3。

下面我们看file_operations:

staticstructfile_operationsdma_fops={

.owner=THIS_MODULE,

.ioctl=s3c_dma_ioctl,

因为这个只是一个简单的例子,所以我们并没有做太多的操作函数,而只是使用ioctl这个函数来实现我们上面说的:对比使用和不使用DMA来实现内存数据的拷贝。下面我们进ioctl中看看他是怎么实现的:

ints3c_dma_ioctl(structinode*inode,structfile*file,unsignedintcmd,unsignedlongarg){

inti;

staticintcnt;

memset(src,0xaa,BUF_SIZE);

memset(dst,0x55,BUF_SIZE);

switch(cmd){

caseMEM_CPY_NO_DMA:

for(i=0;i<BUF_SIZE;i++){

dst[i]=src[i];

if(++cnt>=30){

cnt=0;

if(memcmp(src,dst,BUF_SIZE)==0){

printk("MEM_CPY_NO_DMA ok \n");

}else{

printk("MEM_CPY_NO_DMA file \n");

break;

caseMEM_CPY_DMA:

/*把源,目的,长度告诉DMA */

/*源的物理地址 */

dma_regs->disrc=src_phys;

/*源位于AHB总线,原地址递增 */

dma_regs->disrcc=(0<<1)|(0<<0);

/*目的的物理地址 */

dma_regs->didst=dst_phys;

/*目的位于AHB总线,目的地址递增 */

dma_regs->didstc=(0<<2)|(0<<1)|(0<<0);

/*使能中断,单个传输,软件触发,读写单位为byte */

dma_regs->dcon=(1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0);

/*启动DMA */

dma_regs->dmasktrig=(1<<1)|(1<<0);

/*如何知道DMA什么时候完成,用中断函数通知 */

/*休眠 */

ev_dma=0;

wait_event_interruptible(dma_waitq,ev_dma);

if(++cnt>=30){

cnt=0;

if(memcmp(src,dst,BUF_SIZE)==0){

printk("MEM_CPY_DMA ok \n");

}else{

printk("MEM_CPY_DMA file \n");

break;

return0;}

通过switch语句判断,当我们从应用程序的ioctl中接收到cmd并将其传输到我们驱动中的ioctl中,从而判断是MEM_CPY_NO_DMA还是MEM_CPY_DMA。当cmd为MEM_CPY_NO_DMA时,我们不使用DMA而是用CPU来将数据从源复制到目的,而当cmd为MEM_CPY_DMA时,我们使用DMA将源中的数据拷贝到目的。

讲解到这里,我想很多人要问了,我们既然写了ioctl,那么我们去哪里使用这个ioctl函数呢?

所以我们还要写一个测试程序在应用层测试这个驱动程序。而具体的测试程序为:

/**用法:* ./s3c_dma_test */

#include#include#include#include#include#define MEM_CPY_NO_DMA 0#define MEM_CPY_DMA 1voidprint_usage(char*data){

printf("usage: \n");

printf("%s \n",data);}

intmain(intargc,char**argv){

intfd;

/*判断输入的参数是不是两个 */

if(2!=argc){

print_usage(argv[0]);

return-1;

/*打开DMA设备 */

fd=open("/dev/s3c_dma",O_RDWR);

if(fd<0){

printf("can't open /dev/s3c_dma . \n");

/*判断参数并调用ioctl函数 */

if(strcmp(argv[1],"nodma")==0){

while(1){

ioctl(fd,MEM_CPY_NO_DMA);

}elseif(strcmp(argv[1],"dma")==0){

while(1){

ioctl(fd,MEM_CPY_DMA);

}else{

print_usage(argv[0]);

return0;}

本期先分享到这里,想要进群学习单片机编程的同学可以私信我,回复“我要入门”,与我们一起成长,喜欢的可以点个赞关注我们!

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
曝米哈游36岁员工正月初八猝死 家属:公司仅给3万精神补贴

曝米哈游36岁员工正月初八猝死 家属:公司仅给3万精神补贴

六子吃凉粉
2026-02-27 12:18:03
福特号厕所为何炸了?真相在细节!

福特号厕所为何炸了?真相在细节!

环球策论
2026-02-26 21:28:15
豪取11连胜,打破NBA尘封79年神纪录!3大细节证明圣城马刺回来了

豪取11连胜,打破NBA尘封79年神纪录!3大细节证明圣城马刺回来了

锅子篮球
2026-02-27 14:55:39
2018年,刘美贤和父亲参加活动的合影,那时候尚未成年,一脸青涩

2018年,刘美贤和父亲参加活动的合影,那时候尚未成年,一脸青涩

东方不败然多多
2026-02-27 15:18:59
请假2小时被开除后续:店主真容曝光社死,黑历史被扒,已找律师

请假2小时被开除后续:店主真容曝光社死,黑历史被扒,已找律师

离离言几许
2026-02-26 16:16:45
最可惜的十位革命先烈,每一位都足以改变历史

最可惜的十位革命先烈,每一位都足以改变历史

【历史客栈】
2026-02-25 10:00:31
青海尖扎大桥垮塌致16人死亡,应急管理部披露事故细节:物资采购存在严重漏洞,几颗不合格的螺丝导致事故发生

青海尖扎大桥垮塌致16人死亡,应急管理部披露事故细节:物资采购存在严重漏洞,几颗不合格的螺丝导致事故发生

极目新闻
2026-02-27 17:14:40
俄乌冲突四周年:战场上的那个“傻士兵”,得到了双方的怜悯眷顾

俄乌冲突四周年:战场上的那个“傻士兵”,得到了双方的怜悯眷顾

一纸情书s
2026-02-25 22:44:24
中虎跳峡游客落水事故目击者:同行女子称他们“马上回去就要结婚的”消防仍在搜救

中虎跳峡游客落水事故目击者:同行女子称他们“马上回去就要结婚的”消防仍在搜救

红星新闻
2026-02-27 14:14:12
你在闲鱼上买过什么好东西?网友:人类对咸鱼的开发不足1%

你在闲鱼上买过什么好东西?网友:人类对咸鱼的开发不足1%

另子维爱读史
2026-01-09 21:12:13
王楚钦承认赢得侥幸!林诗栋14-16惜败后轰11-3 向鹏遭林昀儒横扫

王楚钦承认赢得侥幸!林诗栋14-16惜败后轰11-3 向鹏遭林昀儒横扫

林子说事
2026-02-27 16:40:31
外交部:中方正在密切关注巴阿冲突局势发展

外交部:中方正在密切关注巴阿冲突局势发展

界面新闻
2026-02-27 15:36:44
你见过哪些闷声发大财的人?网友:干这个买三套房子,两个门面

你见过哪些闷声发大财的人?网友:干这个买三套房子,两个门面

夜深爱杂谈
2026-02-01 18:57:04
越干净越易过敏?Nature研究揭示:环境的“脏”在默默训练你的免疫系统

越干净越易过敏?Nature研究揭示:环境的“脏”在默默训练你的免疫系统

生物世界
2026-02-26 12:06:47
1.2亿天价狂飙!曼城弃子逆袭切尔西,谁能看懂这波封神?

1.2亿天价狂飙!曼城弃子逆袭切尔西,谁能看懂这波封神?

卿子书
2026-02-27 08:54:16
日本排放核水后,奇怪的事发生了:刚开始各国对海鲜避之不及

日本排放核水后,奇怪的事发生了:刚开始各国对海鲜避之不及

百态人间
2026-02-26 15:21:28
红楼梦:难怪宝玉第一次“干人事”要找袭人,看袭人做了什么动作

红楼梦:难怪宝玉第一次“干人事”要找袭人,看袭人做了什么动作

谈史论天地
2026-02-26 11:13:38
很多人都不知道陈皮茶怎么喝,看看这个就知道了!十款搭配

很多人都不知道陈皮茶怎么喝,看看这个就知道了!十款搭配

健康之光
2026-02-26 17:15:04
顶风作案!上海警方:大学生汤某,刑拘!已干了50多次……

顶风作案!上海警方:大学生汤某,刑拘!已干了50多次……

环球网资讯
2026-02-27 07:24:21
行纳粹礼被皇马制裁球迷:我有两个黑人小孩,我不知道纳粹是什么

行纳粹礼被皇马制裁球迷:我有两个黑人小孩,我不知道纳粹是什么

懂球帝
2026-02-27 09:25:21
2026-02-27 17:51:00
小伙单片机编程
小伙单片机编程
想学单片机的看过来,带你入门
75文章数 264关注度
往期回顾 全部

科技要闻

单张不到五毛!谷歌深夜发布Nano Banana 2

头条要闻

女子遇诈骗怎么也学不会操作 结果骗子当场被"整破防"

头条要闻

女子遇诈骗怎么也学不会操作 结果骗子当场被"整破防"

体育要闻

一场必须要赢的比赛,男篮何止击败了裁判

娱乐要闻

继网暴谷爱凌后 美国欲没收其全部收入

财经要闻

沈明高提共富建议 百姓持科技股国家兜底

汽车要闻

岚图泰山黑武士版3月上市 搭载华为四激光智驾方案

态度原创

时尚
健康
教育
数码
亲子

今年春天最美搭配:西装+半裙,怎么穿都好看!

转头就晕的耳石症,能开车上班吗?

教育要闻

未雨绸缪?多地明确:开学不强制作业检查,不得因作业未完成处罚学生!你家寒假作业怎样了?

数码要闻

内存短缺,英伟达AI工作站涨价了

亲子要闻

压岁钱怎么打理❓4种方式帮娃提高财商❗️

无障碍浏览 进入关怀版