[基础知识]什么叫做DMA?
DMA=Direct Memory Access。这是一种通过硬件实现的数据传输机制。简单的说,就是不在CPU的参与下完成数据的传输。
[/基础知识]
不太明白?我举个简单的例子:
比如有个数组a,我希望把这个数组中的内容传输到另一个数组b中。我们假设这两个数组都是一样大。比如int a[10000];int b[10000];。
那么我可以这样做:
[code=c]for(int x=0;x<sizeof(a)/sizeof(int);x++){
    b[x]=a[x];
}
[/code]
循环将数组中的每个元素进行传递。这是最简单的一种方法,也是最容易理解的方法。
不过这种方法虽然简单,效率可算不上高。如果你了解微机原理和汇编的话就明白了,b[x]=a[x];这句话并非像你看到的那样,把a[x]中的元素值赋给b[x]。
那是怎么一个过程?
实际上是这样的:首先a[x]中的元素值赋给某CPU中的寄存器,然后再将该寄存器的值赋给b[x]。
为什么会这样?
这是因为a和b都是在内存中的,而CPU不允许内存直接进行数据传输。所以在这个过程中CPU必须参一脚当中介。
可想而知每赋值一次都要中介,效率就这么被降下去了。
既然问题是出在CPU当中介这个地方,那么有什么方法可以回避掉这个瓶颈呢?有。那就是DMA。
DMA是一种硬件设备。这种设备的工作原理是这样的:
——首先CPU告诉DMA设备,要有一堆数据需要传输,为了效率而请它出马。(DMA请求)
——DMA收到CPU的消息,开始准备。此时CPU把数据源地址、数据目标地址、传输数据量、传输模式等等参数告诉它。(DMA初始化)
——DMA初始化完,向CPU发送消息“借你的总线用一用,我要开始传输数据了!”(总线出借,DMA启动)
——CPU收到消息后,暂时切断自己与总线的联系。DMA开始传输数据。(DMA数据)
——DMA传输完数据之后,向CPU发送消息“搞定了!总线还给你。”(总线归还)
——CPU说:“干得好!老将出马一个顶俩!辛苦了,你先歇着吧。”DMA设备停止。CPU该干啥干啥。
由于是硬件实现的,所以DMA的速度非常快。快到什么程度呢?在DS上,尤其是数据量非常大的时候,相比于CPU当中介,效率能够提高一百万倍以上。
由于DMA的速度是如此之快,所以大量的数据传输,一般都要求使用DMA。
那么,刚才那个例子就可以写成:
[code=c]dmaCopy((void*)a,(void*)b,sizeof(a));
[/code]
但是DS是个很特殊的平台,在某些情况下,DMA不适用。有些内存区域,DMA是无法访问的。那就是BIOS、TCM和Cache。

[基础知识]BIOS是一块被硬件保护的内存区域。这块区域正常情况下是“读写保护”。也就是说,采用正常的方法是无法访问这块内存的。自然,DMA也无法访问。直接读取BIOS,读出来的全是随机的数据。
那么,我想DUMP BIOS,该怎么做呢?
这就需要一些技巧了。现在我先不说,到教程的最后我再把这个坑给填了。
[/基础知识]
[基础知识]TCM=Tightly Coupled Memory。这是一种高速缓存,据说是被直接集成在CPU芯片中。DS有两种TCM,分别是ITCM(Instruction TCM)和DTCM(Data TCM)。不用解释你们也能知道这两种TCM是干啥用的。
[/基础知识]
由于是高速缓存,所以这两块内存区域被当做特殊的用途。比如某些对时间要求非常严格的代码,就可以被放到ITCM中执行。这可以有效地提高运行速度。某些需要频繁存取的数据,也可以放到DTCM中以节省存取时间。
怎么样把代码放到ITCM中?有两种方法。一种是使用gcc特有的“属性标签”,将指定代码赋予“ITCM”属性,此时该代码会被载入ITCM中执行。还有一种方法是直接将.c源文件改成.itcm.c,此时源文件会被直接编译成在ITCM中运行的目标文件。
而DTCM就方便得多了。虽然两个TCM都是可映射的,也就是说,它们的地址并非固定,但是NDSLIB的lnkscript将这两块TCM映射到了0x01000000和0x0B000000上。既然已经有了固定地址,那么就可以很轻松地访问了。不过,正如刚才所说的,这两块内存空间都是有特殊用途的,所以不建议直接访问。相比于ITCM来说,DTCM更加重要。因为在这块内存中,存在着一个非常重要的对象——栈。
“栈”这种东西我也不详细解释了。局部变量和函数调用的参数,就是靠栈进行传递的。
由于DMA无法访问TCM,所以也就无法访问栈。又由于局部变量是被开辟到栈中,所以DMA也无法对局部变量进行传递。举个例子:
我要往主引擎的标准调色盘中填充随机颜色。
下面的代码就是错误的:
[code=c]void fillRandomColorToMainPalette(){
    u16 tmpPalette[256];
    dmaCopy((void*)tmpPalette,(void*)BG_PALETTE,sizeof(tmpPalette));
}
[/code]
原因很简单,tmpPalette中的数据虽然是随机的,但这个数组是局部变量,被开辟在栈中,DMA无法访问。
下面则是正确方法:
[code=c]void fillRandomColorToMainPalette(){
    u16 tmpPalette[256];
    memcpy((void*)BG_PALETTE,(void*)tmpPalette,sizeof(tmpPalette));
}
[/code]
memcpy不是需要CPU参与吗?那岂不是很慢?
是的。很慢,相比于DMA来说慢多了。不过,目前我们只能用它。教程的最后我将教你一种又快又安全的方法。

[基础知识]什么是Cache?
众所周知CPU的速度非常快。当CPU访问外设的时候,有些外设速度比较慢,响应CPU比较迟钝。此时CPU要么等外设响应,要么继续干它的活等外设的中断信号。
但是有些外设是没有中断的。此时CPU就必须等了。最典型的例子就是内存。
当CPU访问内存的时候,并非像你想象的那样,CPU立刻就能访问到它想访问的内存空间,而是有一个“WaitState”的过程。想想看吧,每访问一次内存都要等上几个机器周期,这可不是个好事~~~尤其是,这个“几”可不是简单的一位数,有些时候甚至能达到3位数。
那么这个问题又该怎么解决呢?那就是Cache了。
Cache是集成在CPU内部的极高速的缓存。注意关键词“极高速”。一般来说,它的访问速度几乎可以媲美CPU。这就意味着,CPU在访问Cache的时候几乎不会浪费多少时间。不过,速度的提升是用容量作为代价的。Cache的容量很小。在DS中,数据缓冲(Data Cache,简写为DC)只有4K,指令缓冲(Instruction Cache,简写为IC)只有8K。
[/基础知识]
那么,我们把常用的数据放到Cache中,CPU在访问的时候直接访问Cache就行了,不用耗费时间去访问内存了。
事实上CPU就是这么做的。在读内存的时候,CPU首先读Cache,看看有没有它想要的数据的“副本”,有的话那就太好了,直接拿过去用。没有的话就只好费点功夫去读内存了。而在写内存的时候,CPU直接写到Cache中,而非直接写到内存中。
哪尼?
确实是这样子的。当Cache写满了之后,此时才将Cache中的数据更新到内存,同时清空Cache。就像寄信一样,所有的信件会首先攒到邮局,到达一定数量之后才会送出去。
不过这又出现一个问题:假如Cache中有某个内存数据的“副本”,那么CPU在读该内存的时候就会直接使用该副本而不用去读内存。那万一内存中的数据被改写,此时CPU再读该内存,读出来的岂不是那个旧的副本而不是最新的内存数据?同样,假如我想DMA一些数据,谁能保证此时内存中的数据就是最新的数据?
某人:那啥,我直接读写Cache得了。鸟内存真它奶奶的麻烦!
很可惜,Cache是完全的黑箱。你不知道它的地址。你也无法直接访问它。
那该怎么办呢?幸好NDSLIB为我们提供了一些函数:
[code=c]// 将整个Data Cache更新到内存
void DC_FlushAll()
// 将Data Cache中指定地址指定大小的区域更新到内存
void DC_FlushRange(const void *base, u32 size)

// 清空整个Data Cache
void DC_InvalidateAll()
// 清空Data Cache中指定地址指定大小的区域
void DC_InvalidateRange(const void *base, u32 size)

// 清空整个Instruction Cache
void IC_InvalidateAll()
// 清空Instruction Cache中指定地址指定大小的区域 
void IC_InvalidateRange(const void *base, u32 size)
[/code]
那么,什么时候使用这些函数呢?
在DMA之前,我需要保证数据源内存中的数据是最新的。所以此时需要Flush,从而使DC中的副本能够更新到内存中。
在DMA之后,我需要保证DC中的副本和内存中的数据是相同的。但是NDSLIB没有更新DC的函数,所以没办法,我们只能把DC中的副本杀掉。此时如果CPU访问内存,由于DC中没有副本,所以就只能直接从内存访问并将访问到的值作为DC中副本了。所以此时需要Invalidate。
至于是用All还是Range,很明显,All的话肯定最保险,但肯定也会更花时间。所以如果你有把握,那就用Range以节省时间;没把握就用All求稳妥。

注意,模拟器在模拟Cache上相当不完善。根据我的观察,三大著名模拟器——NO$GBA、desmume和ideas,都无法正确模拟Cache。所以如果你发现在模拟器上图像正常而到了真机上出现破碎、混乱、颜色异常等等问题,设法把你的DMA函数前后加上DC_Flush...(...);和DC_Invalidate...(...);。

[填坑]正如我所说的,DMA非常之快。一般情况下的数据传输,靠它没问题。不过有些场合DMA无法访问,我又想要快,那该怎么办呢?有请swiCopy和swiFastCopy!
[code=c]#define COPY_MODE_HWORD  (0)
#define COPY_MODE_WORD  (1<<26)
#define COPY_MODE_COPY  (0)
#define COPY_MODE_FILL  (1<<24)
void swiCopy(const void * source, void * dest, int flags);
void swiFastCopy(const void * source, void * dest, int flags);
[/code]
这两个函数是所谓的“BIOS软中断”,又称“系统调用”。你可以把他看成是GBA/DS特有的函数。
这两个函数非常神奇,在GBA中,它们比DMA更快!不过很可惜,在DS中则一点都不快,甚至比memcpy还慢。
这两个函数没有区域限制。不管是BIOS还是内存,不管是ITCM还是DTCM,都能直接访问。
现在我们来改写一下那个随机调色盘的例子:
[code=c]void fillRandomColorToMainPalette(){
    u16 tmpPalette[256];
    swiCopy((void*)tmpPalette,(void*)BG_PALETTE,COPY_MODE_WORD|COPY_MODE_COPY|(sizeof(tmpPalette)>>2));
}
[/code]
注意最后一个参数。它的单位是WORD,即4字节。因此数据大小需要除以4以转换成WORD。
swiFastCopy则比swiCopy更快。你若是追求更高的速度也可以换成swiFastCopy。不过注意,这个函数只能传输半字宽度,也就是2字节的数据,因此不能使用COPY_MODE_WORD这个模式。但是传输数据大小依然以WORD为单位。
[/填坑]
最后我们把坑填了吧。这是DUMP ARM9的BIOS的函数:
[code=c]
#define BIOS_ADDRESS 0xFFFF0000
#define BIOS_SIZE 32768
void dumpARM9BIOS(){
    void* tmpBuffer=calloc(BIOS_SIZE,1);
    swiCopy((void*)BIOS_ADDRESS,(void*)tmpBuffer,COPY_MODE_WORD|COPY_MODE_COPY|(BIOS_SIZE>>2));
    DC_FlushAll();
    FILE* f=fopen("arm9.bios","wb+");
    if(f!=NULL)fwrite(tmpBuffer,BIOS_SIZE,1,f);
    fclose(f);
    free(tmpBuffer);
}
[/code]

关于DMA和它的仇家的更多相关文章

  1. STM32基于HAL库通过DMA读写SDIO

    通过STM32CUBEMX生成DMA读写sdio的工程,再读写过程中总会卡死在DMA中断等待读写完成的while中,最终发现while等待的标志在SDIO的中断里置位的,而SDIO中断优先级如果小于或 ...

  2. z-stack协议uart分析(DMA)

    1.从ZMain里面的main函数开始分析 2.进入int main( void ); HalDriverInit();   //硬件相关初始化,有DMA初始化和UART初始化 3.进入HalDriv ...

  3. STM32之DMA+ADC

    借用小甲鱼的经典:各位互联网的广大网友们.大家早上中午晚上好..(打下小广告,因为小甲鱼的视频真的很不错).每次看小甲鱼的视频自学都是比较轻松愉快的..我在想,如果小甲鱼出STM32的视频,我会一集不 ...

  4. STM32F103之DMA

    一.背景: 需要使用STM32的DAC,例程代码中用了DMA,对DMA之前没有实际操作过,也很早就想知道DMA到底是什么,因此,看了一下午手册,代码和网上的资料,便有了此篇文章,做个记录. 二.正文: ...

  5. ASM:《X86汇编语言-从实模式到保护模式》越计卷:实模式下对DMA和Sound Blaster声卡的控制

    说实话越计卷作者用了16页(我还是删过的),来讲怎么控制声卡,其实真正归纳起来就那么几点. ★PART1:直接存储访问 1. 总线控制设备(bus master) 在硬件技术不发达的早期,处理器是最重 ...

  6. 【SPI】Polling Interrupt DMA

    三種將資料在I/O間傳送的方法有 1. Polling2. Interrupt-driven I/O3. DMA(Direct Memory Access) Polling:最簡單的方式讓I/O de ...

  7. STM32——DMA接收和发送的实现

    最近写程序,需要一段一段数据的接收,再通过其他串口发送出去. 老司机们建议用DMA通信,以节约CPU资源.然后,我听了,发现挺好用的.特此,把自己写的代码贴上了. DMA发送接收的步骤如下: 1.初始 ...

  8. include/asm/dma.h

    /* $Id: dma.h,v 1.7 1992/12/14 00:29:34 root Exp root $ * linux/include/asm/dma.h: Defines for using ...

  9. DMA控制器

    DMA控制器依赖于平台硬件,这里只对i386的8237 DMA控制器做简单的说明,它有两个控制器,8个通道,具体说明如下: 控制器1: 通道0-3,字节操作, 端口为 00-1F 控制器2: 通道 4 ...

随机推荐

  1. tzwhere模块 根据经纬度判断时区

    先说一说这个问题的误区: 1: 根据地理常识,我们知道时区有24个,经度/15=商+余数,此时的商就是是时区,给大组长说了一下,不沾边 又过了几天 2:发现django自带的模块timezone,也叫 ...

  2. nignx 启动,重启命令

    1.查看进程号  ps -ef|grep nginx 强制停止 [root@LinuxServer ~]# pkill -9 nginx 重启 1.验证nginx配置文件是否正确 方法一:进入ngin ...

  3. 基于Web的制造追溯系统DEMO

    写在前面 本文不贴任何代码,以图片为主,也许图片更直观,请各位园友谅解! 很久没有写过博客了,这是2016年的第一篇文章:最近主要是忙着完成公司的一个新项目,逛园子的时间都明显少了,只有下班回到家睡觉 ...

  4. Javascript学习笔记:3种定义函数的方式

    ①使用函数声明语法定义函数 function sum(num1,num2){ return num1+num2; } ②使用函数表达式定义函数 var sum=function(num1,num2){ ...

  5. validate插件深入学习-04自定义验证方法

    自定义验证方法 jQuery.validator.addMethod(name,method,[,message]) name: 方法名 method: function(value,element, ...

  6. 使用vs2010创建、发布、部署、调用 WebService

    原文地址:使用vs2010创建.发布.部署.调用 WebService作者:吴超 一 使用vs2010创建 WebService 1 打开VS2010,菜单    文件->新建->项目2 ...

  7. AWT编程学习01(未完成)

    本文资料来源:<java疯狂讲义> 作者:李刚 终于要学习这一部分了~~虽然很多人(明明是绝大多数的人)说学这个没有用...而且有点过时了...但我觉得很有意思啊...感兴趣就学~~反正多 ...

  8. centos各版本信息

    CentOS version Architectures RHEL base Kernel CentOS release date RHEL release date Delay (days) 2.1 ...

  9. 自定义scrollview右侧的滑动条

    在做这个功能之前我其实是拒绝的,为什么这么说呢,因为我怕麻烦啊!(开玩笑的,怕麻烦就不做程序员了) 很久没有写博客,这次刚好项目中有个有趣的东西,想拿出来分享一下,希望能帮到某些小伙伴. 首先说说需求 ...

  10. Apache Commons Lang

    http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/package- ...