c语言 - 在 GBA 平铺模式下将单字节写入 VRAM 的意外结果,值也写入下一个或上一个字节



我正在尝试使用 GBA 磁贴模式从头开始显示具有单个彩色像素的单个磁贴。

它大部分工作,但它不是设置单个像素,而是在我打算写入的 16 位对齐位置为两个字节设置相同的颜色:

通过 mgba 模拟器运行时生成的图像: 屏幕

模拟器允许检查磁贴、内存和寄存器.
Tilesmap,显示磁贴上设置的两个像素.
VRAM,从磁贴地址开始,显示确实有两个 8bpp 像素集,引用调色板中索引 42.
显示控件中的颜色 注册无意外,在启用背景 0 的第一个磁贴模式下运行。/<>背景 0 控制寄存器, 在 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

我不精通手臂组装,但do_itstrb似乎与影响相匹配,根据手臂文档,它应该在单个字节上运行。

更多信息:

  • 它发生在多个模拟器上:我通常使用 mgba,它也发生在 vbam 上
  • 其他 rom 在 mgba 上按预期工作
  • 使用 arm-none-eabi-gcc 工具链 v 12.1.0 进行构建
  • 没有任何优化标志
  • 使用-mthumb -mthumb-interwork -mcpu=arm7tdmi -fomit-frame-pointer -ffast-math -fno-strict-aliasing
  • 它也发生在 4bpp 模式下,设置四个像素而不是预期的两个像素
  • 代码直接从 gamepak1 内存运行
  • 当使用不同的图块集/图块地图位置时会发生这种情况
  • 在位图模式下运行时不会发生这种情况:

这有效并在屏幕左上角显示一个绿色像素:

int main() {
VRAM.bitmap[0][0] = rgb(0b1111, 0b11111, 0);
DISPCNT = DISPCNT_MODE_3 | DISPCNT_BACKGROUND_2;
while (1) {};
}

完整的代码库可以在那里找到:github

知道什么可能导致这种情况吗?

>由于@old_timer评论而解决。

VRAM只能通过16位或32位事务访问

将我的类型定义从

typedef unsigned char tile_4bpp[8][4];
typedef unsigned char tile_8bpp[8][8];

typedef unsigned short tile_4bpp[8][2];
typedef unsigned short tile_8bpp[8][4];

修复了在汇编中strb被替换为按预期存储半字的strh的问题:

void do_it() {                                                                                                                                                 
int column = 0;                                                                                                                                              
int row = 0;                                                                                                                                                 
VRAM.tilesets[0].d_tiles[37][row][column >> 1] |= 42 << ((column & 1) ? 8 : 0);                                                                              
column = 1;                                                                                                                                                  
VRAM.tilesets[0].d_tiles[37][row][column >> 1] |= 43 << ((column & 1) ? 8 : 0);                                                                              
}                                                                                                                                                              
                                                                                         
                                                                                         
int main() {                                                                                                                                                   
// [tilemap][row][column] = tile_index                                                                                                                       
VRAM.tilemaps[20][0][0] = 37;                                                                                                                                
do_it();                                                                                                                                                     
PALETTE.backgrounds[42] = rgb(0b11111, 0b11111, 0);                                                                                                          
PALETTE.backgrounds[43] = rgb(0, 0, 0b11111);                                                                                                                
BG0CNT = BGCNT_SIZE_32X32 | BGCNT_COLOR_8BPP | (20 << BGCNT_TILEMAP_INDEX) | (0 << BGCNT_TILESET_INDEX);                                                     
DISPCNT = DISPCNT_MODE_TILE_0 | DISPCNT_BACKGROUND_0;                                                                                                        
while (1) {};                                                                                                                                                
}  

将两个像素设置为各自的颜色。

这在位图模式下不会发生,因为我用于测试的位图模式是 16bpp 模式,所以我为每个像素设置了一个完整的半字。

我还没有能够在硬件上进行测试,所以我无法判断在模拟器上的 VRAM 上使用字节级操作时观察到的行为是否也会发生在真正的 GBA 上。

memmap:

MEMORY
{
ewram : ORIGIN = 0x02000000, LENGTH = 256K
}
SECTIONS
{
.text : { *(.text*) } > ewram
}

创业公司

.cpu arm7tdmi
.code 32
.globl _start
_start:
ldr sp,=0x03008000
bl notmain
hang:
b hang
.globl PUT16
PUT16:
strh r1,[r0]
bx lr
.globl GET16
GET16:
ldrh r0,[r0]
bx lr
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.globl GET32
GET32:
ldr r0,[r0]
bx lr

notmain.c

extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern void PUT16 ( unsigned int, unsigned int );
extern unsigned int GET16 ( unsigned int );
#define DISPCNT 0x04000000
#define BG0CNT  0x04000008
#define PMEM 0x05000000
#define TMEM 0x06000000
#define VMEM 0x06008000
void notmain ( void )
{
unsigned int ra;
unsigned int rb;
//display control,
//mode 0
//enable BG0
PUT16(DISPCNT,0x0100);
//BG0 control
//256 color palette
//tiles defined at 0x60000000
//screen at 0x60008000
PUT16( BG0CNT,0x1080);
//setup the first 8 colors
PUT16(PMEM+0x0,0x0000); //BLACK
PUT16(PMEM+0x2,0x001F); //RED
PUT16(PMEM+0x4,0x03E0); //GREEN
PUT16(PMEM+0x6,0x03FF); //GREEN+RED
PUT16(PMEM+0x8,0x7C00); //BLUE
PUT16(PMEM+0xA,0x7C1F); //BLUE+RED
PUT16(PMEM+0xC,0x7FE0); //BLUE+GREEN
PUT16(PMEM+0xE,0x7FFF); //BLUE+GREEN+RED (WHITE)
//lets make a few tiles 64 bytes per tile.
ra=TMEM;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000002); ra+=4;
//make screen black/clear screen
ra=VMEM;
for(rb=0;rb<(32*20);rb++)
{
PUT16(ra,0x0000);
ra+=2;
}
//put some tiles on the screen
PUT16(VMEM,1);
}

建:

arm-none-eabi-as --warn --fatal-warnings  startup.s -o startup.o
arm-none-eabi-gcc -c -mcpu=arm7tdmi -Wall -O2 -ffreestanding  notmain.c -o notmain.o
arm-none-eabi-ld -nostdlib -nostartfiles -T memmap startup.o notmain.o -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf -O binary notmain.mb

跑:

vba notmain.mb

我运行Linux,所以我没有vba-m,但我有vba。 这工作正常。 您可以使用 copyjump 包装它或将其构建为 gba rom,您今天可以购买的盒式磁带不关心 gba 文件中的标题,因为盒式磁带固件是实际的 gba rom。 MGBA 似乎想要正确的标题,并且看起来您知道如何做到这一点。

您的关键问题是 VRAM 只能通过 16 或 32 位传输(str 或 strh)而不是 8 位 (strb) 正确访问。 它会弄乱那个半字中的另一个像素。

EWRAM  256Kbytes, can be written as bytes, halfwords, and words
IWRAM  32Kbytes, can be written as bytes, halfwords, and words
PRAM   512 halfwords, can be written as halfwords or words (not bytes)
VRAM   98Kbytes, can be written as halfwords or words (not bytes)

相关内容