Artigos
GBA em Assembly — 6. Lendo os botões
Leia KEYINPUT, interprete bits ativos em zero e faça um quadrado mudar de cor. Aprenda a distinguir apertar de continuar segurando.
Até agora, o programa fazia tudo sozinho. Vamos permitir uma ação do jogador: o quadrado fica verde quando A está solto e vermelho enquanto A está pressionado. Esse exemplo é pequeno o suficiente para enxergar todo o caminho entre um botão físico e os pixels.
Use botoes.s, com gba.ld e rom.py do pacote da série. O programa mantém a espera de VBlank do capítulo anterior.
Roteiro deste capítulo
Antes de começar: o exemplo de pisca do capítulo 5 deve funcionar. Agora você vai trocar a mudança automática de cor por uma ação do jogador.
- Salve
botoes.scompleto ao lado dos arquivos de apoio. Construa com os quatro comandos abaixo e abrabotoes.gba. - No mGBA, confira qual tecla representa A do GBA. Dê foco à janela do jogo antes de testar; a tecla do computador e o botão do console podem ter nomes diferentes.
- Teste em ordem: nenhum botão, segurar A, soltar A. Espere verde, vermelho, verde. Só depois acompanhe
read_keyse o laço principal abaixo. - Na primeira edição, localize
tst r1, #1emmain_loope troque a máscara por 2. Salve, reconstrua e confira que B agora controla a cor. Não altere o teste semelhante dentro deread_keys.
Ponto de conferência: você consegue prever a cor ao segurar e soltar o botão escolhido. Se nada mudar, confira o foco e o mapeamento do emulador antes de alterar Assembly. Inspecionar press_count em um depurador é opcional; o teste visual da cor é suficiente para continuar.
O que existe no lugar de um evento de teclado
No navegador, você pode receber um evento quando uma tecla muda. Neste exemplo do GBA, a CPU consulta o registrador de hardware KEYINPUT, no endereço 0x04000130. Os dez bits inferiores representam os dez botões do console.
| Bit | Máscara decimal | Botão |
|---|---|---|
| 0 | 1 | A |
| 1 | 2 | B |
| 2 | 4 | Select |
| 3 | 8 | Start |
| 4 | 16 | Direita |
| 5 | 32 | Esquerda |
| 6 | 64 | Cima |
| 7 | 128 | Baixo |
| 8 | 256 | R |
| 9 | 512 | L |
Há um detalhe: esses bits são ativos em zero. Um bit zero significa botão pressionado; um significa botão solto. Isso é uma convenção do hardware, não um comportamento da linguagem Assembly.
Com nenhum botão pressionado, os dez bits relevantes valem 0x03FF. Com somente A pressionado, valem 0x03FE. Esses exemplos consideram apenas os dez bits dos controles; os demais não participam da nossa interpretação.
Normalizar a leitura uma vez
Veja LDRH e as larguras de leitura e MVN, AND e BIC para interpretar a rotina.
A rotina do exemplo lê o hardware e guarda uma versão mais conveniente para o restante do programa:
<!-- source: botoes.s#input -->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 inverte os bits: o zero de um botão pressionado vira um. Como o registrador da CPU tem 32 bits, a inversão também afeta bits que não representam botões. Por isso aplicamos a máscara 0x03FF com and.
Depois dessa normalização, o valor em keys tem um bit um para cada botão pressionado. É uma variável na RAM. Nosso programa lê o hardware uma vez por atualização e usa essa fotografia durante aquela volta do laço.
KEYINPUT do hardware -> inverter e limitar a dez bits
keys na RAM -> 1 significa pressionado
Esse é um bom exemplo de uma camada pequena de software que torna o hardware mais fácil de usar sem esconder seu funcionamento.
Segurar e apertar são informações diferentes
A fórmula das novas pressões corresponde ao BIC, ou AND com a máscara invertida.
Se o jogador segurar A por doze quadros, keys terá o bit de A ligado nos doze. Isso é exatamente o que precisamos para manter o quadrado vermelho. Mas seria inadequado para uma ação que deve acontecer apenas uma vez, como comprar uma torre.
Para identificar a transição, conservamos a leitura anterior. No código, r2 recebe o valor antigo de keys antes de gravarmos o novo. Então:
pressed = atual AND (NOT anterior)
bic r1, r1, r2 faz essa operação: mantém em r1 os bits que estão ligados agora e não estavam ligados antes. Guardamos o resultado em pressed.
| Quadro | A agora | A antes | A em pressed |
|---|---|---|---|
| Antes de apertar | 0 | 0 | 0 |
| Primeiro quadro pressionado | 1 | 0 | 1 |
| Continuando a segurar | 1 | 1 | 0 |
| Soltou | 0 | 1 | 0 |
| Apertou novamente | 1 | 0 | 1 |
O exemplo também aumenta press_count apenas quando A aparece em pressed. Esse contador não é desenhado na tela: ele serve para inspeção no depurador e para os testes. Segurar A deve manter a cor vermelha, mas aumentar o contador uma única vez.
Usar os bits para escolher a cor
Consulte TST e o sufixo condicional NE para acompanhar ldrne.
O laço que liga a entrada à imagem é:
<!-- source: botoes.s#button-demo -->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
Após ler os botões, testamos a máscara 1 em keys. Escolhemos verde por padrão e vermelho se o resultado não for zero. Chamamos a mesma fill_rect dos capítulos anteriores.
Observe a divisão: o registrador de entrada informa um estado elétrico dos controles; read_keys transforma a leitura em bits úteis; o laço decide a cor; a escrita na VRAM altera o que o circuito de vídeo exibe. O hardware não possui uma regra “A deixa o quadrado vermelho”. Nós escrevemos essa associação.
Executar e verificar as duas situações
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
Abra botoes.gba. Nas configurações de controles do mGBA, confira qual tecla está associada ao botão A do GBA. Ela não precisa ser a letra A do teclado. Clique na janela do emulador para garantir que ela recebe a entrada.
Sem apertar nada, o quadrado deve estar verde. Segure A: ele deve ficar vermelho e permanecer assim. Solte: deve voltar a verde. Pressionar outro botão isoladamente não deve mudar a cor.
Como exercício, mude somente a máscara usada para escolher a cor, de 1 para 2. Agora B deve controlar a cor. O contador de novas pressões continuará contando A, pois sua máscara está dentro de read_keys. Mudar a leitura visual não muda automaticamente todas as regras do programa.
Outra experiência é usar pressed no lugar de keys na escolha de cor. O vermelho deve durar apenas uma atualização por aperto, mesmo que A continue segurado. Pode ser rápido demais para perceber claramente; o contador ou um teste quadro a quadro ajudam a observar essa diferença.
Para consultar o formato e as transições dos controles, veja Tonc: keypad.
Anterior: tempo e VBlank. Próximo: usar o direcional para mover um quadrado. Índice da série.