掌叔
2009-03-08 19:34:16
摘自:[url]http://blog.sina.com.cn/niubods[/url]
作者:niubo
这几天研究了一下背景和精灵方面的东西,重点研究了一下寄存器。虽然libnds 1.3.1已经给出了一些API函数,避免了直接跟寄存器打交道,但是出于好奇还是很想知道一些底层硬件机制。
在接下来写DS的图像以及背景、精灵之前,我想再解释一些概念,还有一些常规C语言里很少用但在DS程序里很常见的用法。开始想在用到的时候顺手解释一下,但是发现那样的话会很啰嗦,不如直接单拿出来。
另外,发现DS和GBA编程,在理念和架构上真的非常相似,我很多概念上的疑惑都是通过查看GBA编程资料才得以明了。而且GBA编程比DS编程历史悠久,资料也相对丰富些,觉得DS资料太匮乏的话找些GBA编程资料来看也是个不错的选择,对DS编程很有参考价值。
地址
这个概念说实话我也不是很了解,不过可以肯定的是DS使用“存储映像I/O(memory-mapped I/O)”的方法实现CPU和外围设备的输入输出。存储映像I/O就是用同一条地址总线对存储器和I/O设备编址。这样就可以直接通过CPU指令来访问存储器和I/O设备了。
而我们通常说的内存在DS上其实指的是地址从0x04000000开始的到0x43FFFFF这4M大小的区域,称为“主存储器”。虽然我知道神游DS有10M内存,但是编译器肯定按4M内存对变量分配地址,多出来的那部分内存估计是没法用了。
其他还有“控制寄存器”,“显存”等等。我不知道在物理角度这些存储器都在什么芯片上,这已经超出我的研究范围了。
寄存器
这里说的寄存器是I/O寄存器,注意不要和汇编语言里的cpu寄存器搞混了。
DS除了CPU之外还有很多外围设备,比如内存、液晶屏、触摸板、按键之类。从编程角度来看,我们可以通过操作相应的寄存器控制相关硬件的工作方式。
DS的存储空间有限,这些寄存器都是划分出一位或一段位域来作为控制开关。要操作寄存器,可以通过以下几种方法:
1.直接通过地址进行赋值。形如:(*(vu32*)0x04000240) = 0xXXXXXXXX。这样的语句基本上相当于机器码,可读性非常差,没人会用吧。
2.通过宏定义进行赋值。使用宏定义把数字地址变成字符,给寄存器一个易懂的名字。不过这里我遇到了些问题,对控制寄存器的宏定义有两套命名法,一套是GBATEK式命名,继承自当时GBA程序的定义,特点就是名字前有REG_这个前缀,另一套是之前在libnds一直用的命名,特点是名字后有_CR这个后缀。据说是REG_这种命名比较科学,libnds也开始使用这种命名了,但是仍有大量_CR命名,有些乱套。形式为:REG_BG3CNT = BG_BMP16_256x256 | BG_BMP_BASE(0) | BG_PRIORITY(3); 等号两边都是宏,等号右边把各个标志位的数值也定义成宏,然后进行位与运算就行了。
3.通过位域进行赋值。这个在下面讲到位域的时候在进行说明。
4.通过库函数赋值。比较常用的是videoSetMode(MODE_5_2D | DISPLAY_BG3_ACTIVE);这个较简单,隐藏了寄存器的地址,只是把参数直接赋值给寄存器。还有libnds 1.3.1新加入的函数:int id = bgInit(3, BgType_Bmp16, BgSize_B16_256x256, 0,0);把初始化背景涉及到的多个寄存器的操作包含在一个函数中,只需给出几个参数就可以了。
位操作
要从一个字节中取出某一位或几位的值来或者把值写进去,就要用到位操作。DS存储空间有限,什么都得精打细算,所以用到位操作的机会比较多。一般常用的就是位移和掩码。
另外对于乘法和除法,若乘数或除数是二的整数次方,就可以代换成位移操作。cpu对位移操作的速度比乘除运算要快多了。
位域
一个最小的数据类型占一个字节,如果要用到小于一个字节长度的变量,多出来的那些位就浪费掉了。DS上的储存空间是按位计的,所以要充分利用每一位才行。我们可以在一个字节中划分出几个位域来把要占用几个字节的变量压缩到一个字节里去。方法和定义结构体差不多:
struct bs
{
int a:8;
int b:2;
int c:6;
};
定义位域成员的格式为:类型说明符 位域名:位域长度。至于更详细的使用方法网上有很多,这里就不多说了。
可以针对相应的寄存器定义一个位域变量,给每一个标志位建立一个位域成员,通过简单的成员运算符就可以访问每一个标志位,省去了复杂的位操作。不过这涉及到很多指针操作,要消耗掉大量内存,而且寄存器数目众多,所以一般很少用。
值得一提的是在精灵部分为了访问OAM而定义了一个复杂的数据类型SpriteEntry,这是一个通过共用体和位域嵌套定义的数据类型,长度为8个字节。在精灵部分将对这个数据类型的结构进行详细说明。
类型定义
用typedef这个关键字可以给已经存在的数据类型定义一个“别名”。
libnds里有很多自定义类型的变量,比如u16、s16等等。因为C语言并没有严格规定基本数据类型的具体长度,而在DS这种事事需要精打细算的平台上我们必须时刻关注自己的变量长度是多少。要定义一个有确定长度的变量往往要加上一堆修饰关键字。这样另外定义一个简短而又明确的数据类型,在定义变量的时候就很方便了。
要用无符号整型变量,有u8、u16、u32、u64,带符号的整型变量,有s8、s16、s32、s64,当然int8、int16…也是带符号的整型变量。
另外,在定义一个结构体这样的复杂数据类型的时候,要定义一个结构体变量,前面还要带个struct,很烦人,可以用typedef给自己定义的结构体起一个别名,再定义结构体变量的时候就不用带着那个烦人的struct了。
typedef的用法应该是很常见,这里就不再多说。
__attribute__ ((packed))
在DS下使用bmp图片的时候要自己为bmp图片的文件头构造一个结构体,这个结构体中的成员长度不全是32位的整数倍,这就牵扯到一个问题:ARM是一个32位处理器,每次读取四个字节(32位)是最快的,编译器为了使程序运行效率最高而将不是32位整数倍的变量填充上一些空的字节以保证变量在内存中都是四字节对齐的。但是我们创建的结构体要求成员要一个紧接一个,所以要通知编译器让结构体中的成员紧挨在一起。
用法是:
typedef struct
{
char signature[2];
unsigned int fileSize;
unsigned int reserved;
unsigned int offset;
}__attribute__ ((packed)) BmpHeader;
__attribute__是gcc编译器的一个特色,括号里换上其他参数还可以实现其他功能。感兴趣可以到网上查资料。