L16 [Intro to GBA] - Skyline-9/CS2110-Notes GitHub Wiki
Intro to GBA
Programming
Tutorial for GBA stuff here: TONC
- Mode 3 and DMA, sections 5 and 14
Datatypes on the GBA
- Integers
- char (1 byte)
- short int OR short (2 bytes)
- int (4 bytes)
- long int or long (8 bytes)
- Floating point
- The ARM CPU in the GBA doesn't have floating point support
- All floating point operations are software emuluated
Sample Program
//Make sure C doesn't optimize device register away when you don't use it
//This is because you never actually read from it, and you only want to write
#define REG_DISPCTL (*(volatile unsigned short *)0x04000000)
#define BG2 (1 << 10)
#define MODE3 3
#define SCREEN_WIDTH = 240
#define SCREEN_HEIGHT = 160
#define COLOR(r, g, b) (r|g<<5|b<<10)
#define OFFSET(r, c, rowlen) (r * rowlen + c)
//Make sure C doesn't optimize video buffer away when you don't use it
volatile unsigned short *videoBuffer = (unsigned short *)0x6000000;
/* Function Prototype */
void setPixel(int row, int col, unsigned short color);
int main(int argc, char *argv[]) {
//Use Mode 3 and BG2 for our programming!
REG_DISPCTL = BG2 | MODE3;
//Set pixel in row 5 and column 8 to white
setPixel(5, 8, 0x7fff); //videoBuffer[1208] = 0x7fff;
//To set a pixel in the middle to white
setPixel(SCREEN_HEIGHT / 2, SCREEN_WIDTH / 2, 0x7fff);
setPixel(SCREEN_HEIGHT / 2 + 1, SCREEN_WIDTH / 2, COLOR(31, 0, 0));
}
void setPixel(int row, int col, unsigned short color) {
videoBuffer[OFFSET(row, col, SCREEN_WIDTH)] = color;
}
void drawRect(int row, int col, int width, int height, unsigned short color) {
for(int i = row, i < row + width; i++) {
for(int j = col; j < col + height; j++) {
setPixel(i, j, color);
}
}
}
void delay(int n) {
int x = 0;
//Added a number to 8000 takes about an eigth of a second on this machine
for(int i = 0; i < n * 8000; i++) x++;
}
To execute, run
make med
Intro to GBA Recitation Notes
GBA Draw Cycle
- VDraw - GBA copies one row of pixels at a time from the video buffer to the screen (not instantaneous)
- VBlank - Nothing Happens
Tearing
Tearing: the video buffer is updated during VDraw, causing the top half of the screen to show the old image and the bottom half to show the new image.
To avoid tearing, we should update the video buffer during VBlank
0 <= SCANLINECOUNTER <= 159: VDraw
160 <= SCANLINECOUNTER <= 227: VBlank
#define SCANLINECOUNTER *(volatile unsigned short *)0x4000006
Implementing waitForVBlank
if(SCANLINECOUNTER > 160) {
//GBA is in VBlank
}
Issues
- What if scanline is almost at the end of VBlank? There's not enough time to change the video buffer before VDraw
- What if app logic runs too quickly and draws two frames during the same VBlank
To avoid these issues, we always wait for the next full VBlank period
SCANLINECOUNTER == 160
exactly- Wait until scanline is past VBlank and then comes back to VBlank
Direct Memory Access
- Filling entire screen the way above is still way too slow, so we use the Direct Memory Access (DMA) functionality of the GBA to optimize large array copies
- When DMA operates, it "steals" cycles from the CPU, and will return control to the CPU once it completes
- There are 12 DMA registers for four DMA channels; each channel has a source, destination, and control register
- We will use channel 3
The control register sets a few different things, using different bits:
- Whether DMA is currently enabled (En)
- How many elements to copy over (N)
- How to iterate through the destination (DA)
- How to iterate through the source (SA)
- Whether to copy halfwords (16-bit) or words (32-bit) (CS)
Options for iterating through destination/source
- Increment the address with each element (low -> high addresses)
- Decrement the address with each element (high -> low addresses)
- Fix the address with each element (don't change it)
Changing States on Buttons
#define BUTTONS *(volatile u32 *) 0x4000130
#define KEY_DOWN(key, buttons) (~(buttons) & (key))
if (KEY_DOWN(BUTTON_A, buttons)) {
state = next_state;
}
To avoid problems with people holding down key A, we use edge triggered logic
previousB = currentB;
currentB = buttons;
if (KEY_DOWN(BUTTON_A, currentB) && !KEY_DOWN(BUTTON_A, previousB)) {
state = next_state;
}