Skip to content
This repository has been archived by the owner on Jan 12, 2022. It is now read-only.

L17 [GBA Programming DMA]

Richard Luo edited this page Nov 4, 2021 · 3 revisions

GBA Programming

Buttons

Buttons are stupid in GBA

  • 0 is pressed
  • 1 is not pressed

Yes, they are flipped.

#define BUTTONS *(volatile unsigned int *)0x4000130

#define BUTTON_A 0
#define BUTTON_B 1
#define BUTTON_SELECT 2
#define BUTTON_START 3
#define BUTTON_RIGHT 4
#define BUTTON_LEFT 5
#define BUTTON_UP 6
#define BUTTON_DOWN 7
#define BUTTON_L 8
#define BUTTON_R 9

#define NBUTTONS 10

#define KEY_DOWN(key, button) ((~buttons) & (1 >> key))

#define KEY_DOWN_NOW(key) (~(BUTTONS) & key)

The positions of the GBA’s ten buttons can be discovered programmatically by reading from a device register.

Sample Program

#include "dma.h"

//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

/* ==== Colors ==== */
#define COLOR(r, g, b) (r|g<<5|b<<10)
#define BLACK COLOR(0, 0, 0)
#define BLUE COLOR(0, 0, 31)

/* ==== Buttons ==== */
#define BUTTONS *(volatile unsigned int *)0x4000130

#define BUTTON_A 0
#define BUTTON_B 1
#define BUTTON_SELECT 2
#define BUTTON_START 3
#define BUTTON_RIGHT 4
#define BUTTON_LEFT 5
#define BUTTON_UP 6
#define BUTTON_DOWN 7
#define BUTTON_L 8
#define BUTTON_R 9

#define NBUTTONS 10

#define KEY_DOWN(key, button) ((~buttons) & (1 >> key))

#define KEY_DOWN_NOW(key) (~(BUTTONS) & key)

#define OFFSET(r, c, rowlen) (r * rowlen + c)
#define SCANLINECOUNTER *(volatile unsigned short *)0x4000006

/* ==== DMA ==== */
typedef struct {
    const volatile void *src; //Generic pointer (pointer to anything)
    volatile void *dst;
    unsigned int cnt;
} DMA_CONTROLLER;

#define DMA ((volatile DMA_CONTROLLER *) 0x040000B0)

#define DMA_CHANNEL_0 0
#define DMA_CHANNEL_1 1
#define DMA_CHANNEL_2 2
#define DMA_CHANNEL_3 3

#define DMA_DESTINATION_INCREMENT (0 << 21)
#define DMA_DESTINATION_DECREMENT (1 << 21)
#define DMA_DESTINATION_FIXED (2 << 21)
#define DMA_DESTINATION_RESET (3 << 21)

#define DMA_SOURCE_INCREMENT (0 << 23)
#define DMA_SOURCE_DECREMENT (1 << 23)
#define DMA_SOURCE_FIXED (2 << 23)

#define DMA_REPEAT (1 << 25)

#define DMA_16 (0 << 26)
#define DMA_32 (1 << 26)

#define DMA_NOW (0 << 28)
#define DMA_AT_VBLANK (1 << 28)
#define DMA_AT_HBLANK (2 << 28)
#define DMA_AT_REFRESH (3 << 28)

#define DMA_IRQ (1 << 30)
#define DMA_ON (1 << 31)

//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);
void drawRect(int row, int col, int width, int height, unsigned short color);
void delay(int n);
void waitForBlank();

int main(int argc, char *argv[]) {
    //Use Mode 3 and BG2 for our programming!
    REG_DISPCTL = BG2 | MODE3;

    unsigned short bgcolor = BLACK;

    struct ball {
        int row;
        int col;
        int rd;
        int cd;
        unsigned short color;
    };

    struct state {
        int size;
        struct ball balls[MAX_BALLS];
    };

    struct ball balls;

    struct ball *bp, *obp;

    //Current and previous states
    struct cs, ps;

    cs.size = 5;
    bp = &cs.balls;
    bp -> row = 0;
    bp -> col = 0;
    bp -> rd = 0;
    bp -> cd = 0;
    bp -> color = BLUE;

    //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));
   
    while(1) {
        ps = cs; //C copies struct byte by byte

        for(int i = 0; i < NBUTTONS; i++) {
            buttonWasDown[i] = KEY_DOWN(i, BUTTONS) != 0;
        }

        if(buttonWasDown[BUTTON_UP]) {
            cs.size++;
            if(cs.size > 150) {
                cs.size = 150;
            }
        }

        bp = &cs.balls;
        bp -> row = bp -> row + bp -> rd;
        bp -> col += bp -> cd;

        if(bp -> row < 0) {
            bp -> row = 0;
            bp -> rd = -bp -> rd;
        }

        waitForVBlank();

        obp = &ps.balls;
        drawRect(obp -> row, obp -> col, ps.size, ps.size, bgcolor);
    }
}

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) {
    volatile unsigned short lcolor;
    lcolor = color;

    for(int i = row, i < row + width; i++) {
            DMA[3].src = &lcolor;
            DMA[3].dst = &videoBuffer[OFFSET(row + r, col, SCREEN_WIDTH)];
            DMA[3].cnt = width | DMA_ON | DMA_SOURCE_FIXED | DMA_DESTINATION_INCREMENT;
    }
}

void delay(int n) {
    //Delay for n 10ths of a second
    volatile int x = 0;
    for(int i = 0; i < n * 8000; i++) x++;
}

void waitForVBlank() {
    //Empty loop
    while(SCANLINECOUNTER > SCREEN_HEIGHT);
    while(SCANLINECOUNTER < SCREEN_HEIGHT);
}

Wow, look this file is unreadable

We separate this into a few files

lib.h

#include <stdio.h>
#include "DMA.h"
#include "text.h"

extern volatile unsigned short *videoBuffer;

#define REG_DISPCTL (*(volatile unsigned short *)0x4000000)
#define MODE3 (3)
#define BG2 (1<<10)
#define SCREEN_HEIGHT 160
#define SCREEN_WIDTH 240
#define OFFSET(r, c, rowlen) ((r)*(rowlen)+(c))
#define COLOR(r, g, b) ((b)<<10 | (g)<<5 | (r))
#define WHITE COLOR(31,31,31)
#define BLACK COLOR(0,0,0)
#define RED COLOR(31,0,0)
#define BLUE COLOR(0,0,31)
#define GREEN COLOR(0,31,0)
#define GREY COLOR(16,16,16)
#define YELLOW COLOR(31,31,0)
#define CYAN COLOR(0,31,31)
#define MAGENTA COLOR(31,0,31)
#define SCANLINECOUNTER *(volatile short *)0x4000006

/* ==== Buttons ==== */
#define BUTTONS *(volatile unsigned int *)0x4000130
#define BUTTON_A 0
#define BUTTON_B 1
#define BUTTON_SELECT 2
#define BUTTON_START 3
#define BUTTON_RIGHT 4
#define BUTTON_LEFT 5
#define BUTTON_UP 6
#define BUTTON_DOWN 7
#define BUTTON_L 8
#define BUTTON_R 9
#define NBUTTONS (10)
#define KEY_DOWN(key, buttons) (~(buttons) & (1<<key))
#define MAX_BALLS (8)

/* ==== Prototypes ==== */
void setPixel(int row, int col, unsigned short color);
void drawRect(int row, int col, int width, int height, unsigned short color);
void delay(int n);
void waitForVBlank(void);
int rand(void);

DMA.h

typedef unsigned int u32;

/* DMA */
#define REG_DMA0SAD         *(volatile u32*)0x40000B0  // source address
#define REG_DMA0DAD         *(volatile u32*)0x40000B4       // destination address
#define REG_DMA0CNT         *(volatile u32*)0x40000B8       // control register

// DMA channel 1 register definitions
#define REG_DMA1SAD         *(volatile u32*)0x40000BC  // source address
#define REG_DMA1DAD         *(volatile u32*)0x40000C0       // destination address
#define REG_DMA1CNT         *(volatile u32*)0x40000C4       // control register

// DMA channel 2 register definitions
#define REG_DMA2SAD         *(volatile u32*)0x40000C8  // source address
#define REG_DMA2DAD         *(volatile u32*)0x40000CC       // destination address
#define REG_DMA2CNT         *(volatile u32*)0x40000D0       // control register

// DMA channel 3 register definitions
#define REG_DMA3SAD         *(volatile u32*)0x40000D4  // source address
#define REG_DMA3DAD         *(volatile u32*)0x40000D8       // destination address
#define REG_DMA3CNT         *(volatile u32*)0x40000DC       // control register

typedef struct {
    const volatile void *src;
    volatile void *dst;
    unsigned int cnt;
} DMA_CONTROLLER;

#define DMA ((volatile DMA_CONTROLLER *) 0x040000B0)

// Defines
#define DMA_CHANNEL_0 0
#define DMA_CHANNEL_1 1
#define DMA_CHANNEL_2 2
#define DMA_CHANNEL_3 3

#define DMA_DESTINATION_INCREMENT (0 << 21)
#define DMA_DESTINATION_DECREMENT (1 << 21)
#define DMA_DESTINATION_FIXED (2 << 21)

#define DMA_SOURCE_INCREMENT (0 << 23)
#define DMA_SOURCE_DECREMENT (1 << 23)
#define DMA_SOURCE_FIXED (2 << 23)

#define DMA_REPEAT (1 << 25)

#define DMA_16 (0 << 26)
#define DMA_32 (1 << 26)

#define DMA_NOW (0 << 28)
#define DMA_AT_VBLANK (1 << 28)
#define DMA_AT_HBLANK (2 << 28)
#define DMA_AT_REFRESH (3 << 28)

#define DMA_IRQ (1 << 30)
#define DMA_ON (1 << 31)