[基础知识]什么叫做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. 要慎用mysql的enum字段的原因

    背景:时下都流行enum类型的使用tinyint,那enum就真没有用的价值了么? PHP低级编程的兄弟是这样来看这个问题的,我作下笔录如下,期望能客观的理解这个enum字段的优点及缺点: 膘哥观点: ...

  2. 百度-official

    1.请描述html5新增的一些标签,描述这些标签的用法和语义 2.css属性position的属性值有哪些,描述它们的作用 3.常见的浏览器端的存储技术有哪些,以及它们的优缺点 4.程序定义如下: v ...

  3. SVN使用手册

    安装Tortoise SVN Icon TortoiseSVN 1.7版本及之后与之前的版本有很大的变化,主要区别如下: 1.7以前的版本会在每个目录中生成一个.svn的隐藏目录.1.7及以后的版本, ...

  4. HTML基础(3)

    1.块元素和内嵌元素(block\inline) 块的特征: 独占一行 不设定宽度,宽度将撑满整行 能设置所有样式 内嵌的特征: 默认同行可以继续跟同类型标签 内容撑开宽度 不支持宽高 不支持上下的m ...

  5. Nginx模块之———— RTMP模块 统计某频道在线观看流的客户数

    获得订阅者人数,可以方便地显示观看流的客户数. 查看已经安装好的模块 /usr/local/nginx/sbin/nginx -V 安装从源编译Nginx和Nginx-RTMP所需的工具 sudo a ...

  6. NTFS系统的ADS交换数据流

    VC++ 基于NTFS的数据流创建与检测 What are Alternate Streams?(交换数据流) NTFS alternate streams , 或者叫streams,或者叫ADS(w ...

  7. 在后台启动受管服务器经常报错:Server may already be running

    报错如下: 1. Unable to obtain lock on /usr/local/odrive/odrive_chen/Middleware/user_projects/domains/oim ...

  8. OpenCV学习笔记(一)——OpenCV安装

    1.无脑安装以下安装文件 cn_visual_studio_2010_ultimate_x86_dvd_532347.iso 2.测试Hello OpenCV 文件→新建→项目 win32应用程序→下 ...

  9. ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

    在安装好的MySQL服务器上,配置了环境变量之后,发现用mysql无法登录,报如题的错误,实在没有办法,决定用安全模式对root用户修改密码: 首先关闭正在运行的MySQL; 在一个终端窗口运行命令: ...

  10. [教程] [承風雅傳HSU]用ES4封裝Win7---ES4 Win7封裝教程(未完待續)

    [教程] [承風雅傳HSU]用ES4封裝Win7---ES4 Win7封裝教程(未完待續) a10036it 发表于 2015-7-27 21:11:19 https://www.itsk.com/t ...