ALAN JAHNEL / Software engineering

Writing

GBA Assembly — 6. Reading the buttons

Read KEYINPUT, interpret active-low bits and make a square change color. Learn to distinguish pressing a button from holding it down.

So far, the program has done everything on its own. Let's let the player act: the square stays green when A is released and red while A is held. This example is small enough to expose the entire path from a physical button to pixels.

Use botoes.s, with gba.ld and rom.py from the series package. The program keeps the previous chapter's VBlank wait.

Your route through this chapter

Before starting: the blinking example from chapter 5 should work. You will now replace automatic color changes with player input.

  1. Save the complete botoes.s beside the supporting files. Build it with the four commands below and open botoes.gba.
  2. In mGBA, check which keyboard key represents GBA A. Focus the game window before testing; the computer key and console button may have different names.
  3. Test in order: no button, hold A, release A. Expect green, red, green. Only then trace read_keys and the main loop below.
  4. For the first edit, find tst r1, #1 in main_loop and change its mask to 2. Save, rebuild, and check that B now controls the color. Leave the similarly named test inside read_keys unchanged.

Checkpoint: you can predict the color while holding and releasing the selected button. If nothing changes, check focus and the emulator mapping before changing assembly. Inspecting press_count in a debugger is optional; the visible color test is enough to proceed.

What replaces a keyboard event

In a browser, you can receive an event when a key changes. In this GBA example, the CPU reads the KEYINPUT hardware register at 0x04000130. Its ten lowest bits represent the console's ten buttons.

BitDecimal maskButton
01A
12B
24Select
38Start
416Right
532Left
664Up
7128Down
8256R
9512L

There is one detail: these bits are active-low. A zero means pressed; a one means released. This is a hardware convention, not a property of assembly language.

With no buttons pressed, the ten relevant bits are 0x03FF. With only A pressed, they are 0x03FE. These examples consider only the ten control bits; other bits do not participate in our interpretation.

Normalizing the reading once

See LDRH and load widths and MVN, AND, and BIC to interpret the routine.

The example reads the hardware and saves a version that is more convenient for the rest of the program:

<!-- source: en/botoes.s#input -->
ARM assembly
read_keys:
    ldr r0, =KEYINPUT
    ldrh r1, [r0]
    mvn r1, r1
    ldr r0, =0x03ff
    and r1, r1, r0
    ldr r0, =keys
    ldr r2, [r0]
    str r1, [r0]
    bic r1, r1, r2
    ldr r0, =pressed
    str r1, [r0]
    tst r1, #1
    ldr r0, =press_count
    ldr r2, [r0]
    addne r2, r2, #1
    str r2, [r0]
    bx lr

mvn inverts the bits: a pressed button's zero becomes one. Since a CPU register has 32 bits, the inversion also affects bits that do not represent buttons. We therefore apply mask 0x03FF with and.

After normalization, keys contains a set bit for each pressed button. It is a RAM variable. Our program reads the hardware once per update and uses that snapshot for the remainder of the iteration.

Plain text
hardware KEYINPUT     -> invert and keep ten bits
keys in RAM              -> 1 means pressed

This is a small software layer that makes hardware easier to use while keeping its behavior visible.

Holding and pressing are different information

The new-press formula corresponds to BIC, or AND with an inverted mask.

If the player holds A for twelve frames, its bit is set in keys for all twelve. That is exactly what keeps our square red. It would be inappropriate for an action that should happen once, such as buying a tower.

To detect the transition, we retain the previous reading. The code loads the old value of keys into r2 before storing the new one. Then:

Plain text
pressed = current AND (NOT previous)

bic r1, r1, r2 performs that operation: it keeps bits in r1 that are set now but were not set before. We store the result in pressed.

FrameA nowA beforeA in pressed
Before pressing000
First pressed frame101
Still holding110
Released010
Pressed again101

The example also increments press_count only when A appears in pressed. That counter is not drawn: it is used for debugger inspection and tests. Holding A should keep the square red but increase the counter only once.

Using bits to choose a color

Look up TST and the NE condition suffix to follow ldrne.

The loop connecting input to the image is:

<!-- source: en/botoes.s#button-demo -->
ARM assembly
main_loop:
    bl wait_vblank
    bl tick
    bl read_keys
    ldr r0, =keys
    ldr r1, [r0]
    tst r1, #1
    ldr r4, =0x03e0
    ldrne r4, =0x001f
    mov r0, #104
    mov r1, #64
    mov r2, #32
    mov r3, #32
    bl fill_rect
    b main_loop

After reading the buttons, we test mask 1 in keys. We select green by default and red when the result is nonzero, then call the same fill_rect used earlier.

Notice the division of work: the input register reports the controls' electrical state; read_keys turns it into useful bits; the loop selects the color; the write to VRAM changes what the video circuitry displays. The hardware has no rule saying “A makes the square red.” We wrote that association.

Running and checking both cases

Shell
arm-none-eabi-as -mcpu=arm7tdmi -g -o botoes.o botoes.s
arm-none-eabi-ld -T gba.ld -o botoes.elf botoes.o
arm-none-eabi-objcopy -O binary botoes.elf botoes.gba
python3 rom.py botoes.gba

Open botoes.gba. In mGBA's input settings, check which keyboard key is mapped to the GBA's A button. It need not be the letter A on your keyboard. Click the emulator window to ensure it receives input.

With nothing pressed, the square should be green. Hold A: it should turn red and stay red. Release A: it should turn green again. Another button by itself should not change its color.

For an exercise, change only the mask used to select the color from 1 to 2. B should now control the color. The new-press counter will still count A because its mask is inside read_keys. Changing one visual rule does not automatically change every other rule.

Another experiment is to use pressed instead of keys for the color. Red should then last just one update per press, even while A remains held. It may be too quick to see clearly; the counter or frame-by-frame testing helps reveal the difference.

For the control format and transitions, see Tonc: keypad.

Previous: timing and VBlank. Next: moving a square with the D-pad. Series index.

Back to home