GBA学习-基础篇

配合b站教程https://www.bilibili.com/video/BV1fZ4y1H7xN学习

GBA工具安装

1:下载安装devkitPro。直接下载的免安装压缩包,解压之后需要修改msys2里的配置文件:具体路径:devkitPro/msys2/etc/profile。在里面修改DEVKITPRO='devkitPro文件夹路径'DEVKITARM='devkitARM路径'

PATH=''中添加’:devkitPro路径/tools/bin:devkitARM路径/bin完成之后就可以使用msys2了。

2:访问www.coranac.comproject选项卡/tonc/tonc downloads/example codeproject选项卡/usenti/usenti-17.10.zip

下载完成后,解压在devkitPro文件夹内,运行msys2进入code文件夹,输入make build_all编译code里所有的代码,产生.gba文件。

##帮助文件下载地址
http://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.zip
http://problemkaputt.de/gbatek.htm

GBA的内存

GBA没有系统,要直接对硬件进行操作
每“8位”组成“1字节”
0x00000000 开始 16K 为系统 ROM
0x02000000 开始 256K 为 CUP 外部工作内存
0x03000000 开始 32K CUP内部工作内存
0x04000000 I/O 寄存器,控制屏幕输入输出
0x05000000 存储色板,颜色信息
0x06000000 背景图层,存储地图等
0x07000000 存储精灵
0x08000000 存储 ROM

统一编址,直接对内存操作。

GBA 的 IO 显示控制

/IO硬件设置
GBA 采用小端存储,因此内存是从高位到低位进行读取,每8位算一个读取单位。
0x 0000 0000 0000 0000 从右开始为第 0 位
选择 Mode 3 ,240 x 160 的像素屏幕,所以第 0 , 1 位,置1;0000 0000 0000 0011
Mode 3 只支持 BG2 ,所以第 10 位,置 1;0000 01000 0000 0011
确定了内存中的数值,接着赋值给 0x04000000 这个地址位。
(unsigned short*)0x04000000 = 0x0403;
这样就设置好了显示模式。
再将第 80 行, 100 列像素点显示红色。0000 0000 0000 0000,从右向左,每5位分别控制红,绿,蓝。
寻找地址,显示地址为 0x06000000 + (240 * 80 + 100) * 2
设置一个红点 为 0x001f。0000 0000 0001 1111 。
*(unsigned short*)(0x06000000 + (240 * 80 + 100) * 2) = 0x001f;

完成代码:创建main.c ,复制以下代码

int main(){
   
	//声明常量const
	const unsigned MODE3 = 0x0403;//0000 0100 0000 0011
	//指针变量,表示所在地址的指针,指针类变量和指针所指地址的内容一致。
	unsigned short* SCREEN = (unsigned short*)0x04000000;
	//设置视频模式和图层3
	//*为取址符,将指针所指内容取出。
	*SCREEN = MODE3;
	//第80行,100列像素点显示红色
	//0000 0000 0000 0000,从右向左,每5位分别控制红,绿,蓝
	*(unsigned short*)(0x06000000 + (240 * 80 + 100) * 2) = 0x001f;
	return 0;
}

使用 msys2 进入文件所在位置,执行以下代码。

arm-none-eabi-gcc -c main.c -o main.o
arm-none-eabi-gcc main.o -specs=gba.specs -omain.elf
arm-none-eabi-objcopy -O binary main.elf main.gba
gbafix main.gba -t first

或者将其写入.sh文件中,在msys2中用sh build.sh进行编译,为防止编译失败不能发现,可在开头加入rm *.o *.elf *.gba将以前的文件删除掉。

位运算

int main(){
   
	/*位运算
	与 或 异或 取反 位移
	a:0x46a5 0100 0110 1010 0101
	b:0x82e7 1000 0010 1110 0111
	
	与运算:a & b	a 与 b 每一位相与,同为 1 则得 1 剩下都为 0
	0100 0110 1010 0101
	1000 0010 1110 0111
	0000 0010 1010 0101
	
	或运算:a | b	a 与 b 每一位相或,只要有 1 则为 1 没有 1 则为 0 
	0100 0110 1010 0101
	1000 0010 1110 0111
	1100 0110 1110 0111

	异或运算:a ^ b	a 与 b 每一位相异或,不同为 1 相同则为 0 
	0100 0110 1010 0101
	1000 0010 1110 0111
	1100 0100 0100 0010

	取反运算:~a	a的每一位取反

	移位运算:左移:<< 右移:>>
	a << 5:a 左移 5 位,空出补 0 
	a >> 5:a 右移 5 位,空出补 0 
	*/
	
	//可以单独设置每个模式的 16 位常量,需要用哪些模式将他们进行“或运算”即可

	//定义常量,define 可不带“;”
	//#define MODE3 0x0403
	//可以把define内容放入“.h”文件中,然后在main.c文件中“#include ".h"”即可。
	
	//声明常量const
	const unsigned MODE3 = 0x0403;
	//指针变量,表示所在地址的指针,指针类变量和指针所指地址的内容一致。
	unsigned short* SCREEN = (unsigned short*)0x04000000;
	//设置视频模式和图层3
	//*为取址符,将指针所指内容取出。
	*SCREEN = MODE3;//0000 0100 0000 0011
	//第80行,100列像素点显示红色
	//0000 0000 0000 0000,从右向左,每5位分别控制红,绿,蓝
	//偏移了 (240 * 80 + 100) * 2 个像素点,一个像素点是2字节,所以要 x2 
	//内存地址以 1 字节为单位变化。
	//所以偏移量,加上基础值 0x06000000 得到的才是内存地址。060096c8
	for(int i = 0 ; i < 38400 ; i ++){
   
		*((unsigned short*)0x06000000 + i) = 0x001f;
	}
	
	return 0;


}

将各种 Mode 分别 define ,再写在".h"文件中,用 include 包含(参照 IO 的图片)如下:

#define MODE0 0
#define MODE1 1
#define MODE2 2
#define	MODE4 4
#define MODE5 5

#define BG0 0x0100
#define BG1 0x0200
#define BG2 0x0400
#define BG3 0x0800
//左移一位

#define DISPCNT *(unsigned short*)0x04000000
#define VRAM (unsigned short*)0x06000000

使用时相"或"即可MODE3 | BG2

GBA显示图片

需要使用工具usenti可在http://www.coranac.com/projects/usenti/下载
首先需要了解显示图片的原理:
1:一个数组中全是 RGB 的 16 位数字的数据,代表了一个图片的所有数据。
2:使用循环将每一个值赋值给内存的每一个位置,即可在显示屏上显示。

###第一步:usenti 处理图片
注意只能显示 240 x 160 的图片,需先将图片进行处理,可使用 ps - 图像 - 图像大小 进行修改
usenti 打开图片"kinoGBA",点击image - Export - 保存类型 GBA source -保存注意名字不要用汉字)出现如下:
b站关注吉诺儿kino喵

按照图中设置即可,图片无法显示则按照以下:
1:左上角方框内,Gfx 下第一个选项改为 bitmap(GBA),第二个选项改为 16
2:右下角方框内,选择 u16,之后点击左下角 ok 即可,出来选项框选什么都无所谓。

之后保存的文件夹内会出现 kinoGBA.c 和 kinoGBA.h 接下来在main.c 文件中引入kinoGBA.h
#include kinoGBA.h

打开 kinoGBA.c 会发现,里面是一个数组 kinoGBABitmap[38400] ,这个就是需要遍历赋值的数组。数组里是 16 位的数值,所以计算字节数时,需要将大小x2。

遍历代码如下:

#include "mygba.h"
#include "kinoGBA.h"

//写入图片“kino.png”
//图片像素只能是 240 x 160
//需要使用 usenti 工具

int main(){
   
	
	for (int i = 0;i < 38400 ;i ++ ){
   
		*(VRAM + i) = kinoGBABitmap[i];
	}
	
	DISPCNT = MODE3 | BG2;
	return 0;
}

接下来需要编译文件,kinoGBA.c 和 main.c 都需要编译,build.sh 文件如下:

#传入参数为文件夹名称,不用带“/”

#$1 代表传入的参数,./代表当前文件夹,如果不需要切换文件夹,就可以删掉"./$1/"或者参数输入"."
rm ./$1/*.o ./$1/*.elf ./$1/*.gba

#kinoGBA 也要编译
arm-none-eabi-gcc -O3 -c ./$1/kinoGBA.c -o ./$1/kinoGBA.o

#arm 代表使用 ARM 处理器进行编译,none 代表没有操作系统,e 代表嵌入式
#a 代表应用,b 代表二进制,i 代表接口。
arm-none-eabi-gcc -O3 -c ./$1/main.c -o ./$1/main.o

#这个地方也要加 kinoGBA.o 文件
arm-none-eabi-gcc ./$1/kinoGBA.o ./$1/main.o -specs=gba.specs -o ./$1/main.elf

arm-none-eabi-objcopy -O binary ./$1/main.elf ./$1/main.gba

gbafix ./$1/main.gba -t first

之后在 msys2 中使用sh build.sh .即可编译文件。然后将 main.gba 拖入 VBA 模拟器中即可显示图片。

帧缓冲

GBA 显示图片是一帧一帧显示的,切换帧时需要时间。显示图片的原理是一层一层像素点从上到下,从左到右扫描。
240 x 160 的像素点扫描时,会有间隔,如下图
b站关注卡缇娅也不知道鸭嘎

图片描述如下:
每行像素点扫描完 240 个之后,会有 68 个空白像素点。160 行像素点扫描完之后,会有 68 行空白像素点。
这些空白像素点扫描时间,被称为:垂直扫描空白。一般需要在此段时间内,进行换帧。

换帧的模式为"模式4"。

模式4:

介绍:
1:色板数据部分,为索引值,是图像色板,定义好的颜色,存储在 0x05000000 之后。共 256 色。
2:图形部分,显示图像的部分,存储在 0x06000000 之后,因为直接引用色板索引,所以一个像素只占8位,38400 -> 19200 可以存储2个帧
3:设置好 2 个帧之后,需要在 0x04000000 上第 4 位控制显示第几帧,可以在头文件".h"中定义

#define FRAME1 0x00	//显示第 1 张图
#define FRAME2 0x10;	//0000 0000 0001 0000 显示第 2 张图

4:0x04000006 所在的数据可以查看屏幕扫描的当前位置。在".h"文件中定义:
#define VCOUNT *(unsigned short*)0x04000006
5:第一帧的内存地址为:0x06000000 - 0x06009FFF 40KB的内存
第二帧的内存地址为:0x0600A000 - 0x06013FFF 40KB的内存
在".h"文件中添加:
#define VRAM2 (unsigned short*)0x0600A000
当 VCOUNT < 160 时,说明还在扫描图片,反之,则在垂直扫描空白。

下面为帧缓冲实例,
1:准备一张 240 x 320 的图,使用 usenti 导出,导出时,需注意:
1)左上角方框的 bpp 需为 8;
2)右上角方框最上方的 Pal 需打钩;
这时,导出的文件中,发现".c"文件中是两个数组。色板数据和图像数据。
图像数据因为是 2 帧的图,所以还是 38400 的下标。色板数据为 256 下标。
此时,需将色板数据导入 0x05000000 ,将图像数据导入 0x06000000 。
因此,在"mygba.h"中,加入:
#define PAL *(unsigned short*)0x05000000
然后使用循环,将数组放入 PAL 中。
代码如下

// 这里是 19200 ,要一次放入两个帧。
// VRAM 是 0x06000000 - 0x06009FFF 40KB的内存
// VRAM2 是 0x0600A000 - 0x06013FFF 40KB的内存
// 需要在".h"文件中定义
for(int i = 0;i < 19200; i ++){
   
    if(i < 256){
   
    	*(PAL + i) = gbaPicPal[i];
    }
    *(VRAM + i) = gbaPicBitmap[i];
    *(VRAM2 + i) = gbaPicBitmap[i + 19200];
}

色板数据和图片数据载入完成后,现在图片数据区有两帧图。使用 FRAME1 和 FRAME2 进行切换,代码如下:

// 优化编译 -O3 会优化掉死循环内的代码,所以要把"build.h"中的编译选项"-O3"删掉,或改为"-O0"。
while(1){
   
	//设置模式 4 背景 2 第 1 个帧
	DISPCNT = MODE4 | BG2 | FRAME1;
	
	//等待一次垂直扫描空白,当 VCOUNT >= 160 时,在空白,所以要等下一次空白。
	//不然会跳帧。
	while(VCOUNT >= 160);
	while(VCOUNT < 160);
	
	//设置第 2 个帧
	DISPCNT = MODE4 | BG2 | FRAME2;
	
	while(VCOUNT >= 160);
	while(VCOUNT < 160);
}

"build.h"文件进行相应修改即可。

模式5

介绍:
颜色与模式 3 一样丰富,但因为颜色数据太多,所以图片数据内存变小,只能显示 160 x 128 的图片。要全屏显示需要拉伸图片。因为使用了 32768 色,所以图片数据是 16 位

实例:
1:找一张图片处理成 160 x 128 的规格,然后 usenti 导出,此时==左上方框内 bpp 为 16 ,右下方框要选 u16 。
2:打开".c"文件发现,其中数组角标为 40960 。
3:两帧图片存储位置与模式 4 相同:0x06000000 - 0x06009FFF 和 0x0600A000 - 0x0601 3FFF。
4:将循环条件改为 40960 的一半 20480 ,循环里面数组角标加的数字也是 20480。
5:把模式全改成 MODE5 ,还有".sh"文件内的文件名,以及各种需要修改的名称 修改完成,编译即可。
代码如下:
".c"文件

#include "mygba.h"
#include "GBAPic.h"

//写入图片“GBAPic.png”
//图片像素只能是 160 x 128
//需要使用 usenti 工具

int main(){
   
	
	DISPCNT = MODE5 | BG2;

	//*(unsigned short*)0x4000020=(255/1.5);
	//*(unsigned short*)0x4000022=0;
	//*(unsigned short*)0x4000024=0;
	//*(unsigned short*)0x4000026=(255/1.25);


	//循环写入图片数据
	for (int i = 0;i < 20480 ;i ++ ){
   
		*(VRAM + i) = GBAPicBitmap[i];
		*(VRAM2 + i) = GBAPicBitmap[i + 20480];
	}
	
	while(1){
   
		//设置模式 4 背景 2 第 1 个帧
		DISPCNT = MODE5 | BG2 | FRAME1;
		
		//等待一次垂直扫描空白,当 VCOUNT >= 160 时,在空白,所以要等下一次空白。
		//不然会跳帧。
		while(VCOUNT >= 160);
		while(VCOUNT < 160);
		
		//设置第 2 个帧
		DISPCNT = MODE5 | BG2 | FRAME2;
		
		while(VCOUNT >= 160);
		while(VCOUNT < 160);
	}
	return 0;
}

"mygba.h"文件

#define MODE0 0
#define MODE1 1
#define MODE2 2
#define MODE3 3
#define	MODE4 4
#define MODE5 5

#define BG0 0x0100
#define BG1 0x0200
#define BG2 0x0400
#define BG3 0x0800

//显示第 1 张图
#define FRAME1 0x00
//0000 0000 0001 0000 显示第 2 张图
#define FRAME2 0x10

//模式设置地址
#define DISPCNT *(unsigned short*)0x04000000
//查看屏幕扫描到哪一行
#define VCOUNT *(unsigned short*)0x04000006

//画板数据地址
#define PAL (unsigned short*)0x05000000
//第一帧地址
#define VRAM (unsigned short*)0x06000000
//第二帧图的地址
#define VRAM2 (unsigned short*)0x0600A000


//相或

"build.sh"文件 注意:一定要改"main.c"的"-O3"不然会将死循环优化

#传入参数为文件夹名称,不用带“/”

#$1 代表传入的参数,./代表当前文件夹,如果不需要切换文件夹,就可以删掉"./$1/"或者参数输入"."
rm ./$1/*.o ./$1/*.elf ./$1/*.gba

#GBAPic 也要编译
arm-none-eabi-gcc -O3 -c ./$1/GBAPic.c -o ./$1/GBAPic.o

#arm 代表使用 ARM 处理器进行编译,none 代表没有操作系统,e 代表嵌入式
#a 代表应用,b 代表二进制,i 代表接口。
#此处要改 -O0
arm-none-eabi-gcc -O0 -c ./$1/main.c -o ./$1/main.o

#链接 GBAPic.o
arm-none-eabi-gcc ./$1/GBAPic.o ./$1/main.o -specs=gba.specs -o ./$1/main.elf

arm-none-eabi-objcopy -O binary ./$1/main.elf ./$1/main.gba
#修复 gba 文件头,使其可以在掌机上运行。
gbafix ./
  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值