GBA tile模式写入VRAM单字节意外写入相邻字节问题排查
GBA Tile模式下单字节写入VRAM出现相邻字节重复赋值问题
问题现象
我正从零开始尝试使用GBA tile模式显示带单个彩色像素的单个tile。
功能基本可运行,但并未按预期设置单个像素,而是在我要写入的16位对齐位置的两个字节中都写入了相同颜色值:
- 屏幕显示效果:mgba模拟器中的运行结果
模拟器支持查看tile、内存与寄存器状态,相关状态截图如下:
- Tilemap视图:显示tile上有两个像素被设置
- VRAM视图:从tile起始地址开始显示,确实有两个8bpp像素被设置,引用了调色板中索引为42的颜色
- Display Control Register:配置无异常,运行在首个tile模式且启用了背景0
- Background 0 Control register:已配置为8bpp模式
相关代码
主程序代码
#include "screen.h" void do_it() { // [tile_index][row][column] = palette index VRAM.tilesets[3].d_tiles[37][0][0] = 42; } int main() { // [tilemap][row][column] = tile_index VRAM.tilemaps[0][0][0] = 37; do_it(); PALETTE.backgrounds[42] = rgb(0b1111, 0b11111, 0); BG0CNT = BGCNT_SIZE_32X32 | BGCNT_COLOR_8BPP | (0 << BGCNT_TILEMAP_INDEX) | (3 << BGCNT_TILESET_INDEX); DISPCNT = DISPCNT_MODE_TILE_0 | DISPCNT_BACKGROUND_0; while (1) {}; }
screen.h 相关定义片段
typedef unsigned char tile_4bpp[8][4]; typedef unsigned char tile_8bpp[8][8]; typedef union { tile_4bpp s_tiles[512]; tile_8bpp d_tiles[256]; } char_block; extern union { char_block tilesets[4]; screen_block tilemaps[32]; color bitmap[HEIGHT][WIDTH]; } VRAM;
在do_it函数中,我仅在硬编码位置执行了一次单字节赋值操作,但内存中目标地址以及与其组成2字节对齐的相邻地址都被写入了该值:执行VRAM.tilesets[3].d_tiles[37][0][0] = 42; 和 VRAM.tilesets[3].d_tiles[37][0][1] = 42; 会得到完全相同的执行结果。
排查记录
我最初怀疑是结构体/联合体/类型定义存在错误,但反汇编结果看起来正常:
Disassembly of section .text: 080000e8 <do_it>: 80000e8: 4b02 ldr r3, [pc, #8] ; (80000f4 <do_it+0xc>) 80000ea: 4a03 ldr r2, [pc, #12] ; (80000f8 <do_it+0x10>) 80000ec: 212a movs r1, #42 ; 0x2a 80000ee: 5499 strb r1, [r3, r2] 80000f0: 46c0 nop ; (mov r8, r8) 80000f2: 4770 bx lr 80000f4: 06000000 streq r0, [r0], -r0 80000f8: 0000c940 andeq ip, r0, r0, asr #18 080000fc <main>: 80000fc: b510 push {r4, lr} 80000fe: 4b09 ldr r3, [pc, #36] ; (8000124 <main+0x28>) 8000100: 2225 movs r2, #37 ; 0x25 8000102: 801a strh r2, [r3, #0] 8000104: f7ff fff0 bl 80000e8 <do_it> 8000108: 4b07 ldr r3, [pc, #28] ; (8000128 <main+0x2c>) 800010a: 2254 movs r2, #84 ; 0x54 800010c: 4907 ldr r1, [pc, #28] ; (800012c <main+0x30>) 800010e: 5299 strh r1, [r3, r2] 8000110: 4b07 ldr r3, [pc, #28] ; (8000130 <main+0x34>) 8000112: 228c movs r2, #140 ; 0x8c 8000114: 801a strh r2, [r3, #0] 8000116: 4b07 ldr r3, [pc, #28] ; (8000134 <main+0x38>) 8000118: 2280 movs r2, #128 ; 0x80 800011a: 0052 lsls r2, r2, #1 800011c: 801a strh r2, [r3, #0] 800011e: 46c0 nop ; (mov r8, r8) 8000120: e7fd b.n 800011e <main+0x22> 8000122: 46c0 nop ; (mov r8, r8) 8000124: 06000000 streq r0, [r0], -r0 8000128: 05000000 streq r0, [r0, #-0] 800012c: 000003ef andeq r0, r0, pc, ror #7 8000130: 04000008 streq r0, [r0], #-8 8000134: 04000000 streq r0, [r0], #-0
我对ARM汇编并不精通,但do_it中的strb指令确实对应上述赋值操作,根据ARM官方文档说明,该指令应当仅操作单个字节。
已确认的信息
- 该问题在多个模拟器上复现:日常使用mgba,在vbam上也存在同样问题
- 其他ROM在mgba上运行正常
- 使用arm-none-eabi-gcc v12.1.0工具链编译
- 未添加任何优化编译选项
- 编译参数为
-mthumb -mthumb-interwork -mcpu=arm7tdmi -fomit-frame-pointer -ffast-math -fno-strict-aliasing - 4bpp模式下也存在同样问题,会设置4个像素而非预期的2个
- 代码直接从gamepak内存运行
- 更换tilesets/tilemaps位置后问题仍然存在
- 在bitmap模式下不会出现该问题,以下代码可正常运行,在屏幕左上角显示单个绿色像素:
int main() { VRAM.bitmap[0][0] = rgb(0b1111, 0b11111, 0); DISPCNT = DISPCNT_MODE_3 | DISPCNT_BACKGROUND_2; while (1) {}; }
完整代码仓库地址:github
内容的提问来源于stack exchange,提问作者Globoplox
相关产品推荐
相关产品推荐

