I'm trying to display a single tile with a single colored pixel using GBA tile mode, starting from scratch.
It mostly work, but instead of setting a single pixel, it set the same color to the two bytes at the 16bits aligned location where I intend to write:
Resulting image when running through the mgba emulator: Screen
The emulator allows to inspect the tiles, memory and register.
Tilesmap, showing the two pixels set on the tiles.
VRAM, starting at the tiles address, showing that there are indeed two 8bpp pixel set, referencing the color in palette at index 42.
Display Control Register nothing unexpected, running in first tile mode with background 0 enabled.
Background 0 Control register, in 8bpp mode.
Main:
#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) {};
}
Useful parts of 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;
So in the function do_it
, I am setting a single byte value, only once at an hardcoded location.
Yet in memory, it set the value to the given address AND the one that complete 2 bytes alignment:
VRAM.tilesets[3].d_tiles[37][0][0] = 42;
and VRAM.tilesets[3].d_tiles[37][0][1] = 42;
have the same outcome.
I thought I could have done something badly in my structs/unions/types definitions, but assembly looks fine:
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
I'm not fluent in arm assembly, but the strb
in do_it
seems to match the affectation and it should operate on a single byte according to the arm doc.
Some more info:
- It happens on multiple emulator: I usually use mgba, it happens on vbam too
- Other roms work as intended on mgba
- Building with arm-none-eabi-gcc toolchain v 12.1.0
- Without any optimisations flags
- Using
-mthumb -mthumb-interwork -mcpu=arm7tdmi -fomit-frame-pointer -ffast-math -fno-strict-aliasing
- It also happens with 4bpp mode, setting four pixels instead of expected two
- Code is running from gamepak1 memory directly
- It happens when using differents tilesets/tilemaps locations
- It does not happens when running in bitmap mode:
This works and display a SINGLE green pixel on the top left of the screen:
int main() {
VRAM.bitmap[0][0] = rgb(0b1111, 0b11111, 0);
DISPCNT = DISPCNT_MODE_3 | DISPCNT_BACKGROUND_2;
while (1) {};
}
Full codebase can be found there: github
Any idea of what could cause this ?
CodePudding user response:
Solved thanks to @old_timer comments.
VRAM can only be accessed with 16 or 32 bit transactions
Changing my typedefs from
typedef unsigned char tile_4bpp[8][4];
typedef unsigned char tile_8bpp[8][8];
to
typedef unsigned short tile_4bpp[8][2];
typedef unsigned short tile_8bpp[8][4];
Fixed the issue, in assembly the strb
is replaced by a strh
that store the half-word as expected:
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) {};
}
Will set the two pixels with their respective colors.
It did not happen in bitmap mode because the bitmap mode I used for testing was 16bpp mode, so I was setting a full half-word for each pixels.
I have not yet been able to test on hardware, so I can't tell if the observed behavior when using byte level operation on VRAM on emulator would have happened on a real GBA too.
CodePudding user response:
memmap:
MEMORY
{
ewram : ORIGIN = 0x02000000, LENGTH = 256K
}
SECTIONS
{
.text : { *(.text*) } > ewram
}
startup.s
.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);
}
build:
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
run:
vba notmain.mb
I run linux so I do not have vba-m but I do have vba. And this works fine. You can wrap this with a copyjump or build it as a gba rom and the cartridges you can buy today do not care about the title header in the gba file because the cartridge firmware is the actual gba rom. mgba seems to want the proper header, and looks like you know how to do that.
Your key problem is that VRAM can only be properly accessed with 16 or 32 bit transfers (str or strh) not 8 bit (strb). It will mess up the other pixel in that halfword.
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)