Você está na página 1de 243

Tutorial de programação

de jogos para Atari 2600

Linguagem Assembly
para o processador 6502

(com a pretensão de ser quase completo e útil)

por Stanley A. Sales


Brasília-DF, 2007
Introdução......................................................................................................... 6
Referência ......................................................................................................... 8
História .............................................................................................................. 9
O 6502 ............................................................................................................. 11
Página de memória ............................................................................................... 11
Registradores........................................................................................................ 12
Accumulator ........................................................................................................................ 12
Index Register X ................................................................................................................. 12
Index Register Y ................................................................................................................. 12
Program Counter ................................................................................................................ 12
Stack Pointer ...................................................................................................................... 12
Processor Status Register .................................................................................................. 12
Carry Flag (C) ............................................................................................................... 13
Zero Flag (Z)................................................................................................................ 13
Interrupt Disable (I)........................................................................................................... 13
Decimal Mode (D) ............................................................................................................ 13
Break Command (B) ......................................................................................................... 13
Overflow Flag (V) ............................................................................................................ 13
Negative Flag (N) ............................................................................................................ 14
Modos de endereçamento .................................................................................... 14
Immediate Addressing ........................................................................................................ 14
Absolute Addressing........................................................................................................... 14
Zero page Addressing ........................................................................................................ 14
Implied Addressing ............................................................................................................. 15
Indirect Absolute Addressing.............................................................................................. 15
Absolute Indexed Addressing ............................................................................................. 15
Zero Page Indexed Addressing .......................................................................................... 15
Indexed Indirect Addressing ............................................................................................... 15
Indirect Indexed Addressing ............................................................................................... 15
Relative Addressing............................................................................................................ 15
Accumulator Addressing..................................................................................................... 16
As instruções do 6502 .......................................................................................... 17
Load and Store Group ........................................................................................................ 18
Arithmetic Group................................................................................................................. 19
Increment and Decrement Group ....................................................................................... 19
Register Transfer Group ..................................................................................................... 19
Logical Group ..................................................................................................................... 20
Compare and Bit Test Group.............................................................................................. 20
Shift and Rotate Group ....................................................................................................... 21
Jump and Branch Group..................................................................................................... 21
Stack Group........................................................................................................................ 21
Status Flag Change Group ................................................................................................. 22
Subroutine and Interrupt Group.......................................................................................... 22
Detalhes das instruções....................................................................................... 22
Instruções Aritméticas ........................................................................................................ 22
Instruções de Incremento e Decremento ........................................................................... 23
Instruções Lógicas.............................................................................................................. 24
Salto, Desvio, Comparação e Teste de Bits....................................................................... 25
Instruções de Mudança e Rotação..................................................................................... 26
Instruções de Transferência ............................................................................................... 31
Instruções de Pilha ............................................................................................................. 31
Instruções de Subrotina...................................................................................................... 33
A ordem de salvar na pilha ................................................................................................. 34
Instruções de set e reset .................................................................................................... 36
Outras Instruções ............................................................................................................... 37
Opcodes ilegais aceitos no 6502 do 2600 ........................................................... 37
ANC .................................................................................................................................... 37
ANE..................................................................................................................................... 38
ARR .................................................................................................................................... 38
ASR..................................................................................................................................... 38
DCP .................................................................................................................................... 38
ISB ...................................................................................................................................... 38
LAS ..................................................................................................................................... 39
LAX ..................................................................................................................................... 39
LXA ..................................................................................................................................... 39
RLA ..................................................................................................................................... 39
RRA .................................................................................................................................... 40
SAX..................................................................................................................................... 40
SBC..................................................................................................................................... 40
SBX..................................................................................................................................... 40
SHA..................................................................................................................................... 40
SHS..................................................................................................................................... 40
SHX..................................................................................................................................... 41
SHY..................................................................................................................................... 41
SLO..................................................................................................................................... 41
SRE..................................................................................................................................... 41
Programando em Assembly para o 6502 ..................................................... 42
Exemplo de carregamento, transferência e deslocamento ................................................ 45
Exemplo de operação aritmética normal, BCD com e sem Carry...................................... 49
Exemplo de lógica, incremento e decremento de X e Y .................................................... 50
Casos interessantes ........................................................................................................... 51
Exemplos de armazenamento e variáveis.......................................................................... 51
Exemplo de incremento/decremento de registrador e variável .......................................... 53
Utilizando os modos de endereçamento ............................................................................ 54
Exemplos de comparação e desvio.................................................................................... 58
A televisão ...................................................................................................... 62
Conhecendo a estrutura da televisão.................................................................. 62
Exploração de uma imagem ............................................................................................... 62
Tubo de raios catódicos (CRT – Cathode Ray Tube) ........................................................ 65
O protocolo do 2600 para a TV ............................................................................ 69
O 2600 ............................................................................................................. 71
O TIA e o 6502 ....................................................................................................... 71
Descrição geral do TIA ......................................................................................... 71
Os registradores ................................................................................................................. 71
Sincronismo .......................................................................................................... 72
Temporização horizontal .................................................................................................... 72
Sincronismo com o 6502 .................................................................................................... 73
Temporização vertical......................................................................................................... 73
WSYNC (Wait for Sync)...................................................................................................... 76
RSYNC (Reset Sync) ......................................................................................................... 77
VSYNC................................................................................................................................ 77
VBLANK.............................................................................................................................. 77
O Playfield (campo de jogo) ............................................................................................... 77
PF0, PF1 e PF2 .................................................................................................................. 78
CTRLPF .............................................................................................................................. 78
Mísseis (M0 e M1) .............................................................................................................. 78
ENAM0 e ENAM1 ............................................................................................................... 79
Bola (BL) ............................................................................................................................. 79
ENABL ................................................................................................................................ 79
VDELBL, VDELP0 e VDELP1 ............................................................................................ 79
Players (P0 e P1)................................................................................................................ 80
GRP0 e GRP1 .................................................................................................................... 80
COLUBK, COLUPF, COLUP0 e COLUP1 ......................................................................... 81
REFP0 e REFP1................................................................................................................. 81
NUSIZ0 e NUSIZ1 .............................................................................................................. 81
Os objetos móveis .............................................................................................................. 82
Cor e luminosidade............................................................................................................. 82
Posicionamento horizontal.................................................................................................. 82
RESP0, RESP1, RESM0, RESM1 e RESBL ..................................................................... 83
RESMP0 e RESMP1 .......................................................................................................... 83
Movimento Horizontal ......................................................................................................... 83
HMP0, HMP1, HMM0, HMM1 e HMBL .............................................................................. 84
HMOVE............................................................................................................................... 84
HMCLR ............................................................................................................................... 85
Prioridades dos objetos ...................................................................................................... 85
Colisões .............................................................................................................................. 85
CXCLR................................................................................................................................ 85
Som..................................................................................................................................... 86
Tom..................................................................................................................................... 86
AUDC0 e AUDC1 ............................................................................................................... 86
Freqüência .......................................................................................................................... 87
AUDF0 e AUDF1 ................................................................................................................ 87
Volume................................................................................................................................ 87
AUDV0 e AUDV1................................................................................................................ 87
Portas de entrada ............................................................................................................... 87
Portas de entrada dumped (INPT0 a INPT3) ..................................................................... 88
Portas de entrada latched (INPT4 e INPT5)....................................................................... 88
O PIA (6532)........................................................................................................... 88
O Timer ............................................................................................................................... 88
Setando o timer .................................................................................................................. 88
Lendo o timer...................................................................................................................... 88
Quando o timer chega a zero ............................................................................................. 88
RAM .................................................................................................................................... 89
As portas de entrada/saída ................................................................................................ 89
Porta B - Chaves do console (somente leitura).................................................................. 89
Porta A - Controladores de Mão ......................................................................................... 89
Setando para entrada ou saída .......................................................................................... 89
Joysticks ............................................................................................................................. 90
Paddle (pot) ........................................................................................................................ 90
Teclado ............................................................................................................................... 90
Ciclos de máquina ................................................................................................ 91
Conceito de contagem........................................................................................................ 91
Ciclos de CPU em relação aos pixels da tela..................................................................... 91
Como se lembrar de quanto tempo gasta o que ................................................................ 92
Instruções de desvio........................................................................................................... 92
Instruções matemáticas rápidas ......................................................................................... 92
Instruções de armazenamento ........................................................................................... 92
Instruções weenie............................................................................................................... 92
Instruções matemáticas lentas ........................................................................................... 92
Instruções de pilha.............................................................................................................. 93
Outras instruções................................................................................................................ 93
A contagem de ciclos na prática......................................................................................... 93
Como lidar com o (indirect),Y ............................................................................................. 94
Como gastar ciclos de máquina ......................................................................................... 94
Começando a programar ............................................................................... 96
O ambiente ............................................................................................................ 96
Nosso primeiro programa .................................................................................. 106
O cabeçalho...................................................................................................................... 106
O rodapé ........................................................................................................................... 106
O programa....................................................................................................................... 107
Declarando variáveis ........................................................................................................ 109
Sincronizando com a TV................................................................................................... 111
Exibindo alguma imagem ................................................................................................. 118
A contagem errada de ciclos ............................................................................................ 131
Utilizando o timer .............................................................................................................. 136
Desenhando gráficos.......................................................................................... 147
Desenhando um jogador .................................................................................................. 147
Posicionando o gráfico ..................................................................................................... 154
Regsitradores de movimento horizontal ........................................................................... 155
Utilizando o joystick .......................................................................................................... 160
Desenhando um playfield ................................................................................................. 165
Detectando colisão ........................................................................................................... 170
Outros objetos .................................................................................................... 174
A bola................................................................................................................................ 174
O míssil ............................................................................................................................. 176
O console ............................................................................................................ 179
Som...................................................................................................................... 182
Números .............................................................................................................. 184
Sharing bytes .................................................................................................................... 198
Bankswitching .............................................................................................. 202
Uma ROM de 2 bancos .................................................................................................... 204
Uma ROM de 8 bancos .................................................................................................... 211
Utilizando bancos como uma coleção de subrotinas ....................................................... 226
Hacking ......................................................................................................... 237
Conclusão ..................................................................................................... 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Introdução
Após vários meses coletando informações sobre programação de jogos para o Atari 2600,
resolvi escrever esse tutorial na tentativa de reunir, em um único documento, todas as
informações que consegui.

Esse tutorial não é um guia genérico, ou seja, não é o dono da verdade que tenta fazer com
que as pessoas leiam e pensem que é a única forma das coisas serem feitas. De fato, é só
uma descrição de como eu aprendi (o pouco que sei) e como faço as coisas acontecerem no
Atari. Isso dará um norte para quem ainda não sabe absolutamente nada (e nem por onde
começar) e para quem já sabe será uma ótima fonte de erros para poderem criticar e corrigir
(hehehe).

Já dizia o sábio: “Quando você transcreve o trabalho de alguém, é plágio. Quando você
transcreve o trabalho de várias pessoas, é pesquisa”.

Como eu aprendi lendo textos de várias pessoas, esse tutorial é resultado de uma pesquisa,
portanto não é plágio. Além do mais, em nenhum dos textos havia ressalva sobre
transcrever parte ou todo seu conteúdo. E sejamos francos: se alguma coisa for útil nesse
tutorial, quem me garante que alguém não vai copiá-lo, mudar algumas coisas, dar seu
toque pessoal, e depois fazer a mesma coisa que estou fazendo (e até melhor)?

Portanto, vamos deixar de blá blá blá e começar a tentar entender como o Atari funciona.

E:\Atari\SDK\doc\Tutorial.doc Página 6 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Algumas coisas sobre esse tutorial


• Como não me lembro de algumas das fontes de informação, não as citarei. As que eu
me lembro estão na próxima página.

• Não citarei também de onde baixar os programas, fontes, ROMs que uso, pois não
faço a mínima idéia de como consegui. O máximo que farei é dar o nome e versão do
programa, daí é só pesquisar na internet e baixar. O único que eu sei é o AtariPaint
que eu mesmo escrevi.

• Toda vez que me referir ao Atari 2600, será somente 2600.

E:\Atari\SDK\doc\Tutorial.doc Página 7 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Referência
Esse tutorial é uma tradução tosca e uma compilação dos seguintes textos:

2600-101 – A tutorial by Kirk Israel


2600 Programming for Newbies by Andrew Davie
Assembly in One Step – RTK/23-07-1997
Stella Programmer’s Guide by Steve Wright
ATARI 2600 Advanced Programming Guide by Paul Slocum
Nick Bensema's Guide to Cycle Counting on the Atari 2600

e mais alguns outros...

Algumas coisas são de minha autoria mesmo.

E:\Atari\SDK\doc\Tutorial.doc Página 8 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

História
Como não poderia deixar de ser, vamos contar um pouco da história do 2600. Essa
lengalenga toda pode ser ignorada, se quiser.

O texto abaixo é um dos poucos que eu lembro onde li. É uma tradução tosca do .html que
vem junto com o emulador do 2600 que eu uso: o Stella.

Tudo começou na década de 70. Os jogos arcades começaram a aparecer e foram um


sucesso, tanto que despertou o interesse para versões caseiras (no sentido de tê-los em
casa para jogar, pois em princípio, eram somente fliperamas que ficavam em bares,
parques, etc.).

Então em 1975, a Atari lançou o Home Pong. Outras empresas como a Magnavox e Coleco
seguiram a idéia e lançaram seus próprios consoles. Em 1976, a Fairchild Camera and
Instrument apresentou o sistema Channel F, que foi o primeiro sistema de video game
baseado na utilização de cartuchos. A indústria reconheceu que sistemas de cartuchos
seriam o futuro dos jogos eletrônicos e partiram para essa direção. Em janeiro de 1977, a
RCA lançou o Studio II, outro sistema baseado em cartuchos, apesar de projetado somente
em preto e branco e focado para títulos educacionais. Então, em outubro de 1977, a Atari
lançou o Atari VCS (Video Computer System) com uma oferta inicial de 9 jogos. Esse
sistema foi, mais tarde, chamado de Atari 2600,

A Atari teve as maiores vendas em 1978 e lançou mais jogos como Outlaw, Spacewar e
Breakout. Só que internamente, estava com problemas. Nolan Bushnell, o inventor do Pong
e fundador da Atari, deixou a empresa. Em 1979 a Atari continuou sua linha e lançou mais
12 jogos com sucesso. Entretanto, a Atari agora enfrentava a concorrência da Mattel
Intellivision e Magnavox Odyssey2.

A Atari precisava de um mega-hit em 1980 para poder ganhar da concorrência, e conseguiu


fazendo uma versão home de um jogo do Japão chamado Space Invaders. Esse jogo foi tão
popular que as pessoas compravam o 2600 só para poder jogar Space Invaders em casa.
Seguindo essa linha, a Atari lançou o Adventure, que foi o primeiro jogo a conter um ovo de
páscoa (colocando um objeto em uma certa área revelava o nome do programador, Warren
Robinett). O ano de 1980 foi importante por outra razão: a criação da Activision. A empresa
era formada por 4 ex-funcionários da Atari. Eles lançaram 4 jogos inicialmente: Dragster,
Fishing Derby, Checkers e Boxing. Os jogos foram bem recebidos pelo público, e mostraram
que o 2600 era capaz de melhores jogos que a própria Atari vinha produzindo.

Em 1981, a indústria do video game era praticamente uma competição entre o 2600 e o
Intellivision. Enquanto o Intellivision era tecnologicamente superior, o 2600 continuava líder
em vendas. A Atari lançou a versão home do Asteroids, que foi um grande sucesso.
Inspirado pelo sucesso da Activision, outro grupo de desenvolvimento de software chamado
Imagic foi formado. Contudo, eles não lançaram nenhum jogo até 1982. Outra empresa,
Games by Apollo, foi criada no Texas e lançou vários jogos. A Coleco entrou no mercado em
1982 com o graficamente superior Colecovision. Para combater o novo sistema, a Atari
lançou o 5200, um sistema tecnologicamente comparável. O 2600 caiu $100 no preço para
continuar competitivo. Então uma empresa chamada Arcadia lançou um periférico chamado
Supercharger que rodava jogos em uma fita K7. Isso permitiu carregamentos múltiplos e
expandiu a capacidade do 2600.

A Atari lançou o Pac-Man e E.T. naquele ano. Apesar de ter vendido muitas cópias do Pac-
Man, esse jogo foi considerado uma versão pobre do seu similar arcade.

E:\Atari\SDK\doc\Tutorial.doc Página 9 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Depois que a Atari aceitou a produção de jogos por terceiros, várias empresas surgiram,
como a Venturevision, Spectravision, Telesys, CBS, 20th Century Fox, US Games, M
Network, Tigervision, Data Age, Imagic e Coleco. Havia até mesmo uma empresa que
lançou uma linha de jogos X-Rated (pornôs) para o 2600 chamada Mystique.

Para resumir a coisa, a conseqüência do surgimento de todas essas empresas (aumento da


concorrência e todo mundo querendo ganhar dinheiro) fez com que o preço do 2600 caísse
ainda mais. Havia muita oferta para pouca procura, o mercado estava saturado e então
muitas dessas empresas fecharam as portas. A Atari tentou ainda lançar o 7800 e prometeu
melhorar o 2600 (gráfico e som). Nada disso aconteceu. O caos se instalou (dramático,
não?).

A Atari ainda lançou alguns jogos em 1985. A Activision lançou o Cosmic Commuter e
Ghostbusters, mas os jogos não venderam bem. Ainda assim, a Atari vendeu 1 milhão de
consoles nesse ano.

A Nintendo trouxe o NES para a América, e como foi um sucesso, mostrou que os video
games ainda teriam um lugar nos Estados Unidos. A Atari então decidiu que talvez seria
uma boa idéia lançar o 7800 que tinha em estoque e produzir mais jogos para o 2600. O
7800 foi lançado com apenas 3 jogos, apesar de ser compatível com o 2600. Eles também
redesenharam o 2600 como 2600Jr. uma máquina com as mesmas características, mas um
novo visual e campanha de marketing.

Em 1987 a Atari lançou novos títulos, como Jr. Pac-Man e também licenciou jogos de outras
empresas como Donkey Kong e Q*Bert. Outros jogos apareceram como Epyx e Exus. Em
1988, a Atari recontratou Nolan Bushnell e anunciou novos títulos, incluindo Secret Quest
(um jogo escrito pelo próprio Bushnell). A Atari continuou fabricando jogos até 1989.

E:\Atari\SDK\doc\Tutorial.doc Página 10 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O 6502
O processador utilizado no 2600 é o 6502 (um melhoramento do 6507). Então vamos passar
rapidamente pela arquitetura desse processador.

O 6502 foi fabricado com tecnologia MOS na década de 70 e era o principal processador
numa grande variedade de microcomputadores como o Apple II, BBC Model B, Acorn
Electron, Atari 800 e o Commodore 64, Vic 20 e modelos PET. Ele tem um conjunto
relativamente simples de instruções para os padrões modernos, o que tornava barata sua
produção.

Especificações básicas
Velocidade de clock 1, 2 e até 3 MHz
Tamanho da palavra 8 bits
Entrada/Saída Memória mapeada

Não há registradores de entrada/saída no processador, então um conjunto de endereços de


memória é alocado para os dispositivos de entrada/saída. O hardware assegura que se um
valor é escrito no endereço E000h (por exemplo), ele será enviado direto para um dispositivo
de saída específico.

Endereçamento
BUS de endereço 16 bits
Formato Little endian

O endereçamento compreende valores de 0000000000000000b até 1111111111111111b,


ou seja, de 0000h até FFFFh, portanto 64KB de endereçamento. E é feito no formato little
endian, ou seja, os endereços são especificados com o byte menos significativo primeiro.

Um endereço de 16 bits precisa ser especificado em 2 bytes consecutivos. Por exemplo, um


processador little endian endereça 458Dh como 8Dh seguido pelo byte 45h.

Página de memória
A memória é vista como um conjunto de páginas de 256 bytes. A primeira página (0000h a
00FFh) é chamada de página zero (zero page) e pode ser acessada usando um modo
especial de endereçamento que permite usar instruções menores e, portanto, de execução
mais rápida. Isso se torna útil para armazenar tabelas de valores ou endereços que são
acessados freqüentemente pelo programa.

A segunda página (0100h a 01FFh) é usada para a pilha do sistema. Ela mantém um
histórico de valores, especialmente durante chamadas de subrotinas e não pode ser movida.

Outros locais reservados


FFFAh-FFFBh Endereço do NMI handler
FFFCh-FFFDh Endereço Reset handler
FFFEh-FFFFh BRK/IRQ

E:\Atari\SDK\doc\Tutorial.doc Página 11 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Registradores
O 6502 tem somente 6 registradores. 5 deles são de 8 bits e 1 é de 16 bits.

Registrador Nome Tamanho


A Accumulator 8 bits
X Index Register X 8 bits
Y Index Register Y 8 bits
PC Program Counter 16 bits (PCH e PCL)
S Stack Pointer 8 bits
P Processor Status (Flags) 8 bits

Accumulator
Usado para todas as operações lógicas e aritméticas (exceto incrementos e decrementos).
Os dados devem ser carregados nesse registrador antes de poderem ser manipulados.

Index Register X
Geralmente usado para contadores ou offsets para acessar a memória. Conteúdos podem
ser comparados com locais de memória e incrementados e decrementados. Diferentemente
de outros registradores (inclusive o Y), pode ser usado para obter uma cópia do ponteiro da
pilha ou mudar seu valor.

Index Register Y
Geralmente usado para contadores ou offsets para acessar memória. Conteúdos podem ser
comparados com locais de memória e incrementados ou decrementados.

Program Counter
Contém o endereço da próxima instrução a ser executada. É incrementado automaticamente
pelo hardware, mas pode ser alterado por saltos, loops, condições e chamadas/retornos de
subrotinas.

Stack Pointer
O 6502 usa uma pilha de 256 bytes localizada na página 1 (0100h a 01FFh). O ponteiro da
pilha é um registrador de 8 bits que guarda o byte menos significativo do próximo local livre
na pilha. Isso significa que a pilha não pode ser movida. A pilha começa em 01FFh e
decresce. Quando um byte é colocado na pilha, o ponteiro da pilha é decrementado. Quando
alguma coisa é retirada da pilha, o ponteiro é incrementado. O hardware não detecta o
estouro da pilha. O programador deve assegurar que o programa não use mais que o
espaço da pilha.

Processor Status Register

Bit 7 6 5 4 3 2 1 0
N V - B D I Z C

Também conhecido como Flags Register. Usado para indicar os resultados de uma
operação. Cada bit no registrador significa uma condição diferente (um estado diferente).
Algumas das instruções permitem testar os valores dos vários bits, setá-los, limpá-los (zerá-
los), colocar todos na pilha e retirar.

E:\Atari\SDK\doc\Tutorial.doc Página 12 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Carry Flag (C)


O Carry flag é setado se a última operação causou um excesso (estouro ou overflow) do bit
mais significativo (bit 7) do resultado ou uma falta (underflow) do bit menos significativo (bit
0). Essa condição é setada durante as instruções aritméticas, instruções de comparação e
durante mudanças lógicas (shifts). Pode ser explicitamente setado usando a instrução Set
Carry flag (SEC) e limpo (zerado) com a instrução Clear Carry flag (CLC).

Zero Flag (Z)


O Zero flag é setado se o resultado da última operação for zero.

Interrupt Disable (I)


O Interrupt Disable flag é setado se o programa executou uma instrução Set Interrupt
Disable (SEI). Enquanto esse flag estiver setado, o processador não responderá a
interrupções de dispositivos externos até que ele seja limpo (zerado) pela instrução Clear
Interrupt Disable (CLI).

Decimal Mode (D)


Enquanto o Decimal Mode flag estiver setado, o processador obedecerá às regras da
aritmética BCD (Binary Coded Decimal) durante adição e subtração. O flag pode ser
explicitamente setado usando a instrução Set Decimal flag (SED) e limpo (zerado) com a
instrução Clear Decimal flag (CLD).

Break Command (B)


O Break Command flag é setado quando uma instrução BRK é executada e uma interrupção
é gerada pelo processador.

Overflow Flag (V)


O Overflow flag é setado durante operações aritméticas se o resultado for um complemento
de 2 inválido. Por exemplo: adicionando 2 números positivos e resultando em um número
negativo (64 + 64 = -128).

Para entendermos melhor, 1 byte tem 8 bits. O oitavo bit é usado como sinal do número.

Bit 7 6 5 4 3 2 1 0
sinal

Segundo o exemplo acima, 64d é 01000000b, assim:

Bit 7 6 5 4 3 2 1 0
sinal
Valor 0 1 0 0 0 0 0 0

Sendo o bit 7 (sinal) igual a 0 o número é positivo. Somando 64 + 64 temos 128. Isso em
binário é 10000000b. Então:

Bit 7 6 5 4 3 2 1 0
sinal
Valor 1 0 0 0 0 0 0 0

E:\Atari\SDK\doc\Tutorial.doc Página 13 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Agora o bit 7 é 1, dizendo que o número é negativo.

Negative Flag (N)


O Negative flag é setado se o resultado da última operação tiver o bit 7 (mais significativo)
setado a 1. No caso do exemplo acima, 64 + 64 = 128. O resultado (128) estaria em um
registrador, o Negative flag seria setado a 1, pois o bit 7 do resultado é 1, indicando que o
número é negativo.

Modos de endereçamento
O 6502 tem 13 modos de endereçamento (formas de como acessar a memória). São eles:

Modo Formato
Imediato #aa
Absoluto aaaa
Zero page aa
Implícito
Absoluto indireto (aaaa)
Absoluto indexado, X aaaa, X
Absoluto indexado, Y aaaa, Y
Zero page indexado, X aa, X
Zero page indexado, Y aa, Y
Indireto indexado (aa, X)
Indexado indireto (aa), Y
Relativo aaaa
Accumulator A

Immediate Addressing – Endereçamento imediato


O valor dado é um número para ser usado imediatamente pela instrução. Por exemplo:

LDA #$15 ; carrega o valor 15h no acumulador.

Absolute Addressing – Endereçamento absoluto


O valor dado é um endereço de 16 bits de um local da memória que contém o valor de 8 bits
a ser usado. Por exemplo:

STA $3E32 ;armazena o valor que está no acumulador no local 3E32h da


;memória.

Zero page Addressing – Endereçamento zero page


Os primeiros 256 locais da memória (0000h a 00FFh) são chamados zero page. Os
próximos 256 locais da memória (0100h a 01FFh) são chamados página 1, etc. Instruções
que fazem uso da zero page economizam memória por não usar um 00h extra para indicar
a parte alta do endereço. Por exemplo:

LDA $0023 ; funciona, mas usa um byte extra


LDA $23 ; endereço zero page

E:\Atari\SDK\doc\Tutorial.doc Página 14 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Implied Addressing – Endereçamento implícito


Muitas instruções têm somente 1 byte e não fazem referência à memória. Utilizam
endereçamento implícito. Por exemplo:

CLC ; limpa (zera) o Carry flag


DEX ; decrementa o registrador X
TYA ; transfere o valor de Y para A

Indirect Absolute Addressing – Endereçamento absoluto indireto


Somente usado pela instrução JMP (JuMP). Ela pega o endereço dado e o usa como um
ponteiro para a parte baixa de um endereço de 16 bits na memória, então pula para esse
endereço. Por exemplo:

JMP ($2345) ; pula para o endereço em 2345h baixo e 2346h alto

Portanto, se o endereço 2345h contém o valor EAh e o endereço 2346h contém 12h então a
próxima instrução executada será aquela armazenada no endereço 12EAh. Lembrando que
o endereço no 6502 é little endian (baixo/alto).

Absolute Indexed Addressing – Endereçamento indexado absoluto


O endereço final é encontrado tomando o endereço dado como base e adicionando o valor
atual de X ou Y a ele como um offset (deslocamento). Por exemplo:

LDA $F453,X ; onde X contém o valor 3

Carrega o acumulador com o conteúdo do endereço F453h + 3h = F456h

Zero Page Indexed Addressing – Endereçamento indexado zero page


O mesmo que o Absolute Indexed, mas o endereço dado é zero page o que economiza 1
byte de memória.

Indexed Indirect Addressing – Endereçamento indireto indexado


Encontra o endereço de 16 bits começando pelo local dado mais o valor do registrador X. O
valor é o conteúdo desse endereço. Por exemplo:

LDA ($B4, X) ; onde X contém o valor 6

Fornece um endereço de B4h + 6h = BAh. Se BAh e BBh contém 12h e EEh


respectivamente, então o endereço final é EE12h. O valor no local EE12h é colocado no
acumulador.

Indirect Indexed Addressing – Endereçamento indexado indireto


Encontra o endereço de 16 bits contido no local dado (e no seguinte). Adiciona a esse
endereço o valor do registrador Y. Obtém o valor armazenado no novo endereço. Por
exemplo:

LDA ($B4),Y ; onde Y contém o valor 6

Se B4h contém EEh e B5h contém 12h então o valor no local da memória 12EEh + Y(6) =
12F4h é obtido e colocado no acumulador.

Relative Addressing – Endereçamento relativo


Instruções de desvio utilizam endereçamento relativo. O próximo byte é um offset com sinal

E:\Atari\SDK\doc\Tutorial.doc Página 15 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

do endereço atual e a continuação da execução é o endereço da próxima instrução. Por


exemplo:

BNE $7F ; (desvia o fluxo do programa se zero flag é reset)

Essa instrução soma 127 (7Fh = 127d) ao registrador PC atual e então começa a executar
as instruções a partir daquele endereço. Outro exemplo:

BEQ $F9 ; (desvia o fluxo do programa se zero flag é set)

Essa instrução soma -7 ao registrador PC atual e então começa a executar as instruções a


partir daquele endereço. Daí vem a pergunta: se F9h = 255d como soma -7? Simples: Como
o oitavo bit é o bit de sinal, então temos a possibilidade de números que vão de -128 (80h)
até +127 (7Fh). Então, se o bit mais alto está setado e o número é 7Fh temos um desvio
negativo. Mas qual o tamanho/distância desse desvio? Se o valor é menor que 80h positivo
ele desvia a quantidade de bytes especificada. Se o valor é 7Fh (negativo) então será o
complemento de 2 do valor dado na direção negativa. Relembrando: O complemento de 2
de um número é encontrado trocando todos os 1s e 0s e somando 1. Então:

FFh = 1111 1111 ; original


0000 0000 ; complemento de 1
+ 1
--------------
0000 0001 ; complemento de 2, portando FFh = -1

Então para clarear ainda mais o exemplo acima temos que:

F9h = 11111001 ; original


00000110 ; complemento de 1
+ 1
--------------
00000111 ; complemento de 2, portanto F9h = -7

Nesse caso, se o registrador PC estivesse com F03Ch, a instrução colocaria nele F035h,
pois ela diz “você está em F03Ch e vá para 7 bytes para trás”. O processador,
obedientemente, subtrai de F03Ch os 7 bytes e encontra F035h. Por fim executa a instrução
nesse novo endereço e seus subseqüentes até encontrar outra instrução de desvio, ou não.

Na prática, programadores de assembly usam labels e o assembler (compilador) faz o


cálculo de quantos bytes devem ser cada desvio em cada linha do programa que tem
instrução de desvio. Note que os desvios só podem ser feitos para endereços entre -128 e
+127 bytes de distância do endereço atual (o valor do registrador PC). O 6502 não permite
desvios para endereços absolutos.

Accumulator Addressing – Endereçamento com acumulador


Como no endereçamento implícito, o objeto da instrução é o acumulador e não precisa ser
especificado.

E:\Atari\SDK\doc\Tutorial.doc Página 16 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

As instruções do 6502
Vimos até agora a arquitetura do processador e os modos de como acessar a memória.
Agora vamos ver as instruções e o que cada uma delas faz. De forma simples e geral temos:

Grupo de carregamento (load) e armazenamento (store) Grupo de mudança (shift) e rotação (rotate)

Mnemônico Descrição Flags afetados Mnemônico Descrição Flags afetados

LDA Load Accumulator N,Z ASL Arithmetic Shift Left N,Z,C


LDX Load X register N,Z LSR Logical Shift Right N,Z,C
LDY Load Y register N,Z ROL Rotate Left N,Z,C
STA Store Accumulator ROR Rotate Right N,Z,C
STX Store X register Grupo de Salto (jump) e Desvio (branch)

STY Store Y register JMP Jump to another location


Grupo aritmético BCC Branch if Carry flag clear
ADC Add with Carry N,V,Z,C BCS Branch if Carry flag set
SBC Subtract with Carry N,V,Z,C BEQ Branch if Zero flag set
Grupo de incremento e decremento BMI Branch if Negative flag set
INC Increment a memory location N,Z BNE Branch if Zero flag clear
INX Increment the X register N,Z BPL Branch if Negative flag clear
INY Increment the Y register N,Z BVC Branch if Overflow flag clear
DEC Decrement a memory location N,Z BVS Branch if Overflow flag set
DEX Decrement the X register N,Z Grupo de pilha

DEY Decrement the Y register N,Z TSX Transfer Stack pointer to X N,Z
Grupo de transferência TXS Transfer X to Stack pointer
TAX Transfer Accumulator to X N,Z PHA Push Accumulator on Stack
TAY Transfer Accumulator to Y N,z PHP Push Processor Status on Stack
TXA Transfer X to Accumulator N,Z PLA Pull Accumulator from Stack N,Z
TYA Transfer Y to Accumulator N,Z PLP Pull Processor Status from Stack Todos
Grupo lógico Grupo de mudança do Status flag register

AND Logical AND N,Z CLC Clear Carry flag C


EOR Exclusive OR N,Z CLD Clear Decimal Mode flag D
ORA Logical Inclusive OR N,Z CLI Clear Interrupt Disable flag I
Grupo de comparação e teste de bit CLV Clear Overflow flag V
CMP Compare Accumulator N,Z,C SEC Set Carry flag C
CPX Compare X register N,Z,C SED Set Decimal Mode flag D
CPY Compare Y register N,Z,C SEI Set Interrupt Disable flag I
BIT Bit Test N,V,Z Grupo de subrotina e interrupção

JSR Jump to a subroutine


RTS Return from subroutine
BRK Force an interrupt B
RTI Return from interrupt Todos
NOP No operation

E:\Atari\SDK\doc\Tutorial.doc Página 17 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Há 56 instruções no 6502. Muitas dessas instruções fazem uso de mais de um modo de


endereçamento e cada combinação instrução/modo de endereçamento tem um único
opcode hexadecimal que o especifica exatamente.

Load and Store Group


Instrução Descrição Modo de Endereçamento Formato Opcode

Absolute LDA $aaaa $AD


Zero Page LDA $aa $A5
Immediate LDA #$aa $A9
Absolute Indexed, X LDA $aaaa,X $BD
LDA Load Accumulator
Absolute Indexed, Y LDA $aaaa,Y $B9
Zero Page Indexed, X LDA $aa,X $B5
Indexed Indirect LDA ($aa,X) $A1
Indirect Indexed LDA ($aa),Y $B1
Absolute LDX $aaaa $AE
Zero Page LDX $aa $A6
LDX Load X Register Immediate LDX #$aa $A2
Absolute Indexed, Y LDX $aaaa,Y $BE
Zero Page Indexed, Y LDX $aa,Y $B6
Absolute LDY $aaaa $AC
Zero Page LDY $aa $A4
LDY Load Y Register Immediate LDY #$aa $A0
Absolute Indexed, X LDY $aaaa,X $BC
Zero Page Indexed, X LDY $aa,X $B4
Absolute STA $aaaa $8D
Zero Page STA $aa $85
Absolute Indexed, X STA $aaaa,X $9D
STA Store Accumulator Absolute Indexed, Y STA $aaaa,Y $99
Zero Page Indexed, X STA $aa,X $95
Indexed Indirect STA ($aa,X) $81
Indirect Indexed STA ($aa),Y $91
Absolute STX $aaaa $8E
STX Store X Register Zero Page STX $aa $86
Zero Page Indexed, Y STX $aa,Y $96
Absolute STY $aaaa $8C
STY Store Y Register Zero Page STY $aa $84
Zero Page Indexed, X STY $aa,X $94

E:\Atari\SDK\doc\Tutorial.doc Página 18 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Arithmetic Group
Instrução Descrição Modo de Endereçamento Formato Opcode

Absolute ADC $aaaa $6D


Zero Page ADC $aa $65
Immediate ADC #$aa $69
Absolute Indexed, X ADC $aaaa,X $7D
ADC Add with Carry
Absolute Indexed, Y ADC $aaaa,Y $79
Zero Page Indexed, X ADC $aa,X $75
Indexed Indirect ADC ($aa,X) $61
Indirect Indexed ADC ($aa),Y $71
Absolute SBC $aaaa $ED
Zero Page SBC $aa $E5
Immediate SBC #$aa $E9
Absolute Indexed, X SBC $aaaa,X $FD
SBC Subtract with Carry
Absolute Indexed, Y SBC $aaaa,Y $F9
Zero Page Indexed, X SBC $aa,X $F5
Indexed Indirect SBC ($aa,X) $E1
Indirect Indexed SBC ($aa),Y $F1

Increment and Decrement Group


Instrução Descrição Modo de Endereçamento Formato Opcode

Absolute INC $aaaa $EE


Increment a memory Zero Page INC $aa $E6
INC
location Absolute Indexed, X INC $aaaa,X $FE
Zero Page Indexed, X INC $aa,X $F6
INX Increment the X register Implied INX $E8
INY Increment the Y register Implied INY $C8
Absolute DEC $aaaa $CE
Decrement a memory Zero Page DEC $aa $C6
DEC
location Absolute Indexed, X DEC $aaaa,X $DE
Zero Page Indexed, X DEC $aa,X $D6
DEX Decrement the X register Implied DEX $CA
DEY Decrement the Y register Implied DEY $88

Register Transfer Group


Instrução Descrição Modo de Endereçamento Formato Opcode

TAX Transfer accumulator to X Implied TAX $AA


TAY Transfer accumulator to Y Implied TAY $A8
TXA Transfer X to accumulator Implied TXA $8A
TYA Transfer Y to accumulator Implied TYA $98

E:\Atari\SDK\doc\Tutorial.doc Página 19 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Logical Group
Instrução Descrição Modo de Endereçamento Formato Opcode

Absolute AND $aaaa $2D


Zero Page AND $aa $25
Immediate AND #$aa $29
Absolute Indexed, X AND $aaaa,X $3D
AND Logical AND
Absolute Indexed, Y AND $aaaa,Y $39
Zero Page Indexed, X AND $aa,X $35
Indexed Indirect AND ($aa,X) $21
Indirect Indexed AND ($aa),Y $31
Absolute EOR $aaaa $4D
Zero Page EOR $aa $45
Immediate EOR #$aa $49
Absolute Indexed, X EOR $aaaa,X $5D
EOR Exclusive OR
Absolute Indexed, Y EOR $aaaa,Y $59
Zero Page Indexed, X EOR $aa,X $55
Indexed Indirect EOR ($aa,X) $41
Indirect Indexed EOR ($aa),Y $51
Absolute ORA $aaaa $0D
Zero Page ORA $aa $05
Immediate ORA #$aa $09
Absolute Indexed, X ORA $aaaa,X $1D
ORA Logical Inclusive OR
Absolute Indexed, Y ORA $aaaa,Y $19
Zero Page Indexed, X ORA $aa,X $15
Indexed Indirect ORA ($aa,X) $01
Indirect Indexed ORA ($aa),Y $11

Compare and Bit Test Group


Instrução Descrição Modo de Endereçamento Formato Opcode

Absolute CMP $aaaa $CD


Zero Page CMP $aa $C5
Immediate CMP #$aa $C9
Absolute Indexed, X CMP $aaaa,X $DD
CMP Compare accumulator
Absolute Indexed, Y CMP $aaaa,Y $D9
Zero Page Indexed, X CMP $aa,X $D5
Indexed Indirect CMP ($aa,X) $C1
Indirect Indexed CMP ($aa),Y $D1
Absolute CPX $aaaa $EC
CPX Compare X register Zero Page CPX $aa $E4
Immediate CPX #$aa $E0
Absolute CPY $aaaa $CC
CPY Compare Y register Zero Page CPY $aa $C4
Immediate CPY #$aa $C0
Absolute BIT $aaaa $2C
BIT Bit Test
Zero Page BIT $aa $24

E:\Atari\SDK\doc\Tutorial.doc Página 20 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Shift and Rotate Group


Instrução Descrição Modo de Endereçamento Formato Opcode

Absolute ASL $aaaa $0E


Zero Page ASL $aa $06
ASL Arithmetic Shift Left Accumulator ASL A $0A
Absolute Indexed, X ASL $aaaa,X $1E
Zero Page Indexed, X ASL $aa,X $16
Absolute LSR $aaaa $4E
Zero Page LSR $aa $46
LSR Logical Shift Right Accumulator LSR A $4A
Absolute Indexed, X LSR $aaaa,X $5E
Zero Page Indexed, X LSR $aa,X $56
Absolute ROL $aaaa $2E
Zero Page ROL $aa $26
ROL Rotate Left Accumulator ROL A $2A
Absolute Indexed, X ROL $aaaa,X $3E
Zero Page Indexed, X ROL $aa,X $36
Absolute ROR $aaaa $6E
Zero Page ROR $aa $66
ROR Rotate Right Accumulator ROR A $6A
Absolute Indexed, X ROR $aaaa,X $7E
Zero Page Indexed, X ROR $aa,X $76

Jump and Branch Group


Instrução Descrição Modo de Endereçamento Formato Opcode

Absolute JMP $aaaa $4C


JMP Jump to another location
Indirect JMP ($aaaa) $6C
BCC Branch if carry flag clear Relative BCC aa $90
BCS Branch if carry flag set Relative BCS aa $B0
BEQ Branch if zero flag set Relative BEQ aa $F0
BMI Branch if negative flag set Relative BMI aa $30
BNE Branch if zero flag clear Relative BNE aa $D0
BPL Branch if negative flag clear Relative BPL aa $10
BVC Branch if overflow flag clear Relative BVC aa $50
BVS Branch if overflow flag set Relative BVS aa $70

Stack Group
Instrução Descrição Modo de Endereçamento Formato Opcode

TSX Transfer stack pointer to X Implied TSX $BA


TXS Transfer X to stack pointer Implied TXS $9A
PHA Push accumulator on stack Implied PHA $48
PHP Push processor status on stack Implied PHP $08
PLA Pull accumulator from stack Implied PLA $68
PLP Pull processor status from stack Implied PLP $28

E:\Atari\SDK\doc\Tutorial.doc Página 21 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Status Flag Change Group


Instrução Descrição Modo de Endereçamento Formato Opcode

CLC Clear carry flag Implied CLC $18


CLD Clear decimal mode flag Implied CLD $D8
CLI Clear interrupt disable flag Implied CLI $58
CLV Clear overflow flag Implied CLV $B8
SEC Set carry flag Implied SEC $38
SED Set decimal mode flag Implied SED $F8
SEI Set interrupt disable flag Implied SEI $78

Subroutine and Interrupt Group


Instrução Descrição Modo de Endereçamento Formato Opcode

JSR Jump to a subroutine Absolute JSR $aaaa $20


RTS Return from subroutine Implied RTS $60
BRK Force an interrupt Implied BRK $00
RTI Return from interrupt Implied RTI $40
NOP No operation Implied NOP $EA

Os microprocessadores gastam muito tempo movendo dados na memória. Dado de um local


é carregado em um registrador e armazenado em outro local, normalmente com algum
processo intermediário como adição, subtração etc. Dados na memória podem ser
carregados diretamente nos registradores A, X e Y, entretanto o registrador A tem mais
modos de endereçamento disponíveis.

Detalhes das instruções


Aqui apresentaremos maiores detalhes sobre as instruções com alguns exemplos simples.
Na próxima seção, criaremos programas simples em um simulador para praticarmos o que
foi/será visto em teoria e nos familiarizar com a arquitetura do 6502 em funcionamento.

Instruções Aritméticas
ADC – ADd to accumulator with Carry (soma ao acumulador com vai 1)
SBC - SuBtract from accumulator with Carry (subtrai do acumulador com empréstimo)

O 6502 tem 2 modos aritméticos: binário e decimal. Ambas, soma e subtração, implementam
o Carry flag a conter “vai 1” e “vem 1” (empréstimos). Note que no caso da subtração, é
necessário setar o Carry flag com o oposto do carry que está sendo subtraído.

A adição deve seguir essa forma:


CLC
ADC ...
.
.
ADC ...
.
.
.
Limpa-se (zera) o Carry flag e então realiza-se todas as somas. O “vai 1” entre as somas
será tratado no Carry flag.

E:\Atari\SDK\doc\Tutorial.doc Página 22 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

A subtração segue o mesmo formato:

SEC
SBC ...
.
.
SBC ...
.
.
Neste caso, setamos o Carry flag primeiro e então fazemos as subtrações.

Juntamente com o Carry flag, instruções aritméticas também alteram os flags N, Z e V:

Z = 1 se o resultado é zero, caso contrário o flag é zero


N = 1 se o bit 7 do resultado é 1, caso contrário o flag é zero
V = 1 se o bit 7 do acumulador foi mudado, uma mudança de sinal

Exemplo:

C = não importa
64h ; primeira parcela
clc
C = 0
adc 39h ; segunda parcela
9Dh ; soma

Após o CLC o Carry passou (ou permaneceu) com zero. Se ele fosse 1 o resultado seria
diferente (e não o esperado).

C = 1
64h ; primeira parcela
adc 39h ; segunda parcela
9Dh ; soma
01h ; Carry
9Eh ; resultado final

A mesma regra vale para a subtração. Para fixar: se não quiser que o Carry flag interfira no
resultado, deve-se zerá-lo antes de uma adição e setá-lo antes de uma subtração.

Instruções de Incremento e Decremento


INC - INCrement memory by one (incrementa memória em 1)
INX - INcrement X by one (incrementa o registrador X em 1)
INY - INcrement Y by one (incrementa o registrador Y em 1)
DEC - DECrement memory by one (decrementa memória em 1)
DEX - DEcrement X by one (decrementa o registrador X em 1)
DEY - DEcrement Y by one (decrementa o registrador Y em 1)

O 6502 tem instruções para incrementar/decrementar os registradores de índice e memória.


Note que não existe instrução para incrementar/decrementar o acumulador. No 65C02
existem as instruções INA e DEA que fazem isso, no 6502 não. As instruções de
registradores de índice são de modo implícito por razões óbvias enquanto as instruções INC
e DEC usam modos de endereçamento. Todas as instruções INC e DEC alteram os
seguintes flags:

Z = 1 se o resultado é zero, caso contrário o flag é zero


N = 1 se o bit 7 é 1, caso contrário o flag é zero
E:\Atari\SDK\doc\Tutorial.doc Página 23 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Instruções Lógicas
AND - AND memory with accumulator
ORA - OR memory with Accumulator
EOR - Exclusive-OR memory with accumulator

Essas instruções realizam operações lógicas de acordo com as tabelas verdade abaixo. Os
flags afetados são:
Z = 1 se o resultado é zero
N = 1 se o bit 7 do resultado é 1
Instrução AND
0 0 0
0 1 0
1 0 0
1 1 1 Ambos

Instrução ORA
0 0 0
0 1 1 Ou um
1 0 1 ou
1 1 1 ambos

Instrução EOR
0 0 0 Ou um
0 1 1 ou outro
1 0 1 mas não
1 1 0 ambos

Portanto, FFh AND 0Fh = 0Fh, pois:

1111 1111 ; FFh


and 0000 1111 ; 0Fh
-----------------------
0000 1111 ; 0Fh

A instrução AND é útil para mascarar bits. Por exemplo, para mascarar a parte alta (high
order, ou também nibble superior) de um valor (byte), basta fazer um AND do valor com 0Fh.

36h AND 0Fh = 06h

A instrução OR é útil para setar um bit em particular.

80h OR 08h = 88h

1000 0000 ; 80h


or 0000 1000 ; 08h
-----------------------
1000 1000 ; 88h

A instrução EOR é útil para inverter bits.

AAh eor FFh = 55h

E:\Atari\SDK\doc\Tutorial.doc Página 24 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

1010 1010 ; AAh


eor 1111 1111 ; FFh
-----------------------
0101 0101 ; 55h

Salto, Desvio, Comparação e Teste de Bits


JMP - JuMP to another location (GOTO) (salta para outro local, vá para)
BCC - Branch on Carry Clear, C = 0 (desvia se Carry flag = 0)
BCS - Branch on Carry Set, C = 1 (desvia se Carry flag = 1)
BEQ - Branch on EQual to zero, Z = 1 (desvia se Zero flag = 1)
BNE - Branch on Not Equal to zero, Z = 0 (desvia se Zero flag = 0)
BMI - Branch on MInus, N = 1 (desvia se Negative flag = 1)
BPL - Branch on PLus, N = 0 (desvia se Negative flag = 0)
BVS - Branch on oVerflow Set, V = 1 (desvia se Overflow flag = 1)
BVC - Branch on oVerflow Clear, V = 0 (desvia se Overflow flag = 0)
CMP - CoMPare memory and accumulator (compare a memória com o A)
CPX - ComPare memory and X (compare a memória com o X)
CPY - ComPare memory and Y (compare a memória com o Y)
BIT - test BITs (teste os bits)

Esse grupo inclui todas as instruções que alteram o fluxo do programa ou realizam uma
comparação de valores ou bits.

JMP simplesmente seta o registrador PC para o endereço dado. A execução do programa,


então, prossegue nesse novo endereço.

As instruções de desvio são saltos relativos. Elas causam o desvio para um novo endereço
que é ou 127 bytes além do endereço corrente (PC) ou 128 bytes antes do endereço
corrente (PC). Códigos que usam instruções de desvio são realocáveis e podem rodar em
qualquer lugar na memória. Eles são relativos pois, em vez das instruções dizerem para ir
para um endereço específico elas dizem ao processador mais ou menos assim: “Em relação
ao endereço atual de PC que é onde a execução do programa está atualmente, vá para n
bytes antes (ou n bytes depois, conforme o caso)”.

As 3 instruções de comparação são usadas para setar os bits dos flags. Depois de uma
comparação, freqüentemente desvia-se para um novo local do programa baseado no
resultado dessa comparação. Esse resultado é simplesmente a mudança de determinados
flags. O relacionamento entre os valores comparados e os flags é:

Comparação N Z C
Valor de A, X ou Y < valor da memória 1 0 0
Valor de A, X ou Y = valor da memória 0 1 1
Valor de A, X ou Y > valor da memória 0 0 1

A instrução de teste de bits testa bits da memória com o acumulador, mas não muda nada.
Somente o flag é mudado. É feito um AND lógico do conteúdo do local da memória
especificado com o acumulador e então os flags são setados como segue:

N = recebe o bit 7 do valor da memória antes do AND


V = recebe o bit 6 do valor da memória antes do AND
Z = é 1 se o resultado do AND é zero, caso contrário o flag é zero
E:\Atari\SDK\doc\Tutorial.doc Página 25 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Portanto, se 23h (que, nesse caso, é o endereço de memória) contém o valor 7Fh e o
acumulador contém 80h, ao executar a instrução:

BIT $23

resultará em:
V=1
Z=1
N=0

Por que? Vejamos em binário:


7Fh = 01111111b ; o que está na memória no local 23h
and 80h = 10000000b ; o valor do acumulador
--------------------------------
00h = 00000000b ; 7Fh AND 80h

Antes do AND, o bit 7 do valor 7Fh é zero, portanto o flag N = 0. O bit 6 desse mesmo valor
(ainda antes do AND) é 1, portanto o flag V = 1. O resultado, ou seja, o valor obtido após a
instrução AND é zero, portanto o flag Z = 1.

Instruções de Mudança e Rotação


ASL - Accumulator Shift Left (muda para a direita os bits do acumulador)
LSR - Logical Shift Right (muda para esquerda os bits do acumulador)
ROL - ROtate Left (rotaciona o acumulador para a esquerda)
ROR - ROtate Right (rotaciona o acumulador para a direita)

Use essas instruções para mover os bits pelo acumulador ou memória. O que acontece no
registrador é mais ou menos:

Flag 7 6 5 4 3 2 1 0 Flag Instrução


C < < < < < < < < ASL
> > > > > > > > C LSR
C < < < < < < < < C ROL
C > > > > > > > > C ROR

Z = 1 se o resultado é zero
N = 1 se o bit 7 é 1. No LSR ele sempre é zero

Vamos entender melhor essas instruções. Suponha que temos o valor 63h no acumulador.
Em binário fica assim:

63h = 01100011b

Ao executarmos um ASL, por exemplo, os bits são deslocados para a esquerda. Assim:

Bit Carry 7 6 5 4 3 2 1 0 Valor


Instrução X 0 1 1 0 0 0 1 1 63h
ASL 0 1 1 0 0 0 1 1 0 C6h

E:\Atari\SDK\doc\Tutorial.doc Página 26 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Como sabemos, 1 byte tem 8 bits então a cada deslocamento o bit 7 (o mais à esquerda)
tem que sair do byte, pois não há mais “vaga” no byte. Esse bit que foi deslocado à
esquerda e “saiu” do byte está agora no Carry flag, ou seja, o Carry flag assumiu o valor 0
(que era o bit 7 antes do ASL). Note que o bit zero (o mais à direita) foi preenchido com zero.
Agora fazemos mais uma vez o ASL, assim:

Bit Carry 7 6 5 4 3 2 1 0 Valor


Instrução 0 1 1 0 0 0 1 1 0 C6h
ASL 1 1 0 0 0 1 1 0 0 8Ch

A mesma coisa vale para o LSR. Só que, em vez de ser o bit 7 (o mais à esquerda), o Carry
flag recebe o valor do bit 0 (o mais à direita) e o bit 7 recebe zero. Vamos rotacionar o
mesmo exemplo para a direita:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 1 1 0 0 0 1 1 X 63h
LSR 0 0 1 1 0 0 0 1 1 31h

Antes do primeiro LSR, não nos importa o que estava no Carry flag. Depois do LSR, o Carry
ficou com o valor do bit 1 (o mais à direita), e o bit 7 recebeu zero. Os outros bits foram
deslocados para a direita. Mais uma vez:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 1 1 0 0 0 1 1 31h
LSR 0 0 0 1 1 0 0 0 1 18h

Antes desse LSR o Carry já era 1 devido ao LSR anterior. O bit 0 era 1 e após o LSR, esse 1
foi para o Carry e os outros bits foram deslocados para a direita. O bit 7 foi preenchido com
zero. De novo:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 1 1 0 0 0 1 18h
LSR 0 0 0 0 1 1 0 0 0 0Ch

Antes do LSR o Carry era 1 devido ao LSR anterior. O bit 0 era 0 e após o LSR, esse 0 foi
para o Carry e os outros bits foram deslocados para a direita. O bit 7 foi preenchido com
zero.

Além de outras vantagens as instruções ASL e LSR tem uma função interessante:
multiplicação e divisão. Vamos ver a tabela verdade de 0 a 8.

E:\Atari\SDK\doc\Tutorial.doc Página 27 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Binário Decimal Hexadecimal


0 0 0 0 0d 0h
0 0 0 1 1d 1h
0 0 1 0 2d 2h
0 0 1 1 3d 3h
0 1 0 0 4d 4h
0 1 0 1 5d 5h
0 1 1 0 6d 6h
0 1 1 1 7d 7h
1 0 0 0 8d 8h

Vamos supor que eu tenha o valor 00000001b, que na tabela é 1d. Agora vou fazer um ASL.

Bit Carry 7 6 5 4 3 2 1 0 Valor


Instrução 0 0 0 0 0 0 0 0 1 01d
ASL 0 0 0 0 0 0 0 1 0 02d

Deslocando os bits para a esquerda, valor do bit 0 passou para o bit 1, tornando-se 2 em
decimal. Agora mais uma vez:

Bit Carry 7 6 5 4 3 2 1 0 Valor


Instrução 0 0 0 0 0 0 0 1 0 02d
ASL 0 0 0 0 0 0 1 0 0 04d

Agora o 2d passou para 4d. Isso quer dizer a cada deslocamento, o valor foi multiplicado por
2 (em relação ao último resultado, ou seja, cada resultado foi multiplicado por 2. Em relação
ao primeiro valor temos a potência de 2).

O mesmo ocorre para o LSR. Deslocando os bits para a direita, temos a divisão de cada
resultado por 2 ou a raiz (com índice sendo potência de 2) do número inicial. Com isso fica
fácil fazer qualquer multiplicação/divisão. Por exemplo, tenho o valor 3d e quero multiplicá-lo
por 5d. O resultado tem que ser 15d. Como fazer?

Bit Carry 7 6 5 4 3 2 1 0 Valor


Instrução 0 0 0 0 0 0 0 1 1 03d
0 0 0 0 0 0 1 1 0 06d
ASL
0 0 0 0 0 1 1 0 0 12d

Chegamos em 12d, agora basta somar o valor inicial 3d que o resultado dá 15d. Então:
12d = 00001100b
clc
adc 03d = 00000011b
---------------------------------
15d = 00001111b

E:\Atari\SDK\doc\Tutorial.doc Página 28 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Usamos o CLC para limpar o Carry flag, só para garantir que nenhum “vai 1” ou “vem 1”
pudesse alterar o resultado.

Como podemos observar, tanto no ASL quanto no LSR, os bits dos extremos vão para o
Carry e no próximo ASL ou LSR, os bits dos extremos vão de novo para o Carry e o valor
anterior do Carry é perdido. Exemplo:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 0 1 0 1 0 X 10d
LSR 0 0 0 0 0 1 0 1 0 05d

O Carry recebeu o zero que estava no bit 0 (mais à direita). Fazendo novamente:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 0 0 1 0 1 0 05d
LSR 0 0 0 0 0 0 1 0 1 02d

O Carry recebeu o 1 que estava no bit 0 (mais à direita). O valor zero que estava no Carry se
perdeu. De novo:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 0 0 0 1 0 1 02d
LSR 0 0 0 0 0 0 0 1 0 01d

O Carry recebeu o zero que estava no bit 0 (mais à direita). O valor 1 que estava no Carry se
perdeu. Novamente:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 0 0 0 0 1 0 01d
LSR 0 0 0 0 0 0 0 0 1 00d

O Carry recebeu o 1 que estava no bit 0 (mais à direita). O valor zero que estava no Carry se
perdeu.

Agora notamos que, o byte inicialmente era 10d (00001010b) agora é zero (00000000b),
pois o bit 7 (mais à esquerda) foi preenchido com zero a cada LSR executado. Essa é a
diferença entre o ASL/LSR e ROL/ROR. Vamos fazer o mesmo usando ROR, supondo que
o Carry é zero inicialmente.

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 0 1 0 1 0 0 10d
ROR 0 0 0 0 0 1 0 1 0 05d

O Carry recebeu o zero que estava no bit 0 (mais à direita) e o bit 7 (mais à esquerda)
recebeu o que estava no Carry antes do ROR. Fazendo novamente:

E:\Atari\SDK\doc\Tutorial.doc Página 29 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 0 0 1 0 1 0 05d
ROR 0 0 0 0 0 0 1 0 1 02d

O Carry recebeu o 1 que estava no bit 0 (mais à direita) e o bit 7 (mais à esquerda) recebeu
o que estava no Carry antes do ROR, ou seja, o valor zero que foi para o Carry no primeiro
ROR. De novo:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 0 0 0 0 0 1 0 1 02d
ROR 1 0 0 0 0 0 0 1 0 129d

O Carry recebeu o zero que estava no bit 0 (mais à direita) e o bit 7 (mais à esquerda)
recebeu o que estava no Carry antes do ROR, ou seja, o valor 1 que foi para o Carry no
segundo ROR. Novamente:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 1 0 0 0 0 0 0 1 0 129d
ROR 0 1 0 0 0 0 0 0 1 64d

O Carry recebeu o 1 que estava no bit 0 (mais à direita) e o bit 7 (mais à esquerda) recebeu
o que estava no Carry antes do ROR, ou seja, o valor zero que foi para o Carry no terceiro
ROR. Se fizermos mais um ROR, temos:

Bit 7 6 5 4 3 2 1 0 Carry Valor


Instrução 0 1 0 0 0 0 0 0 1 64d
ROR 1 0 1 0 0 0 0 0 0 160d

Com isso, fazemos com que os 4 bits menos significativos (bit 0 ao bit 3, ou seja, os 4 mais
à direita) passassem para os 4 bits mais significativos (bit 4 ao bit 7, ou seja, os mais à
esquerda) e vice-versa. Se fizermos mais 4 RORs, o byte voltará a ter seu valor original.
Então o LSR e o ASL tratam o byte da seguinte forma:

ASL = Carry
Os bits se deslocam da direita para a esquerda, passam pelo Carry e se perdem.

ASL = Carry
Os bits se deslocam da esquerda para a direita, passam pelo Carry e se perdem.

ROR = Carry

Os bits se deslocam da esquerda para a direita, através do Carry, e retornam ao registrador.

ROL = Carry

Os bits se deslocam da direita para a esquerda, através do Carry, e retornam ao registrador.

E:\Atari\SDK\doc\Tutorial.doc Página 30 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Instruções de Transferência
TAX - Transfer Accumulator to X (transfere do A para o X)
TAY - Transfer Accumulator to Y (transfere do A para o Y)
TXA - Transfer X to Accumulator (transfere do X para o A)
TYA - Transfer Y to Accumulator (transfere do Y para o A)

Instruções de transferência movem os valores entre os registradores do 6502. Os flags Z e N


são modificados de acordo com o valor movido. Por exemplo:

LDA #$80 ; carrega o acumulador com o valor 80h


TAX ; transfere o valor do registrador A para o registrador X

Nesse caso como o bit 7 é 1, pois 80h = 1000000b, o N flag será 1. Outro exemplo:

LDX #$00 ; carrega o registrador X com o valor 00h


TXA ; transfere o valor do registrador X para o registrador A

Nesse caso como o valor é zero, então o Z flag será 1.

Instruções de Pilha
TSX - Transfer Stack pointer to X (transfere o ponteiro da pilha para o X)
TXS - Transfer X to Stack pointer (transfere o X para o ponteiro da pilha)
PHA - PusH Accumulator on stack (salva o acumulador na pilha)
PHP - PusH Processor status on stack (salva flags na pilha)
PLA - PulL Accumulator from stack (restaura o acumulador da pilha)
PLP - PulL Processor status from stack (restaura flags da pilha)

As instruções TSX e TXS tornam a manipulação da pilha possível. Mais adiante veremos
como recuperar valores salvos na pilha sem descarregá-la, utilizando para isso o TSX. As
instruções que salvam valores na pilha (iniciadas por PH) e as que restauram valores da
pilha (iniciadas por PL) são úteis para salvar/restaurar valores dos registradores e dos flags.

Vejamos como isso é feito. Conforme já dito o registrador S (stack pointer ou ponteiro da
pilha) aponta sempre para o fim da pilha. No caso do 6502, está no final da página de
memória 1. Então o registrador S tem o valor de 1FFh.

Quando salvamos um valor na pilha, o S é decrementado de 1, apontando para o novo local


livre na pilha para podermos salvar mais dados. Quando restauramos um valor da pilha, o S
é incrementado de 1, deixando o local (onde o valor recuperado estava) livre novamente
para salvamos outros dados.

Exemplo:

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h 00h 00h 00h

S = 1FFh ; o registrador stack pointer aponta para o fim da pilha

Agora supomos que o programa salva e restaura alguns valores da pilha.

LDA #$5B ; carrega o registrador A com o valor 5Bh


PHA ; salva na pilha

E:\Atari\SDK\doc\Tutorial.doc Página 31 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Nesse momento a pilha fica:

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h 00h 00h 5Bh

E o stack pointer aponta para o próximo local livre: S = 1FEh

Mais um pouco de programa:

LDA #$99 ; carrega o registrador A com o valor 99h


PHA ; salva na pilha
LDA #$C3 ; carrega o registrador A com o valor C3h
PHA ; salva na pilha

Nesse momento a pilha está:

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h C3h 99h 5Bh

E o stack pointer apontando para 1FCh.

Agora vamos restaurar os valores:

PLA ; restaura o valor da pilha

Nesse momento, o stack pointer aponta para 1FDh, pega o valor que está lá (C3h) e coloca
no registrador A e permanece (o stack pointer) com o valor de 1FDh.

TAY ; transfere o valor de A para Y


PLA ; restaura o valor da pilha

Nesse momento, o stack pointer aponta para 1FEh, pega o valor que está lá (99h) e coloca
no registrador A e permanece (o stack pointer) com o valor de 1FEh.

TAX ; transfere o valor de A para X


PLA ; restaura o valor da pilha

Nesse momento, o stack pointer aponta para 1FFh, pega o valor que está lá (5Bh) e coloca
no registrador A e permanece (o stack pointer) com o valor de 1FFh.

Agora temos os registradores Y com C3h, X com 99h e o A com 5Bh.

E a pilha, como ela está agora? Está assim:

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h C3h 99h 5Bh

Ainda está com todos os valores. Eles não são zerados quando são restaurados. Eles
permanecem lá até serem sobrescritos. O stack pointer agora aponta para 1FFh. Quando
salvarmos algum valor novamente na pilha, o 6502 sobrescreverá o valor atual em 1FFh,
que é 5Bh, pelo novo valor a ser salvo.

E:\Atari\SDK\doc\Tutorial.doc Página 32 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Instruções de Subrotina
JSR - Jump to SubRoutine (vá para subrotina mas terá que voltar)
RTS - ReTurn from Subroutine (retorne da subrotina)
RTI - ReTurn from Interrupt (retorne da interrupção)

Assim como a instrução JMP, a instrução JSR faz a execução do programa saltar de onde
está e recomeçar o processamento em outro lugar (no endereço dado). Diferentemente da
instrução JMP, a instrução JSR salva o endereço da próxima instrução de onde o
processamento estava. Isso, para o 6502 saber para onde voltar. Por exemplo:

LDA #$41 ; carrega A com o valor 41h (letra A)


JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
LDA #$42 ; carrega A com o valor 42h (letra B)
JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
.
.
.mostraNaTela
.
.
.
RTS ; retorna da subrotina

Quando um programa é carregado, ele vai para algum lugar. Esse lugar é identificado no
processador. Esse lugar é onde o registrador PC vai percorrer para executar cada instrução.
Supondo que o programa acima foi para o endereço inicial F026h, ficaria assim:

F026 LDA #$41 ; carrega A com o valor 41h (letra A)


F028 JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F02B LDA #$42 ; carrega A com o valor 42h (letra B)
F02D JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F030 .
.
FB5A .mostraNaTela ; suponha que a rotina está em FB5Ah
.
FB63 RTS ; retorna da subrotina

Cada instrução ocupa um certo número de bytes. Por isso, se uma instrução tem apenas 1
byte, ela ocupa somente um offset. Se tiver 2, 2 offsets e assim por diante. Por isso, no
nosso exemplo, os offsets tem valores não consecutivos. Vamos agora analisar o que
acontece quando uma instrução JSR é executada.

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h 00h 00h 00h

S = 1FFh ; o registrador stack pointer aponta para o fim da pilha

Suponha que a pilha esteja vazia como acima (o registrador S apontando para o final dela).
Quando o 6502 executa o JSR acontece o seguinte:

1. ele salva na pilha o offset da instrução seguinte ao JSR


2. ele desvia o fluxo de execução do programa para o endereço dado no JSR

Então:

E:\Atari\SDK\doc\Tutorial.doc Página 33 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

F026 LDA #$41 ; carrega A com o valor 41h (letra A)


F028 JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F02B LDA #$42 ; carrega A com o valor 42h (letra B)
F02D JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F030 .

Quando o registrador PC chega em F028h, o 6502 encontra a instrução JSR mostraNaTela.


Ele salva na pilha o offset da instrução seguinte ao JSR. A pilha fica então:

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h 00h 2Bh F0h

S = 1FDh ; o registrador stack pointer aponta para o fim da pilha

Na pilha foi salvo o offset F02Bh (no formato little endian, ou seja, o byte menos significativo
primeiro) e o stack pointer (registrador S) decrementou em 2.

Agora o 6502 verifica que o label mostraNaTela está no offset FB5Ah, ele então coloca esse
valor no registrador PC dizendo “agora vamos executar as instruções a partir desse novo
endereço”. O 6502 executa então as instruções do offset FB5Ah e subseqüentes até o offset
FB63h quando encontra a instrução RTS. Essa instrução diz “retorne para o lugar de onde
veio”. Mas de onde ele veio? O lugar está na pilha. Então o 6502 restaura o que está na
pilha e coloca no registrador PC. A execução passa então a ser a partir desse novo
endereço e os subseqüentes.

F026 LDA #$41 ; carrega A com o valor 41h (letra A)


F028 JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F02B LDA #$42 ; carrega A com o valor 42h (letra B)
F02D JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F030 .
.
.
O PC está com F02Bh e passa a executar a partir daí. Note que o 6502 não salva o
endereço do JSR. Se fosse assim, quando retornasse, ele encontraria o JSR de novo e
ficaria num loop infinito. Ele salva o offset seguinte ao da instrução JSR e, quando retorna,
volta para depois do JSR, dando continuidade ao fluxo do programa. Após um RTS, o stack
pointer (S) é incrementado em 2.

A ordem de salvar na pilha


É muito importante saber a ordem com a qual os dados foram salvos na pilha. Vamos ver 2
exemplos:

LDX #$01 ; carrega X com 01h


LDY #$02 ; carrega Y com 02h
LDA #$03 ; carrega A com 03h
PHA ; salva A
TYA ; A = Y (ou seja transfere o valor de Y para A)
PHA ; salva A (ou seja salva o valor de Y)
TXA ; A = X (ou seja transfere o valor de X para A)
PHA ; salva A (ou seja salva o valor de X)

Foram colocados os valores na pilha na seguinte ordem: A, Y e X. Para restaurar os valores


para seus respectivos registradores a ordem inversa tem que ser feita. Assim:

E:\Atari\SDK\doc\Tutorial.doc Página 34 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

PLA ; restaura A (o valor de X que está na pilha)


TAX ; X = A (ou seja transfere o valor de A para X)
PLA ; restaura A (o valor de Y que está na pilha)
TAY ; Y = A (ou seja transfere o valor de A par a Y)
PLA ; restaura A (o valor do próprio A que está na pilha)

Se invertermos, por exemplo, onde tem TAX colocarmos TAY e onde tem TAY colocarmos
TAX, os registradores X e Y terão seus valores trocados.

Agora o exemplo 2: vamos tomar o exemplo do JSR.

F026 LDA #$41 ; carrega A com o valor 41h (letra A)


F028 JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F02B LDA #$42 ; carrega A com o valor 42h (letra B)
F02D JSR .mostraNaTela ; vai para a subrotina chamada mostraNaTela
F030 .

Quando o registrador PC chega em F028h, o 6502 encontra a instrução JSR mostraNaTela.


Ele salva na pilha o offset da instrução seguinte ao JSR. A pilha fica então:

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h 00h 2Bh F0h

S = 1FDh ; o registrador stack pointer aponta para o fim da pilha

Agora vamos supor que a rotina chamada mostraNaTela, inicie no offset FB5Ah como no
exemplo do JSR explicado anteriormente. Até aí tudo bem. Nada de anormal. O PC recebe
FB5Ah e passa a executar as instruções a partir desse offset.

Entretanto, vamos supor que dentro dessa rotina temos uma instrução que salva algum valor
na pilha. Veja abaixo.

FB5A .mostraNaTela ; suponha que a rotina está em FB5Ah


FB5B LDA #$33 ; A = 33h
FB5D PHA ; salva na pilha
.
.
FB63 RTS ; retorna da subrotina

Nota: Só para efeito de ilustração foi colocado que o LDA #$33 está no offset FB5Bh. Na
verdade o nome da rotina serve apenas para o compilador, sendo totalmente ignorado na
execução. O que o compilador faz é colocar o endereço da instrução que está logo abaixo
do nome da rotina no programa fonte como sendo o destino da instrução JSR chamadora.
Então, na verdade, o LDA #$33 estaria (nesse exemplo) no offset FB5A e o JSR desviaria o
fluxo da execução diretamente para ele.

Ok, retomando a linha de raciocínio, o programa agora executa a rotina mostraNaTela. Ele
carrega o registrador A com o valor 33h e salva na pilha. A pilha fica assim:

Endereço 1F7h 1F8h 1F9h 1FAh 1FBh 1FCh 1FDh 1FEh 1FFh
Valor 00h 00h 00h 00h 00h 00h 33h 2Bh F0h

S = 1FCh ; o registrador stack pointer aponta para o fim da pilha

E:\Atari\SDK\doc\Tutorial.doc Página 35 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Daí ele continua suas execuções e, de repente, encontra o RTS. O que vai acontecer? Ele
vai restaurar 2 valores na pilha, pensando que são o offset seguinte ao offset do JSR
chamador (é o que ele sempre faz). Ele pega esse valor e coloca no registrador PC. Daí o
PC continua a execução a partir desse novo endereço. E o que ele vai restaurar dessa vez?
Simples. Ele vai restaurar os valores 33h e 2Bh. Como ele considera o formato little endian,
ele vai colocar no registrador PC o valor 2B33h e vai desviar o fluxo da execução para esse
endereço. O que acontece? Pau (dispensa comentários). Além do mais, o stack pointer
agora aponta para o endereço 1FEh da pilha. Para que não ocorra esse erro, a rotina tinha
que ser escrita assim:

FB5A mostraNaTela ; suponha que a rotina está em FB5Ah


FB5B LDA #$33 ; A = 33h
FB5D PHA ; salva na pilha
.
.
FB62 PLA ; restaura da pilha
FB63 RTS ; retorna da subrotina

Com a restauração de A antes do retorno da subrotina, tudo funciona perfeitamente: O valor


33h é restaurado da pilha, o stack pointer é incrementado em 1 (passando a ser 1FDh), e
quando o RTS é executado, os valores 2Bh e F0h são restaurados (no formato F02Bh) e o
stack pointer é incrementado em 2 (sendo 1FFh). O PC agora tem o valor F02Bh e executa
as instruções a partir desse novo endereço, que é a continuação depois do JSR.

Concluindo: Para toda instrução que salva algo na pilha, deve-se ter instrução que restaura
da pilha. Salvou, restaurou. É como utilizar parêntesis em expressões algébricas: abriu,
fechou. Um para um. Compiladores de linguagens de alto nível reconhecem quando os
delimitadores não estão balanceados (a quantidade de abertura é diferente da quantidade
de fechamento). O mesmo vale para: para todo IF tem um ENDIF, para todo WHILE tem um
WEND, para todo DO tem um LOOP, para todo FOR tem um NEXT, para todo '(' tem um ')',
para toda '{' tem uma '}'. Tudo isso depende da linguagem de programação. Em assembly
não vale a regra: para todo PHA tem um PLA, para todo PHP tem um PLP. Isso só na
cabeça do programador. O compilador não tá nem aí. Se salvou e não restaurou há 99% de
chance de dar pau e o compilador não avisa. Esse 1% restante é que pode-se salvar e
nunca precisar restaurar o valor. Daí fica o lixo na pilha, ocupando espaço. Coisa feia.

Instruções de set e reset


CLC - CLear Carry flag (limpa o Carry flag)
CLD - CLear Decimal mode (limpa o Decimal mode flag)
CLI - CLear Interrupt disable (limpa o Interrupt dissable flag)
CLV - CLear oVerflow flag (limpa o Overflow flag)
SEC - SEt Carry (seta o Carry flag)
SED - SEt Decimal mode (seta o Decimal mode flag)
SEI - SEt Interrupt disable (seta o Interrupt disable flag)

Essas são instruções de 1 byte que modificam os flags. CLC e SEC são instruções de uso
particular em adição e subtração respectivamente. Antes de qualquer adição (usando ADC)
fazemos um CLC para limpar o Carry, caso contrário o resultado pode ser 1 a mais do que o
esperado. Para a subtração (usando SBC), usamos SEC para assegurar que o Carry está
setado, pois seu complemento será subtraído do resultado. Em adições ou subtrações de
vários bytes, deve-se fazer um CLC ou SEC somente 1 vez antes da operação inicial. Por
exemplo, para somar um número de 16 bits que está no endereço 2324h devemos:

E:\Atari\SDK\doc\Tutorial.doc Página 36 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

LDA $23 ; pega o byte menos significativo


CLC ; limpa o Carry flag
ADC #$02 ; soma 2 ao valor. O Carry vai ser 1 se o resultado for 255
STA $23 ; salva o resultado
LDA $24 ; pega o byte mais significativo
ADC #$00 ; soma zero para adicionar o Carry (esteja setado ou não)
STA $24 ; salva o resultado

A mesma coisa na subtração:

LDA $23 ; pega o byte menos significativo


SEC ; seta o Carry
SBC #$02 ; subtrai 2
STA $23 ; salva o resultado
LDA $24 ; pega o byte mais significativo
SBC #$00 ; subtrai 0 e o Carry (se houver empréstimo)
STA $24 ; salva o resultado

Outras Instruções
NOP - No Operation (nenhuma ação)
BRK – BreaK (pare)

NOP é justamente isso, não faça nada. Útil para apagar/substituir instruções já existentes,
mantendo o programa com o mesmo tamanho. BRK acarreta numa parada forçada do
processamento normal e o 6502 vai imediatamente iniciar a execução da rotina que está no
endereço $FFFEh e $FFFFh.

Opcodes ilegais aceitos no 6502 do 2600


Opcodes ilegais são instruções que fazem o trabalho de mais de uma instrução válida. Por
exemplo:

LAX $nn; carrega o A e o X com o valor nn

É o mesmo que:

LDA #$nn
TAX

Ou ainda:

LDA #$nn
LDX #$nn

Além dos relacionados a seguir, existem outros. Entretanto, muitos dependem da versão do
6502. Os que não funcionam no compilador que utilizaremos não serão relacionados aqui.

ANC
Faz um AND do byte com o acumulador. Se o resultado é negativo então o Carry é setado.
Afeta os flags N, Z, e C.

Instrução Modo de Endereçamento Formato Opcode

ANC Immediate ANC #arg $0B

E:\Atari\SDK\doc\Tutorial.doc Página 37 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

ANE
Operação exata desconhecida. Leia documentos de referência para maiores informações.

Instrução Modo de Endereçamento Formato Opcode

ANE Immediate ANE #arg $8B

ARR
Faz um AND do byte com o acumulador, então rotaciona 1 bit para a direita no acumulador e
verifica os bits 5 e 6. Afeta os flags N, V, Z e C. Se ambos forem 1, seta o Carry e limpa o
Overflow; se ambos forem 0, limpa o Carry e o Overflow; se somente o bit 5 for 1, seta o
Overflow e limpa o Carry; se somente o bit 6 for 1, seta o Carry e o Overflow

Instrução Modo de Endereçamento Formato Opcode

ARR Immediate ARR #arg $6B

ASR
Faz um AND do byte com o accumulator, então desloca 1 bit para a direita no acumulador.
Afeta os flags N, Z e C

Instrução Modo de Endereçamento Formato Opcode

ASR Immediate ASR #arg $4B

DCP
Subtrai 1 da memória (sem empréstimo). Afeta o flag C.

Instrução Modo de Endereçamento Formato Opcode

Zero page DCP arg $C7


Zero page, X DCP arg, X $D7
Absolute DCP arg $CF
DCP Absolute, X DCP arg, X $DF
Absolute, Y DCP arg, Y $DB
(Indirect, X) DCP (arg, X) $C3
(Indirect), Y DCP (arg), Y $D3

ISB
Incrementa a memória em 1, então subtrai a memória do acumulador (com empréstimo).
Afeta os flags N, V, Z e C.

Instrução Modo de Endereçamento Formato Opcode

Zero page ISB arg $E7


Zero page, X ISB arg, X $F7
Absolute ISB arg $EF
ISB Absolute, X ISB arg, X $FF
Absolute, Y ISB arg, Y $FB
(Indirect, X) ISB (arg, X) $E3
(Indirect), Y ISB (arg), Y $F3

E:\Atari\SDK\doc\Tutorial.doc Página 38 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

LAS
Faz um AND da memória com o stack pointer, transfere o resultado para o acumulador, para
o registrador X e para o próprio stack pointer. Afeta os flags N e Z.

Instrução Modo de Endereçamento Formato Opcode

LAS Absolute LAS arg, Y $BB

LAX
Carrega o acumulador e o registrador X com o valor da memória. Afeta os flags N e Z.

Instrução Modo de Endereçamento Formato Opcode

Zero page LAX arg $A7


Zero page, Y LAX arg, Y $B7
Absolute LAX arg $AF
LAX
Absolute, Y LAX arg, Y $BF
(Indirect, X) LAX (arg, X) $A3
(Indirect, Y) LAX (arg), Y $B3

LXA
Faz um AND do byte com o acumulador, então transfere o acumulador para o registrador X.
Afeta os flags N e Z.

Instrução Modo de Endereçamento Formato Opcode

LXA Immediate LXA #arg $AB

RLA
Rotaciona 1 bit para a esquerda na memória, então faz um AND do acumulador com a
memória. Afeta os flags N, Z e C.

Instrução Modo de Endereçamento Formato Opcode

Zero page RLA arg $27


Zero page, X RLA arg, X $37
Absolute RLA arg $2F
RLA Absolute, X RLA arg, X $3F
Absolute, Y RLA arg, Y $3B
(Indirect, X) RLA (arg, X) $23
(Indirect), Y RLA (arg), Y $33

E:\Atari\SDK\doc\Tutorial.doc Página 39 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

RRA
Rotaciona 1 bit para a direita na memória, então soma a memória ao acumulador (com vai
1). Afeta os flags N, V, Z e C.

Instrução Modo de Endereçamento Formato Opcode

Zero page RRA arg $67


Zero page, X RRA arg, X $77
Absolute RRA arg $6F
RRA Absolute, X RRA arg, X $7F
Absolute, Y RRA arg, Y $7B
(Indirect, X) RRA (arg, X) $63
(Indirect), Y RRA (arg), Y $73

SAX
Faz um AND do registrador X com o acumulador e armazena o resultado no registrador X,
então subtrai o byte do registrador X (com empréstimo). Afeta os flags N, Z e C.

Instrução Modo de Endereçamento Formato Opcode

SAX Immediate SAX #arg $CB

SBC
O mesmo que o opcode válido $E9 (SBC #byte). Afeta os flags N,V,Z e C.

Instrução Modo de Endereçamento Formato Opcode

SBC Immediate SBC #byte $EB

SBX
Faz um AND do registrador X com o acumulador e armazena o resultado em X, então
subtrai o byte do registrador X (sem empréstimo). Afeta os flags N, Z e C.

Instrução Modo de Endereçamento Formato Opcode

SBX Immediate SBX #byte $CB

SHA
Faz um AND do registrador X com o acumulador, então faz um AND do resultado com 7 e
armazena na memória. Afeta os flags -.

Instrução Modo de Endereçamento Formato Opcode

Absolute, Y SHA arg, Y $9F


SHA
(indirect), Y SHA (arg), Y $93

SHS
Faz um AND do registrador X com o acumulador e armazena o resultado no stack pointer,
então faz um AND do stack pointer com o byte mais singnificativo do endereço de destino do
argumento + 1. Armazena o resultado na memória. Afeta os flags -.

E:\Atari\SDK\doc\Tutorial.doc Página 40 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Instrução Modo de Endereçamento Formato Opcode

SHS Absolute, Y SHS arg, Y $9B

SHX
Faz um AND do registrador X com o byte mais significativo do endereço de destino do
argumento + 1. Armazena o resultado na memória. Afeta os flags -.

Instrução Modo de Endereçamento Formato Opcode

SHX Absolute, Y SHX arg, Y $9E

SHY
Faz um AND do registrador com o byte mais significativo do endereço de destino do
argumento + 1. Armazena o resultado na memória. Afeta os flags -.

Instrução Modo de Endereçamento Formato Opcode

SHY Absolute, X SHY arg, X $9C

SLO
Desloca para a esquerda 1 bit na memória, então faz um OR do acumulador com a
memória. Afeta os flags N, Z e C.

Instrução Modo de Endereçamento Formato Opcode

Zero page SLO arg $07


Zero page, X SLO arg, X $17
Absolute SLO arg $0F
SLO Absolute, X SLO arg, X $1F
Absolute, Y SLO arg, Y $1B
(Indirect, X) SLO (arg, X) $03
(Indirect), Y SLO (arg), Y $13

SRE
Desloca para a direita 1 bit na memória, então faz um EOR do acumulador com a memória.
Afeta os flags N, Z e C.

Instrução Modo de Endereçamento Formato Opcode

Zero page SRE arg $47


Zero page, X SRE arg, X $57
Absolute SRE arg $4F
SRE Absolute, X SRE arg, X $5F
Absolute, Y SRE arg, Y $5B
(Indirect, X) SRE (arg, X) $43
(Indirect), Y SRE (arg), Y $53

E:\Atari\SDK\doc\Tutorial.doc Página 41 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Programando em Assembly para o 6502


Os programas de jogos do 2600 eram gravados em chips de memória do tipo ROM
(pronuncia-se rom e não rum, pois é um acrônimo de Read Only Memory e a palavra only
pronuncia-se ónly e não únly).

Crítica construtiva: muitas pessoas pronunciam CD-RUM quando se referem àquele


dispositivo de leitura de CDs para computador. Na verdade, deve-se pronunciar CD ROM
(com O em vez de U).

Só para constar: em inglês, a pronúncia de ROM como RUM esbarra na palavra room que
significa quarto, sala.

Voltando ao assunto... Conforme dizíamos, os programas de jogos do 2600 eram gravados


em chips de memória do tipo ROM e nativamente em assembly (lembrando que assembler é
o compilador e assembly é a linguagem).

Nesse tutorial vamos nos limitar somente à linguagem assembly. Existe o AtariBasic, um
BASIC feito para escrever jogos para Atari, na verdade, escreve-se o programa em BASIC, e
o AtariBasic transforma-o em assembly e depois usa o compilador assembly. Quem quiser
saber a respeito, basta procurar na internet. Nesse tutorial, será o assembly mesmo, puro,
tudo feito na mão mesmo.

Então vamos lá. Partindo do pressuposto que você, que está lendo esse tutorial, já sabe
programar, tem lógica, conhece todas aquelas coisas sobre sistema de numeração e suas
bases e um mínimo sobre operadores lógicos, podemos continuar. Estou dizendo isso
porque, apesar de começar do começo, não vou chegar ao extremo de passar por aquele
processo maçante de explicar sobre numeração binária, octal, decimal e hexadecimal. Nem
sobre os operadores lógicos E, OU, NÃO e OU-EXCLUSIVO a fundo, posso no máximo dar
uma relembrada sucinta no assunto como já foi feito com os operadores lógicos. Essas
coisas já devem ser de conhecimento (e domínio) de qualquer programador.

Vimos até agora a arquitetura, acesso à memória e as instruções do 6502. Agora vamos ter
alguma prática na coisa.

Para aprendermos o assembly do 6502 vamos utilizar o 6502 Macroassembler & Simulator
versão 1.2.5. (vide figuras 1 e 2). Não vamos começar direto programando com a finalidade
de rodar a ROM no emulador do Atari. Resolvi dar uma explicada usando o simulador para
poder mostrar de forma mais fácil como 6502 funciona.

Figura 1

E:\Atari\SDK\doc\Tutorial.doc Página 42 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 2

Se você encontrou o programa na internet, baixou, instalou e pretende usá-lo para


acompanhar esse tutorial, a hora é agora.

Criamos um arquivo novo (procedimentos relacionados à operação básica do Windows não


serão explicados). O formulário onde digitaremos os programas é aberto (figura 3).

Figura 3

A primeira coisa é saber que no assembly para 6502, as instruções devem estar a pelo
menos 1 espaço da margem esquerda. Se não houver esse espaço, o compilador
considerará como sendo um label.

Agora temos que dizer em que lugar na memória queremos que nosso programa rode. Para
isso usamos a diretiva .ORG (de ORiGin?) (vide figura 4).

E:\Atari\SDK\doc\Tutorial.doc Página 43 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 4

Esse programa mostra no lado direito a descrição, sintaxe e exemplo de como usar a
instrução que está sendo inserida. No nosso caso, vamos dizer que queremos que o
programa comece em 1000h. Para isso, na frente do .ORG já digitado (e com um espaço
depois) colocamos $1000.

É interessante nesse programa digitar em minúsculo. Ao teclar ENTER no fim da linha, se o


comando digitado existir, ele converte para maiúsculo. Se isso não acontecer é porque: o
comando não existe ou você desabilitou essa funcionalidade nas configurações do
programa.

Em assembly é fundamental a organização e comentar as linhas do programa. É


interessante colocar todas as instruções na mesma coluna. Os labels, variáveis e constantes
devem ser escritos a partir da margem esquerda (sem espaço, caso contrário o compilador
tentará compilá-las como comandos e emitirá um erro de sintaxe). Os comentários em
assembly são identificados pelo compilador como “tudo que está depois de um ponto-e-
vírgula ';'”. Os labels, normalmente são escritos com um ponto '.' no início. As constantes,
normalmente são escritas em maiúsculo. Números começando com @ são binários no caso
desse simulador, pois no DASM o símbolo é %. Começando com $ são hexadecimais. Para
que um registrador seja carregado com o número indicado e não entenda que aquele
número é um endereço de memória (e pegar o valor lá), deve-se colocar uma # antes.

Exemplos:

LDA $01 ; número em hexadecimal indica coloque o valor que está


; no endereço 01h da memória no registrador A
LDA #$01 ; número em hexadecimal que será colocado em A
LDA #%00000001 ; número binário que será colocado em A
LDA #10 ; será colocado em A o valor 0Ah = 10d

E:\Atari\SDK\doc\Tutorial.doc Página 44 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Exemplo de carregamento, transferência e deslocamento


Vamos digitar o seguinte programa:

.ORG $1000

LDA #$01 ; A = 1
LDX #$02 ; X = 2
LDY #$03 ; Y = 3
PHA ; salva A na pilha
TXA ; A = X
PHA ; salva A na pilha
TYA ; A = Y
PHA ; salva A na pilha
LDA #$01 ; A = 1
ASL ; A = 2
ASL ; A = 4
ASL ; A = 8
ASL ; A = 16
ASL ; A = 32
ASL ; A = 64
ASL ; A = 128 (N flag é 1 indicando número negativo)
ASL ; A = 0 e Carry flag = 1
LDA #@10000000 ; A = 128
LSR ; A = 64
LSR ; A = 32
LSR ; A = 16
LSR ; A = 8
LSR ; A = 4
LSR ; A = 2
LSR ; A = 1
LSR ; A = 0 e Carry flag = 1
PLA ; restaura A
PLA ; restaura A
PLA ; restaura A
BRK ; termina o programa

Com esse programa mostraremos, na prática, os comandos vistos: carregar registradores,


transferir valores entre registradores, salvar na pilha, restaurar da pilha e deslocamento para
a esquerda e direita. E nota-se que o deslocamento para a esquerda acarreta na potência de
2 do número original ou multiplicação do número anterior por 2 (exemplo: 8 = 2^3 e 8 = 4 * 2)
e que o deslocamento para a direita é igual a raiz com índice sendo potência de 2 ou o
número anterior dividido por 2 (exemplo: raiz quarta de 16 = 2, raiz quadrada de 16 = 8 e 16
/ 2 = 8).

Para rodar o programa, tecle F7 (compile). Sempre que alterar alguma coisa no programa
deve-se teclar F7. Se houver algum erro, aparecerá um aviso informando a linha do erro. Se
tudo estiver Ok tecle F6 (debug). O simulador coloca uma seta ao lado da primeira linha do
programa, aguardando um comando para executá-la.

Vamos executar passo a passo o programa e ver o que acontece com os registradores e
pilha. Quando F6 é teclado, o programa abre um monte de janelas. Vamos deixar visíveis
somente a 6502 µP Register & Status e a 6502 µP Stack (vide figura 5). Para executar o
programa passo a passo basta teclar F11. Cada vez que teclamos F11 o simulador executa
1 linha do programa. Então vamos executar e observar a janela dos registradores. Para
parar a execução do programa e reiniciar tecle F6 2 vezes.

E:\Atari\SDK\doc\Tutorial.doc Página 45 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 5

Observe os flags. Estão todos zerados (desmarcados). Observe que o registrador PC está
com 1000h (figura 6), que é o valor que colocamos na diretiva ORG dizendo que o programa
será alocado a partir desse endereço.

Figura 6

Ao teclar F11 pela primeira vez, a 1ª linha é executada e podemos observar na janela de
registradores que o A está agora com o valor 1. O programa mostra esses valores nos
formatos hexadecimal, binário, decimal e o caracter correspondente. Observe que o PC
agora é 1002h, indicando que a instrução LDA #$01, ocupa 2 bytes de memória. Teclamos
F11 mais 2 vezes. Agora X e Y também estão com seus valores. Observamos que o PC está
com 1006h. As instruções que colocaram valores em X e Y também ocupam 2 bytes de
memória cada uma (figura 7). De agora em diante, observe o PC sempre que tiver
curiosidade. Chamaremos sua atenção para esse registrador somente quando rodarmos
exemplos de salto, desvio e chamada de subrotina.

E:\Atari\SDK\doc\Tutorial.doc Página 46 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 7

Agora, antes de teclar F11, observe na janela de registradores o registrador S (figura 7). Ele
está com o valor FFh, ou seja, a pilha está vazia. Tecle F11 e observe esse registrador. Ele
foi decrementado em 1 quando a instrução PHA foi executada. Na frente dele apareceu o
número 01, que é o valor que foi colocado na pilha. Observe também a janela da pilha
(stack). Agora a primeira linha (numerada com 1FFh) contém o valor 01 (figuras 8 e 9).

Figura 8

Figura 9

E:\Atari\SDK\doc\Tutorial.doc Página 47 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Tecle F11 para as próximas instruções e observe o que ocorre: A fica com o valor de X
(quando executa TXA), esse valor é salvo na pilha (PHA), A fica com o valor de Y (quando
executa TYA), esse valor é salvo na pilha (PHA) e depois carregamos A com o valor 1
novamente. Agora as instruções ASL vão deslocar os bits do A para a esquerda. Observe na
janela dos registradores o valor de A em binário enquanto tecla F11. O 1 vai se deslocando
para a esquerda. Agora veja que acontece o que foi descrito anteriormente. Quando A for
128, o bit 7 será 1 (o 1 foi deslocado para a esquerda até chegar no bit 7). Isso faz com que
o N flag seja 1, indicando que o número é negativo (figura 10).

Figura 10

Ao teclar F11 novamente, o último ASL faz o A se deslocar para a esquerda. Como o 1 já
está no extremo, ele vai para o Carry flag e o A fica zerado, pois à medida em que os bits
foram deslocados para a esquerda, foram entrando zeros pela direita. Nesse momento,
observe o Carry e o Zero flags. Ambos estão em 1. Indicando que o bit que saiu do
registrador A foi 1 (e está no carry) e o resultado da operação acarretou no registrador A ser
zero (daí o Zero flag ficou 1) – vide figura 11. Simples.

Figura 11

Continuemos. No próximo passo carregamos A com 128 (escrevemos em binário só para


ilustrar). Como o bit 7 é 1, então o N flag é 1. O C flag está 1 devido ao resultado do último
ASL e nesse caso não faz diferença. Teclamos F11 várias vezes até percorrermos os LSRs.
O último LSR faz a mesma coisa que o último ASL: C e Z flags são setados.

A próxima instrução (PLA) restaura o último valor colocado na pilha. Então A = 3. Teclamos
F11 novamente e agora A = 2 e novamente A = 1. A pilha agora está vazia. Ao executar a
instrução BRK o programa termina. Tecle F6 para fechar o debugger e retornar ao editor.

Agora vamos rodar um programa simples para vermos como funciona o ROR e o ROL.
Digite (ou copie e cole, depende da preguiça) o programa abaixo:

E:\Atari\SDK\doc\Tutorial.doc Página 48 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.ORG $1000

LDA #$0A ; A = 10d, 00001010b


ROR
ROR
ROR
ROR
ROR ; nesse ponto os 4 bits baixos passaram para os 4 bits altos
; e vice-versa. Lembrando que 4 bits = 1 nibble
ROR
ROR
ROR
ROR ; nesse ponto A = ao valor original (10d)
BRK ; termina o programa

Experimente fazer agora com o ROL.

Vamos fazer agora um programa para verificar as particularidades de soma e subtração


vistas anteriormente. Nesse exemplo vamos realizar os cálculos no modo normal e BCD
para visualizarmos a diferença entre eles. Vamos também ver como o Carry flag é
importante.

Exemplo de operação aritmética normal, BCD com e sem Carry


.ORG $1000

CLD ; modo normal


CLC ; Carry = 0
LDA #$01 ; A = 1
ADC #$05 ; A = A + 5

LDA #$FE ; A = FEh (FEh = 254d)


ADC #$03 ; A = A + 3
BRK ; termina o programa

Nesse exemplo, nada demais. O programa colocará 1 em A e depois somará 5 resultando


em 6 no A. Depois, na outra soma, o Carry entra em ação. O A terá o valor 254 e será
somado 3. É para resultar em 257 certo? Vamos ver em binário:

254d = FEh = 11111110b


03d = 03h = 00000011b
------ ---- ---------------
257d 101h 100000001b < aqui tem 9 bits estourando os
< 8 bits que compõem 1 byte

Esse 9º bit do resultado vai para o Carry. Então o A ficou com 00000001b e o Carry com 1.
Outro exemplo:

.ORG $1000

CLD ; modo normal


SEC ; Carry = 1
LDA #$01 ; A = 1
ADC #$05 ; A = A + 5
BRK ; termina o programa

Nesse exemplo iniciamos a soma com o Carry em 1. O resultado é: A = 7, pois A + 5 + Carry


= 1 + 5 + 1 = 7.

E:\Atari\SDK\doc\Tutorial.doc Página 49 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Agora faça os mesmos programas alterando a instrução CLD para SED e observe a
diferença. Faça também a subtração lembrando que a instrução é SBC. Veja o
comportamento quando o Carry é 1 e quando é 0.

Exemplo de lógica, incremento e decremento de X e Y

.ORG $1000

LDA #$0A ; A = 0Ah 00001010b


AND #$33 ; A = 02h 00000010b
LDA #$33 ; A = 33h 00110011b
AND #$0A ; A = 02h 00000010b
LDA #$45 ; A = 45h 01000101b
AND #$0F ; A = 05h 00000101b
LDA #$45 ; A = 45h 01000101b
AND #$F0 ; A = 40h 01000000b
ORA #$0C ; A = 4Ch 01001100b
EOR #$12 ; A = 5Eh 01011110b
LDA #$AA ; A = AAh 10101010b
EOR #$FF ; A = 55h 01010101b
EOR #$FF ; A = AAh 10101010b
BRK ; termina o programa

No caso do programa acima, as 4 primeiras linhas querem dizer que “a ordem dos fatores
não altera o produto”. Se eu carregar A com um valor M e fizer em AND com um valor N, ou
carregar A com valor N e fizer um AND com um valor M, dá no mesmo. As 4 linhas
seguintes são exemplo de mascaramento. Muito útil quando queremos eliminar o nibble
inferior ou superior de um byte. Os 2 últimos EORs mostram a propriedade dessa instrução
de inverter bits.. Um byte que recebe 2 EORs é igual ao (resulta no) próprio byte. Para quem
sabe um pouco sobre álgebra booleana, é fácil entender o AND, ORA e EOR.

O AND é: 0 e 0 dá 0
0 e 1 dá 0
1 e 0 dá 0
1 e 1 dá 1

por isso 0Ah = 00001010b


33h = 00110011b
and ----- ---------------
02h 00000010h

O ORA é: 0 ou 0 dá 0
0 ou 1 dá 1
1 ou 0 dá 1
1 ou 1 dá 1

por isso 40h = 01000000b


0Ch = 00001100b
ora ----- ---------------
4Ch 01001100b

O EOR é: 0 ou exclusivo 0 dá 0
0 ou exclusivo 1 dá 1
1 ou exclusivo 0 dá 1
1 ou exclusivo 1 dá 0

E:\Atari\SDK\doc\Tutorial.doc Página 50 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Por isso 4Ch = 01001100b


12h = 00010010b
eor ----- ---------------
5Eh 01011110

Casos interessantes
do AND
45h = 01000101b 45h = 01000101b
0Fh = 00001111b F0h = 11110000b
----- --------------- ----- ---------------
05h = 00000101b 40h = 01000000b

O que aconteceu? O AND tem a propriedade de mascarar bits. No caso acima eu tenho o
valor 45h. Fazendo AND 0Fh extraí o 5 e eliminei (mascarei) o 4. Fazendo AND F0h extraí o
4 e eliminei (mascarei) o 5.

do EOR
AAh = 10101010b 55h = 01010101b
FFh = 11111111b FFh = 11111111b
----- --------------- ----- ---------------
55h 01010101b AAh 10101010b

O que aconteceu? Quando se faz um EOR de um bit com 1, o valor do bit é invertido.

do AND e do ORA
LDA #$41 ; A = caracter 'A' (maiúsculo)
ORA #$20 ; A = caracter 'a' (minúsculo)
AND #$DF ; A = caracter 'A' (maiúsculo)

Carregamos o acumulador com o valor 41h que representa o caracter 'A' maiúsculo.
Fazendo o ORA 20h convertemos para minúsculo e fazendo o AND DFh convertemos para
maiúsculo novamente.

Exemplos de armazenamento e variáveis


No 6502, armazenamos valores com as instruções STA, STX e STY (registradores A, X e Y
respectivamente). Veja o programa.

.ORG $1000

LDA #$10 ; A = 10h


LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h

STA $80 ; armazena A no endereço 80h da memória


STX $81 ; armazena X no endereço 81h da memória
STY $82 ; armazena Y no endereço 82h da memória
BRK ; termina o programa

Mas é complicado lembrar onde guardamos se tivermos que trabalhar com os números dos
endereços. Para isso criamos labels para esses endereços e chamamos de variáveis.

No simulador fica assim:

E:\Atari\SDK\doc\Tutorial.doc Página 51 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

variavelA = $80
variavelX = $81
variavelY = $82

.ORG $1000

LDA #$10 ; A = 10h


LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h

STA variavelA ; armazena A no endereço 80h da memória


STX variavelX ; armazena X no endereço 81h da memória
STY variavelY ; armazena Y no endereço 82h da memória
BRK ; termina o programa

No simulador, quando teclar F6 para rodar o programa passo a passo, tecle ALT+3 para
abrir a janela 6502 µP Zero Page e tecle ALT+2 para abrir a janela 6502 µP Memory. Role
(através da scroll bar de cada janela) até encontrar o endereço 80h ou próximos para ver
como os valores são armazenados nesses endereços que são nossas variáveis. Nos nossos
programas, os valores estarão a partir do endereço 80h, mas podem ser alterados, basta
localizá-los depois nas janelas. Se na janela Memory você rolar o conteúdo até o endereço
próximo de 1000h verá um monte de valores diferentes de 00h que são, nada mais nada
menos, que nosso programa compilado e carregado nesse endereço. Veja figura 12.

Nota: o offset é a primeira coluna de cada janela (zero page e memory). Você pode clicar
nas janelas de memória (zero page e memory) com o botão direito do mouse, escolher a
opção “display from address...” e digitar 0x0080 daí as janelas mostrarão a partir do
endereço da primeira variável.

Figura 12

E:\Atari\SDK\doc\Tutorial.doc Página 52 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Exemplo de incremento/decremento de registrador e variável


Vamos relembrar que no 6502 não existe o INA nem o DEA (só no 65C02). Por isso, só
podemos incrementar com instruções específicas os registradores X e Y. Assim:

.ORG $1000

LDA #$10 ; A = 10h


LDX #$20 ; X = 20h
INX ; X = 21h
LDY #$30 ; Y = 30h
INY ; Y = 31h
INY ; Y = 32h
DEX ; X = 20h
DEX ; X = 1Fh
BRK ; termina o programa

Para incrementar/decrementar locais da memória (variáveis) usamos o INC e o DEC.

variavelA = $80
variavelX = $81
variavelY = $82

.ORG $1000

LDA #$10 ; A = 10h


LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h

STA variavelA ; armazena A no endereço 80h da memória


STX variavelX ; armazena X no endereço 81h da memória
STY variavelY ; armazena Y no endereço 82h da memória

INC variavelA ; variavelA = 11h


DEC variavelY ; variavelY = 2Fh

LDA variavelX ; A = 20h


TAX ; X = A
LDA variavelA ; A = 11h
LDY variavelY ; Y = 2Fh
BRK ; termina o programa

No programa acima, utilizamos no final o LDA nome-da-variavel para recuperar (ler) valores
das variáveis. Vale lembrar que a forma como o programa está escrito, é só para
exercitarmos os comandos, não sendo necessariamente obrigatório ser dessa forma.

O LDA variavelX carrega no registrador A o conteúdo da memória no local 81h que está
sendo referenciado pelo label variavelX. O TAX transfere esse valor para o registrador X. O
LDA variavelA carrega em A o valor da variavelA e o LDY carrega o valor da variavelY.

A outra forma de escrever é:

LDX variavelX ; X = 20h


LDA variavelA ; A = 11h
LDY variavelY ; Y = 2Fh

Ora, se sabemos que o valor da variavelX vai para o registrador X então fazemos um LDX
direto e não precisamos passar pelo registrador A (não precisa fazer LDA). Com isso
economizamos 1 comando: o TAX. Fazendo LDX, o valor foi para o X direto. Fazendo LDA

E:\Atari\SDK\doc\Tutorial.doc Página 53 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

depois temos que transferir para o X. Essa economia (de 1 byte) no 2600 é muito
importante, pois como vamos ver mais adiante, a arquitetura dele é bem simples e restrita,
não nos dando o luxo de gastar bytes do jeito que quisermos.

Só para adiantar um pouco o assunto e fazer uma comparação, hoje em dia falamos em
Gigabytes nos HDs dos PCs. No 2600, temos jogos de 1KB, 2KB, 4KB, 8KB, 16KB e 32KB,
dependendo do jogo em si e da ROM usada. As memórias RAM nos PCs hoje também são
tratadas em termos de Gigabytes. No 2600 (pasmem) são apenas 128 bytes. É isso mesmo:
128 bytes e ainda assim você não pode usar toda ela para guardar dados. Os 128 bytes são
compartilhados com a pilha, portanto (como veremos logo) se a pilha atingir a área de dados
e vice-versa, uma catástrofe acontece. É importante ter em mente os limites de chamadas
de subrotinas e a utilização da memória no 2600.

Utilizando os modos de endereçamento


Vamos agora escrever um programa usando alguns modos de endereçamento e ver como
funcionam e para que servem.

variavelA = $80
variavelX = $81
variavelY = $82

.ORG $1000

LDA #$10 ; A = 10h


LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h

STA variavelA ; armazena A no endereço 80h da memória


STX variavelX ; armazena X no endereço 81h da memória
STY variavelY ; armazena Y no endereço 82h da memória
LDY #$00 ; Y = 00h
LDA variavelA,y ; A = 10h
INY ; Y = Y + 01h
LDA variavelA,y ; A = 20h
INY ; Y = Y + 01h
LDA variavelA,y ; A = 30h
BRK ; termina o programa

O programa acima é semelhante aos anteriores. Ele salva os valores 10h, 20h e 30h nas
variáveis variavelA, variavelX e variavelY respectivamente. Mas para recuperar os valores a
coisa muda um pouco. Vamos ver as 2 primeiras linhas:

LDY #$00 ; Y = 00h


LDA variavelA,y ; A = 10h

Aqui o Y = 00h e a linha seguinte diz ao 6502: “carregue em A o valor que está em variavelA
+ Y”. Como a variavelA é um label que identifica o endereço 80h (nesse exemplo) e Y = 00h,
a tradução fica: “carregue em A o valor que está em 80h + 00h”. Como sabemos 80h + 00h =
80h, então o valor do endereço 80h vai ser colocado em A (A vai ficar com 10h). Depois Y é
incrementado em 01h com a instrução INY. Daí a linha seguinte diz a mesma coisa que a
LDA anterior, só que a tradução agora fica: “carregue em A o valor que está em 80h + 01h”.
Como sabemos 80h + 01h = 81h, então o valor do endereço 81h vai ser colocado em A (A
vai ficar com 20h). A mesma coisa vale para as 2 linhas seguintes, terminando o A com 30h.
Imagine que o LDA variavelA é o start da coisa e que o Y é o ponteiro. Em relação ao start o
ponteiro pega os valores dependendo do seu próprio valor. Simples. Escrevendo o programa
conforme segue, temos o mesmo resultado dos anteriores:

E:\Atari\SDK\doc\Tutorial.doc Página 54 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

variavelA = $80
variavelX = $81
variavelY = $82

.ORG $1000

LDA #$10 ; A = 10h


STA variavelA ; armazena A no endereço 80h da memória
LDA #$20 ; X = 20h
STA variavelX ; armazena A no endereço 81h da memória
LDA #$30 ; Y = 30h
STA variavelY ; armazena A no endereço 82h da memória

LDY #$00 ; Y = 00h


LDA variavelA,y ; A = 10h
PHA ; salva
INY ; Y = Y + 01h
LDA variavelA,y ; A = 20h
TAX ; X = A
INY ; Y = Y + 01h
LDA variavelA,y ; A = 30h
TAY ; Y = A
PLA ; restaura
BRK ; termina o programa

O programa acima coloca os valores 10h, 20h e 30h nas variáveis variavelA, variavelX e
variavelY respectivamente. Agora vamos recuperar esses valores, colocá-los nos seus
respectivos registradores, utilizando modo de endereçamento. Lemos o valor que está em
80h + 00h (variavelA = 80h e Y = 00h), que corresponde ao valor (10h) que queremos deixar
no registrador A, só que esbarramos num dilema: mais à frente, carregaremos em A outros
valores, então precisamos salvar esse valor atual. Para isso usamos o PHA e colocamos o
valor na pilha. Daí incrementamos Y em 1 e carregamos o valor que está em 80h + 01h =
81h, que é o valor (20h) que deve ser colocado em X. Daí transferimos para o X usando o
TAX. Incrementamos Y novamente e carregamos em A o valor que está em 80h + 02h =
82h, que é o valor (30h) que deve ficar em Y e transferimos ele para o Y com TAY. Por fim,
restauramos o valor de A com PLA. Observamos que em vez de

LDA variavelA,y
TAX

poderíamos ter feito

LDX variavelA,y

somente. Daí vem a pergunta: o mesmo vale para

LDA variavelA,y
TAY

substituir por

LDY variavelA,y

A resposta é não. Não pode fazer LDY variavelA,y. Um LDY só pode ter X como indexador e
LDX só pode ter Y como indexador. É importante também observar que o Y foi o último
registrador a ter seu valor recuperado, pois como não vamos mais precisar dele como
índice, pode ter seu valor final recuperado.

E:\Atari\SDK\doc\Tutorial.doc Página 55 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

variavelA = $80
variavelY = $82

.ORG $1000

LDA #$82 ; A = 82h (representa o low byte do endereço da variavelY


STA variavelA ; armazena A no endereço 80h da memória
LDA #$00 ; A = 00h (representa o high order do endereço da variavelY
STA variavelA+1 ; armazena A no endereço 81h da memória
LDY #$30 ; Y = 30h
STY variavelY ; armazena Y no endereço 82h da memória
LDY #$00 ; Y = 00h
LDA (variavelA),y ; A = 30h (valor da variavelY)
BRK

No programa acima, utilizamos outro modo de endereçamento. Relembrando que um


endereço é composto de 2 bytes, então como a variavelA vai armazenar um endereço, ela
ocupa 2 bytes da memória (80h e 81h). A primeira linha carrega o endereço (low order) da
variávelY no registrador A (mais adiante veremos uma forma mais bonitinha de fazer isso,
por enquanto é só para termos uma melhor visualização). A segunda linha, armazena esse
valor (o endereço da variavelY) na variavelA (na high order). Depois colocamos 00h em A e
armazenamos na low order da variavelA. Colocamos zero porque as variáveis estão na zero
page. Então o endereço da variávelY que é 0082h ficou armazenado na variavelA como
8200h (veja figura 13).

Figura 13

Depois colocamos o valor #$30h na variavelY. Agora vamos utilizar Y como índice.
Colocamos o valor 00h em Y e depois fazemos LDA (variavelA),Y. Essa linha diz ao 6502 o
seguinte: “O valor que está na variavelA indexada por Y deve ser considerado como um
endereço de memória. Então leia esse valor, cosidere-o como endereço de memória,
recupere o valor que está nesse novo endereço e coloque-o em A”.

E:\Atari\SDK\doc\Tutorial.doc Página 56 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Então 6502 lê o valor 0082h, considera-o como um endereço, pega da memória o valor que
está nesse endereço (que é 30h) e coloca em A.

Nota: Observe que usamos outra forma de acessar uma variável com mais de 1 byte. Nesse
tópico, vimos como os índices funcionam para acessar os outros bytes da variável. Nesse
último exemplo, utilizamos o “+1” para acessar o outro byte da variávelA (STA variavelA+1),
ou seja, se a variávelA está em 80h, a variavelA+1 está em 81h. É o mesmo que: sendo X =
00h, STA variavelA,x acessa o endereço 80h e, sendo X = 01h, STA variavelA,x acessa o
endereço 81h. É a mesma coisa. A diferença está na compilação. STA variavelA vai gerar o
opcode 8D80h (físico no programa compilado) enquanto que variavelA+1 vai gerar o opcode
8D81h (físico no programa compilado). Tanto que colocar no lugar do +1 uma variável
simplesmente não funciona. Exemplo:

STA variavelA+5 ; funciona


STA variavelA+X ; sendo X registrador ou variável não funciona

Somente constantes são aceitas.

Até agora trabalhamos com variáveis de apenas 1 byte (exceto no último exemplo). Vamos
nos aprofundar mais em variáveis de mais de 1 byte. Vamos fazer nossa variavelA ocupar 5
bytes nos próximos exemplos. Como vínhamos utilizando:

Variáveis
A X Y
1 byte 1 byte 1 byte

Agora vamos defini-las como:

Variáveis
A X Y
1 byte 1 byte 1 byte 1 byte 1 byte 1 byte 1 byte

Para isso basta escrevermos dessa forma (no simulador):

variavelA = $80
variavelX = $85
variavelY = $86

A variavelA vai de 80h a 84h totalizando 5 bytes. Agora vamos ver como podemos acessá-la
e armazenar/recuperar valores nessa variável. Vamos dar um exemplo de como fazer isso
utilizando os métodos já vistos e vamos dar outros exemplos utilizando o conceito de loop.
Para fazermos loops, vamos utilizar as instruções de comparação e desvio, já vistas
anteriormente.

Primeiramente, vamos fazer do modo já visto (com uma pequena diferença), supondo que
queremos colocar o valor 3E9B436A11h na variavelA, 20h na variavelX e 30h na variavelY.

variavelA = $80
variavelX = $85
variavelY = $86

.ORG $1000

E:\Atari\SDK\doc\Tutorial.doc Página 57 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

LDX #$00 ; X = 00h


LDA .valorParaColocarEmA,x ; lê byte (número/valor)
STA variavelA,x ; salva byte (número/valor)
INX ; X = X + 01h
LDA .valorParaColocarEmA,x ; lê byte (número/valor)
STA variavelA,x ; salva byte (número/valor)
INX ; X = X + 01h
LDA .valorParaColocarEmA,x ; lê byte (número/valor)
STA variavelA,x ; salva byte (número/valor)
INX ; X = X + 01h
LDA .valorParaColocarEmA,x ; lê byte (número/valor)
STA variavelA,x ; salva byte (número/valor)
INX ; X = X + 01h
LDA .valorParaColocarEmA,x ; lê byte (número/valor)
STA variavelA,x ; salva byte (número/valor)
LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h
STX variavelX ; salva em variavelX
STY variavelY ; salva em variavelY
BRK ; termina o programa

.valorParaColocarEmA
.byte $3E, $9B, $43, $6A, $11

Vamos tentar entender. Introduzimos uma nova diretiva: .byte. Essa diretiva diz
“simplesmente insira esse(s) byte(s) nesse local, não importando o que eles significam”. No
programa acima, colocamos os bytes que para nós significam o valor que queremos colocar
na variavelA e nos referenciamos a eles com o label .valorParaColocarEmA (entendemos
que seja valor para colocar na variavelA, mas resumimos para o nome não ficar extenso).

O programa utiliza o registrador X como indexador. Daí carregamos ele com 0h, lemos de
.valorParaColocarEmA,x e colocamos no registrador A. Logo em seguida, armazenamos na
variavelA,x. Como X nesse primeiro momento é 0h, o valor lido foi de
.valorParaColocarEmA,00h e armazenado em variavelA,00h. Depois incrementamos X em
01h e o valor lido foi de .valorParaColocarEmA,01h e armazenado em variavelA,01h. Depois
incrementamos X em 01h e o valor lido foi de .valorParaColocarEmA,02h e armazenado em
variavelA,02h. Depois incrementamos X em 01h e o valor lido foi de
.valorParaColocarEmA,03h e armazenado em variavelA,03h. Depois incrementamos X em
01h e o valor lido foi de .valorParaColocarEmA,04h e armazenado em variavelA,04h.
Totalizando os 5 bytes. Depois carregamos X e Y com seus valores e armazenamos nas
respectivas variáveis.

Exemplos de comparação e desvio


Agora vamos apresentar isso na forma de 2 exemplos com loop. O primeiro faz a mesma
coisa do programa anterior, o segundo também, mas com a diferença de ser mais prático e
mais econômico (economiza bytes). Primeiro exemplo:

variavelA = $80
variavelX = $85
variavelY = $86

.ORG $1000

LDX #$00 ; X = 00h

.pegaProximoByte
LDA .valorParaColocarEmA,x ; A = byte endereçado por X
STA variavelA,x ; armazena na variavelA + X

E:\Atari\SDK\doc\Tutorial.doc Página 58 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

INX ; X = X + 01h
CPX #$05 ; pergunta X = 05h?
BNE .pegaProximoByte ; não. Vá para a linha .pegaProximoByte
; se X for 05h continua daqui para baixo
LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h
STX variavelX ; armazena X na variavelX
STY variavelY ; armazena Y na variavelY
BRK ; termina o programa

.valorParaColocarEmA
.byte $3E, $9B, $43, $6A, $11 ; valor a ser colocado na variavelA

O programa utiliza o registrador X como indexador. Daí carregamos ele com 00h, lemos de
.valorParaColocarEmA,x e colocamos no registrador A. Logo em seguida, armazenamos na
variavelA,x. Daí X é incrementado em 01h (INX). Agora usando a instrução CMP (de
comparação), comparamos X com 05h. Se o valor de X for 05h, indica que já lemos e
salvamos 5 bytes, caso contrário indica que ainda faltam bytes para ler/salvar. Assim:
quando X é 00h, vou para a linha .pegaProximoByte e leio/salvo o 1º byte. Quando X é 01h,
vou para a linha .pegaProximoByte e leio/salvo o 2º byte. Quando X é 02h, vou para a linha
.pegaProximoByte e leio/salvo o 3º byte. Quando X é 03h, vou para a linha
.pegaProximoByte e leio/salvo o 4º byte. Quando X é 04h, vou para a linha
.pegaProximoByte e leio/salvo o 5º byte. Quando X é 05h não vou mais para linha
.pegaProximoByte e continuo as instruções seguintes.

Relembrando que o CPX quando é igual faz com que o Zero flag seja 1. Daí o BNE é falso,
saindo do loop e quando ele é diferente o Zero flag é 0. Daí o BNE é verdadeiro, perfazendo
o loop. No bom português é mais ou menos assim:

CPX #$05 ; compare X com 5


BNE .pegaProximoByte ; vá para a linha .pegaProximoByte se não
; forem iguais (se X for diferente de 5)

CPX = ComPare register X compare X com 5 (no nosso caso)


BNE = Branch if Not Equal desvie (ou vá para) se não for igual

Agora vamos fazer o 2º exemplo (mais prático e mais econômico)

variavelA = $80
variavelX = $85
variavelY = $86

.ORG $1000

LDX #$04 ; X = 4

.pegaProximoByte
LDA .valorParaColocarEmA,x ; A = byte endereçado por X
STA variavelA,x ; armazena na variavelA + X
DEX ; X = X + 1
BPL .pegaProximoByte ; vá para a linha .pegaProximoByte caso
; X seja maior que zero
; se X for < 0 continua daqui para baixo
LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h
STX variavelX ; armazena X na variavelX
STY variavelY ; armazena Y na variavelY
BRK ; termina o programa

E:\Atari\SDK\doc\Tutorial.doc Página 59 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.valorParaColocarEmA
.byte $3E, $9B, $43, $6A, $11 ; valor a ser colocado na variavelA

A diferença básica é: não precisamos mais fazer a comparação (utilizar CPX). Por que? X
inicia com 4 e vem decrescendo (DEX). Quando ele chega a zero o 6502 o considera ainda
positivo pois o bit 7 é zero. Quando ele decresce novamente (00h – 01h) ele passa para FFh
acarretando no bit 7 ser 1. Para o 6502, quando o bit 7 é 1, o número é negativo, sendo
então menor que zero. Daí entra em ação a próxima instrução: BPL = Branch on Plus, ou
seja, desvie (ou vá para) caso seja positivo. Então passo a passo o programa executa
assim:

O programa utiliza o registrador X como indexador. Daí carregamos ele com 04h, lemos de
.valorParaColocarEmA,x e colocamos no registrador A. Logo em seguida, armazenamos na
variavelA,x. Daí X é decrementado em 01h (DEX). A instrução DEX altera o Z flag para 1 se
o X for zero e o N flag para 1 se o bit 7 do X for 1. Agora a instrução BPL verifica se o N flag
é zero. Se for 1 indica que já lemos e salvamos 5 bytes, caso contrário indica que ainda
faltam bytes para ler/salvar. Assim: quando X é 04h, vou para a linha .pegaProximoByte e
leio/salvo o 5º byte. Quando X é 03h, vou para a linha .pegaProximoByte e leio/salvo o 4º
byte. Quando X é 02h, vou para a linha .pegaProximoByte e leio/salvo o 3º byte. Quando X é
01h, vou para a linha .pegaProximoByte e leio/salvo o 2º byte. Quando X é 00h, vou para a
linha .pegaProximoByte e leio/salvo o 1º byte. Quando X é FFh não vou mais para linha
.pegaProximoByte. pois agora sou negativo, e continuo as instruções seguintes.

Relembrando que a instrução DEX altera o Z flag para 1 se o X for zero e o N flag para 1 se
o bit 7 do X for 1. A instrução BPL verifica se o N flag é zero. Se for, o desvio é verdadeiro,
pois o número é positivo. Caso contrário, o desvio é falso, saindo do loop. No bom português
é mais ou menos assim:

DEX ; altera Z e N conforme o valor resultante


BPL .pegaProximoByte ; vá para a linha .pegaProximoByte se X for positivo

Esses loops são semelhantes ao REPEAT...UNTIL ou o DO...WHILE das outras linguagens


de programação, onde os comandos internos do loop são executados pelo menos 1 vez
antes do teste.

Exemplos de salto e chamada de subrotina


Esse primeiro programa é exemplo de salto (incondicional).

variavelA = $80
variavelX = $81
variavelY = $86

.ORG $1000

LDA #$10 ; A = 10h


JMP .saltaRestoDoPrograma ; as linhas abaixo não serão executadas
LDX #$20 ; X = 20h
LDY #$30 ; Y = 30h
STA variavelA ; salva A na variavelA
STX variavelX ; salva X na variavelX
STY variavelY ; salva Y na variavelY

.saltaRestoDoPrograma ; a execução retoma daqui em diante


BRK ; termina o programa

E:\Atari\SDK\doc\Tutorial.doc Página 60 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O exemplo acima é simples. A instrução JMP (Jump) diz ao 6502 para pular para o endereço
do label .saltaRestoDoPrograma. A execução então passa a ser a partir daí. Isso quer dizer
que as instruções abaixo da linha da instrução JMP .saltaRestoDoPrograma nunca serão
executadas, em nenhuma hipótese.

O exemplo seguinte é uma chamada de subrotina. Relembrando que em uma chamada de


subrotina o 6502 salva na pilha o offset seguinte ao offset da linha que chama a subrotina e
vai para a subrotina. Quando encontra uma instrução de retorno, retira da pilha o offset salvo
e vai para esse offset. Execute o programa seguinte no simulador e observe o
comportamento da pilha e do registrador PC, além de observar onde o programa retoma a
execução depois que retorna da subrotina chamada.

variavelA = $80
variavelX = $81
variavelY = $86

.ORG $1000

LDA #$10 ; A = 10h


STA variavelA ; salva A na variavelA
LDX #$20 ; X = 20h
STX variavelX ; salva X na variavelX
JSR .somaVariavelAeX ; chama subrotina
LDY #$30 ; Y = 30h
STY variavelY ; salva Y na variavelY
JSR .subtraiVariavelYeA ; chama subrotina
BRK ; termina o programa

.somaVariavelAeX ; subrotina
CLD ; limpa modo decimal
CLC ; zera Carry
LDA variavelA ; A = 10h
ADC variavelX ; A = A + 20h
RTS ; retorna ao programa principal

.subtraiVariavelYeA ; subrotina
CLD ; limpa modo decimal
SEC ; seta Carry
LDA variavelY ; A = 30h
SBC variavelA ; A = A - 10h
RTS ; retorna ao programa principal

O programa acima faz a soma de uma variável com o registrador A na primeira subrotina e a
subtração de uma variável com o registrador A na segunda subrotina. Faça a seguinte
modificação no programa: retire o RTS da primeira subrotina. O que vai acontecer? O
programa começa a executar a primeira subrotina e não vai encontrar instrução de retorno.
Ele então vai continuar a execução pela segunda subrotina e encontrará o RTS lá. Daí sim
ele retorna ao programa principal. A chamada e execução da segunda subrotina é normal.

Vimos até agora pelo menos 1 exemplo de cada grupo de instruções. Agora podemos
evoluir um pouco para o âmbito do 2600. Uma pergunta: O 2600 dependia do que além da
energia elétrica, do cartucho e da criança que atormentou o pai para comprar o video game?
Resposta: da Televisão. E uma vez que o 2600 somente gera o sinal para TV e quem monta
a imagem na verdade é o programador, temos que saber como a TV funciona para
podermos saber como programar o 2600 para montar as imagens. Só depois dessa teoria
de TV construiremos os programas e nos familiarizaremos com o emulador que eu uso (o
Stella) inclusive o modo debugger dele.

E:\Atari\SDK\doc\Tutorial.doc Página 61 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

A televisão
Agora é a parte séria da coisa. Uma televisão não mostra uma imagem movendo-se
continuamente. De fato, a televisão mostra imagens estáticas em sucessões tão rápidas que
o olho humano percebe como se fossem contínuas. E essas imagens estáticas são
compostas por linhas desenhadas uma após a outra.

Conhecendo a estrutura da televisão


O objetivo principal da TV é aumentar o alcance da visão humana. Assim sendo, um sistema
de TV deve ter a possibilidade de apresentar todas as propriedades de uma cena. Algumas
dessas propriedades são: luz e sombra, cromaticidade (cor), movimento contínuo, contraste
e detalhes. Estas cenas constituem a imagem. A visão humana encarrega-se de transformar
as variações luminosas em sensações (impulsos elétricos) que são levadas ao cérebro por
meio do nervo óptico. Um fato interessante a respeito da visão humana é concernente à
persistência retiniana. A persistência retiniana consiste no fato de que ao vermos uma
imagem, esta fica retida (gravada) durante determinado tempo na retina. Este tempo é cerca
de um décimo de segundo.

Um exemplo objetivo que evidencia a persistência retiniana é o funcionamento de um


ventilador. Quando ligamos o ventilador e as pás da hélice estão à baixa velocidade,
conseguimos vê-las, distintamente; porém quando a velocidade aumenta, começamos a não
mais distinguir as pás. Assim como este, existem milhares de outros exemplos que
comprovam a persistência retiniana e devido a esta propriedade do olho humano é que
temos hoje o cinema e a televisão. Tanto no cinema quanto na televisão as imagens são
fixas, paradas. Porém, tais imagens são apresentadas à nossa visão num tempo menor que
o da persistência retiniana, nos dando assim uma sensação de movimento, conforme a
figura 14.

Figura 14

No cinema a projeção se faz com uma velocidade de 24 quadros por segundo, velocidade
ou freqüência ideal para nos dar a sensação de movimento. Em TV adotou-se um número
um pouco maior: 30 quadros por segundo, os quais são recompostos 60 vezes por segundo
(2 x 30) porque 60 Hertz (60 ciclos por segundo) é a freqüência da rede elétrica do Brasil.
Isso facilita o projeto da televisão: a própria freqüência da rede elétrica sincroniza a imagem
na tela do televisor.

Exploração de uma imagem


Explorar uma imagem consiste em retirar as principais informações desta. Em fotografia, por
exemplo, temos que uma imagem é explorada por meio de pontos. Estes milhares de pontos
é que formam os elementos da imagem. Em TV a exploração é análoga.

Imaginemos a situação da figura 15. Um pintor está traçando linhas brancas sobre um
tabuleiro preto. O funcionamento do equipamento utilizado para realizar o trabalho da pintura
é dirigido por um assistente que conduz um veículo dotado de uma plataforma, onde ficará o
pintor. O pintor permanece nesta plataforma e realiza seu trabalho com um pincel e um pote
de tinta. O assistente recebe certas instruções: para iniciar, ele tem de colocar a plataforma
de tal modo que o pintor possa pintar da esquerda para a direita e de cima para baixo. No
E:\Atari\SDK\doc\Tutorial.doc Página 62 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

final de um certo tempo, teremos o painel todo pintado. Na televisão, a projeção da imagem
em cada quadro é feita de maneira análoga, recebendo o nome de varredura.

Figura 15

A figura 16 ilustra o processo de varredura eletrônica da TV. O quadro é formado pelo


retângulo ABCD.

Figura 16

E:\Atari\SDK\doc\Tutorial.doc Página 63 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O feixe eletrônico inicia a exploração no ponto a e termina a primeira linha no ponto b de


onde retorna à esquerda ao ponto c com uma velocidade muito maior que a utilizada para se
deslocar de a para b. Esta linha onde o feixe eletrônico retorna é chamada de retraço. O
retraço não deve aparecer na tela do televisor, então o circuito apagador do televisor apaga
esse sinal.

No Brasil, EUA, Japão, etc. adotou-se um padrão para formar a imagem. O padrão é de 30
imagens por segundo; cada imagem possui 525 linhas. Portanto é varrido (525 x 30) 15.750
linhas por segundo. A tela de TV deve ter 525 linhas horizontais (figura 17) e 700 linhas
verticais (figura 18) resultando em (525 x 700) 367.500 pontos (figura 19).

Figura 17 Figura 18

Figura 19

Baseado na ilustração do pintor, um artista efetuaria 3 operações simultâneas:


• um movimento horizontal (linhas)
• um movimento vertical (quadros)
• aplicação ou não da pintura (figura 20)

Figura 20

E:\Atari\SDK\doc\Tutorial.doc Página 64 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Analogamente, 3 sinais são básicos em TV:


• sincronismo horizontal para varrer linhas (15.750 Hz)
• sincronismo vertical para varrer quadro (60 Hz)
• sinal de vídeo que contém as informações da cena televisada

A varredura é feita com um entrelaçamento de linhas ímpares e pares. Primeiro, somente as


linhas ímpares são transmitidas e recompostas, conforme a figura 21.

Figura 21

Depois, ainda no mesmo quadro, na outra metade, somente as linhas pares é que são
transmitidas.

Tubo de raios catódicos (CRT – Cathode Ray Tube)


O tubo de raios catódicos (ou cinescópio) é o dispositivo que converte o sinal elétrico em
sinal luminoso, produzindo a imagem na tela. Esta tela é feita de um material fosforescente
aluminizado (figura 22).

Figura 22

A imagem vem sob a forma de corrente elétrica. Após a amplificação, este sinal entra para o
CRT em forma de um feixe de elétrons. O feixe de elétrons é desviado por campos
magnéticos através do YOKE (bobinas defletoras) colocadas no pescoço do tubo (figura 23).

E:\Atari\SDK\doc\Tutorial.doc Página 65 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 23

Os dois tipos de deflexão usados com cinescópio são resumidos na figura 24. As placas
defletoras, marcadas com um V deslocam o feixe para cima e para baixo, e as placas
marcadas com um H deslocam o feixe horizontalmente.

Figura 24

A ação conjunta das defletoras verticais e horizontais é mostrada nas figuras 25 e 26.

E:\Atari\SDK\doc\Tutorial.doc Página 66 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 25

Figura 26

Existem variações de sistemas de TV. Vamos citar o NTSC e PAL. A diferença está na
freqüência de operação, número de linhas etc. O que determina o número de quadros
(frames) que ela apresenta em cada segundo.

Nos sistemas NTSC, o sinal contém 60 imagens/segundo e no PAL, 50 imagens/segundo.


Além disso, imagens no sistema NTSC possuem 525 linhas e no sistema PAL, possuem 625
linhas. Com isso as imagens do sistema PAL têm mais resolução porém são mostradas
menos freqüentemente, enquanto que o NTSC as imagens têm menos resolução mas são
mostradas mais rápido. É importante conhecer essas diferenças pois o programador é quem
vai definir como a imagem será exibida. O 2600 somente gera o sinal.

Mas como são essas linhas? Atrás do tubo de imagem da TV existe um canhão (ou 3 no
caso de TV em cores) que emite um (ou 3) feixe(s) de elétrons para a tela. A tela é recoberta
com fósforo que brilha quando recebe esse feixe de elétrons. Esse feixe é muito fino e onde
ele incide, o fósforo brilha.

O feixe começa a percorrer a tela da TV da esquerda para a direita e de cima para baixo.
Cada vez que ele sai da esquerda e vai para a direita, conta-se 1 linha horizontal. Cada vez
que ele chega no lado direito, volta para a esquerda e desce para iniciar uma nova linha
horizontal, conta-se 1 linha vertical. Só que da direita para a esquerda (quando está voltando
para a esquerda), ele está apagado e não incide na tela (chamado de branco horizontal ou
retraço horizontal). Ele somente incide na tela quando está indo da esquerda para a direita.
E:\Atari\SDK\doc\Tutorial.doc Página 67 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Quando ele chega no final da tela (no canto inferior direito) ele volta para o canto superior
esquerdo e começa tudo novamente (também não desenha na tela nesse momento. É
chamado branco vertical ou retraço vertical). A cada saída do lado superior esquerdo e
chegada no lado inferior direito (percorrendo toda a tela em forma de 'z') damos o nome de
quadro (ou frame). Então no sistema NTSC temos 525 linhas horizontais e no PAL 625
linhas horizontais.

Apesar de dizermos que o sistema NTSC tem 525 linhas e o PAL 625 linhas, a TV tem um
truque chamado interlacing. Consiste em construir a imagem em 2 frames, cada frame
sendo ou as linhas pares da imagem ou as linhas ímpares da imagem. Cada frame é
mostrado a cada 1/30 segundo para o sistema NTSC e a cada 1/25 segundo para o sistema
PAL. A grande sacada nisso tudo é que um único frame de uma imagem de TV é na verdade
somente metade da resolução vertical da imagem. Portanto, um frame NTSC tem 525/2 =
262,5 linhas e o sistema PAL 625/2 = 312,5 linhas. A 0,5 linha extra é usada para indicar se
o frame é o primeiro (linhas pares) ou o segundo (linhas ímpares) da imagem. Então a
quantidade exata de linhas é 262 para NTSC e 312 para PAL. Analogamente vamos ilustrar
uma matriz, na qual cada coluna representa o tempo e cada linha representa... uma linha.
Vamos supor que cada célula é uma luz branca apagada (então as células estarão escuras).
Exemplo com uma matriz representando um display simples:

Tempo > 1 2 3 4
Linha 1
Linha 2
Linha 3
Linha 4
Linha 5
Linha 6
Linha 7

Agora imagine que alguém está percorrendo as células da linha 1 e tempo 1 até a linha 7 e
tempo 4 (da esquerda para a direita e de cima para baixo). Daí quando você disser:
“Acenda!” a célula que está sendo percorrida no momento em que você disse vai se
acender. Você pode fazer isso aleatoriamente ou definir um padrão (que determinará o
formato do que vai aparentar quando as células estiverem acesas). Por exemplo: supondo
que você disse “acenda!” quando estavam sendo percorridas as células: Linha 1 no tempo 3,
Linha 2 no tempo 2, Linha 2 no tempo 3, Linha 3 no tempo 3, Linha 4 no tempo 3, Linha 5 no
tempo 3, Linha 6 no tempo 3, Linha 7 no tempo 2, Linha 7 no tempo 3, Linha 7 no tempo 4.

Tempo > 1 2 3 4
Linha 1
Linha 2
Linha 3
Linha 4
Linha 5
Linha 6
Linha 7

A matriz ficaria assim, dando a idéia do número 1. A tela de uma televisão é uma matriz
enorme com muitas linhas e muitas colunas. O feixe de elétrons fica percorrendo da
esquerda para a direita e de cima para baixo. Quando você der o sinal, o feixe incide no
fósforo e ele se acende. É assim que funciona a TV. Você vai dizer qual ponto (pixel) da TV
irá acender através da programação do 6502 do 2600. Por isso é importante conhecermos a

E:\Atari\SDK\doc\Tutorial.doc Página 68 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

quantidade de linhas da TV e o tempo. Relembrando: NTSC = 262 e PAL = 312. Lembre-se


desses números e tenha em mente como a TV exibe uma imagem. É o programador quem
constrói a imagem, não o 2600.

O protocolo do 2600 para a TV


Estando isso muito bem claro vamos ao protocolo usado para formar um frame na TV.
Vamos tomar como parâmetro o sistema NTSC (262 linhas). Um simples frame consiste de
262 linhas horizontais e cada linha é dividida em 228 clocks. Dessas 262 linhas horizontais,
3 linhas são para o sincronismo vertical (VSYNC), que dizem para a TV começar um novo
frame, 37 linhas de branco vertical (VBLANK), 192 linhas de imagem e 30 linhas de
overscan. As pesquisas realizadas pela Atari mostraram que dessa forma o 2600 funcionaria
com a maioria dos sistemas de TV. Linha horizontal será chamada de scanline daqui por
diante (figura 27).

Figura 27

Cada scanline começa com 68 clocks de branco horizontal (não visto na tela da TV)
seguidos de 160 clocks para percorrer uma linha completa da TV (68 + 160 = 228 clocks
totais). Isso quer dizer que o 2600 tem 160 pixels de resolução horizontal (pixels da tela que
formam cada linha da imagem). Quando o feixe de elétrons chega no fim do scanline, ele
retorna para o lado esquerdo da tela, aguarda 68 clocks de branco horizontal e começa a
desenhar a linha abaixo. Toda a temporização horizontal é o hardware quem controla, mas o
6502 deve “manualmente” controlar a temporização vertical para sinalizar o início do próximo
frame. Quando a última linha do frame anterior é detectada, o 6502 deve gerar 3 linhas de
VSYNC, 37 linhas de VBLANK, 192 linhas de imagem e 30 linhas de overscan. Ambos
VSYNC e VBLANK podem simplesmente ser ligados e desligados no tempo apropriado,
liberando o 6502 para outras tarefas durante sua execução.

E:\Atari\SDK\doc\Tutorial.doc Página 69 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

A imagem atual da TV é desenhada uma linha por vez, tendo o 6502 que entrar com os
dados daquela linha no Television Interface Adaptor (TIA). É um microchip dedicado que
será detalhado mais adiante. O TIA pode e deve ter somente os dados que pertencem
àquela linha que está sendo desenhada, então o 6502 deve estar “a um passo à frente” do
feixe de elétrons em cada linha. Uma vez que o ciclo de máquina do 6502 ocorre a cada 3
clocks do TIA, o programador tem somente 76 ciclos de máquina por linha (cada linha tem
228 clocks / 3 clocks = 76 ciclos) para construir a imagem (na verdade é menos porque o
6502 deve estar à frente do feixe). Para dar mais tempo para o software, é costumeiro (mas
não necessário), atualizar o TIA a cada 2 scanlines. A porção do programa que constrói a
imagem é chamada de Kernel, uma vez que é a essência ou kernel do jogo.

Em geral, os 70 scanlines restantes (3 para VSYNC, 37 para VBLANK e 30 para overscan)


fornecem 5.320 ciclos de máquina (70 linhas x 76 ciclos = 5.320 ciclos totais) para a lógica
do jogo. Atividades como calcular a nova posição do jogador, atualizar o placar e verificar
entradas, são realizadas durante esse tempo.

Vamos detalhar agora a relação entre o 6502 o TIA e a TV para entendermos melhor o
protocolo, ou seja, cada parte do gráfico da figura 27 será desmembrada para podermos ver
o que deve acontecer em cada uma delas, no seu devido tempo.

E:\Atari\SDK\doc\Tutorial.doc Página 70 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O 2600
Agora vamos conhecer a arquitetura do 2600. O processador já vimos. Agora vamos
conhecer outros microchips do video game como o TIA e o PIA.

O TIA e o 6502
É atribuição do programador controlar quantos scanlines são enviados para a TV, mas é o
2600 que prepara o sinal (como cor e intensidade) para qualquer scanline. Essa informação
de cor e intensidade é derivada do estado interno do TIA (Television Interface Adapter). O
TIA é responsável por criar o sinal para um único scanline da TV. O TIA desenha os pixels
na tela. Cada pixel é 1 clock do tempo de processamento do TIA, e há exatamente 228
clocks to TIA em cada scanline. Mas um scanline consiste não somente do tempo que se
gasta para escanear o feixe de elétrons através do tubo de imagem, mas também do tempo
que leva para o feixe de elétrons retornar ao início do próximo scanline (o branco horizontal
ou retraço). Dos 228 clocks, 160 são usados para desenhar pixels na tela (dando-nos a
resolução máxima de 160 pixels por linha) e 68 são consumidos durante o retraço.

O clock do 6502 é derivado do clock do TIA pela divisão por 3. Isto é, para cada clock do
6502, 3 clocks do TIA já se passaram. Portanto, há exatamente 228/3 = 76 ciclos do 6502
por scanline. Memorizando: para cada ciclo do 6502, 3 do TIA. Vale lembrar que 76 ciclos
por scanline x 262 linhas por frame x 60 frames por segundo = número de ciclos por
segundo que o 6502 tem no sistema NTSC. Então, à medida que o 6502 está executando as
instruções do programa, o TIA está enviando dados para cada scanline. A cada ciclo de
tempo do 6502, o TIA já enviou 3 ciclos de informação para a TV. Se o TIA estava nos
primeiros 160 ciclos do scanline, então ele estava desenhando pixels na tela. Se ele estava
no clock 160 a 227, então ele estava fazendo o horizontal blank. Sendo assim, uma vez que
o 6502 está “travado” ao TIA através da temporização compartilhada, é possível para o
programador saber exatamente onde, no scanline, o TIA está atualmente desenhando (qual
pixel). E sabendo onde o TIA está, permite-nos mudar o que está desenhando em posições
específicas do scanline. Naturalmente, para alcançar esse tipo de precisão, o programador
tem que saber exatamente quanto tempo o 6502 leva para executar cada instrução. Por
exemplo, uma combinação de carga e salvamento leva no mínimo 5 ciclos do tempo do
6502. 5 ciclos são quantos pixels na tela? Lembrando que 3 clocks por ciclo do 6502 então 3
x 5 = 15 pixels. Essencialmente, se usamos as mais rápidas combinações de carregamento
e salvamento para mudar a cor (por exemplo) do plano de fundo da tela, então o mais rápido
que isso poderia ser feito é a cada 15 pixels (apenas 11 vezes por scanline).

Descrição geral do TIA


O TIA é um microchip dedicado feito para criar imagem de TV e som a partir das instruções
enviadas pelo 6502. Ele converte os 8 bits paralelos de dados do 6502 em sinais que são
enviados aos circuitos de modulação de vídeo que os combinam e modulam tornando-os
compatíveis com a recepção por um aparelho de TV.

Os registradores
Todas as instruções do TIA são alcançadas pelo endereçamento e escrita nos vários
registradores do microchip. É importante lembrar que um dado é colocado e retido num
registrador até que seja alterado por outra operação de escrita nesse registrador. Por
exemplo, se o registrador de cor de um jogador é setado em vermelho, o jogador será
desenhado na TV na cor vermelha até que o registrador seja mudado. Todos os
registradores são endereçados pelo 6502 como parte de todo o espaço de RAM/ROM.

E:\Atari\SDK\doc\Tutorial.doc Página 71 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Todos os registradores têm locais fixos de endereço e nomes de endereço pré-fixados para
referência mais fácil. Na programação do 2600 no PC é comum utilizarmos o arquivo VCS.H
como include no programa principal. Abra esse aquivo em um editor de textos (Notepad por
exemplo) e veja que há vários nomes (siglas/abreviaturas) como VSYNC, WSYNC, GRP1,
PF0, COLUBK, etc. que fazem referência a um determinado endereço. Por exemplo:

COLUBK = $09 ;xxxx xxx0 Color-Luminance Background

O 09h indica o endereço do registrador do TIA que é responsável pela cor de fundo da tela.
Se você for um gênio, pode decorar todos esses números e saber o que cada um é/faz no
TIA. Ou então, utilize o VCS.H em seus programas para, em vez de decorar números,
aprender os nomes. É muito mais fácil. É como você digitar URLs para acessar sites na
internet em vez de digitar os IPs dos sites, qual é mais fácil de lembrar?

É como os exemplos de variáveis no tópico Exemplo de armazenamento e variáveis (pág. 51


desse tutorial). Os nomes são atribuídos para ficar mais fácil lembrar.

Muitos registradores não usam todos os 8 bits e alguns são usados como strobe ou trigger
de eventos. Um registrador strobe executa sua função no instante em que é escrito (o que é
escrito, ou seja, o valor não importa). Os únicos registradores que podem ser lidos são os de
colisão e os da porta de entrada. Esses registradores são convenientemente dispostos de
forma que os bits de maior acesso sempre aparecem na posição 6 ou 7.

Sincronismo

Temporização horizontal
Quando o feixe de elétrons percorre a tela da TV e chega ao lado direito, ele deve ser
desligado e voltar para o lado esquedo da tela, para iniciar o próximo scanline. O TIA cuida
disso automaticamente, independente do 6502. Um oscilador de 3,58MHz gera os pulsos de
clock chamados "color clocks" que correspondem a 1 pulso do TIA. Esse contador conta 160
color clocks para o feixe alcançar o lado direito, então gera um sinal de sincronismo
horizontal (HSYNC) para retornar o feixe para o lado esquerdo. Ele também gera o sinal
para desligar o feixe (branco horizontal) durante o tempo de retorno que é de 68 color
clocks. Então o total de clocks que o feixe gasta para ir e voltar é de 160 + 68 = 228 color
clocks (figura 28).

Figura 28

E:\Atari\SDK\doc\Tutorial.doc Página 72 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Sincronismo com o 6502


O clock do 6502 é de 3,58MHz dividido por 3, então 1 ciclo de máquina corresponde a 3
color clocks. Poranto, um scanline completo de 228 color clocks tem apenas 76 ciclos de
máquina (228 / 3 = 76). O 6502 deve estar sincronizado com o TIA linha a linha, mas loops e
desvios no programa acarretam em tempos variáveis. Para resolver esse problema de
sincronismo de software, o programador pode usar o registrador strobe WSYNC (Wait for
Sync.). Simplesmente escrevendo no WSYNC faz o 6502 parar até o feixe de elétrons
chegar no lado direito da tela, então o 6502 continua as operações no início dos 68 color
clocks do branco horizontal. Uma vez que o TIA retém todas as instruções até que sejam
alteradas por outra operação de escrita, ele pode ser atualizado a cada 2 ou 3 linhas. A
vantagem é que o programador ganha mais tempo para executar o programa, mas ao preço
de uma resolução vertical menor.

Temporização vertical
Quando o feixe de elétrons percorreu as 262 linhas, deve-se comandar a TV a apagar o
feixe e posicioná-lo no topo da tela para começar um novo frame. Esse comando em forma
de sinal é chamado de sincronismo vertical (vertical sync) e o TIA deve transmiti-lo por no
mínimo 3 scanlines. Isso é conseguido escrevendo um 1 no bit 1 do registrador VSYNC para
ligá-lo, contar pelo menos 2 scanlines, então escrever um 0 no bit 1 do VSYNC para desligá-
lo (figura 29).

Figura 29

Para desligar fisicamente o feixe de elétrons durante o tempo de reposicionamento, a TV


precisa de 37 scanlines de sinal de retraço vertical (ou branco vertical ou, ainda, vertical
blanks). Isso é conseguido escrevendo um 1 no bit 1 do registrador VBLANK para ligá-lo,
contar 37 linhas, então escrever um 0 no bit 1 do VBLANK para desligá-lo. O 6502 está, com
certeza, livre para executar outras instruções durante os comandos de temporização vertical
(VSYNC e VBLANK), vide figura 30.

Figura 30

Após isso vem a parte que desenha a imagem na tela (figura 31).

Figura 31

E:\Atari\SDK\doc\Tutorial.doc Página 73 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Depois, basta aguardar 30 linhas de overscan e o frame está completo. Daí, num loop
infinito, reinicia-se a partir do vertical sync (figura 32).

Figura 32

A linha em vermelho mostra o loop infinito. Começa no VSYNC, vai até o overscan e volta ao
VSYNC. Antes desse loop, o código do programa normalmente é de configuração e
inicialização, e pode ser de qualquer tamanho (desde que não seja tão grande que esse
código mais o código do jogo, que está dentro do loop, ultrapassem o espaço da ROM,
claro) – figura 33.

Figura 33

Recaptulando:
Vamos fixar bem esse tópico, pois se você não souber o que fazer em cada tempo do frame,
a imagem não vai se formar perfeitamente.

Antes de chegar nesse ponto, você pode escrever seu código do tamanho que quiser (pode
levar o tempo que for). Esse código é para configurar jogadores, inimigos, playfield, placar
etc. Porém quando, no seu programa, você comandar o sincronismo vertical, tudo dali por
diante tem que estar no seu devido tempo.

E:\Atari\SDK\doc\Tutorial.doc Página 74 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O sincronismo vertical consiste no comando de ligar o VSYNC, aguardar pelo menos 2


WSYNCs (normalmente 3 WSYNCs) e desligar o VSYNC. Conforme a figura 34, verifica-se
que, para fazer isso temos apenas 3 scanlines.

Um detalhe interessante. Como temos que aguardar 2 ou 3 WSYNCs, significa que o 6502
fica travado até o TIA alcançar o lado direito da tela por 2 ou 3 vezes, ou seja, o 6502 fica
(76 ciclos x 2 WSYNCs = 152 ciclos ou 76 ciclos x 3 WSYNCs = 228 ciclos) à toa nesse
tempo. Podemos então, substituir os WSYNCs por instruções úteis ao programa, ou seja,
fazer alguns cálculos, ter alguma lógica de programa nesse tempo, desde que não
ultrapasse os ciclos correspondentes aos 3 scanlines do sincronismo vertical. Se
ultrapassar, estará invadindo o tempo do VBLANK e o sincronismo se perde.

Mais adiante veremos como pode ser feito.

Figura 34

Depois do VSYNC temos que aguardar 37 VBLANKs (figura 35). Vale a mesma coisa. São
37 scanlines, ou seja, são 37 WSYNCs. Esses WSYNCs são substituídos pela lógica do
programa. Como, nesse ponto, já estamos dentro do loop, aqui "lemos" as chaves do
console (para verificar se ocorreu um select, reset, por exemplo), "lemos" os joysticks e
fazemos cálculos diversos. Então, nesse tempo temos disponíveis 76 ciclos x 37 scanlines =
2.812 ciclos para o programa.

Se seu programa gastar mais, pode colocar parte dele no tempo do overscan (mais abaixo).
Se gastar menos, deve-se então esperar o tempo restante até completar os 37 scanlines.
Isso mesmo. O tempo deve ser exato, nem mais nem menos. Então se seu programa gastar
somente 1.000 ciclos, os outros 1.812 ciclos devem ser aguardados com WSYNC ou então
utilizando o registrador timer para contar o tempo restante. A mesma regra vale para o
VBLANK: se ultrapassar o tempo, o sincronismo se perde. E tem mais: se não usar todo o
tempo, o sincronismo também se perde. Quando o sincronismo se perde, a imagem fica
deformada ou começa no lugar errado da tela, às vezes acarretando na primeira linha da
imagem começar não exatamente no lado esquerdo da tela e sim mais no meio dela.

Veremos como isso é feito mais adiante, quando iniciarmos nossos primeiros programas.
Veremos também como podemos gastar tempo utilizando instruções e o timer para essa
finalidade.

Figura 35

Ok. Depois do vertical blank, sua TV já está na parte em que desenha na tela. Para
desenhar, o programa deve ter um código que corresponda a desenhar na tela e será
executado nesse tempo (figura 36).

E:\Atari\SDK\doc\Tutorial.doc Página 75 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 36

A parte do overscan pode ser utilizada para colocar mais lógica de programa. Ela gasta 30
scanlines, então 76 ciclos x 30 linhas = 2.280 ciclos nesse tempo (figura 37).

Figura 37

Portanto, 228 ciclos de VSYNC + 2.812 ciclos de VBLANK + 2.280 ciclos de overscan =
5.320 ciclos para programação.

Quando começarmos a escrever programas, veremos como as instruções gastam ciclos de


máquina. A soma de cada ciclo que cada instrução gasta tem que estar dentro dos limites
acima especificados, ou seja, se as instruções estão no tempo do VBLANK, a soma dos
ciclos não pode ultrapassar 2.812, se estiverem no overscan, não pode ser superior a 2.280
ciclos. Veremos também que instruções de desvio alteram a contagem de ciclos. Para
adiantar: se o desvio não ocorrer a instrução gasta 2 ciclos e se o desvio ocorrer a instrução
gasta 3 ciclos.

Vimos alguns registradores do TIA. Vamos ilustrá-los para melhor entendimento. Mais
adiante veremos mais registradores e à medida que forem apresentados serão ilustrados.

WSYNC (Wait for Sync)


Esse endereço pára o 6502. Quando o horizontal blank é alcançado, o 6502 é liberado.

Os bits não são usados.

Bit 7 6 5 4 3 2 1 0
WSYNC

E:\Atari\SDK\doc\Tutorial.doc Página 76 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

RSYNC (Reset Sync)


Esse endereço reseta o contador de sincronismo horizontal para definir o início do tempo do
branco horizontal, e é usado para testes do 6502.

Os bits não são usados.

Bit 7 6 5 4 3 2 1 0
RSYNC

VSYNC
Esse endereço controla o tempo de sincronismo vertical escrevendo no bit 1 do latch.

Se o bit 1 for 1, inicia o sincronismo vertical. Se for 0, pára o sincronismo vertical.

Bit 7 6 5 4 3 2 1 0
VSYNC

VBLANK
Esse endereço controla o branco vertical, os latches e os transistores nas portas de entrada
escrevendo nos bits 7, 6 e 1.

Se o bit 1 for 1, inicia o branco vertical. Se for 0, pára o branco vertical


Se o bit 6 for 1, habilita os latches I4 e I5. Se for 0, desabilita os latches I4 e I5
Se o bit 7 for 1, descarrega I6, I1, I2 e I3 para o terra. Se for 0 remove o aterramento.

Bit 7 6 5 4 3 2 1 0
VBLANK

O Playfield (campo de jogo)


O registrador PF é usado para criar um campo de jogo (de agora em diante playfield)
composto de muros, nuvens, barreiras, etc. que são raramente movidos. Esse registrador de
baixa resolução é feito para desenhar somente na metade esquerda da TV. A metade direita
da tela é desenhada através do programa, que seleciona se essa metade será a reflexão ou
duplicação da metade esquerda.

O registrador PF tem 20 bits e eles estão divididos em 3 endereços: PF0, PF1 e PF2. O PF0
tem somente 4 bits e corresponde aos 4 primeiros pixels do playfield, começando do lado
esquerdo da tela da TV. O PF1 corresponde aos 8 pixels seguintes e o PF2 aos últimos 8
pixels que terminam no centro da tela. O registrador PF é scaneado da esquerda para a
direita e onde é 1 a cor do PF é desenhada na tela, e onde é 0 a cor de fundo é desenhada.
Para limpar o playfield, obviamente zeros devem ser escritos no PF0, PF1 e PF2.

Para fazer a metade direita do playfield, tem-se 2 opções: duplicação ou reflexão do lado
esquerdo. Para a duplicação, basta deixar com 0 o bit 0 do registrador CTRLPF (ConTRol
PlayField). Setando o bit 0 para 1, o playfield será refletido.

E:\Atari\SDK\doc\Tutorial.doc Página 77 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

PF0, PF1 e PF2

Bit 7 6 5 4 3 2 1 0
PF0

Bit 7 6 5 4 3 2 1 0
PF1

Bit 7 6 5 4 3 2 1 0
PF2

Modo Duplicado REF = 0


Lado esquerdo da tela Lado direito da tela
4 7 7 0 0 7 4 7 7 0 0 7
PF0 PF1 PF2 PF0 PF1 PF2

Modo Refletido REF = 1


Lado esquerdo da tela Lado direito da tela
4 7 7 0 0 7 7 0 0 7 7 4
PF0 PF1 PF2 PF2 PF1 PF0

CTRLPF
Esse endereço é usado para escrever dentro do registrador do playfield.

Se o bit 0 for 1, o playfield é refletido (REF = 1). Se for 0, o playfield é duplicado (REF = 0).
Se o bit 1 for 1, a cor do placar do lado esquerdo assume a cor do jogador 0 e a cor do
placar do lado direito assume a cor do jogador 1. Se for 0, as cores dos jogadores não
interferem nas cores dos placares.
Se o bit 2 for 1, o playfield tem prioridade sobre os jogadores, ou seja, os jogadores se
movem atrás do playfield. Se for 0, a prioridade do playfield é menor que a dos jogadores, ou
seja, os jogadores movem-se na frente do playfield.

Os bits 4 e 5 determinam o tamanho da bola, da seguinte forma:

Bit 5 Bit 4 Largura


0 0 1 clock
0 1 2 clocks
1 0 4 clocks
1 1 8 clocks

Bit 7 6 5 4 3 2 1 0
CTRLPF

Mísseis (M0 e M1)


Os 2 registradores de mísseis desenham um míssil em qualquer scanline, bastando para
isso setar para 1 o bit 1 dos registradores ENAM0 (para o míssil do jogador 0) e o ENAM1
(para o míssil do jogador 1). Se limpar esses registradores (colocar 0 neles) os mísseis não

E:\Atari\SDK\doc\Tutorial.doc Página 78 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

são desenhados. O lado esquerdo do míssil é posicionado pelo registrador de posição


horizontal, mas o lado direito é uma função que diz a largura do míssil. A largura do míssil é
controlada setando em 1 os bits 4 e 5 do registrador de tamanho NUSIZ0 (para o míssil do
player0) e NUSIZ1 (para o míssil do player 1). Isso tem o efeito de stretching do míssil por 1,
2, 4 ou 8 color clocks.

ENAM0 e ENAM1
Esse endereço habilita os mísseis.

Se o bit 1 for 1, habilita o míssil. Se for 0, desabilita o míssil.

Bit 7 6 5 4 3 2 1 0
ENAMx

Bola (BL)
O registrador da bola (ENABL) funciona de maneira análoga aos registradores de míssil.
Setando o registrador, habilita o gráfico da bola até o registrador ser desabilitado. A bola
também pode ser expandida com larguras de 1, 2, 4 ou 8 color clocks, bastando para isso
setar os bits 4 e 5 do registrador CTRLPF. A bola pode também ser delayed verticalmente 1
scanline. Por exemplo, se a bola foi habilitada no scanline 95, ela pode ser delayed para não
mostrar na tela até o scanline 96, bastando para isso setar o bit 0 do registrador de delay
vertical (VDELBL). A razão para ter um delay vertical é porque muitos programas atualizam
o TIA a cada 2 scanlines. Isso faz com que todos os movimentos verticais dos objetos
tenham 2 saltos, ou seja, supondo que um objeto está se movendo verticalmente na tela e a
lógica do programa para fazer isso está incrementando esse movimento de 1 em 1, na tela o
objeto se moverá de 2 em 2 linhas, pois o TIA não está sendo atualizado de 1 em 1 e sim de
2 em 2. Exemplo: seu programa tem um contador que é incrementado de 1 em 1 e algum
objeto pega o valor do contador para se posicionar verticalmente na tela. Quando o contador
é 1, o objeto aparece na linha 1 da tela. Quando o contador é 2, o objeto continua na linha 1
da tela, pois o TIA não foi atualizado. Quando o contador é 3, o objeto aparece na linha 3 da
tela, pois agora o TIA foi atualizado. Antes ele estava na linha 1 agora aparece na linha 3 (3
- 1 = 2). O uso do delay vertical permite que os objetos se movam 1 scanline por vez,
corrigindo assim esse problema.

ENABL
Esse endereço habilita a bola.

Se o bit 1 for 1, habilita a bolal. Se for 0, desabilita a bola.

Bit 7 6 5 4 3 2 1 0
ENABL

VDELBL, VDELP0 e VDELP1


Esse endereço atrasa o objeto em 1 scanline.

Se o bit 0 for 1, há atraso. Se for 0, não há atraso.

Bit 7 6 5 4 3 2 1 0
VDELxx

E:\Atari\SDK\doc\Tutorial.doc Página 79 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Players (P0 e P1)


Os jogadores são os objetos mais sofisticados de todos. Eles têm todas as características
dos mísseis e bola e mais 3 tipos de movimentos. Os jogadores podem ser modelados (ter
forma de um homem ou um avião) e podem facilmente ser invertidos horizontalmente, e
ainda pode-se ter múltiplas cópias do jogador.

O jogador é desenhado linha a linha como todos os outros gráficos. A diferença aqui é que
cada scanline do jogador tem 8 bits, enquanto que os mísseis e a bola têm 1 bit. Portanto,
um jogador pode ser imaginado como sendo uma matriz Nx8, onde N é o número de linhas
(que no caso pode ser qualquer número) e 8 é o número de colunas (largura do jogador).
Para desenhar o jogador, deve-se setar os bits correspondentes dos registradores GRP0
(para o jogador 0) e GRP1 (para o jogador 1). Esses registradores de 8 bits são escaneados
do bit 7 ao bit 0, e quando um 1 é encotrado, o pixel do jogador é desenhado na cor que está
definida em COLUP0 (para o jogador 0) e COLUP1 (para o jogador 1), e quando um 0 é
encontrado, o pixel não é desenhado e a cor de fundo da tela (COLUBK) permanece. Para
posicionar o jogador verticalmente, simplesmente deixamos o registrador GRP0 (ou GRP1,
depende do jogador) em zero até que o feixe de elétrons esteja no scanline desejado.
Quando ele estiver no scanline que queremos, basta então colocarmos no registrador os
valores correspondentes para desenhar o jogador. Depois, basta zerar o registrador
novamente.

Para mostrar uma imagem refletida do jogador, basta setar o bit 3 do registrador REFP0
(para o jogador 0) ou REFP1 (para o jogador 1). Ao zerarmos os registradores, os jogadores
correspondentes são mostrados em sua forma original novamente.

Cópias múltiplas dos jogadores assim como seu tamanho são controlados setando 3 bits (0,
1 e 2) nos registradores de número e tamanho NUSIZ0 e NUSIZ1. Esses 3 bits selecionam
de 1 a 3 cópias do jogador, espaçamento entre essas cópias, assim como o tamanho do
jogador (cada pixel do jogador pode ser 1, 2 ou 4 color clocks). Quando múltiplas cópias são
selecionadas, o TIA automaticamente cria o mesmo número de cópias de mísseis para
aquele jogador.

O delay vertical para os jogadores funciona exatamente como para a bola: setando o bit 0
nos registradores VDELP0 e VDELP1. Zerando esses registradores, desabilita o delay
vertical.

GRP0 e GRP1
Esses endereços desenham os jogadores na tela.
Se o REFP0 ou REFP1 for 0 a saída serial se dá pelo bit 7, caso contrário pelo bit 0. REFP0
e REFP1 correspondem ao espelhamento dos jogadores GRP0 e GRP1 respectivamente.

Bit 7 6 5 4 3 2 1 0
GRPx

E:\Atari\SDK\doc\Tutorial.doc Página 80 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

COLUBK, COLUPF, COLUP0 e COLUP1


Esses endereços escrevem nos registradores de cor do plano de fundo, playfield e
jogadores, segundo a figura 38.

COR LUMINÂNCIA
Bit 7 6 5 4 3 2 1 0
COLUxx

Figura 38

REFP0 e REFP1
Esses endereços fazem os jogadores serem desenhados de forma normal ou refletida.

Se o bit 3 = 0, então o jogador correspondente é desenhado no modo normal.


Se o bit 3 = 1. então o jogador correspondente é desenhado no modo refletido.

Bit 7 6 5 4 3 2 1 0
REFPx

NUSIZ0 e NUSIZ1
Esses endereços controlam o número e o tamanho dos jogadores e mísseis.

Bit 7 6 5 4 3 2 1 0
NUSIZx

O tamanho dos mísseis é dado pelos bits 4 e 5 conforme segue:

Bit 5 4 Tamanho
0 0 1 clock
0 1 2 clocks
1 0 4 clocks
1 1 8 clocks

O tamanho dos jogadores e quantidade de jogadores e mísseis é dado por:

½ linha da televisão (80 clocks), 8 clocks por quadrado.

E:\Atari\SDK\doc\Tutorial.doc Página 81 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Bit 2 1 0 Shape Descrição


0 0 0 1 cópia
0 0 1 2 cópias (próximas)
0 1 0 2 cópias (médias)
0 1 1 3 cópias (próximas)
1 0 0 2 cópias (afastadas)
1 0 1 double size
1 1 0 3 cópias (médias)
1 1 1 quad size

Os objetos móveis
A todos os 5 objetos móveis (P0, M0, P1, M1, BL) podem ser atribuídos um local horizontal
na tela e movidos para a esquerda ou direita relativamente à sua posição. Posições
verticais, contudo, são tratadas de maneira completamente diferente. Em princípio, esses
objetos aparecem em qualquer scanline que os registradores sejam habilitados. Por
exemplo, supomos que queremos posicionar verticalmente a bola no centro da tela. A tela
tem 192 scanlines e nós queremos que a bola tenha 2 scanlines de espessura. A bola deve
estar desabilitada até o scanline 96, habilitada por 2 scanlines, então desabilitada pelo resto
do frame. Cada tipo de objeto (jogadores, mísseis e bola) tem sua própria característica e
limitações.

Cor e luminosidade
Cor e luminosidade podem ser atribuídas ao plano de fundo (BK), playfield (PF), bola (BL),
jogadores (P0 e P1), mísseis (M0 e M1). Há somente 4 registradores de cor e luminância
para esses 7 objetos, então os objetos formam pares para compartilhar o mesmo registrador
de acordo com a seguinte lista:

COLUP0 P0, M0 (jogador 0 e míssil 0)


COLUP1 P1, M1 (jogador 1 e míssil 1)
COLUPF PF, BL (playfield e bola)
COLUBK (cor de fundo)

Por exemplo, se o registrador COLUP0 for setado para vermelho claro, ambos P0 e M0
serão vermelho claro quando desenhados. Um registrador de cor e luminância é setado
tanto para cor quanto para a luminância utilizando 7 bits desse registrador. Quatro dos bits
selecionam uma das 16 cores possíveis e os outros 3 selecionam um dos 8 níveis de
luminância. Como todos os registradores (exceto os strobe), os dados escritos nesses
registradores são mantidos até serem alterados por outra operação de escrita.

Posicionamento horizontal
O posicionamento horizontal de cada objeto é feito setando seus correspondentes
registradores reset (RESP0, RESP1, RESM0, RESM1, RESBL) os quais são todos
registradores strobe (eles realizam sua função assim que são acessados). Isso faz com que
os objetos sejam posicionados onde quer que o feixe de elétrons esteja quando o registrador
estava resetado. Por exemplo, se o feixe de elétrons estava 60 color clocks em um scanline
quando o RESP0 foi acessado, o jogador 0 será posicionado 60 color clocks do próximo
scanline. Se o P0 está sendo desenhado ou não é função do registrador GRP0, mas se ele
está sendo desenhado, ele será mostrado no color clock 60 do scanline. Zerar esses
registradores em qualquer lugar durante o HSYNC posicionará os objetos no lado esquerdo
da tela (color clock 0). Uma vez que há 3 color clocks por ciclo de máquina, e gasta-se até 5
ciclos de máquina para escrever em um registrador, o programador está limitado a

E:\Atari\SDK\doc\Tutorial.doc Página 82 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

posicionar os objetos a 15 color clocks de intervalo pela tela. Isso pode ser resolvido com
uma "sintonia fina" através do movimento horizontal (Horizontal Motion) detalhado mais à
frente.

Mísseis têm um comando de posicionamento adicional. Setando o bit 1 do registrador reset


missile-to-player (RESMP0, RESMP1) desabilita o míssil (desliga-o) e reposiciona-o
horizontalmente no centro do jogador correspondente. Até que o registrador seja zerado, a
posição horizontal dos mísseis é travada ao centro do jogador correspondente, aguardando
serem disparados novamente.

RESP0, RESP1, RESM0, RESM1 e RESBL


Esses endereços são usados para resetar os jogadores, mísseis e a bola. O objeto
começará a ser desenhado no tempo da linha horizontal que ocorreu o reset.

Os bits não são usados.

Bit 7 6 5 4 3 2 1 0
RESxx

RESMP0 e RESMP1
Esses endereços são usados para resetar a localização horizontal do míssil para o centro de
seu jogador correspondente. Enquanto seu bit 1 for 1, o míssil permanecerá travado no
centro de seu jogador e não será exibido na tela (desabilitado). Quando o bit 1 for zero, o
míssil é habilitado e pode mover-se independentemente do jogador.

Bit 7 6 5 4 3 2 1 0
RESMxx

Movimento Horizontal
O movimento horizontal permite ao programador mover qualquer um dos 5 objetos
relativamente às suas posições atuais. Cada objeto tem um registrador de movimento
horizontal de 4 bits (HMP0, HMP1, HMM0, HMM1, HMBL) que pode ser carregado com um
valor de +7 a -8 (valores negativos são expressos na forma de complemento de 2). Esse
movimento não é executado até que o registrador HMOVE é acessado. Quando ele é
acessado todos os registradores de movimento movem seus respectivos objetos. Os objetos
podem ser movidos repetidamente simplesmente executando HMOVE. Qualquer objeto que
não se mova deve ter seu registrador zerado. Com o comando de posicionamento horizontal
limitado a posicionar objetos a intervalos de 15 color clocks, os registradores de movimento
preenchem as lacunas movendo os objetos +7 a -8 color clocks. Objetos não podem ser
colocados em qualquer posição de color clock na tela. Todos os registradores de movimento
podem ser resetados simultaneamente setando o registrador de clear de movimento
horizontal HMCLR.

Há limitações de temporização para o comando HMOVE. O comando HMOVE deve


imediatamente seguir um WSYNC para assegurar que a operação HMOVE ocorre durante o
branco horizontal. Isso é para dar tempo suficiente para os registradores de movimento
fazerem o que têm que fazer antes que o feixe de elétrons comece a desenhar o próximo
scanline. Também, por considerações internas de hardware (misteriosas), os registradores
de movimento não devem ser modificados por pelo menos 24 ciclos de máquina depois de
um comando HMOVE.

E:\Atari\SDK\doc\Tutorial.doc Página 83 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

HMP0, HMP1, HMM0, HMM1 e HMBL


Esses endereços escrevem dados (valores de movimentação horizontal) nos registradores
de movimento horizontal. Esses registradores movimentarão horizontalmente somente
quando comandados para isso pelo comando de movimento horizontal HMOVE.

Bit 7 6 5 4 3 2 1 0
HMxx

Os valores de movimento são codificados como segue:

Bit 7 6 5 4 Valor Descrição


0 1 1 1 +7
0 1 1 0 +6
0 1 0 1 +5
0 1 0 0 +4 Número de clocks a mover
0 0 1 1 +3 para a esquerda

0 0 1 0 +2
0 0 0 1 +1
0 0 0 0 0 Sem movimento
1 1 1 1 -1
1 1 1 0 -2
1 1 0 1 -3
1 1 0 0 -4 Número de clocks a mover
1 0 1 1 -5 para a direita

1 0 1 0 -6
1 0 0 1 -7
1 0 0 0 -8

Nota: Os registradores de movimento não devem ser modificados durante os 24 ciclos de


máquina imediatamente seguintes a um comando HMOVE. Podem ocorrer erros na
movimentação.

HMOVE
Esse endereço faz com que os registradores de movimento horizontal ajam durante o tempo
de branco horizontal no qual ele ocorreu. Ele deve ocorrer no começo do branco horizontal
de forma a dar tempo para a geração de pulsos de clock extras dentro dos contadores de
posição horizontal. Se o movimento é desejado esse comando deve seguir imediatamente
um WSYNC no programa.

Os bits não são usados.

Bit 7 6 5 4 3 2 1 0
HMOVE

E:\Atari\SDK\doc\Tutorial.doc Página 84 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

HMCLR
Esse endereço limpa todos os registradores de movimento horizontal (sem movimento).

Os bits não são usados.

Bit 7 6 5 4 3 2 1 0
HMCLR

Prioridades dos objetos


A cada objeto é atribuída prioridade, tanto que quando quaisquer 2 objetos se sobrepõem o
que tem prioridade mais alta vai parecer que se move na frente do outro. Para simplificar a
lógica do hardware, os mísseis tem a mesma prioridade de seus correspondentes jogadores,
e a bola tem a mesma prioridade do playfield. O plano de fundo, é claro, tem a menor
prioridade. A seguinte tabela ilustra as prioridades default.

Prioridade Objetos
1 P0, M0
2 P1, M1
3 BL, PF
4 BK

Essas prioridades significam que jogadores e mísseis se moverão na frente do playfield.


Para fazer com que jogadores e mísseis se movam atrás do playfield, deve-se setar o bit 2
do registrador CTRLPF. A seguinte tabela ilustra como as prioridades são afetadas:

Prioridade Objetos
1 PF, BL
2 P0, M0
3 P1, M1
4 BK

Mais um controle de prioridade está disponível para ser usado para mostrar o placar.
Quando o bit 1 do registrador CTRLPF é setado, a metade esquerda do playfield assume a
cor do jogador 0, e a metade direita assume a cor do jogador 1. O placar do jogo pode agora
ser mostrado usando o registrador PF, e o placar será da mesma cor do seu jogador
correspondente.

Colisões
O TIA detecta colisões entre quaisquer dos 6 objetos que ele gera (o playfield e os 5 objetos
móveis). Há 15 possíveis colisões entre 2 objetos que são armazenadas em 15 latches de 1
bit. Cada registrador de colisão contém 2 desses latches os quais são lidos pelo 6502 nos
bits 6 e 7. Um 1 indica que a colisão que ele registra ocorreu. Os registradores de colisão
podem ser lidos a qualquer tempo, mas normalmente isso é feito durante o branco vertical,
depois que todas as colisões possíveis tenham ocorrido. Os registradores de colisão podem
ser resetados simultaneamente acessando o registrador CXCLR.

Os registradores de colisão serão vistos oportunamente.

CXCLR
Esse endereço faz com que todos os latches sejam zero (nenhuma colisão).

E:\Atari\SDK\doc\Tutorial.doc Página 85 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Os bits não são usados.

Bit 7 6 5 4 3 2 1 0
HMCLR

Som
Há 2 circuitos de audio para gerar som. Eles são idênticos mas completamente
independentes e podem ser operados simultaneamente para produzir efeitos de som pelo
alto falante da TV. Cada circuito de audio tem 3 registradores que controlam o gerador de
tom (que tipo de som), uma seleção de freqüência (pitch alto ou baixo do som) e um controle
de volume.

Tom
O gerador de tom é controlado acessando os registradores de controle de audio AUDC0 e
AUDC1. Esses registradores são de 4 bits. Os valores geram diferentes tipos de som.
Alguns são tons puros (como uma flauta), outros têm vários ruídos como um motor de
foguete ou explosão.

AUDC0 e AUDC1
Esses endereços escrevem nos registradores de audio que controlam o ruído e divisão da
saída de audio.

Bit 7 6 5 4 3 2 1 0
AUDCx

Bit 3 2 1 0 Tipo de ruído ou divisão


0 0 0 0 seta para 1
0 0 0 1 4 bit poly
0 0 1 0 div 15 -> 4 bit poly
0 0 1 1 5 bit poly -> 4 bit poly
0 1 0 0 div 2: tom puro
0 1 0 1 div 2: tom puro
0 1 1 0 div 31: tom puro
0 1 1 1 5 bit poly -> div 2
1 0 0 0 9 bit poly (ruído branco)
1 0 0 1 5 bit poly
1 0 1 0 div 31: tom puro
1 0 1 1 seta últimos 4 bits para 1
1 1 0 0 div 6: tom puro
1 1 0 1 div 6: tom puro
1 1 1 0 div 93: tom puro
1 1 1 1 5 bit poly -> div 6

E:\Atari\SDK\doc\Tutorial.doc Página 86 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Freqüência
A seleção de freqüência é controlada acessando os registradores de áudio freqüência (de 5
bits) AUDF0 e AUDF1. Os valores escritos são usados para dividir uma freqüência de
referência de 30KHz, criando pitch mais alto ou mais baixo de qualquer tipo de som gerado
no gerador de tom. Pela combinação de tons puros gerados pelo gerador e tom com a
seleção de freqüência, uma ampla gama de tons podem ser conseguidos.

AUDF0 e AUDF1
Esses endereços escrevem nos registradores divisores de freqüência de audio.

Bit 7 6 5 4 3 2 1 0
AUDFx

Bit 4 3 2 1 0 30KHz dividos por


0 0 0 0 0 sem divisão
0 0 0 0 1 divididos por 2
0 0 0 1 0 divididos por 3
... ... ... ... ... ...
1 1 1 1 0 divididos por 31
1 1 1 1 1 divididos por 32

Volume
O volume é controlado acessando os registradores de volume (de 4 bits) AUDV0 e AUDV1.
Zerando esses registradores, desliga-se o som completamente e escrevendo qualquer valor
até 15, aumenta o som de acordo com o valor.

AUDV0 e AUDV1
Esses endereços escrevem nos registradores de volume de áudio.

Bit 7 6 5 4 3 2 1 0
AUDVx

Bit 3 2 1 0 Saída de áudio


0 0 0 0 sem saída
0 0 0 1 volume mais baixo
0 0 1 0
... ... ... ...
1 1 1 0
1 1 1 1 volume mais alto

Portas de entrada
Há 6 portas de entrada. Seus estados lógicos podem ser obtidos lendo o bit 7 dos endereços
de porta de entrada INPT0 a INPT5. Elas são divididas em 2 tipos: dumped e latched.

E:\Atari\SDK\doc\Tutorial.doc Página 87 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Portas de entrada dumped (INPT0 a INPT3)


Essas 4 portas são usadas para ler até 3 paddles. Cada controle contém um potenciômetro
ajustável, acionado pelo knob no controle. A saída do potenciômetro é usada para carregar
um capacitor no console, e quando o capacitor está carregado, a porta de entrada passa
para 1. O 6502 descarrega o capacitor setando em 1 o bit 7 do VBLANK, então mede o
tempo que leva para detectar a lógica 1 naquela porta. Essa informação pode ser usada
para posicionar objetos na tela baseado na posição do knob no paddle.

Portas de entrada latched (INPT4 e INPT5)


Essas 2 portas têm latches que são ambos habilitados setando, ou desabilitados zerando, o
bit 6 do VBLANK. Quando desabilitado, 0 6502 lê o nível lógico da porta diretamente.
Quando habilitado, o latch é setado para a lógica 1 e permanece assim até que a porta vai
para 0. Quando a porta vai para 0, o latch vai para 0 e permanece assim independetemente
do que acontece na porta. Os botões de disparo do joystick conectam-se a essas portas.

O PIA (6532)
O microchip PIA (Peripheral Interface Adapter) tem 3 funções: um timer programável, 128
bytes de RAM e 2 portas paralelas de 8 bits, ou seja, RAM, portas de entrada/saída (IO
Ports) e Timer: o RIOT

O Timer
O PIA usa o mesmo clock do 6502, de forma que 1 ciclo do PIA ocorre para cada ciclo de
máquina. O PIA pode ser setado para um dos 4 diferentes intervalos, onde cada intervalo é
múltiplo do clock (e portanto ciclos de máquina). Um valor de 1 a 255 é carregado no PIA o
qual será decrementado em 1 a cada intervalo. O timer pode agora ser lido pelo 6502 para
determinar o tempo passado e assim temporizar várias operações de software e mantê-las
sincronizadas com o hardware (TIA).

Setando o timer
O timer é setado escrevendo um valor ou contagem (de 1 a 255) no endereço do intervalo
desejado, de acordo com a seguinte tabela:

Endereço Intervalo Mnemônico


294h 1 TIM1T
295h 8 TIM8T
296h 64 TIM64T
297h 1024 T1024T

Por exemplo, se o valor 100 foi escrito no TIM64T, o timer seria decrementado até 0 em
6400 clocks (64 clocks por intervalo x 100 intervalos), o que poderia ser também 6400 ciclos
de máquina do 6502.

Lendo o timer
O timer pode ser lido tantas vezes quantas forem necessárias depois que ele foi setado
(claro), mas o programador geralmente só está interessado se o timer chegou a 0 ou não. O
timer é lido, simplesmente acessando o INTIM.

Quando o timer chega a zero


O PIA decrementa o valor carregado uma vez a cada intervalo até que chegue a 0. Ele
mantém o 0 por um intervalo, então o contador muda para FFh e decrementa uma vez a
cada ciclo de clock, em vez de uma por intervalo. O propósito desse recurso é permitir ao
E:\Atari\SDK\doc\Tutorial.doc Página 88 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

programador determinar há quanto tempo atrás o timer zerou no evento que o timer foi lido
depois que passou do 0.

RAM
O PIA tem 128 bytes de RAM localizados no mapa de memória do Stella (do endereço 80h
ao FFh). A pilha do 6502 é normalmente localizada de FFh e anteriores, e as variáveis são
normalmente localizadas de 80h em diante.

As portas de entrada/saída
As 2 portas (Porta A e Porta B) são de 8 bits e podem ser setadas para ou entrada ou saída.
A Porta A é usada para interfacear controladores de mão, mas a Porta B é dedicada a ler o
status das chaves do console do Stella.

Porta B - Chaves do console (somente leitura)


A Porta B é hardwired para ser uma porta de entrada somente, que é lida acessando o
endereço SWCHB para determinar o status de todas as chaves do console de acordo com a
seguinte tabela.

Bit Chave Significado


7 dificuldade para o P1 0 = amador (B), 1 = pro (A)
6 dificuldade para o P0 0 = amador (B), 1 = pro (A)
5/4 não usados
3 cor 0 = preto e branco, 1 = colorido
2 não usado
1 select 0 = chave pressionada
0 reset 0 = chave pressionada

Porta A - Controladores de Mão


A Porta A está sob controle total do software para ser configurada como porta de entrada ou
porta de saída. Pode ser então usada para ler ou controlar vários controladores de mão com
os bits de dados definidos diferentemente dependendo do tipo de controle usado.

Setando para entrada ou saída


A Porta A tem um registrador DDR (Data Direction Register) de 8 bits que é acessado no
endereço SWACNT para setar cada pino individual da porta A para entrada ou saída. Os
pinos da Porta A são chamados de PA0 a PA7, e zerando os bits configura-os como
entrada, e setando configura-os como saída. Por exemplo, zerando todo o SWACNT, faz
com que os bits de PA0 a PA7 sejam entrada. Se F0h (11110000b) for escrito no SWACNT
então o PA7, PA6, PA5 e PA4 são saídas, e PA3, PA2 PA1 e PA0 são entradas.

Uma vez que o DDR tem seus pinos configurados para entrada e/ou saída, eles podem ser
lidos ou escritos acessando o endereço SWCHA.

E:\Atari\SDK\doc\Tutorial.doc Página 89 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Joysticks
2 joysticks podem ser lidos configurando a porta toda como entrada e lendo os dados em
SWCHA de acordo com a seguinte tabela:

Bit Direção Jogador


7 direita P0
6 esquerda P0
5 abaixo P0
4 acima P0
3 direita P1
2 esquerda P1
1 abaixo P1
0 acima P1

Um 0 no bit significa que o joystick foi movido para fechar aquela chave. Se o nibble do
jogador estiver todo com 1, indica que o joystick não está se movendo.

Paddle (pot)
Somente os triggers do paddle são lidos do PIA. Os próprios paddles são lidos de INPT0 a
INPT3 do TIA. Os triggers podem ser lidos no SWCHA de acordo com a tabela seguinte:

Bit Paddle n°
7 P0
6 P1
5/4 não usados
3 P2
2 P3
1/0 não usados

Teclado
O teclado tem 12 botões dispostos em 4 linhas e 3 colunas. Um sinal é enviado para uma
linha, então as colunas são verificadas para saber se um botão está pressionado. Então a
próxima linha é sinalizada e todas as colunas sensibilizadas, etc. até que todo o teclado
tenha sido escaneado. O PIA envia os sinais para as linhas, e as colunas são sensibilizadas
pela leitura do INPT0, INPT1 e INPT4 do TIA. Com a Porta A configurada como porta de
saída, os bits de dados enviarão um sinal para as linhas do teclado de acordo com a
seguinte tabela:

Bit Direção Jogador


7 inferior P0
6 terceira P0
5 segunda P0
4 superior P0
3 inferior P1
2 terceira P1
1 segunda P1
0 superior P1

Nota: Um atraso de 400 microsegundos é necessário entre a escrita dessa porta e leitura
das portas de entrada do TIA.

E:\Atari\SDK\doc\Tutorial.doc Página 90 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Ciclos de máquina
Em vários trechos do tutorial falamos a respeito de ciclos de máquina e que as instruções do
6502 gastam determinado número de ciclos.

A contagem de ciclos é um aspecto importante na programação do 2600. Com ela, é


possível o posicionamento de sprites, exibir placar de 6 dígitos, playfields não refletidos e
muitas outras coisas. O uso de contagem de ciclos não se limita a apresentação correta na
tela. É também útil para otimizar o código para que se ajuste dentro do branco vertical,
scanline ou branco horizontal.

Conceito de contagem
Programar o 2600 requer modificar a percepção de espaço e tempo, pois o 2600 tem um
tipo de física onde espaço é tempo. Um frame é 1/60 segundo. Um scanline é 1/20000
segundo. É importante saber quanto código pode ser executado na quantidade de tempo
que leva para desenhar a tela. A unidade de tempo é ciclo.

Ciclos de CPU em relação aos pixels da tela


O relógio da CPU funciona um pouco mais lento comparado ao do TIA. O TIA desenha 3
pixels no tempo que leva para executar 1 ciclo de CPU. Um comando WSYNC pára o 6502
até o branco horizontal, que dura em torno de 20 ciclos de CPU, depois do que, o feixe de
elétrons liga-se e começa a desenhar a imagem novamente. Portanto a posição X do feixe
de elétrons é determinada assim:

X = (ciclos - 20) * 3

onde ciclos é o número de ciclos que passaram desde o branco horizontal. Mas os
registradores são lidos somente a cada 5 ciclos, então a equação deve ser ajustada para
levar isso em conta. Por ora, vamos assumir que arredondaremos para o próximo múltiplo
de 15. Os exemplos envolverão o RESP0, porque já sabemos como ele funciona.

Se X é um número negativo, um RESP0 colocará o jogador 0 no lado esquerdo da tela.


Exemplo:

.inicio
sta WSYNC ; começa a contar aqui, comece com zero 0
nop ; 0+2 = [2] gasta 2 ciclos apesar de não fazer nada
lda #0 ; 2+3 = [5] gasta 3 ciclos
sta $FFFF ; 5+4 = [9] gasta 4 ciclos
rol $FFFE,X ; 9+7 = [16] gasta 7 ciclos
rol ; 16+2 = [18] gasta 2 ciclos
sta RESP0 ; 18+3 = *21* gasta 3 ciclos
dey ; 21+2 = [23] gasta 2 ciclos
bne .inicio ; 23+3 = [26] gasta 2 ciclos se falso e 3 se verdadeiro

Os números antes do sinal de '+' é o número de ciclos que se passaram desde o WSYNC.
Os números dentro dos colchetes é o número de ciclos que passaram ao final de cada
instrução. É bom ter um histórico desse número porque escritas nos registradores do TIA
ocorrem nesse ciclo.

Note que o número 21 está com asteriscos, significando uma escrita num registrador do TIA.
(21 - 20) x 3 = 3 e uma vez que é RESP0, nós arredondamos para 15 e é para onde vai o
jogador 0.

E:\Atari\SDK\doc\Tutorial.doc Página 91 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

A contagem de ciclos é especialmente importante para o RESP0, mas quase todas as


escritas a registradores do TIA são afetadas de algum modo pela contagem de ciclos. Uma
modificação tardia de um jogador ou um míssil no scanline acarretará no mudança de linha
(para cima) do jogador à medida em que se move da esquerda para a direita. Escritas no
playfield devem ser temporizadas de forma a ocorrerem no centro da tela, se desejamos
produzir playfields assimétricos.

Como se lembrar de quanto tempo gasta o que


É meio difícil decorar quanto tempo de ciclos cada instrução gasta. Muitas instruções,
contudo, têm características similares, e então algumas regras podem ser seguidas a fim de
estimar o tempo de cada instrução.

Instruções de desvio
Instrução de desvio como BNE e BCC são mais fáceis do que parecem. Todas as instruções
de desvio gastam 2 ciclos. Elas gastam 3 ciclos se, e somente se, o desvio ocorrer. E ainda,
gastam mais 1 ciclo se o desvio ocorrer para além dos limites de página.

Quando se escreve um código sensível ao tempo, é recomendado que instruções de desvio


somente sejam usadas no (ou próximo do) fim de um loop que comece com STA WSYNC,
ou em pequenos loops que são feitos para gastar um certo número de ciclos.

Instruções matemáticas rápidas


O 6502 tem uma família de opcodes matemáticos rápidos que têm características similares,
e conseqüentemente, o mesmo número de ciclos. Esses opcodes matemáticos rápidos
fazem mais do que alterar registradores ou flags usando bits da memória. Essa família
consiste do ADC, AND, BIT, CMP, CPX, CPY, EOR, LDA, LDX, LDY, ORA e SBC. Nem
todas essas instruções têm todos os modos de endereçamento abaixo, mas essas regras
aplicam-se a quaisquer modos disponíveis. Vejamos o ADC.

ADC #$01 ; +2 Immediate


ADC $99 ; +3 Zero Page
ADC $99,X ; +4 Zero Page,X (ou ,Y)
ADC $1234 ; +4 Absolute
ADC $1234,X ; +4* Absolute,X (ou ,Y)
ADC ($AA,X) ; +6 (Indirect,X)
ADC ($CC),Y ; +5* (Indirect),Y

O asterisco significa que se o indexador da instrução ultrapassar um limite de página,


adiciona-se 1 ciclo. Em alguns casos, apenas 1 ciclo pode não fazer diferença. Note também
que o endereçamento Zero Page,Y está disponível somente para LDX e STX.

Instruções de armazenamento
As instruções STA, STX e STY têm a mesma temporização que as instruções matemáticas,
mas no caso dos endereçamentos Absolute,XY e (Indirect),Y sempre teremos um ciclo extra.

Instruções weenie
Essas instruções não alteram a memória, somente registradores e flags. Elas são CLC,
CLD, CLI, CLV, DEX, DEY, INX, INY, NOP, SEC, SED, SEI, TAX, TAY, TSX, TXA, TSX e
TYA. Elas gastam 2 ciclos.

Instruções matemáticas lentas


Há certas instruções que gastam mais ciclos que uma instrução matemática simples.
Algumas dessas instruções podem funcionar com o acumulador, mas quando um endereço
E:\Atari\SDK\doc\Tutorial.doc Página 92 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

é dado, elas modificam a memória diretamente. As instruções matemáticas lentas são ASL,
DEC, INC, LSR, ROL e ROR.

ROR ; +2 Accumulator
ROR $99 ; +5 Zero Page
ROR $99,X ; +6 Zero Page,X
ROR $1234 ; +6 Absolute
ROR $1234,X ; +7 Absolute,X

Note que quando essas instruções funcionam com o acumulador, elas diminuem para 2
ciclos e se tornam instruções weenie .

Instruções de pilha
As 2 instruções que colocam valores na pilha (PHA e PHP) gastam 3 ciclos cada. As 2
instruções que recuperam valores da pilha (PLA e PLP) gastam 4 ciclos cada.

Outras instruções
JSR gasta 6 ciclos. JMP gasta 3 ciclos no endereçamento Absolute e 5 ciclos no
endereçamento Absolute Indirect, mas o modo Abolute Indirect é para máquinas que têm um
kernel. RTI e RTS gastam 6 ciclos cada. Mas com somente algumas dezenas de instruções
disponíveis por scanline, não temos tempo para ficar executando subrotinas.

A contagem de ciclos na prática


Vamos contar os ciclos no primeiro exemplo mostrando como fica se o desvio não ocorrer.

.inicio
sta WSYNC ; a contagem começa aqui
nop ; [0] +2
bit $CC ; [2] +3
bmi .desviaPraCa ; [5] +2 se o desvio não ocorrer
nop ; [7] +2 os NOPs só servem para gastar tempo
nop ; [9] +2
nop ; [11] +2
nop ; [13] +2
nop ; [15] +2
nop ; [17] +2
nop ; [19] +2

.desviaPraCa
lda $F0 ; [21] +3
sta GRP0 ; *24* +3
lda $F1 ; [27] +3
sta ENAM0 ; *30* +3
sta RESP0 ; *33* +3
sta WSYNC ; a contagem deve recomeçar daqui

Vamos contar os ciclos no segundo exemplo mostrando como fica se o desvio ocorrer.

.inicio
sta WSYNC ; a contagem começa aqui
nop ; [0] +2
bit $CC ; [2] +3
bmi .desviaPraCa ; [5] +3 se o desvio ocorrer e vai para .desviaPraCa
nop ;
nop ;
nop ;
nop ;
nop ;

E:\Atari\SDK\doc\Tutorial.doc Página 93 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

nop ;
nop ;

.desviaPraCa
lda $F0 ; [8] +3
sta GRP0 ; *11* +3
lda $F1 ; [14] +3
sta ENAM0 ; *17* +3
sta RESP0 ; *20* +3
sta WSYNC ; a contagem deve recomeçar daqui

Esse código (que não serve para nada) verifica o bit 8 do local da memória CCh. Se é 1, vai
imediatamente para o label .desviaPraCa (o desvio acontece e gastamos 3 ciclos) e seta a
posição do jogador 0 no ciclo 20 (exemplo 2). Se o teste resultar em 0, então o desvio não
acontece e economizamos 1 ciclo (o desvio gasta só 2 ciclos) e gastamos 14 ciclos
passando pelos NOPs. Agora ele gasta 33 ciclos para posicionar (reset) o jogador 0.

Como lidar com o (indirect),Y


Relembrando que se um (indirect),Y ultrapassar o limite de página, a CPU gasta 1 ciclo
extra. Isso significa que dependendo do valor de Y, a instrução pode gastar 4 ou 5 ciclos.

Em rotinas de exibição na tela, como por exemplo placar com 6 dígitos, as 6 instruções
(Indirect),Y aparecem em 1 scanline. Isso adiciona até 6 ciclos extras que podem ou não ser
gastos. Isso pode fazer com que a temporização seja perdida a menos que os dados
estejam arranjados de forma apropriada.

Tenha certeza de que quando colocar dados no programa, disponha-os de forma que eles
nuca cruzem o limite de página ou sempre cruzem o limite de página.

Tão logo você seja capaz de prever quando um ciclo extra será necessário, você terá um
código estável. Assegure que todos os bytes de uma tabela de gráfico estejam na mesma
página de memória.

Como gastar ciclos de máquina


Às vezes é necessário gastar ciclos de máquina. Devemos “dar um tempo” na execução do
programa com instruções que não fazem nada ou não interfiram em nada, para que o 6502 e
o TIA cheguem num acordo e façam o que têm que fazer no tempo certo. Aqui vão alguns
exemplos de como desperdiçar tempo.

E:\Atari\SDK\doc\Tutorial.doc Página 94 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Ciclos Bytes Instruções Observações


1 1 .w Muda de zero page para absolute. Acrescenta 1 byte de código
Muda de zero page ou absolute para indexed. X deve ser zero.
1 1 ,x
Pode-se usar Y também.
2 1 NOP Não faz nada.
3 2 STA $2D Locais de 2Dh a 3Fh não fazem nada e não são decodificados.
3 2 LDA $2D Em alguns bankswitching pode dar problema.
3 2 DOP Illegal opcode
NOP
4 2 Não faz nada.
NOP
Locais de 2Dh a 3Fh não fazem nada e não são decodificados.
5 2 DEC $2D Em alguns bankswitching pode dar problema.
5 2 STA $1800,x Supondo que podemos escrever na ROM sem problemas
6 2 LDA ($80,x) Supondo que ler de 00h a 7Fh não tenha efeito
NOP
6 3 NOP Não faz nada.
NOP
PHA
7 2 É necessário 1 byte livre na pilha
PLA
LDA ($80,x)
8 3 Supondo que ler de 00h a 7Fh não tenha efeito
NOP
PHA
9 3 PLA É necessário 1 byte livre na pilha
NOP
DEC $2D
Locais de 2Dh a 3Fh não fazem nada e não são decodificados.
9 4 NOP
Em alguns bankswitching pode dar problema.
NOP
DEC $2D Locais de 2Dh a 3Fh não fazem nada e não são decodificados.
10 4
DEC $2D Em alguns bankswitching pode dar problema.
ROL $80
10 4 Deixa 80h inalterado
ROR $80
STA $8000,x Supondo que podemos escrever na ROM sem problemas e
11 4
LDA ($80,x) supondo que ler de 00h a 7Fh não tenha efeito
JSR .fake
...
12 3 São necessários 2 bytes livres na pilha
.fake
RTS
LDA ($80,x)
12 4 Supondo que ler de 00h a 7Fh não tenha efeito
LDA ($80,x)

Nota: Pode-se usar PHA/PHP (1 byte e 3 ciclos) ou PLA/PLP (1 byte e 4 ciclos) de forma
independente, mas deve-se tomar cuidado para não bagunçar a pilha.

E:\Atari\SDK\doc\Tutorial.doc Página 95 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Começando a programar
O ambiente
Você pode escrever seus programas no Edit do DOS, no Notepad do Windows ou em
qualquer outro editor que grave arquivos no modo texto, não formatado. Usamos o Crimson
Editor (figura 39).

Figura 39

Ele é free e faz o trabalho. Se optar por usá-lo, é interessante fazer algumas configurações:

No menu Tools -> Preferences, vá no ítem User Tools (figura 40) e configure atalhos para
compilar, executar e debugar seu programa. Essas opções serão criadas no menu e terão
teclas de atalho. Observe a figura 40.

Em Menu Text colocamos o que vai aparecer no menu: &Compile. O “&” faz o C do Compile
ficar sublinhado no menu.

Em Command colocamos o compilador assembly: DASM.exe e seu caminho

Em Argument colocamos o argumento passado ao compilador. ($FileDir), $(FileName) e


$(FileTitle) são variáveis que o Crimson Editor usa como parâmetros. Para compilar, o
Argument fica assim: $(FileDir)\$(FileName) -f3 -o$(FileDir)\$(FileTitle).bin

As figuras 41 e 42 mostram os outros atalhos criados. Para quem ainda não entendeu: isso
serve para, quando digitarmos um programa, basta teclarmos Ctrl+1 para compilar, Ctrl+2
para executar (depois de compilado) ou Ctrl+3 para debugar. O Ctrl+3 executa também o
Stella com a diferença da opção –debug (figura 42).

E:\Atari\SDK\doc\Tutorial.doc Página 96 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 40

Figura 41

E:\Atari\SDK\doc\Tutorial.doc Página 97 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 42

O próximo passo é fazer com que ele reconheça os comandos do 6502. Como ele pretende
ser um IDE, então temos que ensiná-lo. Ele não vem com os comandos do 6502, então, vou
quebrar o galho e mostrar como fazer.

No diretório de instalação do Crimson Editor você encontrará os subdiretórios conforme a


figura 43.

Figura 43

E:\Atari\SDK\doc\Tutorial.doc Página 98 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Vá no diretório LINK e crie um arquivo chamado EXTENSION.ASM (figura 44). Crie usando
qualquer editor de texto (que grave no formato texto sem formatação) e digite o seguinte:

LANGSPEC:2600-ASM.SPC
KEYWORDS:2600-ASM.KEY

Figura 44

Se ainda tem dúvida, veja a figura 45.

Figura 45

Salve e arquivo e vá no diretório SPEC (figura 46). Crie um arquivo chamado 2600-
ASM.SPC. Digite o seguinte:

# 6502 ASM LANGUAGE DEFINITION FILE FOR CRIMSON EDITOR


# ATARI 2600 STYLE COMPILER - 10/07/2006

$CASESENSITIVE=NO
$HEXADECIMALMARK=$
$QUOTATIONMARK1="
$QUOTATIONMARK2='
$LINECOMMENT=;
PAIRS1=()
$PAIRS2=[]
$PAIRS3={}

E:\Atari\SDK\doc\Tutorial.doc Página 99 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 46

Se ainda tem dúvidas, veja a figura 47.

Figura 47

Agora, no mesmo diretório (SPEC), crie um arquivo chamado 2600-ASM.KEY (figura 48).

Figura 48

E:\Atari\SDK\doc\Tutorial.doc Página 100 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Nele você vai digitar (ou copiar e colar) o seguinte:

[-COMMENT-:GLOBAL]
# 6502 ASM LANGUAGE DEFINITION FILE FOR CRIMSON EDITOR
# ATARI 2600 STYLE COMPILER - 10/07/2006

[KEYWORDS0:GLOBAL]
#instruction set
lda ldy ldx sta sty stx pha php pla plp
tax tay txa tya tsx txs bne beq bmi bpl
bcc bcs bvc bvs bit cpx cpy cmp jmp jsr
rts brk rti adc sbc and ora eor lsr lsl
asl ror rol dec dex dey inc iny inx clc
cli clv sec sed sei nop anc ane arr asr
dcp isb las lax lxa rla rra sax sbx sha
shs shx shy slo sre cld

[KEYWORDS1:GLOBAL]
#assembler directive
org rorg include processor

[KEYWORDS2:GLOBAL]
.ascis .byte .db .ascii .word .dw .dbyte .dd .str .string
.dcb .rs .ds .opt .org .start .end .include .macro .endm
.exitm .if .else .endif .error .repeat .rept .endr repeat repend
db dw ds dc dv mac endm if endif equ word byte
seg seg.u dc.bwl ds.bwl dv.bwl hex err rorg echo rend
align subroutine eqm string set mexit ifconst ifnconst if else
endif eif

[KEYWORDS3:GLOBAL]
*=

[KEYWORDS4:GLOBAL]
#operators
* / % + - >> << > >= <
<= == = != & ^| && || ?
[]´$

[KEYWORDS5:GLOBAL]
#registers
A Y X VSYNC VBLANK WSYNC RSYNC NUSIZ0 NUSIZ1 COLUP0
COLUP1 COLUPF COLUBK CTRLPF RESP0 RESP1 PF0 PF1 PF2 RESP0
RESP1 RESM0 RESM1 RESBL AUDC0 AUDC1 AUDF0 AUDF1 AUDV0 AUDV1
GRP0 GRP1 ENAM0 ENAM1 ENABL HMP0 HMP1 HMM0 HMM1 HMBL
VDELP0 VDELP1 DELBL RESMP0 RESMP1 HMOVE HMCLR CXCLR CXM0P CXM1P
CXP0FB CXP1FB CXM0FB CXM1FB CXBLPF CXPPMM INPT0 INPT1 INPT2 INPT3
INPT4 INPT5 SWCHA SWCHB SWBCNT INTIM TIMINT TIM1T TIM8T TIM64T
T1024T TIM1I TIM8I TIM64I T1024I

Se tem dúvidas, veja a figura 49.

E:\Atari\SDK\doc\Tutorial.doc Página 101 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 49

Agora, para o Crimson Editor ficar bonitinho, vá no menu Tools -> Preferences e vá no ítem
Colors. Altere o esquema de cores a seu gosto e salve. O programa mudará as cores das
palavras (que reconhecerá nos arquivos criados) segundo o esquema. Não se esqueça de
salvar o esquema. A figura 50 mostra meu esquema de cores para General Colors. As
figuras 51 e 52 mostram para Keyword Colors e Miscellaneuos respectivamente.

Outras configurações interessantes (e opcionais) são mostradas nas figuras 53 e 54.

E:\Atari\SDK\doc\Tutorial.doc Página 102 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 50

Figura 51

E:\Atari\SDK\doc\Tutorial.doc Página 103 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 52

Figura 53

E:\Atari\SDK\doc\Tutorial.doc Página 104 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 54

Pronto. Agora basta criarmos um arquivo novo e começarmos a programar. Nosso ambiente
está configurado.

Lembrando que: na configuração do compilador e do emulador, presume-se que você tenha


no seu computador o compilador DASM.EXE e um emulador. No meu caso eu uso o
emulador Stella 2.3.5 (figura 55).

Figura 55

E:\Atari\SDK\doc\Tutorial.doc Página 105 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Nosso primeiro programa


O primeiro programa que escreveremos não fará absolutamente nada de interessante no
Stella (não é um jogo). Servirá apenas para verificarmos se o Crimson Editor está
configurado corretamente e vamos aproveitar esse programa para rodar o debugger do
Stella (só para conhecer). Como o programa não exibirá nada na tela, não tem sentido
rodarmos ele sem ser no modo debug. Abordaremos o uso de debugger mais
profundamente em páginas futuras.

O cabeçalho
Os programas em assembly para o 2600 comumente têm o seguinte cabeçalho:

processor 6502
include vcs.h
include macro.h

org $F000

A primeira linha diz ao compilador o seguinte: “vou utilizar o conjunto de instruções do 6502”.
A segunda linha, inclui no seu programa (só o compilador vê isso) o conteúdo do arquivo
VCS.H. É interessante ter esse arquivo, caso contrário você deverá declarar os nomes dos
registradores e endereços dentro de seu programa. O VCS.H já traz todos eles. Se tiver
esse arquivo, abra-o no Notepad (por exemplo) e veja seu conteúdo. Só não altere nada
nesse arquivo. A terceira linha diz para incluir macros em seu programa. Mais adiante
veremos o uso de macros. Por enquanto, se quiser omitir a terceira linha, pode. Vamos
convencionar aqui o uso das 3 linhas, só por questão de costume.

Nota: Se os arquivos VCS.H e MACRO.H estiverem em outro diretório, esse deve estar
explícito no include. Nos programas aqui apresentados, meus arquivos VCS.H e MACRO.H
estão num subdiretório, chamado INCLUDES, acima do diretório dos meus programas e os
cabeçalhos serão:

processor 6502
include ..\includes\vcs.h
include ..\includes\macro.h

org $F000

A linha org $F000 diz ao Stella que aloque nosso programa no offset F000h. E daí? Se
temos 64K de endereçamento, então 64K = 65536d = 10000h – F000h = 4K = 4096d =
1000h, que é o tamanho da ROM: 4K. Você escreve jogos de até 4K endereçando
inicialmente em F000h. Se o jogo for maior, utilizamos 2 ROMs de 4K (temos 8K para o
jogo). Para mudar de uma ROM para a outra, utilizamos rotinas de bankswitching, que serão
vistas mais adiante (muito mais adiante). Não veremos aqui como escrever ROMs menores
que 4K. Vamos ver mais as de 4K e depois, no tópico de bankswitching, ROM de até 32K.

O rodapé
Os programas para o 2600 devem ter o seguinte código no final:

org $FFFA
.word label_do_início_do_jogo
.word label_do_início_do_jogo
.word label_do_início_do_jogo

Nota: estamos simplificando as coisas aqui. Na programação do 6502, a primeira linha .word

E:\Atari\SDK\doc\Tutorial.doc Página 106 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

é para o NMI, a segunda para o RESET e a terceira para o IRQ. Se não temos rotinas
separadas para cada caso, direcionamos o vetor todo para um único local: o início do jogo.

Lembre-se que .word é uma diretiva que gasta 2 bytes (1 word = 2 bytes). Com as 3 linhas
.word declaradas, criamos um vetor cujos valores de cada posição são o endereço
referenciado pelo label do início do jogo (lembra que o endereço tem 16 bits, ou seja, 2 bytes
= 1 word?). Então se temos 3 linhas com a diretiva .word e cada uma gasta 2 bytes temos 6
bytes no total (duh). Isso tudo é para dizer o porquê do org $FFFA. Ora, FFFAh + 6h =
10000h que totaliza nossos 64K de endereçamento. Simples, não?

O programa
Então vamos escrever o seguinte programa (vide figura 56). Ele é uma fusão dos programas
descritos nas páginas 59 e 60 deste tutorial (com umas poucas diferenças).

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
variavelA = $80 ;
variavelX = $85 ;variáveis
variavelY = $86 ;
;
org $F000 ;endereço onde nosso programa será alocado
;
.inicio ;
ldx #$04 ; X = 4
;
.pegaProximoByte ;
lda .valoresDeA,x ;A = byte endereçado por X
sta variavelA,x ;armazena na variavelA + X
dex ;X = X + 1
bpl .pegaProximoByte ;vá para a linha .pegaProximoByte caso
;X seja maior que zero
;se X for < 0 continua daqui para baixo
ldx #$20 ;X = 20h
ldy #$30 ;Y = 30h
stx variavelX ;armazena X na variavelX
sty variavelY ;armazena Y na variavelY
jmp .segundaParte ;vai para .segundaParte
nop ;não faz nada
;
.valoresDeA ;
.byte $3E, $9B, $43, $6A, $11 ;esses valores vão para o acumulador
.byte $00 ;
;
.segundaParte ;
lda #$10 ;A = 10h
sta variavelA ;salva A na variavelA
ldx #$20 ;X = 20h
stx variavelX ;salva X na variavelX
jsr .somaVariavelAeX ;chama subrotina
ldy #$30 ;Y = 30h
sty variavelY ;salva Y na variavelY
jsr .subtraiVariavelYeA ;chama subrotina
brk ;termina o programa
;
.somaVariavelAeX ;subrotina
cld ;limpa modo decimal
clc ;zera Carry
lda variavelA ;A = 10h
adc variavelX ;A = A + 20h

E:\Atari\SDK\doc\Tutorial.doc Página 107 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

rts ;retorna ao programa principal


;
.subtraiVariavelYeA ;subrotina
cld ;limpa modo decimal
sec ;seta Carry
lda variavelY ;A = 30h
sbc variavelA ;A = A - 10h
rts ;retorna ao programa principal
;
org $FFFA ;
.word .inicio ;NMI
.word .inicio ;Reset
.word .inicio ;IRQ

Figura 56

Se estiver usando o Crimson Editor e ele estiver configurado conforme descrição anterior,
use as teclas de atalho para compilar e debugar o programa digitado. No meu caso, eu usei
Ctrl+1 para compilar, Ctrl+2 para rodar o programa e Ctrl+3 para debugar.

Para quem tá compilando na mão use: DASM arquivo.asm –f3 –oarquivo.bin

O DASM vai compilar o aquivo.asm para arquivo.bin. Depois é só abrir o arquivo.bin no


emulador.

E:\Atari\SDK\doc\Tutorial.doc Página 108 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Declarando variáveis
Veja a declaração das variáveis.

variavelA = $80 ;
variavelX = $85 ;
variavelY = $86 ;

A primeira variável está referenciando o endereço 80h da RAM. Se observar o Stella (na
janela do debugger) verá que a RAM começa no endereço 80h e vai até FFh. A segunda
variável referencia o endereço 85h. Isso quer dizer que a primeira variável tem 5 bytes de
tamanho (80h, 81h, 82h, 83h e 84h). No nosso exemplo usamos a variável como sendo de 5
bytes. Você pode declarar uma variável em 80h e outra em 90h, por exemplo, e usar
somente 1 byte de cada uma. Porém isso é um desperdício de bytes. O ideal é usar
variáveis referenciando a endereços consecutivos, sem “espaços”. Lembre-se: só temos 128
bytes de RAM e ainda temos que compartilhá-los com a pilha. Uma outra forma de declarar
variáveis é:

Seg.U Variaveis ;
org $80 ;
;
variavelA ds 5 ;
variavelX ds 1 ;
variavelY ds 1 ;

Aqui declaramos um segumento de nome Variaveis e dizemos que ele começa em 80h
através do org $80. A partir daí não precisamos mais dizer a qual endereço cada variável se
referencia, e sim o “tamanho” de cada uma delas através do DS (Define Storage). No
exemplo acima. A variávelA tem 5 bytes, a variavelX e a variavelY tem 1 byte cada uma. O
compilador já sabe que, a variavelA começa em 80h e a variávelX começa em 85h, uma vez
que a variavelA tem tamanho 5 e a variavelY está em 86h. Se optar por usar a segunda
opção, lembre-se: você declarou um segmento para as variáveis então deve declarar o
segmento de código. Antes do ORG $F000 deve-se colocar SEG nome_do_segmento.
Assim:

Seg.U Variaveis ;
org $80 ;
;
variavelA ds 5 ;
variavelX ds 1 ;
variavelY ds 1 ;

Seg Codigo ;segmento do código


. ;
. ;seu código vem aqui
. ;

Vimos 2 maneiras de se declarar variáveis. Agora voltemos ao programa. Compile e rode no


emulador. Como eu uso o Stella, vou me referir a ele de agora em diante. Como podemos
notar, o programa só faz cálculos. Não mostra nada na tela. Não monta nenhuma imagem.
Não tem sincronismo com a TV. A intenção desse primeiro programa é de familiarizarmos
com o Crimson Editor e com o Stella. Em vez de rodar o programa direto, vamos entrar no
modo debug do Stella (figura 57) e ver como o programa é executado passo a passo nele.

Ao clicarmos pela primeira vez no botão Step (no canto superior direito), o programa é
executado em 1 passo. Cada clique corresponde a 1 passo de execução. Note, na figura 58,
que agora o PC e o registrador X estão destacados (em vermelho). O emulador destaca os
E:\Atari\SDK\doc\Tutorial.doc Página 109 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

registradores (e endereços de memória) que tiveram seus valores alterados. Eles estão
destacados porque, ao clicarmos em Step pela primeira vez, o programa executou a
instrução LDX #$04 e o PC foi para o próximo offset.

Clique várias vezes em Step e veja a execução do programa. Atente para as


leituras/gravações de memória/registradores, bem como as mudanças de offset do PC
devido às chamadas de subrotina. Para maiores detalhes, veja nas páginas 59 e 60 o
comportamento detalhado desse programa.

Para sair do emulador, feche a janela.

Registradores e flags

128 bytes de RAM

O seu programa

Figura 57

E:\Atari\SDK\doc\Tutorial.doc Página 110 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 58

Sincronizando com a TV
Agora vamos fazer nosso primeiro programa sincronizado com a TV. Ele também não fará
nada, mas através dele veremos a estrutura básica de um programa para o 2600 funcionar.

Vamos tentar ilustrar antes. Conforme a figura 59, antes de iniciar o sincronismo vertical,
podemos fazer qualquer coisa no programa, a qualquer tempo. Ainda não estamos
sincronizados com a TV.

Quando iniciamos o sincronismo (no Vertical Sync), temos que aguardar 3 scanlines. Daí
utilizamos 3 WSYNCs. Como cada scanline gasta 76 ciclos de máquina, temos 3 x 76 = 228
ciclos disponíveis para algum código, mas raramente usamos. Normalmente colocarmos 2
ou 3 WSYNCs e pronto.

Daí começa o Vertical Blank (VBLANK). Ele gasta 37 scanlines. Portanto temos 37 x 76 =
2.812 ciclos de máquina aqui. Dá para escrever muita coisa (código de programa) nesse
tempo. A lógica do programa fica nessa parte.

Depois disso, vem a parte que desenha na tela. São 192 scanlines.

E, por fim, temos o Overscan, que são 30 scanlines. Daí, 30 x 76 = 2.280 ciclos de máquina
aqui. Podemos também escrever muita lógica de programa nessa parte.

Após o Overscan, o processamento deve recomeçar do VSYNC, caracterizando 1 novo


frame.

Temos aí o nosso kernel.

E:\Atari\SDK\doc\Tutorial.doc Página 111 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Antes do VSYNC, você faz o


que quiser, no tempo que
quiser.
Aqui temos 3 WSYNCs
Aqui temos o VBLANK que
são 37 scanlines. A lógica
do jogo fica aqui.

Aqui é a rotina que vai


montar a imagem na TV.
São 192 scanlines e deve-se
respeitar a quantidade de 76
ciclos máximos por scanline.

Aqui temos 30 scanlines de


overscan. Podemos utilizar
código aqui.

Figura 59

Durante o VBLANK e o Overscan, você pode escrever seus códigos não se importando se
eles vão gastar 76 ciclos por scanline. Você só tem que se preocupar com que eles não
ultrapassem seus limites: 2.812 ciclos para o VBLANK e 2.280 ciclos para o Overscan. Por
outro lado, durante os 192 scanlines que desenham na tela, é vital que suas instruções não
ultrapassem 76 ciclos por scanline. Se isso acontecer, o desenho na tela pode não sair
como o esperado.

Mais adiante, veremos esses paus de lógica e matemática. Agora, vamos ver nosso
programa sincronizado com a TV. No primeiro exemplo, ele ainda não mostrará nada na
tela. Será somente a descrição da figura 59.

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label
;aqui podemos escrever qualquer código
.inicioDoFrame ;label
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
sta WSYNC ;

E:\Atari\SDK\doc\Tutorial.doc Página 112 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 37 scanlines
sta WSYNC ;nesses 37 scanlines é que escrevemos a lógica
sta WSYNC ;do jogo
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
lda #$00 ;desliga o VBLANK
sta VBLANK ;
REPEAT 192 ;
sta WSYNC ;aguarda 192 scanlines (aqui desenha na tela)
REPEND ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 30 scanlines
sta WSYNC ;aqui podemos ter mais lógica de jogo
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;

E:\Atari\SDK\doc\Tutorial.doc Página 113 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
sta WSYNC ;
jmp .inicioDoFrame ;vai para o próximo frame
;
ORG $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

Entre os labels .inicioDoJogo e .inicioDoFrame podemos escrever qualquer código (no nosso
exemplo, não tem nada). Do label .inicioDoFrame até o VBLANK, temos 37 WSYNCs, que
correspondem ao branco vertical. Aqui fica a lógica do jogo. Quando escrevermos algo de
útil, substituiremos os STA WSYNC por código que faz alguma coisa. Depois do VBLANK
temos o REPEAT. Como já dissemos, a imagem é composta de 192 scanlines. Então, um
dos comandos do DASM, o REPEAT, diz (em tempo de compilação) para repetir o(s)
comando(s) que está(ão) entre REPEAT e REPEND n vezes. No nosso caso, o compilador
repetirá STA WSYNC 192 vezes na ROM. Do REPEND até o JMP temos 30 STA SWYNC
que correspondem ao overscan. O nosso exemplo poderia ser escrito assim:

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label
;
.inicioDoFrame ;label
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
REPEAT 37 ;
sta WSYNC ;aguarda 37 scanlines
REPEND ;
lda #$00 ;desliga VBLANK
sta VBLANK ;
REPEAT 192 ;
sta WSYNC ;aguarda 192 scanlines
REPEND ;
REPEAT 30 ;
sta WSYNC ;aguarda 30 scanlines
REPEND ;
jmp .inicioDoFrame ;vai para o próximo frame
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

Isso criará um programa enorme na ROM: 543 bytes. Tanto o exemplo com REPEAT-
REPEND quanto o exemplo expandido (o primeiro exemplo), gastam 543 bytes. O REPEAT-

E:\Atari\SDK\doc\Tutorial.doc Página 114 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

REPEND facilita somente o código fonte. Para economizarmos bytes da ROM temos que
usar LOOPs. Assim:

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label
;
.inicioDoFrame ;label
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
ldy #$25 ;25h = 37d
;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
lda #$00 ;desliga VBLANK
sta VBLANK ;
ldy #$C0 ;C0h = 192d
;
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .scanLoop ;vai para .scanLoop se Y > 0
ldy #$1E ;1Eh = 30d
;
.overScan ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .overScan ;vai para .overScan se Y > 0
jmp .inicioDoFrame ;vai para o próximo frame
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

O programa acima difere dos anteriores no que diz respeito a executar as instruções STA
WSYNC. Nos exemplos anteriores, temos 37, 192 e 30 linhas de STA WSYNC que estão na
ROM e o processador as executará uma a uma. No exemplo acima, temos apenas 1 STA
WSYNC para cada parte do programa (VBLANK, TELA e Overscan). Mas no VBLANK esse
STA WSYNC é executado 37 vezes pelo loop. Na tela, ele é executado 192 vezes e no
overscan o STA WSYNC é executado 30 vezes pelo loop. Portanto, não precisamos
escrever o STA WSYNC várias vezes. Com o exemplo acima, gastamos apenas 46 bytes da
ROM. Para saber quantos bytes seu programa está gastando da ROM, basta colocar as
seguintes linhas entre as linhas JMP e ORG $FFFA:

echo (*-$F000)d," Bytes da ROM usados." ;


echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;

E:\Atari\SDK\doc\Tutorial.doc Página 115 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O compilador exibirá na tela o número de bytes do programa e o número de bytes restantes


da ROM. Então o programa fica conforme abaixo e o Crimson Editor exibe os dados da
compilação conforme a figura 60. A mensagem “Terminated with exit code 0” indica que não
há erros de sintaxe no programa e o seu_programa.BIN foi criado normalmente. Mas não
indica que a ROM funcionará.

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label
;
.inicioDoFrame ;label
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
ldy #$25 ;25h = 37d
;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
lda #$00 ;desliga VBLANK
sta VBLANK ;
ldy #$C0 ;C0h = 192d
;
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .scanLoop ;vai para .scanLoop se Y > 0
ldy #$1E ;1Eh = 30d
;
.overScan ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .overScan ;vai para .overScan se Y > 0
jmp .inicioDoFrame ;vai para o próximo frame
;
echo (*-$F000)d," Bytes da ROM usados." ;
echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

Acima, o programa com as linhas ‘echo’ para verificar quantos bytes o programa tem e,
conseqüentemente, o número de bytes restantes na ROM.

E:\Atari\SDK\doc\Tutorial.doc Página 116 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 60

Antes de prosseguirmos com programas que realmente fazem alguma coisa, vamos voltar
rapidamente à tela do debugger do Stella para algumas considerações. Veja a RAM
mostrada na figura 61. Quando iniciamos o programa, a RAM tem valores aleatórios. Isso
acontece também no 2600. Devido às características físicas internas dos microchips, ao ligar
a energia elétrica, os latches (flip-flops que são os bits da RAM) podem assumir valor 0 ou 1.
Isso faz com que cada byte da RAM tenha um valor qualquer, o que pode influir no
funcionamento do jogo. Para contornar esse problema, é comum zerar toda a RAM no início
do programa. Para isso, basta endereçar a RAM num loop e colocar o valor zero lá.

Figura 61

O código que usaremos no início dos programas, que seta o processador e zera a RAM é:

sei ;seta disable interrupt


cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória (coloca A no endereço 0
;indexado por X)
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória

Observe que usamos o illegal opcode LAX. Poderíamos usar LDA $00 e LDX $00. Com o
LAX economizamos 2 bytes de ROM. Lembre-se economia de bytes da ROM, RAM e ciclos
de máquina são a chave para qualquer jogo.

O procedimento de zerar a RAM é feito uma vez quando do início do jogo. Vamos inserir o
código acima em um dos exemplos já mostrados.

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label

E:\Atari\SDK\doc\Tutorial.doc Página 117 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X – 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
.inicioDoFrame ;início do frame
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
ldy #$25 ;25h = 37d
;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
lda #$00 ;desliga VBLANK
sta VBLANK ;
ldy #$C0 ;C0h = 192d
;
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .scanLoop ;vai para .scanLoop se Y > 0
ldy #$1E ;1Eh = 30d
;
.overScan ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .overScan ;vai para .overScan se Y > 0
jmp .inicioDoFrame ;vai para o próximo frame
;
echo (*-$F000)d," Bytes da ROM usados." ;
echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

Exibindo alguma imagem


Chegou a hora de vermos alguma coisa na tela. Vamos escrever o mesmo programa
anterior, agora colocando algum código que nos possibilitará ver seu resultado na tela. A
partir de agora, os programas se tornarão cada vez mais complexos e serão explicadas as
mudanças que ocorrerem.

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label
sei ;seta disable interrupt

E:\Atari\SDK\doc\Tutorial.doc Página 118 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

cld ;limpa modo decimal


lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X – 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
.inicioDoFrame ;início do frame
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
ldy #$25 ;25h = 37d
;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
lda #$00 ;desliga VBLANK
sta VBLANK ;
ldx #$00 ;X = 0
ldy #$C0 ;C0h = 192d
;
.scanLoop ;
inx ;X = X + 1
stx COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .scanLoop ;vai para .scanLoop se Y > 0
ldy #$1E ;1Eh = 30d
;
.overScan ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .overScan ;vai para .overScan se Y > 0
jmp .inicioDoFrame ;vai para o próximo frame
;
echo (*-$F000)d," Bytes da ROM usados." ;
echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

Compile e rode o programa. Veja que a tela apresentada pelo Stella é igual à mostrada na
figura 62. Dentro do loop correspondente à parte que desenha na tela (figura 59, página 112
desse tutorial), fazemos com que o registrador X seja acrescido em 1 unidade e colocamos o
valor no endereço COLUBK que muda a cor de fundo da tela (página 81 desse tutorial).

E:\Atari\SDK\doc\Tutorial.doc Página 119 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 62

Observe que o valor de X é mudado a cada loop então cada linha da tela tem uma cor
diferente. Vamos ver o programa escrito de outra forma:

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X – 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
.inicioDoFrame ;início do frame
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
ldy #$25 ;25h = 37d
;
.aguardaVBLANK ;

E:\Atari\SDK\doc\Tutorial.doc Página 120 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sta WSYNC ;aguarda 1 scanline


dey ;Y = Y – 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
lda #$00 ;desliga VBLANK
sta VBLANK ;
inx ;X = X + 1
ldy #$C0 ;C0h = 192d
;
.scanLoop ;
stx COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .scanLoop ;vai para .scanLoop se Y > 0
ldy #$1E ;1Eh = 30d
;
.overScan ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y – 1
bne .overScan ;vai para .overScan se Y > 0
jmp .inicioDoFrame ;vai para o próximo frame
;
echo (*-$F000)d," Bytes da ROM usados." ;
echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

A instrução que muda o valor de X está fora do loop e a instrução que coloca X como a cor
de fundo da tela está dentro do loop. Isso quer dizer que X terá seu valor incrementado e
esse valor será colocado como cor de fundo da tela 192 vezes, fazendo a tela inteira ter uma
só cor em cada frame. Mas quando o frame muda, a cor muda também. Por exemplo,
supomos que X é 20h. Quando a instrução INX é executada, X passa para 21h e o STX
COLUBK é executado 192 vezes (pois está dentro do loop). Como o registrador endereçado
por COLUBK é um latch, ele retém esse valor até que seja alterado. Daí temos o STA
WSYNC que diz para o 6502 aguardar o fim do scanline. Enquanto o 6502 aguarda o fim do
scanline, o TIA continua desenhando a linha com o valor retido no COLUBK. Então, temos
que 192 linhas da mesma cor = tela toda de uma cor só. Veja as figuras 63 e 64.

Quando os 192 scanlines são desenhados, o loop termina, entra no overscan e vai para o
próximo frame. Daí o X é incrementado novamente e a tela é redesenhada com outra cor. A
tela então se apresenta mudando de cor completamente, em um ciclo infinito. Agora
notamos a diferença entre os 2 últimos programas mostrados: o primeiro deles muda a cor
de fundo a cada linha da tela, o outro muda a cor de fundo a cada frame.

E:\Atari\SDK\doc\Tutorial.doc Página 121 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 63

Figura 64

E:\Atari\SDK\doc\Tutorial.doc Página 122 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Vamos agora juntar esses 2 efeitos apresentados em um único programa. Ele vai mostrar a
tela com cores diferentes por quadro e depois por linha (figuras 62 e 63/64 alternadamente)
e ficará nesse ciclo indefinidamente.

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
Seg.U Variaveis ;
org $80 ;
;
wFlag ds 1 ;se for 0 muda tela. se for 1 muda cor da linha
wContador ds 1 ;contador
;
Seg Codigo ;
org $F000 ;onde nosso programa será alocado
;
.inicioDoJogo ;label
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
.inicioDoFrame ;início do frame
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
ldy #$25 ;25h = 37d
;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
lda #$00 ;desliga VBLANK
sta VBLANK ;
inc wContador ;wContador = wContador + 1
bne .naoMudaTipo ;se wContador <> 0 vai para .naoMudaTipo
lda #$01 ;A = 1
eor wFlag ;inverte wFlag (se 0 = 1, se 1 = 0)
sta wFlag ;salva wFlag
;
.naoMudaTipo ;
lda wFlag ;A = 0?
bne .naoMudaCorDaTela ;não. não muda a cor da tela
inx ;X = X + 1
jmp .naoZeraX ;
;
.naoMudaCorDaTela ;
ldx #$00 ;X = 0 para iniciar mudança da cor das linhas
;
.naoZeraX ;
ldy #$C0 ;C0h = 192d
;
.scanLoop ;

E:\Atari\SDK\doc\Tutorial.doc Página 123 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007
lda wFlag ;A = 0?
beq .naoMudaCorDaLinha ;sim. não muda cor da linha
inx ;muda cor da linha
;
.naoMudaCorDaLinha ;
stx COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
ldy #$1E ;1Eh = 30d
;
.overScan ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .overScan ;vai para .overScan se Y > 0
jmp .inicioDoFrame ;vai para o próximo frame
;
echo (*-$F000)d," Bytes da ROM usados." ;
echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

Primeiramente criamos 2 variáveis. A wFlag que terá 0 ou 1. Se for zero, indica que
queremos mudar a cor por quadro. Se for 1 indica que queremos mudar a cor por linha. A
variávei wContador será incrementada em 1 e será testada. No primeiro teste, se wContador
for diferente de zero, o programa vai para linha .naoMudaTipo. Se wContador for zero, o
programa faz um XOR do valor da wFlag com 1. Isso fará o valor de wFlag ser invertido: se
for 1 passa a ser zero e se for zero passa a ser 1. Daí salvamos o resultado em wFlag para
uma nova comparação no próximo frame.

Se o valor de wFlag for 1


Carregamos em A o valor de wFlag e testamos. Como A = 1, o programa vai para
.naoMudaCorDaTela. Quando entramos no loop, carregamos novamente em A o valor de
wFlag e testamos. Como A = 1, o BEQ não é executado, ou seja, o programa não vai direto
para .naoMudaCorDaLinha e executa o INX dentro do loop. Isso acarreta na mudança da cor
da linha.

Se o valor de wFlag for 0


Carregamos em A o valor de wFlag e testamos. Como A = 0, o programa não vai para
.naoMudaCorDaTela e executa o INX fora do loop. Quando entramos no loop, carregamos
novamente em A o valor de wFlag e testamos. Como A = 0, o BEQ é executado, ou seja, o
programa vai direto para .naoMudaCorDaLinha e não executa o INX dentro do loop. Isso
acarreta na mudança da cor da tela.

O processo se inverte quando a variável wContador chegar a zero novamente. Vale ressaltar
que os programas estão sendo escritos sem nos preocuparmos com gasto de bytes e ciclos
de máquina. Como é para vermos o funcionamento da coisa, deve ser o mais didático
possível. As otimizações virão com o tempo.

Entretanto é importantíssimo destacar o que ocorre no loop desse exemplo. Veja o


fragmento do código:

E:\Atari\SDK\doc\Tutorial.doc Página 124 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

...
...
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
lda wFlag ;A = 0?
beq .naoMudaCorDaLinha ;sim. não muda cor da linha
inx ;muda cor da linha
;
.naoMudaCorDaLinha ;
stx COLUBK ;muda cor de fundo
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...
...

Vamos observar os ciclos de máquina gastos nessa parte do programa. Veja a figura 65.
Montamos uma tabela com os ciclos e analisamos a execução.

Figura 65

Linha Instrução Ciclos Observação


STA WSYNC 0 começa a contagem de ciclos a partir da instrução seguinte
1ª LDA $80 +3 coloca em A o valor de wFlag
2ª BEQ $F041 +2 ou +3 se A for zero vai para .naoMudaCorDaLinha
3ª INX +2 muda a cor da linha
coloca X em COLUBK para mudar a cor de fundo da tela, linha a
4ª STX COLUBK +3
linha
5ª DEY +2 Y=Y–1
6ª BNE $F03C +2 ou +3 próxima linha da tela

Recordando do que foi dito na página 92 desse tutorial: “toda instrução de desvio gasta 2
ciclos se o desvio não ocorrer e 3 ciclos se o desvio ocorrer”. Então, vamos supor que o
wFlag é 1, ou seja, em 80h da memória temos o valor 1. O programa vai executar a primeira
linha e gastar 3 ciclos. Executa a segunda e gasta 2 ciclos. Depois a terceira (mais 2 ciclos)
e assim por diante. Quando ele chegar na última linha (BNE $F03C) ele vai ter gasto: 3 + 2 +
2 + 3 + 2 = 12 ciclos (ainda não executou o BNE $F03C). Depois de executar a sexta linha
ele vai ter gasto 12 + 3 = 15 se o desvio ocorrer (se Y for > 0 o desvio ocorre) e 12 + 2 = 14
se o desvio não ocorrer (se Y = 0 o desvio não ocorre).

Se o valor de wFlag for zero, o desvio no BEQ $F041 vai ocorrer, gastando 3 ciclos e não 2.
Daí, quando ele chegar na última linha (BNE $F03C) ele vai ter gasto: 3 + 3 + 3 + 2 = 11
ciclos (ainda não executou o BNE $F03C). Depois de executar a sexta linha ele vai ter gasto
11 + 3 = 14 ciclos se o desvio ocorrer (se Y > 0 o desvio ocorre) e 11 + 2 = 13 se o desvio
não ocorrer (se Y = 0 o desvio não ocorre).

No nosso programa, a diferença de 1 ciclo entre os casos não interferiu. Mas em outros
programas, esse tipo de diferença pode fazer um estrago. Por isso, é bom acostumarmos a
sempre gastar o mesmo número de ciclos tanto para condições verdadeiras quanto para
falsas dentro do scanLoop, ou seja, na parte que desenha a tela.

E:\Atari\SDK\doc\Tutorial.doc Página 125 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

A figura 66 mostra onde no Stella você pode verificar a contagem de ciclos.

Figura 66

Vamos fazer uma pequena alteração no programa, dentro do loop .scanLoop:

...
...
.scanLoop ;
lda wFlag ;A = 0?
beq .naoMudaCorDaLinha ;sim. não muda cor da linha
inx ;muda cor da linha
nop ;
nop ;nesse tempo a tela é desenhada com a cor
nop ;do que está em X
nop ;o latch COLUBK retém a cor durante todo
nop ;esse tempo.
nop ;esses NOPs determinam o início da coluna
sty COLUBK ;aqui mudamos o valor do COLUBK
nop ;a tela é desenhada com essa nova cor nesse
nop ;tempo, apresentando uma coluna.
nop ;os 3 NOPS determinam a largura da coluna
;
.naoMudaCorDaLinha ;
stx COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...
...

A alteração faz com que parte da tela fique com cores diferentes do restante, por coluna, ou
seja, o feixe de elétrons percorre a tela da esquerda para a direita, desenhando a linha em
determinada cor (dada por X em STX COLUBK), e em certo momento, (depois que ele saiu
da esquerda e está indo para a direita da tela), ele passa a desenhar na tela com a cor
determinada por Y em STY COLUBK. A tela fica como na figura 67. Se adicionarmos NOP
antes do STY COLUBK, a coluna vai se deslocar mais para a direita, pois o programa ficará
mais tempo desenhando o que está em X. Se adicionarmos NOP depois do STY COLUBK, a
coluna ficará mais larga, pois o programa ficará mais tempo desenhando o que está em Y.
Para termos a impressão de que a coluna aparece e desaparece basta alterar o código
acima para:

...
...
nop ;o latch COLUBK retém a cor durante todo
nop ;esse tempo.
nop ;esses NOPs determinam o início da coluna
stx COLUBK ;aqui mudamos o valor do COLUBK
nop ;a tela é desenhada com essa nova cor nesse
nop ;tempo, apresentando uma coluna.
nop ;os 3 NOPs determinam a largura da coluna
;
.naoMudaCorDaLinha ;

E:\Atari\SDK\doc\Tutorial.doc Página 126 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007
sty COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...
...

Figura 67

Vamos animar um pouco as coisas e vermos o que acontece quando colocamos um código
que faz o scroll da tela. Primeiro, vamos fazer scroll do plano de fundo. Para isso altere o
código conforme segue:

a) declare mais uma variável

...
...
Seg.U Variaveis ;
org $80 ;
;
wFlag ds 1 ;se for 0 muda tela. se for 1 muda cor da linha
wContador ds 1 ;contador
wScroll ds 1 ;faz a imagem “rolar”
...
...

b) digite o código (é o mesmo anterior com algumas alterações)

...
...
inc wScroll ;wScroll = wScroll + 1

E:\Atari\SDK\doc\Tutorial.doc Página 127 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.scanLoop ;
lda wFlag ;A = 0?
beq .naoMudaCorDaLinha ;sim. não muda cor da linha
tya ;A = Y
adc wScroll ;A = A + wScroll
tax ;X = A
nop ;
nop ;nesse tempo a tela é desenhada com a cor
nop ;do que está em X
nop ;o latch COLUBK retém a cor durante todo
nop ;esse tempo.
nop ;esses NOPs determinam o início da coluna
sty COLUBK ;aqui mudamos o valor do COLUBK
nop ;a tela é desenhada com essa nova cor nesse
nop ;tempo, apresentando uma coluna.
nop ;os 3 NOPs determinam a largura da coluna
;
.naoMudaCorDaLinha ;
stx COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...
...

O resultado é o mesmo da figura 67, exceto que a maior parte da imagem estará “rolando”
para baixo. A coluna permanece estática. Para fazer a coluna rolar e o restante ficar
estático, basta alterar conforme abaixo:

...
...
stx COLUBK ;aqui mudamos o valor do COLUBK
nop ;a tela é desenhada com essa nova cor nesse
nop ;tempo, apresentando uma coluna.
nop ;os 3 NOPs determinam a largura da coluna
;
.naoMudaCorDaLinha ;
sty COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...
...

Agora, vamos fazer com que nossa coluna se mova horizontalmente, da direita para a
esquerda (figuras 68 e 69). Para isso faça o seguinte:

a) declare mais uma variável

...
...
Seg.U Variaveis ;
org $80 ;
;
wFlag ds 1 ;se for 0 muda tela. se for 1 muda cor da linha
wContador ds 1 ;contador
wScroll ds 1 ;faz a imagem “rolar”
wDesloca ds 1 ;desloca coluna
...
...

E:\Atari\SDK\doc\Tutorial.doc Página 128 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

b) inicialize a variável wDesloca no seguinte trecho do programa

...
...
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
lda #$08 ;A = 8
sta wDesloca ;wDesloca = A
;
.inicioDoFrame ;início do frame
lda #$02 ;liga o VSYNC
...
...

c) altere o código que desenha na tela conforme abaixo

...
...
inc wScroll ;wScroll = wScroll + 1
bne .scanLoop ;aproveita wScroll para causar delay na coluna
dec wDesloca ;wDesloca = wDesloca - 1
bne .scanLoop ;wDesloca = 0? não, desloque a coluna
lda #$08 ;A = 8
sta wDesloca ;posiciona a coluna na direita da tela
;
.scanLoop ;
tya ;A = Y
adc wScroll ;A = A + wScroll
pha ;salva A
ldx wDesloca ;X = wDesloca
;
.horiz ;
dex ;X = X - 1
bne .horiz ;X = 0? Não, continua decrementando X
pla ;restaura A
tax ;X = A
stx COLUBK ;muda a cor de fundo
nop ;a tela é desenhada com essa nova cor,
nop ;nesse tempo, apresentando uma coluna
nop ;os 3 NOPs determinam a largura da coluna
;
.naoMudaCorDaLinha ;
sty COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...
...

O que acontece aqui é bem simples: X tem valor que varia de 8 a 1, dado por wDesloca.
Quando o 6502 entra no loop .horiz, X é decrementado até atingir zero. Quanto maior for o
valor de X, mais tempo o 6502 vai ficar no loop, fazendo com que a coluna demore mais
tempo para aparecer na tela. Quando, então, ele sai do loop, a coluna é desenhada. Quanto
maior o valor de X, mais para a direita estará a coluna e quanto menor o valor de X, mais
para a esquerda estará a coluna, pois o loop .horiz demora menos e a coluna é desenhada
em um tempo bem menor depois do último WSYNC.

E:\Atari\SDK\doc\Tutorial.doc Página 129 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Para que seja possível visualizar o deslocamento, temos que causar um delay, pois o
deslocamento acontece de forma tão rápida que é difícil vê-lo perfeitamente. Aproveitamos a
variável wScroll. Todas as vezes que ela chega a zero, o sistema então decrementa o
wDesloca, movendo assim a coluna. Enquanto wScroll não é zero, a coluna é redesenhada
na mesma posição. Quando wScroll é zero, wDesloca é decrementado e a coluna passa a
ser desenhada um pouco mais para a esquerda.

Figura 68

E:\Atari\SDK\doc\Tutorial.doc Página 130 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 69

A contagem errada de ciclos


Na página 112 deste tutorial foi dito que veríamos paus de lógica e matemática. Chegou a
hora. Intencionalmente, estamos escrevendo nosso código de forma errada, justamente para
chegar nesse ponto. Apesar do programa estar sincronizado com a TV e apresentar uma
imagem na tela, ele está fazendo isso de forma errada. Vejamos uma breve recordação.

Foi dito que após o VSYNC temos 37 scanlines de VBLANK. No nosso primeiro kernel,
gastamos o tempo dos 37 scanlines utilizando 37 linhas STA WSYNC após o VSYNC (vide
páginas 112, 113, 114 e 115 deste tutorial). Como sabemos, 37 scanlines * 76 ciclos = 2.812
ciclos de máquina disponíveis para a lógica do programa antes de entrarmos na área de
desenho da tela (momento em que o feixe de elétrons começa a desenhar na tela).

Vamos destacar os screenshots que vimos até agora. Na figura 70 destacamos parte da
figura 67.

Figura 70

Um dos problemas é a faixa escura no início da tela. A imagem começa a ser desenhada
algumas linhas abaixo da parte superior da tela, ficando uma faixa escura no lugar. Vamos
ver o porque isso está ocorrendo, transcrevendo separadamente as partes do nosso
programa e relacioná-las com as fases do frame.

E:\Atari\SDK\doc\Tutorial.doc Página 131 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.inicioDoJogo ;label
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
lda #$08 ;
sta wDesloca ;

O fragmento acima é a parte que inicializa as variáveis, limpa a memória, etc. É onde pode-
se escrever o que quiser sem se preocupar com o tempo.

.inicioDoFrame ;início do frame


lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;

O fragmento acima é a parte que sincronizamos com a TV (vide figura 71). Temos aí 2 ou 3
scanlines. A partir desse ponto tudo deve estar no seu devido tempo.

Figura 71

Depois do VSYNC, pelo que já vimos, começa o VBLANK. São 37 scanlines de 76 ciclos
cada.
sty VBLANK ;liga VBLANK
ldy #$25 ;25h = 37d
;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0

O fragmento acima conta os 37 scanlines no loop .aguardaVBLANK. Esse é o tempo que


temos para escrever toda a lógica dos nossos programas. Corresponde à figura 72.

Figura 72

E:\Atari\SDK\doc\Tutorial.doc Página 132 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Nossos cáculos para desenhar na tela:

lda #$00 ;desliga VBLANK


sta VBLANK ;
inc wContador ;wContador = wContador + 1
bne .naoMudaTipo ;se wContador <> 0 vai para .naoMudaTipo
lda #$01 ;A = 1
eor wFlag ;inverte wFlag (se 0 = 1, se 1 = 0)
sta wFlag ;salva wFlag
;
.naoMudaTipo ;
lda wFlag ;A = 0?
bne .naoMudaCorDaTela ;não. não muda a cor da tela
inx ;X = X + 1
jmp .naoZeraX ;
;
.naoMudaCorDaTela ;
ldx #$00 ;X = 0 para iniciar mudança da cor das linhas
;
.naoZeraX ;
ldy #$C0 ;C0h = 192d
inc wScroll ;wScroll = wScroll + 1
bne .scanLoop ;aproveita wScroll para causar delay na coluna
dec wDesloca ;wDesloca = wDesloca - 1
bne .scanLoop ;wDesloca = 0? não, desloque a coluna
lda #$08 ;A = 8
sta wDesloca ;posiciona a coluna na direita da tela

O fragmento acima é a lógica do nosso programa. É um trecho pequeno de programa, mas


ainda assim ele gasta seus ciclos.

.scanLoop ;
tya ;A = Y
adc wScroll ;A = A + wScroll
pha ;salva A
ldx wDesloca ;X = wDesloca
;
.horiz ;
dex ;X = X - 1
bne .horiz ;X = 0? Não, continua decrementando X
pla ;restaura A
tax ;X = A
stx COLUBK ;muda a cor de fundo
nop ;a tela é desenhada com essa nova cor,
nop ;nesse tempo, apresentando uma coluna
nop ;os 3 NOPs determinam a largura da coluna
;
.naoMudaCorDaLinha ;
sty COLUBK ;muda cor de fundo
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0

O fragmento acima é a parte que desenha na tela. Ele pode ter 192 scanlines e, de
preferência, 76 ciclos de instrução por scanline. Corresponde à figura 73.

E:\Atari\SDK\doc\Tutorial.doc Página 133 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 73
ldy #$1E ;1Eh = 30d
;
.overScan ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .overScan ;vai para .overScan se Y > 0

O fragmento acima é o overscan. Pode-se usar lógica de programa aqui também (figura 74).

Figura 74
jmp .inicioDoFrame ;vai para o próximo frame

O fragmento acima faz com que o 6502 volte ao início do kernel para desenhar um novo
quadro, caracterizando um loop infinito.

E:\Atari\SDK\doc\Tutorial.doc Página 134 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

No programa que estamos escrevendo (intencionalmente de forma errada), observamos que


a lógica do programa “invade” a parte que desenha na tela. Assim:

Programa Descrição
sta VBLANK liga VBLANK após sincronismo vertical (VSYNC)
ldy #$25
aqui eu estou gastando 37 scanlines * 76 ciclos = 2.812
.aguardaVBLANK ciclos.
sta WSYNC
dey a lógica do programa deveria estar “embutida” nesse tempo
bne .aguardaVBLANK
lda #$00
sta VBLANK
inc wContador
bne .naoMudaTipo
lda #$01
eor wFlag
sta wFlag

.naoMudaTipo
lda wFlag
bne .naoMudaCorDaTela
inx Aqui está a lógica do programa. Ela vem depois dos 37
jmp .naoZeraX scanlines do VBLANK. Isso quer dizer que quando o 6502
executa esse trecho, o TIA já está desenhando na tela.
.naoMudaCorDaTela
ldx #$00

.naoZeraX
ldy #$C0
inc wScroll
bne .scanLoop
dec wDesloca
bne .scanLoop
lda #$08
sta wDesloca

A solução é fazer com que o código da lógica do programa não “invada” o tempo de
desenho da tela. Vamos pensar um pouco: no VBLANK tenho 2.812 ciclos disponíveis. Meu
código gasta N ciclos, então 2.812 – N = tempo restante até entrar no momento de desenhar
na tela. Por exemplo, se meu código gastasse 76 ciclos, então em vez de fazer

ldy #$25 ;25h = 37d


;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
+ meu código

que resulta em 38 scalines (37 scanlines do loop + 1 scanline do meu código, considerando
que meu código gasta 76 ciclos), eu faria

ldy #$24 ;24h = 36d


;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0
+ meu código
E:\Atari\SDK\doc\Tutorial.doc Página 135 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

ou ainda

meu código +
ldy #$24 ;24h = 36d
;
.aguardaVBLANK ;
sta WSYNC ;aguarda 1 scanline
dey ;Y = Y - 1
bne .aguardaVBLANK ;vai para .aguardaVBLANK se Y > 0

que nesse caso é o tempo restante.

Para cada 76 ciclos de código, devo diminuir 1 scanline do VBLANK. Mas ficar contando
ciclos de máquina é uma tarefa difícil. Temos que deixar o hardware cuidar disso para nós.
Para isso basta utilizarmos o timer.

Utilizando o timer
Conforme visto na página 88 desse tutorial, podemos setar o timer do PIA para que
possamos temporizar operações. Quando setamos o timer, ele passa a contar
regressivamente até chegar a zero. Cada ciclo de máquina corresponde a 1 ciclo do PIA, ou
1 contagem do timer. Portanto, podemos utilizar o timer para contar o tempo restante do
exemplo acima. Daí não precisamos mais nos preocupar em ficar contando quantos ciclos
nossa lógica gasta e subtrair dos scanlines restantes. Apenas devemos continuar nos
preocupando em não gastar mais do que 2.812 ciclos de máquina nessa parte. Para utilizar
o timer, basta escrevermos:

lda #$2B ;valor a ser carregado no timer


sta TIM64T ;seta o timer
meu código
;
.aguardaVBLANK ;
lda INTIM ;timer chegou a zero?
bne .aguardaVBLANK ;não. Aguarda fim do VBLANK

Simples, setamos o timer com 2Bh = 43d. Por que? Vamos fazer as contas:

Segundo consta na página 88 desse tutorial, o TIM64T significa que o timer é um divisor por
64, ou seja, a cada 64 ciclos o timer conta 1. Ora, então se eu preciso que ele conte 2.812
ciclos basta fazer:

2.812 ciclos / 64 = 43,9376 ou 43 ciclos = 2Bh

Nota: Não importa o valor da casa decimal: sempre devemos arredondar para baixo.

Com o timer setado e começando a contar, vem o nosso código. Se o nosso código gastou
N ciclos, o timer contém o tempo (2Bh * 64) – N. Daí, quando o 6502 chega no loop
.aguardaVBLANK ele lê o que está no timer. Se for maior que zero, ele fica no loop até o
timer atingir zero. Veja que não há necessidade de decrementar nada dentro do loop. Basta
ler o que está no timer (LDA INTIM). Cada vez que se lê esse endereço, ele traz um valor
decrescente. Com isso podemos escrever e editar nosso código, acrescentando ou
excluindo instruções sem ter que nos preocupar com a contagem de ciclos que estamos
gastando. O timer se encarrega de equilibrar as coisas.

Vamos agora, reescrever nosso código, utilizando o timer e vejamos o que acontece.

E:\Atari\SDK\doc\Tutorial.doc Página 136 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

...
...
.inicioDoFrame ;início do frame
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga VBLANK
lda #$2B ;2Bh = 43d
sta TIM64T ;seta o timer
ldy #$C0 ;C0h = 192d
inc wContador ;wContador = wContador + 1
bne .naoMudaTipo ;se wContador <> 0 vai para .naoMudaTipo
lda #$01 ;A = 1
eor wFlag ;inverte wFlag (se 0 = 1, se 1 = 0)
sta wFlag ;salva wFlag
;
.naoMudaTipo ;
lda wFlag ;A = 0?
bne .naoMudaCorDaTela ;não. não muda a cor da tela
inx ;X = X + 1
jmp .naoZeraX ;
;
.naoMudaCorDaTela ;
ldx #$00 ;X = 0 para iniciar mudança da cor das linhas
;
.naoZeraX ;
inc wScroll ;wScroll = wScroll + 1
bne .aguardaVBLANK ;aproveita wScroll para causar delay na coluna
dec wDesloca ;wDesloca = wDesloca - 1
bne .aguardaVBLANK ;wDesloca = 0? não, desloque a coluna
lda #$08 ;A = 8
sta wDesloca ;posiciona a coluna na direita da tela
;
.aguardaVBLANK ;
lda INTIM ;timer é zero?
bne .aguardaVBLANK ;não. aguarda fim do VBLANK
sta VBLANK ;desliga o VBLANK
;
.scanLoop ;
...
...

Veja nas figuras 75 e 76 a comparação do início de cada imagem.

Sem o timer (errado) Com o timer


Mais distante Mais próximo
do início do do início do
frame frame

Figura 75 Figura 76

Mas ainda assim, há uma faixa escura antes da imagem. Está mais estreita, pois agora
estamos contando certo, mas ainda está lá. Isso acontece porque, o código que sincroniza
com a TV (liga e desliga o VSYNC) gasta ciclos de máquina (ele tem 3 WSYNC, que são
justamente as 3 linhas escuras antes da imagem) e ele está antes de setarmos o timer.
Temos aí 2 opções: diminuir o valor inicial do timer ou colocá-lo antes do VSYNC.

E:\Atari\SDK\doc\Tutorial.doc Página 137 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.inicioDoFrame ;início do frame


lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga VBLANK
lda #$28 ;28h = 40d
sta TIM64T ;seta o timer
ldy #$C0 ;C0h = 192d

ou

.inicioDoFrame ;início do frame


lda #$2B ;2Bh = 43d
sta TIM64T ;seta o timer
lda #$02 ;liga o VSYNC
sta VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga VBLANK
ldy #$C0 ;C0h = 192d

Mas antes de fazermos essa alteração, observe que a primeira linha da coluna aparece
antes do restante dessa coluna (figuras 77 e 78) e acompanha o deslocamento dela.

Figura 77

Figura 78

Os destaques mostram uma barra mais à esquerda da coluna. À medida que a coluna se
desloca para a esquerda, a barra a acompanha. Mas está sempre antes (mais à esquerda)
da coluna. Na verdade, essa barra é a primeira linha da coluna que está defasada em
relação ao resto da coluna. A coluna está sendo desenhada primeiramente em um
determinado ciclo (a primeira linha da coluna é desenhada em um determinado tempo). O
restante da coluna é desenhado em um tempo diferente do primeiro. Esse tempo é em
relação ao último WSYNC, que determina a distância em relação ao lado esquerdo da tela.
O que ocorre é o seguinte: depois que o 6502 executa todo esse código, ele entra no loop
.scanLoop. Daí ele vai executando as instruções, inclusive o loop .horiz que causa um delay,
fazendo a coluna aparecer mais à direita e se deslocar para a esquerda a cada vez que X é
decrementado. Mas na primeira vez que ele entra no .scanLoop, ele está vindo desde o
último WSYNC (que no caso começou quando fez o VSYNC). Do loop .aguardaVBLANK até
o .scanLoop, ele gastou uma certa quantidade de ciclos. Daí ele entra no .scanLoop e vai

E:\Atari\SDK\doc\Tutorial.doc Página 138 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

executando as instruções, gastando assim mais ciclos. A soma dos ciclos gastos no código
fora do .scanLoop com os ciclos gastos no que está dentro do .scanLoop (na primeira vez
que ele entra no .scanLoop, acumula-se os ciclos) faz a primeira linha aparecer em uma
determinada coluna da tela.

Pela lógia, essa linha deveria aparecer depois do restante da coluna. Mas ela aparece antes
porque, como ele veio acumulando ciclos, quando chega a 76 ciclos, ele passa a contar do
zero, referenciando-se pelo lado esquerdo da tela. Por exemplo, se para executar todas as
instruções ele gastou 100 ciclos, então ele vai desenhar na tela no ciclo 100 – 76 = 27,
comprovando que nosso código está gastando mais de 76 ciclos por scanline na primeira
vez que entra no .scanLoop (vem acumulado do .inicioDoFrame).

Dentro do .scanLoop ele encontra um STA WSYNC. Nesse momento, ele pára a execução e
aguarda o feixe de elétrons voltar para o lado esquerdo da tela. Quando isso ocorre, a
contagem de ciclos é zerada e ele executa novamente as instruções do .scanLoop até
passar novamente pelo STA WSYNC. Como podemos concluir, ele executou somente as
instruções dentro do .scanLoop dessa vez. Somente na primeira vez (antes de entrar no
.scanLoop) ele executou aquelas outras instruções, levando assim, mais tempo para
desenhar a primeira linha (mas ela apareceu antes, dando a impressão de que gastou-se
menos tempo, porque ultrapassou ois 76 ciclos aparecendo então no tempo restante). Já a
partir da segunda linha, ele está executando somente as instruções dentro do .scanLoop,
gastando assim menos tempo em relação ao WSYNC. Essa diferença de tempo faz com que
as linhas sejam desenhadas (a partir da segunda) na mesma posição, pois gastam o mesmo
tempo.

Para resolver isso, basta zerarmos a contagem de ciclos tão logo entremos no .scanLoop.
Assim, teremos a mesma contagem de tempo para todas as linhas.

...
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
tya ;A = Y
adc wScroll ;A = A + wScroll
pha ;salva A
ldx wDesloca ;X = wDesloca
;
.horiz ;
dex ;X = X - 1
bne .horiz ;X = 0? não, continua decrementando X
pla ;restaura A
tax ;X = A
stx COLUBK ;muda a cor de fundo
nop ;a tela é desenhada com essa nova cor,
nop ;nesse tempo, apresentando uma coluna
nop ;os 3 NOPs determinam a largura da coluna
sty COLUBK ;muda cor de fundo
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...

E:\Atari\SDK\doc\Tutorial.doc Página 139 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

No programa que estamos escrevendo, o STA WSYNC estava depois das instruções que
desenham na tela. Se houvesse uma mudança em qualquer tempo dentro dos 76 ciclos, ela
seria visível. Mudando o STA WSYNC para o início do loop dizemos que, o que quer que
estivesse sendo desenhado antes de entrar no loop continue sendo até o fim do scanline.
Agora não há mais defasagem de tempo entre as linhas desenhadas (figura 79).

Figura 79

Porém há um outro problema. Vamos destacar a figura 79 conforme a figura 80.

Figura 80

Como podemos observar, a tela começa a ser desenhada a partir da primeira linha da
coluna e não a partir da primeira linha do plano de fundo. Do lado esquerdo, entre as 2
barras vermelhas que destacamos, temos uma linha escura, indicando que nada está sendo
desenhado até aquele momento. Daí temos a primeira linha da coluna. Depois, do lado
direito da coluna, temos o início do desenho do plano de fundo (uma linha verde escuro).

Isso está errado. A linha verde escuro deve começar no lado esquerdo da tela e não depois
da primeira linha da coluna. Só está acontecendo porque dentro do .scanLoop, a instrução
para desenhar a coluna vem antes da instrução para desenhar o plano de fundo.

E:\Atari\SDK\doc\Tutorial.doc Página 140 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Portanto nosso .scanLoop deve ficar assim:

...
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
sty COLUBK ;muda cor de fundo
tya ;A = Y
adc wScroll ;A = A + wScroll
pha ;salva A
ldx wDesloca ;X = wDesloca
;
.horiz ;
dex ;X = X - 1
bne .horiz ;X = 0? não, continua decrementando X
pla ;restaura A
tax ;X = A
stx COLUBK ;muda a cor de fundo
nop ;a tela é desenhada com essa nova cor,
nop ;nesse tempo, apresentando uma coluna
nop ;os 3 NOPs determinam a largura da coluna
sty COLUBK ;muda cor de fundo
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
...

Ou seja, devemos desenhar o plano de fundo antes de desenhar a coluna. Só que isso
acarreta em outro problema. Veja a figura 81.

Figura 81

Quando a coluna está mais para a direita, o valor de X é alto e o loop .horiz consome muito
tempo para causar o delay na coluna. Isso somado ao tempo total do .scanLoop ultrapassa

E:\Atari\SDK\doc\Tutorial.doc Página 141 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

os 76 ciclos por scanline. A imagem se deformou. Devemos então conseguir ciclos. Para
isso basta tirar 2 dos 3 NOPs. A coluna ficará mais estreita, mas a imagem volta a se
estabilizar.

...
.aguardaVBLANK ;
lda INTIM ;timer é zero?
bne .aguardaVBLANK ;não. aguarda fim do VBLANK
sta VBLANK ;desliga o VBLANK
;
.scanLoop ;
sta WSYNC ;
sty COLUBK ;muda cor de fundo
tya ;A = Y
adc wScroll ;A = A + wScroll
pha ;salva A
ldx wDesloca ;X = wDesloca
;
.horiz ;
dex ;X = X - 1
bne .horiz ;X = 0? Não, continua decrementando X
pla ;restaura A
tax ;X = A
stx COLUBK ;aqui mudamos o valor do COLUBK
nop ;a tela é desenhada com essa nova cor nesse
;tempo
.naoMudaCorDaLinha ;
sty COLUBK ;muda cor de fundo
dey ;Y = Y - 1
bne .scanLoop ;vai para .scanLoop se Y > 0
sty COLUBK ;Y = 0 então COLUBK = cor preta
lda #$1E ;A = 1Eh = 30d
sta TIM64T ;setamos o timer
;
.overScan ;
lda INTIM ;timer é zero?
bne .overScan ;não. aguarda fim do overscan
jmp .inicioDoFrame ;vai para o próximo frame ...

Note que utlizamos o timer também para a parte do overscan. Assim podemos colocar mais
lógica de programa nesse tempo sem termos que nos preocupar com a contagem de ciclos.
Subtituimos o loop com 30 WSYNCs pelo timer, como fizemos no VBLANK.

Fizemos tudo isso, tivemos todo esse trabalho, para mostrar o que ocorre quando
escrevemos o programa de forma errada e os ciclos não correspondem ao sincronismo. Há
várias formas de corrigir os erros. Essa aqui apresentada pode não ser a melhor, mas para
fins didáticos dá uma boa noção do que acontece quando instruções certas estão no lugar e
tempo errados. Mostra também que quando se muda um STA WSYNC de lugar, muita coisa
pode acontecer com a imagem. Vale a pena testar o STA WSYNC em vários lugares e ver o
que acontece. Em alguns kernels ele vem no início do loop. Em outros ele vem no fim, e
ainda em outros, ele vem em lugar totalmente distinto... depende exclusivamente do que
(quando e quanto) se está desenhando. Não nos esqueçamos de mudar o timer para o início
do quadro. Assim eliminamos as linhas escuras no topo da imagem (figura 85, código na
página 138).

E:\Atari\SDK\doc\Tutorial.doc Página 142 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 85

Fazer uma imagem aparecer na tela de forma correta é um tanto quanto trabalhoso. O
programa apresentado ainda tem um bug. Fique observando a tela (o programa sendo
executado) e note que, quando a coluna chega no meio da tela (horizontalmente falando),
toda a imagem sobe 1 (uma) linha.

Aqui mostramos como uma instrução pode consertar, estragar as coisas ou simplesmente
mascarar algo que está errado. O ideal é manter tudo dentro de seus limites. As linhas
escuras no início do quadro poderão ser inevitáveis. Depende do kernel. Diminuindo-se o
valor do timer pode-se suprimi-las. Tudo é uma questão de tentativa e erro. De início, teste
valores, mude instruções de lugar. Quando pegar a prática (e a lógica da coisa) vai ver que é
bem fácil ajustar a apresentação de uma imagem. Nesse tutorial vamos apresentar tudo de
forma, digamos, principiante. Não se assustem (os programadores experts, os lords da
programação para 2600). Esse tutorial é para os iniciantes. Estamos tentando mostrar como
começar. O refinamento caberá a cada um. Nas próximas páginas veremos como desenhar
jogadores, playfield e usar o joystick. Veremos ainda como posicionar um objeto na tela, o
míssil, a bola e testar quando um objeto colide com o outro.

A seguir, uma sugestão de estrutura de um programa. Ela serve para a grande maioria dos
kernels. Nada nos impede de testar variações (que existem aos montes).

E:\Atari\SDK\doc\Tutorial.doc Página 143 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
;=================================================================================
;Macros
;=================================================================================
;
;<<< suas macros aqui ou no arquivo MACRO.H
;
;=================================================================================
;Constantes
;=================================================================================
;
;<<< suas constantes aqui
;
;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
;<<< suas variáveis aqui
;
;---------------------------------- ;
;
echo (*-$80)d, " Bytes da RAM usados.";
echo ($100-*)d," Bytes da RAM livres.";
;
;=================================================================================
;Código
;=================================================================================
Seg Codigo ;
org $F000 ;
;
.inicioDoJogo ;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
;
;<<< aqui seta-se as variáveis e registradores
;
;---------------------------------- ;
;Loop Principal ;
;---------------------------------- ;
.inicioDoFrame ;início do frame
lda #$2B ;2Bh = 43d
sta TIM64T ;seta o timer
lda #$02 ;liga o VSYNC
sty VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;

E:\Atari\SDK\doc\Tutorial.doc Página 144 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sta VBLANK ;liga o VBLANK


;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
;
;<<< seu código aqui
;
;---------------------------------- ;
;Aguarda VBLANK ;
;---------------------------------- ;
.aguardaVBLANK ;
lda INTIM ;timer = 0?
bne .aguardaVBLANK ;não. aguarda fim do VBLANK
sta VBLANK ;liga o VBLANK
;
;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
sta WSYNC ;próximo scanline
;<<< nessa parte vai alguma lógica (pouca
;<<< coisa) como mover objetos horizontalmente
;<<< que deve acontecer depois do VBLANK. Não
;<<< exagere aqui, pois a TV já está desenhando
;
;geralmente tem-se a linha abaixo
ldy #$C0 ;Y = 192 scanlines
;---------------------------------- ;
;Scanlines ;
;---------------------------------- ;
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
;
;<<< seu código aqui
;
dey ;desenhou toda a tela?
bne .scanLoop ;não. desenha próxima linha
;
;<<< zerar registradores aqui
;
lda #$1E ;A = 30 linhas de overscan
sta TIM64T ;seta timer
;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
;
;<<< aqui podemos ter mais lógica de jogo
;
;---------------------------------- ;
;Overscan ;
;---------------------------------- ;
.overScan ;
lda INTIM ;timer = 0?
bne .overScan ;não. aguarda fim do overscan
jmp .inicioDoFrame ;vai para o próximo frame
;---------------------------------- ;
;Tabelas ;
;---------------------------------- ;
;
;<<< suas tabelas aqui
;
;---------------------------------- ;
;Gráficos ;
;---------------------------------- ;
;

E:\Atari\SDK\doc\Tutorial.doc Página 145 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;<<< seus gráficos aqui


;
;---------------------------------- ;
;
echo (*-$F000)d," Bytes da ROM usados." ;
echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

O básico de um programa para o 2600 é o apresentado. Mas não é regra. Por exemplo:
• O valor de Y para o scanLoop não é necessariamente 192 linhas. Se você pretende
fazer um jogo que usa somente metade da tela (verticalmente falando), então Y terá o
valor 96.
• Depois do .aguardaVBLANK pode-se ter algum código, mas não pode ser muita
coisa, pois ali já estamos na área de desenho na tela. Se escrevermos muita coisa,
vai aparecer na tela uma ou mais linhas que não fazem parte da imagem, de tamanho
que depende do tempo que leva para executar o que foi escrito até o próximo
WSYNC. Pode-se evitar isso colocando um WSYNC depois desse código, mas daí
desperdiçamos 1 linha da tela. Normalmente, nesse lugar colocamos o HMOVE e
RESPx (que veremos a seguir) para movimentar os objetos horizontalmente na tela.
• O STA WSYNC dentro do .scanLoop não tem que estar no início, depende do que
(quando e quanto) vai ser desenhado.
• O DEY dentro do .scanLoop não tem que estar no fim. Entretanto o teste se toda a
tela foi desenhada deve ser respeitado.
• Não tem que ser necessariamente o registrador Y para o .scanLoop. Pode ser o X ou
ainda uma variável de memória. No caso do X usaríamos DEX no lugar do DEY e no
caso de uma variável usaríamos DEC variável no lugar do DEY.

Dica de programação: como assembly é uma linguagem de difícil compreensão, é boa


prática sempre comentar as linhas/rotinas. Linguagens de programação de nível mais alto,
às vezes, são tão intuitivas e com comandos/sintaxe geralmente inteligíveis que dispensam
comentários por todo o programa. Em assembly não é assim. Você pode até saber o que
está fazendo, mas depois quando precisar voltar em alguma parte do código, se não tiver
comentário, vai demorar um pouco para descobrir o que essa parte faz.

É interessante também sempre utilizar constantes. Assim para valores temos nomes mais
sugestivos. Por exemplo:

a)
lda #$07
sta COLUBK

b)
COR_CINZA = $07

lda #COR_CINZA
sta COLUBK

Às vezes, no meio do código, é preferível nos depararmos como no exemplo B (pois já


descobrimos que estamos colocando a cor cinza como plano de fundo) do que no exemplo
A, que nos obriga a saber que 07h é a cor cinza.

E:\Atari\SDK\doc\Tutorial.doc Página 146 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Desenhando gráficos
Nesta sessão veremos como desenhar jogadores e playfield. Veremos como posicionar o
jogador na tela, detecção de colisão e utilização do joystick que no nosso caso será o
teclado, a menos que você possua um joystick conectado em seu computador.

Desenhando um jogador
Conforme descrito na página 80 desse tutorial, para desenhar um jogador basta acessarmos
GRP0 para o jogador 0 e GRP1 para o jogador 1. O gráfico nada mais é do que uma matriz
de pontos que, dependendo de quais pontos estão ativos, a imagem se forma (vide exemplo
na página 68 desse tutorial).

Vamos a um exemplo clássico. Supondo que nosso jogador será:

Dividimos ele em uma matriz para termos uma noção de cada pixel do jogador:

Agora, atribuindo 1 aos pixels amarelos e 0 aos pixels pretos temos:

Zeros Uns Zeros e Uns Decimal Hexadecimal Shape


00011000 00011000 00011000 24d 18h ...XX...
00111100 00111100 00111100 60d 3Ch ..XXXX..
01111110 01111110 01111110 126d 7Eh .XXXXXX.
11011011 11011011 11011011 219d DBh XX.XX.XX
11111111 11111111 11111111 255d FFh XXXXXXXX
10111111 10111111 10111111 191d BFh X.XXXXXX
11000011 11000011 11000011 195d C3h XX....XX
01111110 01111110 01111110 126d 7Eh .XXXXXX.
00111100 00111100 00111100 60d 3Ch ..XXXX..
00011000 00011000 00011000 24d 18h ...XX...

E:\Atari\SDK\doc\Tutorial.doc Página 147 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Temos assim os valores a serem carregados em GRP0 para desenhar o jogador 0. Para
fazer isso, basta criarmos um loop que, a cada passo, lê uma linha dessa matriz de bits e
coloca o byte (cada linha é 1 byte) no GRP0.

Nota: Para desenhar gráficos de forma fácil e rápida utilizaremos o programa AtariPaint
(figura 86), feito em Visual Basic 6 que, diga-se de passagem, eu mesmo escrevi. Pela falta
de criatividade para desenhar, não gostar muito do TiaPaint (programa para DOS que
desenha playfield) e o CAG (programa descontinuado em Java para desenhar objetos para o
Atari) não estar 100% completo, resolvi escrever esse programa para desenhar jogadores,
playfield e sprites diversos. Talvez futuramente eu escreva um editor de programa. Daí será
uma IDE completa.

Figura 86

A figura 87 mostra um objeto desenhado. Note que nesse caso, as células que representam
os olhos e a boca (com um sorriso que parece um quadro de Da Vinci) não estão na cor
preta e sim tiveram sua cor apagada. Daí, essas células serão o bit zero na matriz de bits
conforme a figura 88.

E:\Atari\SDK\doc\Tutorial.doc Página 148 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 87

Apesar de a interface do programa ser intuitiva, segue alguns apontamentos:

• Clicando-se com o botão esquerdo do mouse sobre a paleta de cores, seleciona-se a


cor do objeto.
• Clicando-se com o botão direito do mouse sobre a paleta de cores, seleciona-se a cor
de fundo. A cor de fundo é só para dar uma melhor visualização de como a coisa vai
ficar no 2600, não sendo registrada no código gerado.
• Clicando-se com o botão esquerdo do mouse sobre a grade (matriz), pinta-se a célula
com a cor escolhida.
• Clicando-se com o botão direito do mouse sobre a grade (matriz), alterna para o
modo de apagamento. Daí clica-se com o botão esquerdo do mouse sobre a célula
que deseja apagar a cor previamente setada.
• Clicando-se novamente com o botão direito do mouse sobre a grade (matriz), retorna
para o modo de desenho.
• Os botões + e – são zoom in e zoom out respectivamente
• O espaço abaixo dos botões de zoom mostra o registrador do playfield de acordo com
a posição do cursor do mouse sobre a grade (matriz).
• Pode-se desenhar/apagar com apenas 1 clique (apenas 1 célula) ou selecionar uma
região.
• No playfield, desenhar/apagar por seleção de região será realizado somente dentro
do registrador atual. Por exemplo, se clicar no PF0 (que tem apenas 4 bits) e
selecionar 10 colunas, quando soltar o botão do mouse, apenas 4 colunas serão
desenhadas (se começou na coluna 0).
• Ao selecionar outra cor e clicar em uma célula que já esteja pintada, toda a linha
E:\Atari\SDK\doc\Tutorial.doc Página 149 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

daquela célula será repintada com a nova cor, e não apenas a célula clicada.
Entretanto, se há células apagadas, essas permanecerão apagadas. Somente as
células que possuem a mesma cor da célula clicada serão repintadas.
• Pintar uma célula/linha/região de preto é diferente de apagar. Quando se apaga, a
célula/linha/região apagada representará o zero na matriz de bits e o 2600 mostrará a
cor de fundo (COLUBK) no lugar desse pixel. Quando pinta-se com a cor preta, a
célula/linha/região colorida representará o bit 1 na matriz de bits e o 2600 mostrará a
cor preta independente da cor de fundo.
• O botão Gera montará a matriz de bits para o objeto desenhado (figura 88).
• A caixa de texto abaixo do botão Gera exibe o label padrão para o objeto. Pode-se
alterá-la.
• Nos modos Simétrico e Refletido, basta desenhar a metade esquerda do playfield.

Figura 88

A figura 88 mostra nossa matriz de bits. Pode-se salvar o arquivo e depois incluí-lo no
programa principal ou então copiar o texto e colar no programa principal. A linha colorTable
contém bytes que representam a cor de cada linha do objeto. Como utilizamos apenas o
amarelo, todos os bytes são 0Fh. Assim, no nosso programa não precisamos utilizar essa
tabela de cores. Podemos simplesmente declarar que COLUP0 é 0Fh e pronto. Se nosso
objeto tivesse mais de 1 cor, ou seja, se por exemplo ele fosse um arco-iris, daí sim a linha
colorTable teria valores diversos e faria sentido utilizá-la no programa principal (para cada
linha lida do objeto, lê-se 1 byte da tabela de cores). De qualquer forma, o programa gera o
colorTable. Utilizá-lo ou não depende da necessidade de cada um.

E:\Atari\SDK\doc\Tutorial.doc Página 150 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Vamos agora escrever um programa que exibe o jogador 0 e depois analisar alguns pontos.

Utilizando o modelo apresentado nas páginas 144, 145 e 146 desse tutorial, altere as
sessões conforme abaixo:

;=================================================================================
;Constantes
;=================================================================================
COR_AZUL = #$80 ;cor azul
COR_AMARELA = #$1E ;cor amarela
;
;=================================================================================
;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
YPosFromBot ds 1 ;distância em relação a parte de baixo da tela
;
;---------------------------------- ;
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda #COR_AZUL ;cor azul
sta COLUBK ;plano de fundo
lda #COR_AMARELA ;cor amarela
sta COLUP0 ;player0
lda #$80 ;posição em relação a parte inferior da tela
sta YPosFromBot ;salva em YPostFromBot
;
;---------------------------------- ;
;---------------------------------- ;
;Scanlines ;
;---------------------------------- ;
.scanLoop ;
sta WSYNC ;aguarda 1 scanline
tya ;A = Y
sec ;carry = 1
sbc YPosFromBot ;posição em relação à parte inferior da tela
adc #PLAYER0HEIGHT ;adiciona a altura do player
bcc .naoDesenha ;hora de desenhar? não. vá para .naoDesenha
tax ;X = A
lda Player0,x ;carrega Player0[x]
.byte $2C ;pula próxima instrução
;
.naoDesenha ;
lda #$00 ;pára de desenhar
sta GRP0 ;coloca valor em GRP0
dey ;desenhou toda a tela?
bne .scanLoop ;não. desenha próxima linha
;
;<<< zerar registradores aqui
;
lda #$1E ;A = 30 linhas de overscan
sta TIM64T ;seta timer
;---------------------------------- ;

E:\Atari\SDK\doc\Tutorial.doc Página 151 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;---------------------------------- ;
;Gráficos ;
;---------------------------------- ;
Player0 ;
.byte %00011000 ; | XX |
.byte %00111100 ; | XXXX |
.byte %01111110 ; | XXXXXX |
.byte %11000111 ; |XX XXX|
.byte %10111111 ; |X XXXXXX|
.byte %11111111 ; |XXXXXXXX|
.byte %11011011 ; |XX XX XX|
.byte %01111110 ; | XXXXXX |
.byte %00111100 ; | XXXX |
.byte %00011000 ; | XX |
;
PLAYER0HEIGHT = *-Player0 ;altura do player (número de linhas)
;---------------------------------- ;

Compile e rode o programa. A tela apresentada deve ser como a da figura 89.

Por que ele é invertido verticalmente (está de cabeça para baixo)? Porque aproveitamos o
loop do .scanLoop para desenhar o gráfico. Como tipicamente o registrador usado para o
loop tem seu valor decrementado, então começamos a ler o gráfico de seu offset final para o
offset inicial. Entretanto, nada nos impede de criarmos um loop crescente e assim começar a
ler o gráfico a partir do offset inicial e desenhá-lo então de cabeça para cima, porém, um
loop crescente gastaria mais tempo de máquina e mais bytes da ROM (alguma coisa em
torno de 2 ou 3 ciclos e bytes) pois temos que realizar a comparação via instrução CMP (ou
CPX ou CPY). E como na programação do 2600 tempo e byte são fundamentais, é melhor
desenharmos de cabeça para baixo do que gastar tempo de máquina e espaço da ROM.

Definimos as constantes, inicializamos os registradores de cor e a variável que determina a


posição vertical inicial do player em relação à parte inferior da tela.

No loop, colocamos em A o valor de Y, ou seja, a linha que está sendo desenhada.


Subtraímos o valor de YPosFromBot e adicionamos a altura do player. Por exemplo, se Y for
A0h, subtraímos 80h (do nosso caso) = 20h e somamos a altura do player 0Ah = 2Ah. Nesse
momento é adicionado também o carry e ele então passa de 1 para 0. O carry era 1 porque
setamos ele com o SEC antes da subtração e nesse caso ele continuou 1 porque o
resultado da subtração não precisou do “vem um”, ou seja, o subtraendo (80h) é menor que
o minuendo (A0h). Com isso, o desvio BCC ocorre fazendo com que o fluxo vá para
.naoDesenha. Nessa linha, é colocado o valor 0 em A e logo em seguida armazenado em
GRP0, como se estivéssemos desenhando o player com bits 0, assim em vez de ser
desenhado um pixel com a cor do player, não é desenhado nenhum pixel, ficando visível a
cor de fundo da tela. Em outras palavras, estamos desenhando o player com nada.

Mas quando Y = 7Fh e subtraímos 80h, o carry passa para 0 já na subtração e A = FFh.
Somamos 0Ah e A fica com 09h e o carry agora é 1. Então, o desvio não ocorre no BCC e a
linha TAX é executada. X agora tem o valor de A e em seguida colocamos em A o valor
endereçado por Player0[X] (que é a última linha da matriz de bits do nosso player). A linha
do programa .BYTE $2C diz ao 6502 para pular a próxima instrução do programa, assim a
linha LDA #$00 não é executada. Com isso, A permanece com o valor que estava (a última
linha da matriz de bits) e o 6502 coloca esse valor em GRP.

E:\Atari\SDK\doc\Tutorial.doc Página 152 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Esse processo continua até desenhar todo o player. Após a primeira linha da matriz de bits
ser desenhada, Y está com o valor 75h. Subtraindo-se 80h temos F5h. Adicionando-se 0Ah
do player temos FFh, não alterando o estado do carry (continua com 0). Sendo assim o
desvio BCC ocorre, colocando em A o valor zero e armazenando-o em GRP0.

Sem esse controle o nosso player ficaria como na figura 90.

Figura 89

O teste BCC determina o início (quando começar a desenhar) e o fim (quando parar de
desenhar) o player.

E:\Atari\SDK\doc\Tutorial.doc Página 153 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 90

Posicionando o gráfico
Agora vamos fazer nosso jogador aparecer onde quisermos na tela. Para alterar a posição
vertical inicial é só alterarmos o valor de YPosFromBot. Para alterar a posição horizontal
inicial basta alterar o programa conforme segue:

;---------------------------------- ;
;Aguarda VBLANK ;
;---------------------------------- ;
.aguardaVBLANK ;
lda INTIM ;timer = 0?
bne .aguardaVBLANK ;não. aguarda fim do VBLANK
sta VBLANK ;liga o VBLANK
;
;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
sta WSYNC ;próximo scanline
lda #$8C ;lado direito
sta HMP0 ;move para a direita -8 clocks
and #$0F ;mascara high nibble
tay ;Y = A
;
.posicionaPlayer0 ;
dey ;Y = Y-1
bne .posicionaPlayer0 ;decrementa até atingir a posição
sta RESP0 ;reseta player0 nesse clock
sta HMOVE ;posiciona player
;---------------------------------- ;

Para maiores detalhes sobre os endereços RESPx, HMPx e HMOVE vide páginas 83 e 84
desse tutorial.

E:\Atari\SDK\doc\Tutorial.doc Página 154 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

As instruções de reset fixam a posição do objeto no lugar da linha horizontal da tela onde o
clock se encontra. Utilizamos um algoritmo para gastar ciclos necessários e chamar um
reset. Com isso o objeto associado será colocado nessa posição quando se escrever nos
seus registradores.

O HMOVE provoca o movimento horizontal do objeto de acordo com sua posição inicial,
segundo os valores de seu registrador de movimento horizontal (HMPx, HMMx, HMBL). Para
que o objeto se mova horizontalmente, não basta escrever nos registradores de movimento
horizontal e sim, deve-se invocar imediatamente depois de um WSYNC, para que comece a
contar exatamente no começo do scanline. Como todo strobe, não importa o valor que se
escreve no HMOVE.

Regsitradores de movimento horizontal


Com os valores de -8 a +7, indica-se o movimento horizontal relativo à posição atual dos
objetos. Deve utilizar com o HMOVE.

Nota: Não se deve atualizar os registradores HMxxx depois que já se passaram 24 ciclos de
máquina pois resultados inesperados podem ocorrer.

O HMCLR limpa os 5 registradores HMxxx.

O formato do HMOVE é uma maneira de se alcançar coordenadas precisas de um objeto. O


nibble baixo (low nibble) contém a posição bruta (ou baixa precisão) e indica quantas vezes
deve-se fazer um loop (ou ciclo de delay) de 5 ciclos cada um até posicionar o objeto, o qual
nos dá uma precisão de 15 pixels. O nibble alto (high nibble) indica a posição fina (ou
precisa) do objeto escrevendo-se um número entre -8 e +7 em HMPx. Os números negativos
movem o objeto para a direita e os positivos para a esquerda, lembrando que esse
movimento só se realiza quando se executa HMOVE.

Uma tabela como a mostrada a seguir é necessária para que o algoritmo de posicionamento
funcione.

Nota: a tabela não deve cruzar um limite de página

horizTable
.byte $00,$F0,$E0,$D0,$C0,$B0,$A0,$90
.byte $71,$61,$51,$41,$31,$21,$11,$01,$F1,$E1,$D1,$C1,$B1,$A1,$91
.byte $72,$62,$52,$42,$32,$22,$12,$02,$F2,$E2,$D2,$C2,$B2,$A2,$92
.byte $73,$63,$53,$43,$33,$23,$13,$03,$F3,$E3,$D3,$C3,$B3,$A3,$93
.byte $74,$64,$54,$44,$34,$24,$14,$04,$F4,$E4,$D4,$C4,$B4,$A4,$94
.byte $75,$65,$55,$45,$35,$25,$15,$05,$F5,$E5,$D5,$C5,$B5,$A5,$95
.byte $76,$66,$56,$46,$36,$26,$16,$06,$F6,$E6,$D6,$C6,$B6,$A6,$96
.byte $77,$67,$57,$47,$37,$27,$17,$07,$F7,$E7,$D7,$C7,$B7,$A7,$97
.byte $78,$68,$58,$48,$38,$28,$18,$08,$F8,$E8,$D8,$C8,$B8,$A8,$98
.byte $79,$69,$59,$49,$39,$29,$19,$09,$F9,$E9,$D9,$C9,$B9,$A9,$99
.byte $7A,$6A,$5A,$4A,$3A,$2A,$1A,$0A,$FA,$EA,$DA,$CA,$BA,$AA,$9A

O algoritmo é muito simples. Exemplo:

Supomos que queremos posicionar o player0 no pixel 33, então:


sta WSYNC ;começa scanline
ldx #$21 ;+3 [3] X = 33d
lda horizTable,x ;+4 [7]

E:\Atari\SDK\doc\Tutorial.doc Página 155 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O código acima pega o valor que está na tabela na posição horizTable[33] que é D2h.

sta HMP0 ;+3 [10]

Colocamos a posição fina em HMP0. O TIA ignora os 4 bits do low nibble do byte (vide
página 84 desse tutorial que o HMP0 só usa o high nibble – 4 bits altos – do byte) e mantém
os 4 bits altos em HMP0, ou seja, o high nibble fica com o valor Dh que, em um formato de 4
bits é -3. Entretanto, não se faz nada com esse valor.

and #$0F ;+2 [12]

Agora mascaramos o high nibble do valor que está em A (D2h). Com o mascaramento do
high nibble, A passa a ser 2h (vide página 51 desse tutorial), ou seja, o acumulador agora
contém a posição bruta (ou de baixa precisão) que é o número de vezes que queremos que
haja o ciclo de delay. No nosso caso, 2 vezes.

tax ;+2 [14]

Colocamos o valor em X que utilizaremos como contador no nosso loop de delay.

.posicionaPlayer0 ;
dex ;+2 [16] X = X-1
bpl .posicionaPlayer0 ;+2 [18 + x*5]

O processamento do loop acima pode ser descrito como:

dex ;+2 [16] X = 1


bpl ;+3 [19] pois o desvio ocorre
dex ;+2 [21] X = 0
bpl ;+3 [24] pois o desvio ocorre
dex ;+2 [26] X = -1 = FFh
bpl ;+2 [28] pois o desvio não ocorre

Por fim, posicionamos o player.

sta RESP0 ;+3 [21 + x*5]

Isso gasta 3 ciclos assim que completamos 31 ciclos até esse ponto. 31 ciclos x 3
pixels/ciclo = 93 pixels desde o último WSYNC. Já que o tempo do branco horizontal é de 68
pixels, o player deveria começar a ser desenhado no pixel 93 – 68 = 25. Porém, por alguma
razão, o TIA mostra o player 5 pixels depois do que deveria ser, ou seja, começa a mostrar o
player no pixel 30. Esses 5 pixels já estão contemplados na tabela.

sta WSYNC ;próximo scanline


sta HMOVE ;move o player

Essa última instrução ativa o movimento fino que havíamos colocado no HMP0. Já que
HMP0 contém -3, o TIA move o player 3 pixels para a direita, até o pixel 33. Pronto. Simples.

Isso funciona para players. Porém o delay de pixels é só de 4 ciclos para mísseis e bola,
assim, para esses objetos em vez de fazer LDA horizTable,X fazemos LDA horizTable+1,X.

Para o nosso programa, fazer as seguintes alterações:

E:\Atari\SDK\doc\Tutorial.doc Página 156 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;=================================================================================
;Constantes
;=================================================================================
COR_AZUL = #$80 ;cor azul
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posiciona no pixel 33d
;
;=================================================================================
;---------------------------------- ;
;Aguarda VBLANK ;
;---------------------------------- ;
.aguardaVBLANK ;
lda INTIM ;timer = 0?
bne .aguardaVBLANK ;não. aguarda fim do VBLANK
sta VBLANK ;liga o VBLANK
;
;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
sta WSYNC ;próximo scanline
ldx #PLAYER0HPOS ;posição horizontal do player0
lda horizTable,x ;lê valor na tabela
sta HMP0 ;posiciona player0
and #$0F ;mascara high nibble
tax ;X = A
;
.posicionaPlayer0 ;
dex ;X = X-1
bpl .posicionaPlayer0 ;decrementa até atingir a posição
sta RESP0 ;reseta player0 nesse clock
sta WSYNC ;próximo scanline
sta HMOVE ;posiciona player0
;---------------------------------- ;
;---------------------------------- ;
;Tabelas ;
;---------------------------------- ;
horizTable ;
.byte $00,$F0,$E0,$D0,$C0,$B0,$A0,$90
.byte $71,$61,$51,$41,$31,$21,$11,$01,$F1,$E1,$D1,$C1,$B1,$A1,$91
.byte $72,$62,$52,$42,$32,$22,$12,$02,$F2,$E2,$D2,$C2,$B2,$A2,$92
.byte $73,$63,$53,$43,$33,$23,$13,$03,$F3,$E3,$D3,$C3,$B3,$A3,$93
.byte $74,$64,$54,$44,$34,$24,$14,$04,$F4,$E4,$D4,$C4,$B4,$A4,$94
.byte $75,$65,$55,$45,$35,$25,$15,$05,$F5,$E5,$D5,$C5,$B5,$A5,$95
.byte $76,$66,$56,$46,$36,$26,$16,$06,$F6,$E6,$D6,$C6,$B6,$A6,$96
.byte $77,$67,$57,$47,$37,$27,$17,$07,$F7,$E7,$D7,$C7,$B7,$A7,$97
.byte $78,$68,$58,$48,$38,$28,$18,$08,$F8,$E8,$D8,$C8,$B8,$A8,$98
.byte $79,$69,$59,$49,$39,$29,$19,$09,$F9,$E9,$D9,$C9,$B9,$A9,$99
.byte $7A,$6A,$5A,$4A,$3A,$2A,$1A,$0A,$FA,$EA,$DA,$CA,$BA,$AA,$9A
;
;---------------------------------- ;

Nota: o valor de PLAYER0HPOS deve estar entre 0 e 159 pois, recordando, a resolução
horizontal da tela é de 160 pixels.

Anteriormente dissemos que a tabela não deve ultrapassar um limite de página. Para
verificar se a tabela está ultrapassando um limite de página, basta criarmos uma macro que
fará esse teste em tempo de compilação. Caso o limite de página seja ultrapassado, o
DASM emitirá um erro e abortará a compilação.

Para incluir essa macro, coloque-a no arquivo MACRO.H ou diretamente no código


conforme alterações a seguir.

E:\Atari\SDK\doc\Tutorial.doc Página 157 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;=================================================================================
;Macros
;=================================================================================
MAC CHECKPAGE
IF >. != >{1}
ECHO ""
ECHO "ERRO: Páginas diferentes! (", {1}, ",", ., ")"
ECHO ""
ERR
ENDIF
ENDM
;
;=================================================================================
;---------------------------------- ;
;Tabelas ;
;---------------------------------- ;
horizTable ;
.byte $00,$F0,$E0,$D0,$C0,$B0,$A0,$90
.byte $71,$61,$51,$41,$31,$21,$11,$01,$F1,$E1,$D1,$C1,$B1,$A1,$91
.byte $72,$62,$52,$42,$32,$22,$12,$02,$F2,$E2,$D2,$C2,$B2,$A2,$92
.byte $73,$63,$53,$43,$33,$23,$13,$03,$F3,$E3,$D3,$C3,$B3,$A3,$93
.byte $74,$64,$54,$44,$34,$24,$14,$04,$F4,$E4,$D4,$C4,$B4,$A4,$94
.byte $75,$65,$55,$45,$35,$25,$15,$05,$F5,$E5,$D5,$C5,$B5,$A5,$95
.byte $76,$66,$56,$46,$36,$26,$16,$06,$F6,$E6,$D6,$C6,$B6,$A6,$96
.byte $77,$67,$57,$47,$37,$27,$17,$07,$F7,$E7,$D7,$C7,$B7,$A7,$97
.byte $78,$68,$58,$48,$38,$28,$18,$08,$F8,$E8,$D8,$C8,$B8,$A8,$98
.byte $79,$69,$59,$49,$39,$29,$19,$09,$F9,$E9,$D9,$C9,$B9,$A9,$99
.byte $7A,$6A,$5A,$4A,$3A,$2A,$1A,$0A,$FA,$EA,$DA,$CA,$BA,$AA,$9A
;
CHECKPAGE horizTable ;testa se ultrapassou limite de página
;
;---------------------------------- ;

Criamos a macro e a colocamos logo abaixo da tabela. Assim o compilador verificará se a


tabela está ultrapassando limites de página. No caso acima, o erro da figura 91 será gerado:

Figura 91

Para solucionar esse problema, devemos mover nossa tabela para um local onde ela fique
toda dentro de uma única página de memória. Temos 2 formas de fazer isso:

1 – mover a tabela dentro do código do programa de um lugar para outro até encontrar um
local adequado, por exemplo, mover a tabela para o início do código (início da ROM)
2 – fazer com que o compilador alinhe a tabela para a próxima página

Para alinhar a tabela basta fazer a seguinte alteração no programa:

E:\Atari\SDK\doc\Tutorial.doc Página 158 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;---------------------------------- ;
;Tabelas ;
;---------------------------------- ;
align 256 ;alinha para a próxima página
;
horizTable ;
.byte $00,$F0,$E0,$D0,$C0,$B0,$A0,$90
.byte $71,$61,$51,$41,$31,$21,$11,$01,$F1,$E1,$D1,$C1,$B1,$A1,$91
.byte $72,$62,$52,$42,$32,$22,$12,$02,$F2,$E2,$D2,$C2,$B2,$A2,$92
.byte $73,$63,$53,$43,$33,$23,$13,$03,$F3,$E3,$D3,$C3,$B3,$A3,$93
.byte $74,$64,$54,$44,$34,$24,$14,$04,$F4,$E4,$D4,$C4,$B4,$A4,$94
.byte $75,$65,$55,$45,$35,$25,$15,$05,$F5,$E5,$D5,$C5,$B5,$A5,$95
.byte $76,$66,$56,$46,$36,$26,$16,$06,$F6,$E6,$D6,$C6,$B6,$A6,$96
.byte $77,$67,$57,$47,$37,$27,$17,$07,$F7,$E7,$D7,$C7,$B7,$A7,$97
.byte $78,$68,$58,$48,$38,$28,$18,$08,$F8,$E8,$D8,$C8,$B8,$A8,$98
.byte $79,$69,$59,$49,$39,$29,$19,$09,$F9,$E9,$D9,$C9,$B9,$A9,$99
.byte $7A,$6A,$5A,$4A,$3A,$2A,$1A,$0A,$FA,$EA,$DA,$CA,$BA,$AA,$9A
;
CHECKPAGE horizTable ;testa se ultrapassou limite de página
;
;---------------------------------- ;

Com isso, não importa o tamanho do código que está antes da tabela. Ela sempre será
colocada na página seguinte à página em que o resto código terminou. Isso implica em
desperdício de bytes da ROM. Por exemplo, se nosso código vai de F000h até F06Ch, a
tabela será alocada em F101h, ficando F101h-F06Ch-01h = 94h bytes sem uso.

À medida em que for sendo escrito mais código, esses bytes serão utilizados e a tabela
continuará no mesmo lugar. Entretanto, se nosso código chegar por exemplo em F105h, a
tabela será alocada (em tempo de compilação) para a próxima página.

Nesse tutorial, para fins didáticos e manutenção do modelo de código fonte apresentado,
vamos usar o alinhamento em vez de mudar a tabela de lugar dentro do código.

Após compilado, rodamos o programa e o player0 deve aparecer na posição (pixel) 33 da


tela conforme a figura 92.

Alterando-se, então, o valor de PLAYER0HPOS, posicionamos o player0 em qualquer ponto


da tela (horizontal) e alterando-se o valor de YPosFromBot, posicionamos o player0 em
qualquer linha da tela.

E:\Atari\SDK\doc\Tutorial.doc Página 159 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 92

Utilizando o joystick
Agora vamos colocar interatividade na coisa. Utilizando o teclado (ou joystick se tiver
instalado), vamos mover o jogador por toda a tela. Para isso altere o programa conforme
segue:

;=================================================================================
;Constantes
;=================================================================================
COR_AZUL = #$80 ;cor azul
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posição horizontal inicial do player0
MOVER_ESQUERDA = #$10 ;high nibble = 1
MOVER_DIREITA = #$F0 ;high nibble = -1
;
;=================================================================================
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda #COR_AZUL ;cor azul
sta COLUBK ;plano de fundo
lda #COR_AMARELA ;cor amarela
sta COLUP0 ;player0
lda #$80 ;posição em relação a parte inferior da tela
sta YPosFromBot ;salva em YPostFromBot
sta WSYNC ;próximo scanline
ldx #PLAYER0HPOS ;posição horizontal do player0
lda horizTable,x ;lê valor na tabela
sta HMP0 ;posiciona player0
and #$0F ;mascara high nibble
tax ;X = A

E:\Atari\SDK\doc\Tutorial.doc Página 160 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;
.posicionaPlayer0 ;
dex ;X = X-1
bpl .posicionaPlayer0 ;decrementa até atingir a posição
sta RESP0 ;reseta player0 nesse clock
sta WSYNC ;próximo scanline
sta HMOVE ;posiciona player0
;
;---------------------------------- ;
;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
ldx #$00 ;assegura que o player não se movimentará
lda SWCHA ;lê endereço do joystick
lsr ;acima?
bcs .naoAcima1 ;não. verifica abaixo
;<<<seu código aqui
;
.naoAcima1 ;
lsr ;abaixo?
bcs .naoAbaixo1 ;não. verifica esquerda
;<<<seu código aqui
;
.naoAbaixo1 ;
lsr ;esquerda?
bcs .naoEsquerda1 ;não. verifica direita
;<<<seu código aqui
;
.naoEsquerda1 ;
lsr ;direita?
bcs .naoDireita1 ;não. verifica acima
;<<<seu código aqui
;
.naoDireita1 ;
lsr ;acima?
bcs .naoAcima0 ;não. verifica abaixo
inc YPosFromBot ;YPosFromBot = YPosFromBot+1
;
.naoAcima0 ;
lsr ;abaixo?
bcs .naoAbaixo0 ;não. verifica esquerda
dec YPosFromBot ;YPosFromBot = YPosFromBot-1
;
.naoAbaixo0 ;
lsr ;esquerda?
bcs .naoEsquerda0 ;não. verifica direita
ldx #MOVER_ESQUERDA ;
;
.naoEsquerda0 ;
lsr ;direita?
bcs .naoDireita0 ;não. verifica tiro
ldx #MOVER_DIREITA ;
;
.naoDireita0 ;
lda INPT5 ;botão de tiro pressionado?
bmi .naoBotao1 ;não. verifica botão do outro joystick
;
.naoBotao1 ;
lda INPT4 ;botão de tiro pressionado?
bmi .naoBotao0 ;não. continua
;
.naoBotao0 ;
stx HMP0 ;direção e velocidade do player0
;---------------------------------- ;

E:\Atari\SDK\doc\Tutorial.doc Página 161 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;---------------------------------- ;
;Aguarda VBLANK ;
;---------------------------------- ;
.aguardaVBLANK ;
lda INTIM ;timer = 0?
bne .aguardaVBLANK ;não. aguarda fim do VBLANK
sta VBLANK ;liga o VBLANK
;
;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
sta WSYNC ;próximo scanline
sta HMOVE ;posiciona player0
;
;---------------------------------- ;

Observe que retiramos o algoritmo de posicionamento horizontal do player da sessão Lógica


do Jogo abaixo do Aguarda VBLANK e colocamos esse algoritmo na sessão Inicializações.
Se deixássemos como estava, o player sempre ficaria no pixel 33 a cada frame, não se
movimentando pela tela. Colocando esse algoritmo na sessão Inicializações, o player é
colocado no pixel 33 apenas quando iniciamos o programa, ficando livre para se movimentar
dentro do loop principal do programa (.inicioDoFrame). Veja como ficou a sessão Aguarda
VBLANK e a sessão Inicializações.

Na sessão Lógica do jogo colocamos o código que lê o endereço dos joysticks e então
testamos seus bits. De acordo com cada bit, sabemos qual joystick foi acionado e sua
direção ou botão.

Caso tenha dúvida, leia novamente sobre a instrução LSR nas páginas 26 a 30 e sobre os
joysticks na página 90 desse tutorial.

Vamos dar aqui uma rápida visão do código. Colocamos em A o status de SWCHA e em
cada LSR deslocamos os bits para a direita. Se o bit mais à direita for 1, quando o LSR
executar, esse bit vai para o carry fazendo com que o desvio no BCS ocorra. Caso o bit seja
zero, o LSR faz esse zero ir para o carry e o BCS não ocorre, indicando assim que aquela
direção (ou botão) do joystick foi acionada(o).

Uma outra forma de testar os joysticks é dada a seguir.

;=================================================================================
;Constantes
;=================================================================================
COR_AZUL = #$80 ;cor azul
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posição horizontal inicial do player0
MOVER_ESQUERDA = #$10 ;high nibble = 1
MOVER_DIREITA = #$F0 ;high nibble = -1
ABAIXO1 = #%00000001 ;máscara joystick1 abaixo
ACIMA1 = #%00000010 ;máscara joystick1 acima
ESQUERDA1 = #%00000100 ;máscara joystick1 esquerda
DIREITA1 = #%00001000 ;máscara joystick1 direita
ABAIXO0 = #%00010000 ;máscara joystick0 abaixo
ACIMA0 = #%00100000 ;máscara joystick0 acima
ESQUERDA0 = #%01000000 ;máscara joystick0 esquerda
DIREITA0 = #%10000000 ;máscara joystick0 direita
;
;=================================================================================

E:\Atari\SDK\doc\Tutorial.doc Página 162 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
ldx #$00 ;assegura que o player não se movimentará
lda #ACIMA1 ;lê máscara para testar joystick
bit SWCHA ;acima?
bne .naoAcima1 ;não. verifica abaixo
;<<<seu código aqui
;
.naoAcima1 ;
lda #ABAIXO1 ;lê máscara para testar joystick
bit SWCHA ;abaixo?
bne .naoAbaixo1 ;não. verifica esquerda
;<<<seu código aqui
;
.naoAbaixo1 ;
lda #ESQUERDA1 ;lê máscara para testar joystick
bit SWCHA ;esquerda?
bne .naoEsquerda1 ;não. verifica direita
;<<<seu código aqui
;
.naoEsquerda1 ;
lda #DIREITA1 ;lê máscara para testar joystick
bit SWCHA ;direita?
bne .naoDireita1 ;não. verifica acima
;<<<seu código aqui
;
.naoDireita1 ;
lda #ACIMA0 ;lê máscara para testar joystick
bit SWCHA ;acima?
bne .naoAcima0 ;não. verifica abaixo
dec YPosFromBot ;YPosFromBot = YPosFromBot-1
;
.naoAcima0 ;
lda #ABAIXO0 ;lê máscara para testar joystick
bit SWCHA ;abaixo?
bne .naoAbaixo0 ;não. verifica esquerda
inc YPosFromBot ;YPosFromBot = YPosFromBot+1
;
.naoAbaixo0 ;
lda #ESQUERDA0 ;lê máscara para testar joystick
bit SWCHA ;esquerda?
bne .naoEsquerda0 ;não. verifica direita
ldx #MOVER_ESQUERDA ;
;
.naoEsquerda0 ;
lda #DIREITA0 ;lê máscara para testar joystick
bit SWCHA ;direita?
bne .naoDireita0 ;não. verifica tiro
ldx #MOVER_DIREITA ;
;
.naoDireita0 ;
lda INPT5 ;botão de tiro pressionado?
bmi .naoBotao1 ;não. verifica botão do outro joystick
;
.naoBotao1 ;
lda INPT4 ;botão de tiro pressionado?
bmi .naoBotao0 ;não. continua
;
.naoBotao0 ;
stx HMP0 ;direção e velocidade do player0
;---------------------------------- ;

E:\Atari\SDK\doc\Tutorial.doc Página 163 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Qual a diferença entre as 2 formas apresentadas? Simples. Na primeira forma, cada LSR
gasta 1 byte de ROM e 2 ciclos de máquina. Na segunda forma, cada LDA gasta 2 bytes de
ROM e também 2 ciclos de máquina. Além disso, cada BIT gasta 3 bytes de ROM e 4 ciclos
de máquina. Em resumo, a primeira forma gasta 1 byte e 2 ciclos em cada LSR, a segunda
forma gasta 5 bytes e 6 ciclos no par LDA e BIT.

Podemos concluir então que a primeira forma é mais econômica (nesse caso).

No nosso exemplo, a primeira forma utiliza o registrador A somente para testar a posição do
joystick. Se precisássemos desse registrador para outra coisa, dentro do teste, teríamos que
salvá-lo (PHA) e depois restaurá-lo (PLA). Daí, dependendo do número de PHAs e PLAs,
essa primeira forma já não seria tão vantajosa.

Já a segunda forma carrega em A as máscaras e lê o SWCHA toda vez que precisa, não
dependendo do que estava antes. Daí não precisamos salvar o que está em A. Tudo
depende da aplicação. Nos nossos exemplos não vamos nos preocupar com otimizações,
pois nosso tutorial tem propósitos didáticos. Por isso vamos gastar ciclos e bytes sem nos
preocuparmos com desperdício. Isso dará uma visão mais ampla e melhor entendimento de
como a coisa funciona. Mais uma vez, as otimizações ficarão por conta de cada um, com o
tempo.

O funcionamento do código é o seguinte: quando acionamos o joystick para baixo (para


trás), a variável YPosFromBot é incrementada, ou seja, a diferença entre o total de linhas da
tela e o valor da variável é cada vez menor. Assim o player é apresentado cada vez mais
perto da parte inferior da tela. O oposto ocorre quando acionamos o joystick para cima (para
frente).

Já para o movimento horizontal a coisa acontece quando escrevemos valores que indicam a
direção e velocidade (veja tabela na página 84 desse tutorial) no HMPx (no nosso exemplo
HMP0 para o player0). Assim que escrevemos em HMOVE, o player se move de acordo
com o que está em HMP0.

Agora vamos fazer com que o player “se vire” para a direção que está se movendo.

.naoAbaixo0 ;
lda #ESQUERDA0 ;lê máscara para testar joystick
bit SWCHA ;esquerda?
bne .naoEsquerda0 ;não. verifica direita
ldx #MOVER_ESQUERDA ;
ldy #$08 ;D3 do REFP0 = 1
sty REFP0 ;espelha o player0
;
.naoEsquerda0 ;
lda #DIREITA0 ;lê máscara para testar joystick
bit SWCHA ;direita?
bne .naoDireita0 ;não. verifica tiro
ldx #MOVER_DIREITA ;
ldy #$00 ;D3 do REFP0 = 0
sty REFP0 ;cancela espelhamento do player0
;
.naoDireita0 ;

Compile e rode o programa. Quando mover o player para a esquerda a imagem deve ser
como a da figura 93. Mova-o para a direita e esquerda alternadamente e veja a mudança
aparente. Isso só é possível de ser notado em gráficos assimétricos, por razões óbvias.

E:\Atari\SDK\doc\Tutorial.doc Página 164 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 93

Desenhando um playfield
Vamos desenhar um playfield simples para o nosso jogador. O playfield será como o da
figura 94. Faça as seguintes alterações no programa:

;=================================================================================
;Constantes
;=================================================================================
COR_AZUL = #$80 ;cor azul
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posição horizontal inicial do player0
MOVER_ESQUERDA = #$10 ;high nibble = 1
MOVER_DIREITA = #$F0 ;high nibble = -1
PLAYFIELD_REFLETIDO = #$01 ;playfield refletido
;
;=================================================================================
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda #COR_AZUL ;cor azul
sta COLUBK ;plano de fundo
lda #PLAYFIELD_REFLETIDO ;
sta CTRLPF ;playfield refletido
lda #$80 ;posição em relação a parte inferior da tela
sta YPosFromBot ;salva em YPostFromBot
sta WSYNC ;próximo scanline
ldx #PLAYER0HPOS ;posição horizontal do player0
lda horizTable,x ;lê valor na tabela
sta HMP0 ;posiciona player0
and #$0F ;mascara high nibble
tax ;X = A

E:\Atari\SDK\doc\Tutorial.doc Página 165 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;
.posicionaPlayer0 ;
dex ;X = X-1
bpl .posicionaPlayer0 ;decrementa até atingir a posição
sta RESP0 ;reseta player0 nesse clock
sta WSYNC ;próximo scanline
sta HMOVE ;posiciona player0
;
;---------------------------------- ;
.naoBotao0 ;
stx HMP0 ;direção e velocidade do player0
lda #COR_ROSA ;
sta COLUPF ;playfield cor de rosa
lda #COR_AMARELA ;cor amarela
sta COLUP0 ;player0
;---------------------------------- ;
;---------------------------------- ;
;Scanlines ;
;---------------------------------- ;
.scanLoop ;
lda PlayField0-1,y ;lê o playfield0[y]
sta PF0 ;
lda PlayField1-1,y ;lê o playfield1[y]
sta PF1 ;
lda PlayField2-1,y ;lê o playfield2[y]
sta PF2 ;
tya ;A = Y
sec ;carry = 1
sbc YPosFromBot ;posição em relação à parte inferior da tela
adc #PLAYER0HEIGHT ;adiciona a altura do player
bcc .naoDesenha ;hora de desenhar? não. vá para .naoDesenha
tax ;X = A
lda Player0,x ;carrega Player0[x]
.byte $2C ;pula próxima instrução
;
.naoDesenha ;
lda #$00 ;pára de desenhar
sta GRP0 ;coloca valor em GRP0
sta WSYNC ;aguarda 1 scanline
dey ;desenhou toda a tela?
bne .scanLoop ;não. desenha próxima linha
sty COLUPF ;limpa COLUPF
sty COLUP0 ;limpa COLUP0
lda #$1E ;A = 30 linhas de overscan
sta TIM64T ;seta timer
;---------------------------------- ;

Com essas alterações, após compilar e rodar o programa, a tela deve ser como a da figura
95. Mova o player por toda a tela. Note que ele passa “por cima” (na frente) do playfield.

É claro que não devemos nos esquecer de incluir o mapa de bits do playfield na sessão
Gráficos do nosso programa. Se estiver usando o AtariPaint, basta clicar em Gera para que
o programa gere o mapa de bits do playfield (assim como foi feito para o player), vide página
150 desse tutorial o exemplo com o player.

Não vamos incluir a matriz do playfield aqui, pois no nosso exemplo estamos utilizando 192
scanlines * 3 (PF0, PF1 e PF2). Daí, quem se aventurar em imprimir esse tutorial vai gastar
muitas páginas só com o playfield (não estamos usando nenhum algoritmo de otimização em
nossos exemplos, lembre-se que esse tutorial tenta ser o mais didático possível, então...).

E:\Atari\SDK\doc\Tutorial.doc Página 166 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Vamos fazer com que ele se mova “por baixo” (por trás) do playfield. Para isso faça a
seguinte alteração:

;=================================================================================
;Constantes
;=================================================================================
COR_AZUL = #$80 ;cor azul
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posição horizontal inicial do player0
MOVER_ESQUERDA = #$10 ;high nibble = 1
MOVER_DIREITA = #$F0 ;high nibble = -1
PLAYFIELD_REFLETIDO = #$01 ;playfield refletido
PLAYFIELD_PRIORIDADE = #$04 ;bit 2 = 1
;
;=================================================================================
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda #COR_AZUL ;cor azul
sta COLUBK ;plano de fundo

lda #PLAYFIELD_REFLETIDO | #PLAYFIELD_PRIORIDADE ;

sta CTRLPF ;playfield refletido


lda #$80 ;posição em relação a parte inferior da tela
sta YPosFromBot ;salva em YPostFromBot
sta WSYNC ;próximo scanline
ldx #PLAYER0HPOS ;posição horizontal do player0
lda horizTable,x ;lê valor na tabela
sta HMP0 ;posiciona player0
and #$0F ;mascara high nibble
tax ;X = A
;
.posicionaPlayer0 ;
dex ;X = X-1
bpl .posicionaPlayer0 ;decrementa até atingir a posição
sta RESP0 ;reseta player0 nesse clock
sta WSYNC ;próximo scanline
sta HMOVE ;posiciona player0
;
;---------------------------------- ;

A tela deve ficar como a da figura 96.

Vamos entender o que acontece. De acordo com a página 78 desse tutorial, o endereço
CTRLPF controla várias características do playfield. O bit zero quando é zero diz ao TIA
para desenhar o playfield de modo duplicado. Se for 1, o playfield é desenhado de modo
refletido.

O bit 2 do CTRLPF controla a prioridade do playfield sobre os outros objetos. Se o bit for
zero, o playfield não tem prioridade e os objetos passam na frente (por cima) dele. Se o bit
for 1, o playfield tem a prioridade, então os objetos passam por trás (por baixo) dele.

Declaramos então 2 constantes: a PLAYFIELD_REFLETIDO que tem o valor 01h e a


PLAYFIELD_PRIORIDADE que tem o valor 04h. Convertendo para binário temos:

PLAYFIELD_REFLETIDO = 00000001b
PLAYFIELD_PRIORIDADE = 00000100b

E:\Atari\SDK\doc\Tutorial.doc Página 167 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

A linha LDA #PLAYFIELD_REFLETIDO | #PLAYFIELD_PRIORIDADE está dizendo para


juntar esses dois valores. O operador lógico “|” faz um OR (vide página 24 desse tutorial
para relembrar como funciona o OR).

Então 00000001b
or 00000100b
---------------
00000101b

Colocamos em CTRLPF o valor 00000101b que seta então seus bits 0 e 2 (da direita para a
esquerda).

Nosso playfield então é refletido e tem prioridade sobre os outros objetos.

Figura 94

E:\Atari\SDK\doc\Tutorial.doc Página 168 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 95

Figura 96

E:\Atari\SDK\doc\Tutorial.doc Página 169 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Detectando colisão
Nosso jogador está atravessando o playfield. Vamos então detectar quando o jogador e o
playfield colidem e assim determinar limites de movimento para o jogador.

Veja a figura 97 logo a seguir.

Figura 97

No nosso caso estamos lidando com o player0 e o playfield. Então para detectarmos sua
colisão basta testarmos o bit 7 do CXP0FB.

Altere o programa conforme segue:

.naoDireita1 ;
lsr ;acima?
bcs .naoAcima0 ;não. verifica abaixo
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega o bit 7
bcs .naoSobe0 ;colidiu? sim. não sobe player na tela
inc YPosFromBot ;YPosFromBot = YPosFromBot+1
.byte $2C ;pula próxima instrução
;
.naoSobe0 ;
dec YPosFromBot ;YPosFromBot = YPosFromBot-1
pla ;restaura estado do joystick
;
.naoAcima0 ;
lsr ;abaixo?
bcs .naoAbaixo0 ;não. verifica esquerda
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega bit 7
bcs .naoDesce0 ;colidiu? sim. não desce o player na tela
dec YPosFromBot ;YPosFromBot = YPosFromBot-1

E:\Atari\SDK\doc\Tutorial.doc Página 170 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.byte $2C ;pula próxima instrução


;
.naoDesce0 ;
inc YPosFromBot ;YPosFromBot = YPosFromBot+1
pla ;restaura estado do joystick
;
.naoAbaixo0 ;
lsr ;esquerda?
bcs .naoEsquerda0 ;não. verifica direita
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega bit 7
bcs .naoVaiEsquerda ;colidiu? sim. não vai para a esquerda
ldx #MOVER_ESQUERDA ;
ldy #$08 ;D3 do REFP0 = 1
sty REFP0 ;espelha o player0
.byte $2C ;pula próxima instrução
;
.naoVaiEsquerda ;
ldx #MOVER_DIREITA ;move para a direita
pla ;restaura estado do joystick
;
.naoEsquerda0 ;
lsr ;direita?
bcs .naoDireita0 ;não. verifica tiro
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega bit 7
bcs .naoVaiDireita ;colidiu? sim. não vai para a direita
ldx #MOVER_DIREITA ;
ldy #$00 ;D3 do REFP0 = 0
sty REFP0 ;cancela espelhamento do player0
.byte $2C ;pula próxima instrução
;
.naoVaiDireita ;
ldx #MOVER_ESQUERDA ;vai para a esquerda
pla ;restaura estado do joystick
;
.naoDireita0 ;
lda INPT5 ;botão de tiro pressionado?
bmi .naoBotao1 ;não. verifica botão do outro joystick
;
.naoBotao1 ;
lda INPT4 ;botão de tiro pressionado?
bmi .naoBotao0 ;não. continua
;
.naoBotao0 ;
stx HMP0 ;direção e velocidade do player0
stx CXCLR ;limpa registradores de colisão
;---------------------------------- ;

O programa tem vários bugs mas como ele é somente para fins didáticos e a idéia está
sendo atendida, vamos deixar com esses bugs. O que queremos aqui é mostrar sobre
detecção de colisão entre objetos.

Movimente o player contra o playfield. Por exemplo para a direita. Quando ele colidir com o
playfield o desvio (BCS) após o ASL ocorrerá e o player retornará um pouco para a
esquerda. O mesmo ocorre para as demais direções: o player sempre será recuado para a
direção oposta. Fizemos assim porque o player precisa estar separado do playfield para que
o movimento volte a ficar liberado. Se quando ocorresse a colisão simplesmente
parássemos o player, ele ficaria em contato com o playfield, acusando uma colisão infinita.

E:\Atari\SDK\doc\Tutorial.doc Página 171 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Assim todos os outros movimentos ficariam suspensos. Um exemplo interessante é o jogo


Adventure, quando forçamos aquele quadrado (que é o player) contra o playfield (as
margens das telas e do labirinto). O efeito é o mesmo.

Experimente também mudar os valores das constantes de movimento horizontal, por


exemplo, mude

MOVER_ESQUERDA = #$10 ;high nibble = 1


MOVER_DIREITA = #$F0 ;high nibble = -1

para

MOVER_ESQUERDA = #$30 ;high nibble = 3


MOVER_DIREITA = #$D0 ;high nibble = -3

Estudar as várias formas de fazer isso é um bom exercício. Aqui é só exemplo (com bug).
Veja uma outra forma para nosso player construída no AtariPaint e vista no Stella (figura 98).

Figura 98

Como nesse exemplo são várias cores por linha, dentro do .scanloop deve-se fazer LDA
colorTable,x e STA COLUP0 entre as linhas TAX e LDA Player0,x conforme segue:

E:\Atari\SDK\doc\Tutorial.doc Página 172 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;---------------------------------- ;
;Scanlines ;
;---------------------------------- ;
.scanLoop ;
lda PlayField0-1,y ;lê o playfield0[y]
sta PF0 ;
lda PlayField1-1,y ;lê o playfield1[y]
sta PF1 ;
lda PlayField2-1,y ;lê o playfield2[y]
sta PF2 ;
tya ;A = Y
sec ;carry = 1
sbc YPosFromBot ;posição em relação à parte inferior da tela
adc #PLAYER0HEIGHT ;adiciona a altura do player
bcc .naoDesenha ;hora de desenhar? não. vá para .naoDesenha
tax ;X = A
lda colorTable,x ;lê cor da linha
sta COLUP0 ;define cor da linha do player0
lda Player0,x ;carrega Player0[x]
.byte $2C ;pula próxima instrução
;
.naoDesenha ;
lda #$00 ;pára de desenhar
sta GRP0 ;coloca valor em GRP0
sta WSYNC ;aguarda 1 scanline
dey ;desenhou toda a tela?
bne .scanLoop ;não. desenha próxima linha
sty COLUPF ;limpa COLUPF
sty COLUP0 ;limpa COLUP0
lda #$1E ;A = 30 linhas de overscan
sta TIM64T ;seta timer
;---------------------------------- ;
;---------------------------------- ;
;Gráficos ;
;---------------------------------- ;
Player0 ;
.byte %11111111 ; |XXXXXXXX|
.byte %11011011 ; |XX XX XX|
.byte %01111110 ; | XXXXXX |
.byte %00111100 ; | XXXX |
.byte %00011000 ; | XX |
.byte %00100100 ; | X X |
.byte %01000010 ; | X X |
.byte %01000010 ; | X X |
.byte %00100100 ; | X X |
.byte %00000000 ; | |

PLAYER0HEIGHT = *-Player0 ;

colorTable ;
.byte $1E,$1E,$1E,$1E,$0E,$24,$42,$5E,$D8,$00

;---------------------------------- ;

Assim, para cada linha desenhada, o 6502 lê uma cor da tabela de cores.

E:\Atari\SDK\doc\Tutorial.doc Página 173 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Outros objetos
Vamos ver agora de forma bem rápida os outros objetos (bola e míssil). A idéia básica da
coisa é semelhante aos players. Só algumas diferenças.

A bola
Como já vimos na página 79 desse tutorial, podemos habilitar a bola setando o bit 1 do
ENABL e, conforme a página 82, o ENABL compartilha sua cor e luminosidade com o
playfield, ou seja, a bola terá a mesma cor e luminosidade do playfield (o que está em
COLUPF).

Vamos fazer as seguintes alterações no nosso programa.

;=================================================================================
;Constantes
;=================================================================================
COR_AZUL = #$80 ;cor azul
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posição horizontal inicial do player0
MOVER_ESQUERDA = #$10 ;high nibble = 1
MOVER_DIREITA = #$F0 ;high nibble = -1
PLAYFIELD_REFLETIDO = #$01 ;playfield refletido
PLAYFIELD_PRIORIDADE = #$04 ;bit 2 = 1
LIGA_DESLIGA_BOLA = #$02 ;bit 1 ativo (%00000010)
ALTURA_DA_BOLA = #$01 ;altura da bola (só 1 scanline)
;
;=================================================================================
;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
YposFromBot ds 1 ;distância em relação a parte de baixo da tela
ativaDesativaBola ds 1 ;status da bola
;
;---------------------------------- ;
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda #COR_AZUL ;cor azul
sta COLUBK ;plano de fundo
lda #LIGA_DESLIGA_BOLA ;A = 2
sta ativaDesativaBola ;ativa bola

lda #PLAYFIELD_REFLETIDO | #PLAYFIELD_PRIORIDADE ;

sta CTRLPF ;playfield refletido


lda #$80 ;posição em relação a parte inferior da tela
sta YPosFromBot ;salva em YPostFromBot
sta WSYNC ;próximo scanline
ldx #PLAYER0HPOS ;posição horizontal do player0
lda horizTable,x ;lê valor na tabela
sta HMP0 ;posiciona player0
and #$0F ;mascara high nibble
tax ;X = A
;
.posicionaPlayer0 ;
dex ;X = X-1
bpl .posicionaPlayer0 ;decrementa até atingir a posição

E:\Atari\SDK\doc\Tutorial.doc Página 174 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sta RESP0 ;reseta player0 nesse clock


;
;---------------------------------- ;
.naoBotao1 ;
lda INPT4 ;botão de tiro pressionado?
bmi .naoBotao0 ;não. continua
lda #LIGA_DESLIGA_BOLA ;A = 2
eor ativaDesativaBola ;alterna entre ativa e desativa
sta ativaDesativaBola ;salva novo status
;
.naoBotao0 ;
;---------------------------------- ;
;Scanlines ;
;---------------------------------- ;
.scanLoop ;
lda PlayField0-1,y ;lê o playfield0[y]
sta PF0 ;
lda PlayField1-1,y ;lê o playfield1[y]
sta PF1 ;
lda PlayField2-1,y ;lê o playfield2[y]
sta PF2 ;
lda #LIGA_DESLIGA_BOLA ;
cmp ativaDesativaBola ;
bne .naoDesenhaBola ;
tya ;A = Y
sec ;carry = 1
sbc YPosFromBot ;posição em relação à parte inferior da tela
adc #ALTURA_DA_BOLA ;adiciona a altura da bola
bcc .naoDesenhaBola ;desenhar? não. vá para .naoDesenhaBola
lda #LIGA_DESLIGA_BOLA ;
.byte $2C ;pula próxima instrução
;
.naoDesenhaBola ;
lda #$00 ;pára de desenhar
sta ENABL ;coloca valor em ENABL
sta WSYNC ;aguarda 1 scanline
dey ;desenhou toda a tela?
bne .scanLoop ;não. desenha próxima linha
sty COLUPF ;limpa COLUPF
sty COLUP0 ;limpa COLUP0
lda #$1E ;A = 30 linhas de overscan
sta TIM64T ;seta timer
;---------------------------------- ;

Na sessão Inicializações, seto a variável ativaDesativaBola com o valor 02h = 00000010b,


ou seja, o bit 1 é 1, fazendo com que a bola seja ativada.

Na sessão Lógica do Jogo, nesse exemplo mostrada somente a parte alterada, fazemos o
seguinte: ao pressionar o botão do joystick, o desvio no BMI não ocorre e as instruções
abaixo do BMI são executadas.

A primeira instrução abaixo do BMI carrega A com 02h. A instrução seguinte faz um OU-
Exclusivo (XOR) desse valor com o valor que está em ativaDesativaBola (para quem se
esqueceu o que faz o eor, releia as páginas 24, 50 e 51 desse tutorial).

Isso quer dizer que, cada vez que o botão do joystick é pressionado, o valor alterna entre 1 e
0, servindo assim como uma chave liga-desliga.

Depois salvamos o resultado em ativaDesativaBola.

E:\Atari\SDK\doc\Tutorial.doc Página 175 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

No loop .scanLoop comparamos a variável ativaDesativaBola com 02h. Se não forem iguais,
então o programa não desenha a bola, caso contrário ele desenhará a bola usando o
mesmo algoritmo para desenhar o player (página 151 desse tutorial). A diferença é que, em
vez de escrevermos em GRP0, escrevemos em ENABL.

O míssil
O míssil é a mesma coisa da bola. Vamos ver o código:

;=================================================================================
;Constantes
;=================================================================================
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posição horizontal inicial do player0
MOVER_ESQUERDA = #$10 ;high nibble = 1
MOVER_DIREITA = #$F0 ;high nibble = -1
LIGA_MISSIL = #$00 ;ativa míssil
DESLIGA_MISSIL = #$02 ;desativa míssil
ALTURA_DO_MISSIL = #$01 ;altura do míssil (só 1 scanline)
;
MISSIL0HPOS = PLAYER0HPOS + $04 ;míssil + metade da largura do player0
;
;
;=================================================================================
;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
YPosFromBot ds 1 ;distância em relação a parte de baixo da tela
YPosFromBotMissil ds 1 ;distância em relação a parte de baixo da tela
;
;---------------------------------- ;
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda #DESLIGA_MISSIL ;A = 2
sta RESMP0 ;desativa míssil
lda #$80 ;posição em relação a parte inferior da tela
sta YPosFromBot ;salva em YPosFromBot
sta WSYNC ;próximo scanline
ldx #PLAYER0HPOS ;posição horizontal do player0
lda horizTable,x ;lê valor na tabela
sta HMP0 ;posiciona player0
and #$0F ;mascara high nibble
tax ;X = A
;
.posicionaPlayer0 ;
dex ;X = X-1
bpl .posicionaPlayer0 ;decrementa até atingir a posição
sta RESP0 ;reseta player0 nesse clock
sta WSYNC ;
ldx #MISSIL0HPOS ;posição horizontal do missil0
lda horizTable,x ;lê valor na tabela
sta HMM0 ;posiciona missil0
and #$0F ;mascara high nibble
tax ;X = A
;
.posicionaMissil0 ;
dex ;X = X-1
bpl .posicionaMissil0 ;decrementa até atingir a posição
sta RESM0 ;posiciona missil0

E:\Atari\SDK\doc\Tutorial.doc Página 176 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sta WSYNC ;
sta HMOVE ;
;---------------------------------- ;
;aqui é parte da rotina Lógica do Jogo
.naoBotao1 ;
lda INPT4 ;botão de tiro pressionado?
bmi .naoBotao0 ;não. Continua
lda #LIGA_MISSIL ;A = 0
sta RESMP0 ;ativa míssil
lda YPosFromBot ;A = posição do player0
sta YPosFromBotMissil ;posição do míssil0 = posição do player0
;
.naoBotao0 ;
stx HMP0 ;direção e velocidade do player0
stx HMM0 ;direção e velocidade do missil0
stx CXCLR ;limpa registradores de colisão
inc YPosFromBotMissil ;desloca míssil para cima
bne .naoMovimentaMissil ;se míssil no topo da tela, pára de deslocar
lda #DESLIGA_MISSIL ;A = 2
sta RESMP0 ;desativa míssil
;
.naoMovimentaMissil ;
lda #COR_AMARELA ;A = cor amarela
sta COLUP0 ;para pintar o míssil
;---------------------------------- ;
;Scanlines ;
;---------------------------------- ;
.scanLoop ;
tya ;A = Y
sec ;carry = 1
sbc YPosFromBotMissil ;posição em relação à parte inferior da tela
adc #ALTURA_DO_MISSIL ;adiciona a altura do míssil
bcc .naoDesenhaMissil ;desenhar? não. vá para .naoDesenhaMissil
lda #DESLIGA_MISSIL ;desativa míssil
.byte $2C ;pula próxima instrução
;
.naoDesenhaMissil ;
lda #LIGA_MISSIL ;ativa míssil
sta ENAM0 ;coloca valor em ENAM0
tya ;A = Y
sec ;carry = 1
sbc YPosFromBot ;posição em relação à parte inferior da tela
adc #PLAYER0HEIGHT ;adiciona a altura do player
bcc .naoDesenhaPlayer0 ;desenhar? não. vá para .naoDesenhaPlayer0
tax ;X = A
lda colorTable,x ;lê cor da linha
sta COLUP0 ;define cor da linha do player0
lda Player0,x ;carrega Player0[x]
.byte $2C ;pula próxima instrução
;
.naoDesenhaPlayer0 ;
lda #$00 ;pára de desenhar
sta GRP0 ;coloca valor em GRP0
sta WSYNC ;aguarda 1 scanline
dey ;desenhou toda a tela?
bne .scanLoop ;não. desenha próxima linha
ldy COLUP0 ;reseta cor do player0
lda #$1E ;A = 30 linhas de overscan
sta TIM64T ;seta timer
;---------------------------------- ;

A figura 99 mostra o player com o míssil disparado.

E:\Atari\SDK\doc\Tutorial.doc Página 177 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Utilizamos o RESMP0 para ativar/desativar o míssil. Quando zeramos esse endereço, o


míssil está livre para se deslocar pela tela. Quanto setamos seu bit 1, o míssil fica travado no
centro do player. Para maiores informações sobre o RESMP0, vide página 83 desse tutorial.

Figura 99

E:\Atari\SDK\doc\Tutorial.doc Página 178 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O console
Vamos ver como podemos verificar o estado das chaves do console, ou seja, qual a posição
das chaves de dificuldade A e B (player0 e player1), se o select ou reset foi acionado. O
princípio é o mesmo dos joysticks. Vejamos o código.

;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
YPosFromBot ds 1 ;distância em relação a parte de baixo da tela
YPosFromBotMissil ds 1 ;distância em relação a parte de baixo da tela
wConsole ds 1 ;salva status do console
;
;---------------------------------- ;
;---------------------------------- ;
;Lógica do jogo ;
;---------------------------------- ;
lda SWCHB ;lê console
sta wConsole ;salva status do console
lsr ;reset pressionado?
bcc .inicioDoJogo ;sim. reinicia programa
lsr ;select pressionado?
bcs .selectNaoPressionado ;não. continua execução
;<< aqui vem o código caso o select seja
;<< pressionado
.selectNaoPressionado ;
ldx #$00 ;assegura que o player não se movimentará
lda SWCHA ;lê endereço do joystick
lsr ;abaixo?
bcs .naoAbaixo1 ;não. verifica acima
;<<<seu código aqui
;
.naoAbaixo1 ;
lsr ;acima?
bcs .naoAcima1 ;não. verifica esquerda
;<<<seu código aqui
;
.naoAcima1 ;
lsr ;esquerda?
bcs .naoEsquerda1 ;não. verifica direita
;<<<seu código aqui
;
.naoEsquerda1 ;
lsr ;direita?
bcs .naoDireita1 ;não. verifica acima
;<<<seu código aqui
;
.naoDireita1 ;
lsr ;acima?
bcs .naoAcima0 ;não. verifica abaixo
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega o bit 7
bcs .naoSobe0 ;colidiu? sim. não sobe player na tela
inc YPosFromBot ;YPosFromBot = YPosFromBot+1
.byte $2C ;pula próxima instrução
;
.naoSobe0 ;
dec YPosFromBot ;YPosFromBot = YPosFromBot-1

E:\Atari\SDK\doc\Tutorial.doc Página 179 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

pla ;restaura estado do joystick


;
.naoAcima0 ;
lsr ;abaixo?
bcs .naoAbaixo0 ;não. verifica esquerda
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega bit 7
bcs .naoDesce0 ;colidiu? sim. não desce o player na tela
dec YPosFromBot ;YPosFromBot = YPosFromBot-1
.byte $2C ;pula próxima instrução
;
.naoDesce0 ;
inc YPosFromBot ;YPosFromBot = YPosFromBot+1
pla ;restaura estado do joystick
;
.naoAbaixo0 ;
lsr ;esquerda?
bcs .naoEsquerda0 ;não. verifica direita
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega bit 7
bcs .naoVaiEsquerda ;colidiu? sim. não vai para a esquerda
ldx #MOVER_ESQUERDA ;
ldy #$08 ;D3 do REFP0 = 1
sty REFP0 ;espelha o player0
.byte $2C ;pula próxima instrução
;
.naoVaiEsquerda ;
ldx #MOVER_DIREITA ;move para a direita
pla ;restaura estado do joystick
;
.naoEsquerda0 ;
lsr ;direita?
bcs .naoDireita0 ;não. verifica tiro
pha ;salva estado do joystick
lda CXP0FB ;lê registrador de colisão
asl ;pega bit 7
bcs .naoVaiDireita ;colidiu? sim. não vai para a direita
ldx #MOVER_DIREITA ;
ldy #$00 ;D3 do REFP0 = 0
sty REFP0 ;cancela espelhamento do player0
.byte $2C ;pula próxima instrução
;
.naoVaiDireita ;
ldx #MOVER_ESQUERDA ;vai para a esquerda
pla ;restaura estado do joystick
;
.naoDireita0 ;
lda INPT5 ;botão de tiro pressionado?
bmi .naoBotao1 ;não. verifica botão do outro joystick
;
.naoBotao1 ;
lda INPT4 ;botão de tiro pressionado?
bmi .naoBotao0 ;não. continua
lda #DESLIGA_MISSIL ;A = 2
sta RESMP0 ;centraliza míssil no player
lda #LIGA_MISSIL ;A = 0
sta RESMP0 ;libera míssil
lda YPosFromBot ;
sta YPosFromBotMissil ;posição vertical igual para os 2 objetos
;
.naoBotao0 ;
stx HMP0 ;move player0

E:\Atari\SDK\doc\Tutorial.doc Página 180 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

lda wConsole ;restaura status do console


asl ;dificuldade do player1
bcc .dificuldade1emB ;dificuldade em A? não. vai para B
;<< aqui vem o código caso a chave de
;<< dificuldade do player1 seja pressionada
.dificuldade1emB ;
asl ;dificuldade do player0
bcc .dificuldade0emB ;dificuldade em A? não. vai para B
ldx #$00 ;míssil não se move horizontalmente
.byte $2C ;pula próxima instrução
;
.dificuldade0emB ;
nop ;assume o mesmo valor de X para o player0, ou
nop ;seja, se MOVER_DIREITA ou MOVER_ESQUERDA
stx HMM0 ;move míssil
stx CXCLR ;limpa latches de colisão
inc YPosFromBotMissil ;míssil sobre pela tela
bne .naoMovimentaMissil ;míssi chegou no topo da tela? Sim. desliga-o
lda #DESLIGA_MISSIL ;desliga míssil
sta RESMP0 ;trava míssil
;
.naoMovimentaMissil ;
lda #COR_AMARELA ;A = cor amarela
sta COLUP0 ;para pintar o míssil

Os dados retornados em SWCHB estão descritos na página 89 desse tutorial.

Para testar o reset, rode o programa e mova o player para qualquer posição, então tecle F2
(no Stella é a tecla default para o reset). O player deve voltar para a posição inicial.

Nesse código também testamos a chave de dificuldade do player0. No Stella, F5 posiciona a


chave em A e F6 posiciona a chave em B (do player0).

Rode o programa, dispare um míssil e movimente o player horizontalmente. Se a chave de


dificuldade estiver em B, o míssil acompanhará (horizontalmente) o player. Ao teclar F5, o
míssil não mais acompanha o player. É a mesma idéia dos mísseis do River Raid,
Megamania, etc.

O funcionamento é bem simples: ao dispararmos um míssil, o programa centraliza o míssil


no player colocando 02h, ou seja, setando o bit 1 do RESMP0 e logo em seguida, zera
RESMP0 para liberar o míssil (o míssil pode se movimentar independentemente do player).
Depois, fazemos com que a posição Y do míssil seja a mesma do player.

Depois restauramos o estado do console, salvo em wConsole, e verificamos a chave de


dificuldade. Se ela estiver em A, zeramos o HMM0, ou seja, dizemos ao TIA para não mover
o míssil horizontalmente (vide tabela na página 84 desse tutorial). Se estiver em B,
simplesmente colocamos em HMM0 o mesmo valor que o player tem para se movimentar
horizontalmente, fazendo com que o míssil o acompanhe.

Nota: Os 2 NOPs são necessários pois, a linha .byte $2C diz para pular a próxima instrução.
Como não temos nenhuma instrução para colocar lá, ou seja, estamos aproveitando o valor
que já vem em X, os 2 NOPs são necessários. Assim, a linha .byte $2C manda pular os 2
NOPs. Se colocássemos apenas 1 NOP (como a lógica manda, pois NOP é uma instrução)
o programa não funcionaria. Faça a experiência: comente 1 dos NOPs, compile e rode o
programa. Faça um disparo e tecle F5 (se estiver usando o Stella). O programa
simplesmente trava.

E:\Atari\SDK\doc\Tutorial.doc Página 181 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Som
Para exemplificar a utilização de som, vamos colocar no nosso programa os efeitos sonoros
do jogo Pitfall. Faça as seguintes alterações:

;=================================================================================
;Constantes
;=================================================================================
COR_AMARELA = #$1E ;cor amarela
PLAYER0HPOS = #$21 ;posição horizontal inicial do player0
MOVER_ESQUERDA = #$10 ;high nibble = 1
MOVER_DIREITA = #$F0 ;high nibble = -1
LIGA_MISSIL = #$00 ;ativa míssil
DESLIGA_MISSIL = #$02 ;desativa míssil
ALTURA_DO_MISSIL = #$01 ;altura do míssil (só 1 scanline)
;
MISSIL0HPOS = PLAYER0HPOS + $04 ;míssil + metade da largura do player0
;
;offset dos sons do Pitfall
SOUND_JUMP = #$20 ;Harry pula
SOUND_TREASURE = #$25 ;Harry pega tesouro
SOUND_DEAD = #$31 ;Harry morre
SOUND_FALLING = #$53 ;Harry cai no buraco
;
;=================================================================================
;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
YPosFromBot ds 1 ;distância em relação a parte de baixo da tela
YPosFromBotMissil ds 1 ;distância em relação a parte de baixo da tela
soundIdx ds 1 ;índice da tabela de sons (0 = sem som)
soundDelay ds 1 ;toca uma nota a cada 4 frames
;
;---------------------------------- ;
;aqui é parte da rotina Lógica do Jogo
.naoBotao1 ;
lda INPT4 ;botão de tiro pressionado?
bmi .naoBotao0 ;não. continua
lda #DESLIGA_MISSIL ;A = 2
sta RESMP0 ;centraliza míssil no player
lda #LIGA_MISSIL ;A = 0
sta RESMP0 ;libera míssil
lda YPosFromBot ;
sta YPosFromBotMissil ;posição vertical igual para os 2 objetos
lda #SOUND_FALLING ;tipo de som a emitir
sta soundIdx ;salva
;
.naoBotao0 ;
stx HMP0 ;move player0
lda wConsole ;restaura status do console
asl ;dificuldade do player1
bcc .dificuldade1emB ;dificuldade em A? não. vai para B
;<< aqui vem o código caso a chave de
;<< dificuldade do player1 seja pressionada
.dificuldade1emB ;
asl ;dificuldade do player0
bcc .dificuldade0emB ;dificuldade em A? não. vai para B
ldx #$00 ;míssil não se move horizontalmente
.byte $2C ;pula próxima instrução
;

E:\Atari\SDK\doc\Tutorial.doc Página 182 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.dificuldade0emB ;
nop ;assume o mesmo valor de X para o player0, ou
nop ;seja, se MOVER_DIREITA ou MOVER_ESQUERDA
stx HMM0 ;move míssil
stx CXCLR ;limpa latches de colisão
inc YPosFromBotMissil ;míssil sobre pela tela
bne .naoMovimentaMissil ;míssi chegou no topo da tela? Sim. desliga-o
lda #DESLIGA_MISSIL ;desliga míssil
sta RESMP0 ;trava míssil
;
.naoMovimentaMissil ;
lda #COR_AMARELA ;A = cor amarela
sta COLUP0 ;para pintar o míssil
ldy #$00 ;desliga tom
ldx soundIdx ;X = 0?
beq .noSound ;sim. não emite som
inc soundDelay ;soundDelay = soundDelay + 1
lda soundDelay ;A = soundDelay
and #$03 ;momento de emitir som/mudar nota?
bne .skipNext ;não. não toca nota ainda
inc soundIdx ;próxima nota
;
.skipNext: ;
lda soundTab-1,x ;lê nota da tabela
bpl .contSound ;continua emitindo som
sty soundIdx ;pára de emitir som
;
.contSound: ;
sta AUDF0 ;coloca nota em AUDF0
ldy #$01 ;liga tom (4 bit poly)
;
.noSound: ;
sty AUDC0 ;tom
lda #$04 ;volume 4
sta AUDV0 ;seta volume
;---------------------------------- ;
;---------------------------------- ;
;Tabelas ;
;---------------------------------- ;
align 256 ;
;
soundTab ;
.byte $13, $13, $13, $13, $13, $13, $13, $09, $0b, $0b, $0b, $0b, $0b, $0b, $0b, $0b
.byte $0b, $0b, $0b, $0b, $09, $0b, $09, $0b, $0b, $0b, $0b, $0b, $0b, $0b, $8b, $06
.byte $04, $03, $02, $84
.byte $13, $13, $0e, $0b, $09, $09, $09, $0b, $09, $09, $09, $89
.byte $1d, $1d, $1d, $1d, $1d, $1d, $1d, $1d, $1d, $1a, $1a, $19, $19, $19, $19, $19
.byte $19, $1d, $1d, $1d, $1d, $1d, $14, $15, $14, $15, $14, $15, $14, $15, $14, $15
.byte $14, $95
.byte $18, $19, $1a, $1b, $1c, $1d, $1e, $9f
;
CHECKPAGE horizTable ;
;
;---------------------------------- ;

Altere a linha LDA #SOUND_FALLING para uma das outras 3 constantes que se referem ao
som, compile e rode o programa. Pressione o botão de tiro do joystick 0 para disparar um
míssil. O som é emitido nesse momento.

Faça uma experiência: coloque uma instrução INX entre as linhas LDX soundIdx e BEQ
.noSound, compile e rode o programa.

O trecho
E:\Atari\SDK\doc\Tutorial.doc Página 183 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007
ldy #$01 ;liga tom (4 bit poly)
;
.noSound: ;
sty AUDC0 ;tom

gera o tom do som. Esse tom será dividido pela freqüência em em AUDF0. Essa divisão é
dada pelos valores lidos na tabela de som, ou seja, damos o tom e as notas são os valores
que dizem para dividir esse tom em várias freqüências. Vamos fazer um teste: altere a linha
LDY #$01 do trecho acima para LDY #$08. Com isso, em vez de termos um tom de 4 bit
poly, teremos um tom de 5 bit poly -> div 6. Compile e rode o programa. Veja a diferença do
tom. Experimente outros valores (de 01h a 08h). O soundDelay nos dá o tempo de duração
das notas. No nosso caso a cada 4 frames uma nova nota é lida. Maiores detalhes sobre
AUDC0, AUDF0 e AUDV0 estão nas páginas 86 e 87 desse tutorial.

Números
Aqui, veremos um exemplo de como mostrar números na tela. Podemos montar números
através do playfield ou mesmo usar os players 0 e 1.

Nos jogos do 2600, é comum vermos placar de 1 (para número de vidas), 2, 3 e até 6
dígitos. No nosso caso vamos exemplificar um contador de 4 dígitos com a utilização dos
players. Vamos começar um novo programa.

processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
;=================================================================================
;Macros
;=================================================================================
MAC CHECKPAGE
IF >. != >{1}
ECHO ""
ECHO "ERRO: páginas diferentes! (", {1}, ",", ., ")"
ECHO ""
ERR
ENDIF
ENDM
;=================================================================================
;Constantes
;=================================================================================
COR_AMARELA = #$1E ;cor amarela
COR_VERMELHA = #$40 ;cor vermelha
PLAYER0HPOS = #$35 ;posição horizontal inicial do player0
PLAYER1HPOS = #$24 ;posição horizontal inicial do player1
;
;---------------------------------- ;
;
echo (*-$80)d, " Bytes da RAM usados.";
echo ($100-*)d," Bytes da RAM livres.";
;=================================================================================
;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
wNumeros ds 2 ;4 dígitos
wNumerosPtr ds 8 ;cada dígito é endereçado por 16 bit
wContador ds 1 ;contador de uso geral

E:\Atari\SDK\doc\Tutorial.doc Página 184 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;
;---------------------------------- ;
;=================================================================================
;Código
;=================================================================================
Seg Codigo ;
org $F000 ;
;
.inicioDoJogo ;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda #COR_AMARELA ;cor amarela
sta COLUP0 ;cor do player0
; lda #COR_VERMELHA ;cor vermelha
sta COLUP1 ;cor do player1
lda #$00 ;A = 0
ldx #$03 ;número de dígitos
;
.limpaNumeros ;
sta wNumeros,x ;zera números
dex ;X = X-1. X=0?
bpl .limpaNumeros ;não. continua zerando
sta wContador ;zera wContador
lda #$01 ;A = 1
sta NUSIZ0 ;2 cópias (próximas)
sta NUSIZ1 ;2 cópias (próximas)
;---------------------------------- ;
;Loop Principal ;
;---------------------------------- ;
.inicioDoFrame ;início do frame
lda #$2B ;2Bh = 43d
sta TIM64T ;seta o timer
lda #$02 ;liga o VSYNC
sty VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
lda wNumeros ;A = centena/milhar atuais
sta COLUBK ;muda cor de fundo
inc wContador ;wContador = wContador+1
lda #$05 ;A = 5 (delay para o incremento dos números)
cmp wContador ;wContador = A?
bne .naoIncrementaNumeros ;não. não incrementa números
ldx #$01 ;X = 4 dígitos
;
.incNumLoop ;esse loop faz o placar contar até 9999
inc wNumeros,x ;incrementa unidade/centena dependendo de X
lda wNumeros,x ;A = unidade/centena dependendo de X
and #$0F ;mascara high nibble
cmp #$0A ;A = 10?
bne .naoIncrementaDezenaMilhar ;nao. sai do loop com o X apontando ainda

E:\Atari\SDK\doc\Tutorial.doc Página 185 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;para a unidade/centena, ou seja, a


;dezena/milhar fica como está
lda wNumeros,x ;lê valor em wNumeros0[X] onde X indica se
;unidade/centena
and #$F0 ;mascara low nibble
clc ;limpa carry flag
adc #$10 ;adiciona 10
sta wNumeros,x ;salva
cmp #$A0 ;A = 160? (high nibble = 10?)
bne .naoIncrementaDezenaMilhar ;nao. sai do loop com o X apontando ainda
;para a dezena/milhar
lda #$00 ;A = 0
;
.naoZera ;
sta wNumeros,x ;zera unidade/dezena ou centena/milhar
dex ;aponta para centena e milhar
bpl .incNumLoop ;faz novo cálculo
;
.naoIncrementaDezenaMilhar ;
lda #$00 ;A = 0
sta wContador ;reseta wContador
;
.naoIncrementaNumeros ;
jsr .setaPonteiros ;muda de banco
;
;---------------------------------- ;
;Aguarda VBLANK ;
;---------------------------------- ;
.aguardaVBLANK ;
lda INTIM ;timer = 0?
bne .aguardaVBLANK ;não. aguarda fim do VBLANK
sta VBLANK ;liga o VBLANK
sta WSYNC ;próximo scanline
ldx #PLAYER0HPOS ;posição do próximo player
ldy #$01 ;rotina afetará Player 1
;
.proximoPlayer ;
lda HorzTable,x ;lê coordenadas segundo posição X
sta HMP0,y ;seta para Players 0 ou 1
and #$0F ;mascara high nibble
tax ;X = A
;
.posicionaPlayer ;
dex ;X = X-1
bpl .posicionaPlayer ;X = 0? não. continua decrementando
sta RESP0,y ;reseta player nesse clock
sta WSYNC ;em relação ao lado esquerdo da tela
sta HMOVE ;move players
ldx #PLAYER1HPOS ;posição do player1
dey ;próximo player
bpl .proximoPlayer ;calcula para o próximo player
sta WSYNC ;próximo scanline
ldy #$08 ;Y = 8 linhas para desenhar na tela
;
;----------------------------------;
;Scanloop ;
;----------------------------------;
.scanLoop ;
lax (wNumerosPtr+6),y ;5
lda (wNumerosPtr+4),y ;5
pha ;3
;8
lda (wNumerosPtr),y ;5
sta.w GRP1 ;4 milhar

E:\Atari\SDK\doc\Tutorial.doc Página 186 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

repeat 3 ;
nop ;2 * 3 = 6
repend ;
;15
lda (wNumerosPtr+2),y ;5 centena
sta GRP0 ;3
;8
pla ;4
sta GRP1 ;3 unidade
;7
stx GRP0 ;3
sta WSYNC ;próximo scanline
dey ;Y = Y-1
bne .scanLoop ;desenhou as 8 linhas? não. continua desenhando
sty GRP0 ;reset GRP0 (pára de desenhar)
sty GRP1 ;reset GRP1 (pára de desenhar)
sty COLUBK ;reset COLUBK (pára de desenhar)
;
;-----------------------------------;
;Overscan ;
;-----------------------------------;
lda #$23 ;30 scanlines
sta TIM64T ;seta timer
;
.overScan ;
lda INTIM ;timer = 0?
bne .overScan ;não. continua overscan
jmp .inicioDoFrame ;próximo quadro
;-----------------------------------;
;Seta ponteiros para a matriz de números ;
;-----------------------------------;
.setaPonteiros ;
ldx #$01 ;X = 4 dígitos
ldy #$06 ;Y = 16 bits cada ponteiro
;
.setaNumeros ;
txa ;A = X
pha ;salva
lda wNumeros,x ;
;
.segundoDigito ;
pha ;salva número lido
and #$0F ;mascara high nibble
tax ;X = dígito
lda numTable,x ;lê na tabela de números o dígito X
sta wNumerosPtr,y ;salva o endereço no ponteiro (low order)
lda #>numTable ;obtém high order
sta wNumerosPtr+1,y ;salva high order
pla ;restaura número lido
lsr ;divide por 16 para
lsr ;fazer com que o nibble
lsr ;superior passe para
lsr ;o nibble inferior
dey ;
dey ;decrementa ponteiro
cpy #$04 ;já fez a dezena?
beq .segundoDigito ;não. calcule a dezena
cpy #$00 ;já fez o milhar?
beq .segundoDigito ;não. calcule o milhar
pla ;restaura
tax ;X = A
dex ;segundo dígito já montado?
bpl .setaNumeros ;não. recalcule novamente para o segundo dígito
rts ;retorna para a rotina principal

E:\Atari\SDK\doc\Tutorial.doc Página 187 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;---------------------------------- ;
;Tabelas ;
;---------------------------------- ;
;
align 256 ;coloca tabela na próxma página
;
HorzTable ;
.byte $00,$F0,$E0,$D0,$C0,$B0,$A0,$90 ;
.byte $71,$61,$51,$41,$31,$21,$11,$01,$F1,$E1,$D1,$C1,$B1,$A1,$91
.byte $72,$62,$52,$42,$32,$22,$12,$02,$F2,$E2,$D2,$C2,$B2,$A2,$92
.byte $73,$63,$53,$43,$33,$23,$13,$03,$F3,$E3,$D3,$C3,$B3,$A3,$93
.byte $74,$64,$54,$44,$34,$24,$14,$04,$F4,$E4,$D4,$C4,$B4,$A4,$94
.byte $75,$65,$55,$45,$35,$25,$15,$05,$F5,$E5,$D5,$C5,$B5,$A5,$95
.byte $76,$66,$56,$46,$36,$26,$16,$06,$F6,$E6,$D6,$C6,$B6,$A6,$96
.byte $77,$67,$57,$47,$37,$27,$17,$07,$F7,$E7,$D7,$C7,$B7,$A7,$97
.byte $78,$68,$58,$48,$38,$28,$18,$08,$F8,$E8,$D8,$C8,$B8,$A8,$98
.byte $79,$69,$59,$49,$39,$29,$19,$09,$F9,$E9,$D9,$C9,$B9,$A9,$99
.byte $7A,$6A,$5A,$4A,$3A,$2A,$1A,$0A,$FA,$EA,$DA,$CA,$BA,$AA,$9A
;
CHECKPAGE HorzTable ;testa se ultrapassou limite de página
;
numTable ;
.byte <zero-1, <um-1, <dois-1, <tres-1, <quatro-1
.byte <cinco-1, <seis-1, <sete-1, <oito-1, <nove-1
;
zero .byte %00111100 ;matriz de bits do número 0
.byte %01100010 ;
.byte %01010010 ;
.byte %01010010 ;
.byte %01001010 ;
.byte %01001010 ;
.byte %01000110 ;
.byte %00111100 ;
;
um .byte %01111110 ;matriz de bits do número 1
.byte %00011000 ;
.byte %00011000 ;
.byte %00011000 ;
.byte %00011000 ;
.byte %00011000 ;
.byte %00111000 ;
.byte %00011000 ;
;
dois .byte %01111110 ;matriz de bits do número 2
.byte %01100000 ;
.byte %00110000 ;
.byte %00011000 ;
.byte %00001100 ;
.byte %00000110 ;
.byte %01100110 ;
.byte %00111100 ;
;
tres .byte %00111100 ;matriz de bits do número 3
.byte %01000010 ;
.byte %00000010 ;
.byte %00011100 ;
.byte %00011100 ;
.byte %00000010 ;
.byte %01000010 ;
.byte %00111100 ;
;
quatro .byte %00001100 ;matriz de bits do número 4
.byte %00001100 ;

E:\Atari\SDK\doc\Tutorial.doc Página 188 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.byte %00001100 ;
.byte %01111110 ;
.byte %01001100 ;
.byte %00101100 ;
.byte %00011100 ;
.byte %00001100 ;
;
cinco .byte %00011000 ;matriz de bits do número 5
.byte %01100100 ;
.byte %00000010 ;
.byte %00000010 ;
.byte %01100100 ;
.byte %01011000 ;
.byte %01000000 ;
.byte %01111110 ;
;
seis .byte %00111100 ;matriz de bits do número 6
.byte %01000010 ;
.byte %01000010 ;
.byte %01111100 ;
.byte %00100000 ;
.byte %00010000 ;
.byte %00001000 ;
.byte %00000100 ;
;
sete .byte %00001000 ;matriz de bits do número 7
.byte %00001000 ;
.byte %00001000 ;
.byte %00000100 ;
.byte %00000100 ;
.byte %00000010 ;
.byte %00000010 ;
.byte %01111110 ;
;
oito .byte %00111100 ;matriz de bits do número 8
.byte %01000010 ;
.byte %01000010 ;
.byte %00111100 ;
.byte %00111100 ;
.byte %01000010 ;
.byte %01000010 ;
.byte %00111100 ;
;
nove .byte %00111100 ;matriz de bits do número 9
.byte %00000010 ;
.byte %00000010 ;
.byte %00000010 ;
.byte %00111110 ;
.byte %01000010 ;
.byte %01000010 ;
.byte %00111100 ;
;
CHECKPAGE numTable ;testa se ultrapassou limite de página
;
;---------------------------------- ;
;
echo (*-$F000)d," Bytes da ROM usados." ;
echo ($1000-(*-$F000))d,"Bytes da ROM livres." ;
;
org $FFFA ;
.word .inicioDoJogo ;NMI
.word .inicioDoJogo ;Reset
.word .inicioDoJogo ;IRQ

E:\Atari\SDK\doc\Tutorial.doc Página 189 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Entendendo o programa:

Primeiramente criamos uma variável wNumero que conterá os números a serem exibidos.
Essa variável tem tamanho 2 para representar os 4 dígitos. Poderíamos criar a mesma
variável com tamanho 4 e assim cada byte conteria 1 dígito. No nosso caso, criamos uma
variável de tamanho 2 para comportar 4 dígitos, ou seja, cada nibble representará 1 dígito.

1 byte 1 byte
1 nibble (1º dígito) 1 nibble (2º dígito) 1 nibble (3º dígito) 1 nibble (4º dígito)
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0

Depois, criamos outra variável que funcionará como ponteiro (para quem conhece Pascal e
C, sabe do que estamos falando). Essa variável tem como tamanho o dobro do número de
dígitos que queremos exibir na tela. Cada par de bytes dessa variável conterá o endereço
correspondente ao número na matriz de bits que será exibida na tela (lembrando que o
endereço é representado por 16 bits, ou seja, 2 bytes). O formato é little endian, então em
cada par, o primeiro byte conterá o offset e o segundo byte a página de memória.

Utilizaremos a variável wContador para causar um delay na contagem dos números e assim
podermos visualizar melhor seu progresso.

Na sessão Inicializações, setamos ambos os players com a cor amarela. Para setar o player
1 com a cor vermelha, basta descomentar a linha LDA #COR_VERMELHA (retirar o ponto e
vírgula do início dessa linha).

No trecho

lda #$00 ;A = 0
ldx #$03 ;número de dígitos
;
.limpaNumeros ;
sta wNumeros,x ;zera números
dex ;X = X-1. X=0?
bpl .limpaNumeros ;não. continua zerando

zeramos a variável wNumero. No caso desse programa, esse trecho pode ser excluído, pois
a memória toda é zerada no início do código (veja na página 117 desse tutorial o trecho que
zera a memória, comum a todos os programas do 2600). Deixaremos o trecho acima por
questões de ilustração.

Em seguida, no trecho

lda #$01 ;A = 1
sta NUSIZ0 ;2 cópias (próximas)
sta NUSIZ1 ;2 cópias (próximas)

Por que fazemos isso? A resposta é simples: precisamos de 4 dígitos mas temos apenas 2
players para representá-los. Então duplicamos o número de players setando o bit 0 dos
registradores NUSIZ0 e NUSIZ1 (vide páginas 81 e 82 desse tutorial). Colocando o valor 1
nos registradores, criamos para cada player, 2 cópias próximas.

Agora vem a parte que incrementa os números, ou seja, mostraremos na tela os números
sendo incrementados de 1, desde 0000 até 9999. Para isso usamos o trecho abaixo.

E:\Atari\SDK\doc\Tutorial.doc Página 190 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007
lda wNumeros ;A = centena/milhar atuais
sta COLUBK ;muda cor de fundo
inc wContador ;wContador = wContador+1
lda #$05 ;A = 5 (delay para o incremento dos números)
cmp wContador ;wContador = A?
bne .naoIncrementaNumeros ;não. não incrementa números
ldx #$01 ;X = 4 dígitos
;
.incNumLoop ;esse loop faz o placar contar até 9999
inc wNumeros,x ;incrementa unidade/centena dependendo de X
lda wNumeros,x ;A = unidade/centena dependendo de X
and #$0F ;mascara high nibble
cmp #$0A ;A = 10?
bne .naoIncrementaDezenaMilhar ;nao. sai do loop com o X apontando ainda
;para a unidade/centena, ou seja, a
;dezena/milhar fica como está
lda wNumeros,x ;lê valor em wNumeros0[X] onde X indica se
;unidade/centena
and #$F0 ;mascara low nibble
clc ;limpa carry flag
adc #$10 ;adiciona 10
sta wNumeros,x ;salva
cmp #$A0 ;A = 160? (high nibble = 10?)
bne .naoIncrementaDezenaMilhar ;nao. sai do loop com o X apontando ainda
;para a dezena/milhar
lda #$00 ;A = 0
;
.naoZera ;
sta wNumeros,x ;zera unidade/dezena ou centena/milhar
dex ;aponta para centena e milhar
bpl .incNumLoop ;faz novo cálculo
;
.naoIncrementaDezenaMilhar ;
lda #$00 ;A = 0
sta wContador ;reseta wContador
;
.naoIncrementaNumeros ;
jsr .setaPonteiros ;muda de banco

As linhas

lda wNumeros ;A = centena/milhar atuais


sta COLUBK ;muda cor de fundo

estão aí só para dar um efeito a mais. Elas farão a cor de fundo mudar conforme nossos
números são incrementados. Nossa variável wNumeros é de 2 bytes, sendo que o primeiro
byte conterá os dígitos da centena e milhar e o segundo byte conterá os dígitos da unidade e
dezena. Então a linha LDA wNumeros carrega em A a centena e milhar. Daí setamos a cor
de fundo com STA COLUBK. Se fizéssemos LDA wNumeros+1, a cor mudaria conforme o
valor da unidade e dezena e como resultado, a mudança da cor se daria de forma mais
rápida. Essas linhas são totalmente dispensáveis.

inc wContador ;wContador = wContador+1


lda #$05 ;A = 5 (delay para o incremento dos números)
cmp wContador ;wContador = A?
bne .naoIncrementaNumeros ;não. não incrementa números

O trecho acima causa um delay na contagem para podermos visualizar melhor a evolução
dos números. Mude o valor na linha LDA #$05 para alterar o delay.

E:\Atari\SDK\doc\Tutorial.doc Página 191 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Agora um pouco de cálculo (vamos detalhar o código de incremento dos números):

ldx #$01 ;X = 4 dígitos


;
.incNumLoop ;esse loop faz o placar contar até 9999
inc wNumeros,x ;incrementa unidade/centena dependendo de X
lda wNumeros,x ;A = unidade/centena dependendo de X
and #$0F ;mascara high nibble
cmp #$0A ;A = 10?
bne .naoIncrementaDezenaMilhar ;nao. sai do loop com o X apontando ainda
;para a unidade/centena, ou seja, a
;dezena/milhar fica como está

O trecho acima incrementa o nibble inferior de cada byte, ou seja, se considerarmos 4


dígitos incrementa a unidade e a centena, dependendo do valor de X. Como X inicialmente é
1 então incrementaremos o byte que representa a unidade e dezena.

wNumeros = 2 bytes

wNumeros
Byte 1 Byte 2

wNumeros[0]
wNumeros[1]

Valores de X

Como é feito? Ora, utilizando uma das formas de endereçamento. Nossa variável wNumeros
tem 2 bytes. Carregamos 01h em X e então fazemos INC wNumeros,X. É o mesmo que
fazer INC wNumeros[1], onde [1] é o índice. Daí carregamos esse valor em A e em seguida
mascaramos para extrair somente o nibble inferior, ou seja, zeramos o nibble superior.
Então, comparamos esse valor resultante com 0Ah (10d). Se A ainda não é 10d então não
há necessidade de incrementar o nibble superior, daí o desvio BNE é satisfeito.

lda wNumeros,x ;lê valor em wNumeros0[X] onde X indica se


;unidade/centena
and #$F0 ;mascara low nibble
clc ;limpa carry flag
adc #$10 ;adiciona 10
sta wNumeros,x ;salva
cmp #$A0 ;A = 160? (high nibble = 10?)
bne .naoIncrementaDezenaMilhar ;nao. sai do loop com o X apontando ainda
;para a dezena/milhar

O trecho acima é executado quando A (do trecho anterior) é 10d. Ele é responsável por
incrementar o nibble superior do byte, ou seja, se considerarmos 4 dígitos incrementa a
dezena e o milhar, dependendo do valor de X. Como X inicialmente é 1 então
incrementaremos a dezena. O desvio não ocorre e o código acima é executado. Carregamos
em A novamente o valor que está em wNumeros[x] e agora mascaramos para extrair o
nibble superior, ou seja, zeramos o nibble inferior. Adicionamos 10h, assim teremos valores
do tipo 00h, 10h, 20h, 30h... ou seja, somente o nibble superior é incrementado. Daí
testamos se esse valor é A0h. Se A ainda não é A0h então não há necessidade de zerar os
números.

E:\Atari\SDK\doc\Tutorial.doc Página 192 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007
lda #$00 ;A = 0
;
.naoZera ;
sta wNumeros,x ;zera unidade/dezena ou centena/milhar
dex ;aponta para centena e milhar
bpl .incNumLoop ;faz novo cálculo

O trecho acima é executado quando A (do trecho anterior) é A0h. Ele é responsável por
zerar o número quando a condição anterior não é satisfeita. Daí decrementamos X (que era
1 inicialmente) e X passa a ser 0. Como 0 é positivo, o desvio no BPL ocorre, direcionando o
fluxo novamente para .incNumLoop. Agora o 6502 vai fazer tudo de novo, porém com X
sendo 0, ou seja, agora ele fará os cálculos para o próximo byte (centena e milhar). Simples.

Mas não há uma forma mais fácil de fazer isso? A resposta é sim. No código apresentado,
estamos lidando com os nibbles dos bytes manualmente. Há uma forma simples e prática de
fazer isso automaticamente. O código apresentado se transformará em:

;---------------------------------- ;
;Loop Principal ;
;---------------------------------- ;
.inicioDoFrame ;início do frame
lda #$2B ;2Bh = 43d
sta TIM64T ;seta o timer
lda #$02 ;liga o VSYNC
sty VSYNC ;
sta WSYNC ;
sta WSYNC ;aguarda 3 scanlines
sta WSYNC ;
ldy #$00 ;desliga VSYNC
sty VSYNC ;
sta VBLANK ;liga o VBLANK
lda wNumeros ;A = centena/milhar atuais
sta COLUBK ;muda cor de fundo
inc wContador ;wContador = wContador+1
lda #$10 ;A = 5 (delay para o incremento dos números)
cmp wContador ;wContador = A?
bne .naoIncrementaNumeros ;não. não incrementa números
ldx #$01 ;X = 4 dígitos
php ;salva flags
sed ;seta decimal mode
;
.incrementaCentenaMilhar ;
clc ;limpa carry flag
lda wNumeros,x ;lê par de dígitos de acordo com X
adc #01 ;soma 1
sta wNumeros,x ;armazena novo valor
bne .naoIncCentenaMilhar ;chegou a zero?
dex ;sim. incrementa centena/milhar
jmp .incrementaCentenaMilhar ;incrementa
;
.naoIncCentenaMilhar ;
plp ;restaura flags
lda #$00 ;A = 0
sta wContador ;reseta wContador
;
.naoIncrementaNumeros ;
jsr .setaPonteiros ;muda de banco
;

Muito mais simples. E tudo isso por causa de um flag. O Decimal Mode flag. Quando
setamos esse flag, os valores dos registradores em operações de soma e subtração são
E:\Atari\SDK\doc\Tutorial.doc Página 193 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

tratados como números decimais e não hexadecimais. Assim, se o valor de A for 09d, por
exemplo, ao somarmos 1, em vez de ele ser 0Ah, ele será 10d.

Se você quiser trabalhar com números hexadecimais, porém interpretá-los como decimais
sem utilizar o Decimal flag, basta adicionar 06h ao byte caso ele seja maior que 09h. Por
exemplo:

0Ah + 06h = 10h

Ou seja, para nossa interpretação, 10h = 0Ah em decimal. Outro exemplo:

13h + 06h = 19h

Convertendo 13h para decimal temos 19d. Então 19d e 19h são iguais (se ignoramos a
base, claro).

Porém:

14h + 06h = 1Ah

Convertendo 1Ah para decimal temos 20d. Como resolver? Basta pegarmos o nibble inferior.
Como ele é maior que 09h somamos 06h novamente. Então:

1Ah >> nibble inferior = 0Ah


Ou seja, 0Ah + 06h = 10h

Agora somamos os nibbles superiores do 1Ah e do resultado 10h = 20h.

E se for 1Fh + 06h? O resultado é 25h. Mas 1Fh = 31d, então:

Nibble inferior de 25h = 05h + 06h = 0Bh

0Bh é maior que 09h então 0Bh + 06h = 11h

Agora basta fazermos 11h + o nibble superior de 25h = 2h e temos: 11h + 20h = 31h que, se
ignorarmos a base, é igual a 31d (31 = 31).

Nota: Para pegar o nibble superior basta fazer o número AND F0h e para pegar o nibble
inferior basta fazer o número AND 0Fh.

Voltando ao nosso exemplo, utilizamos o PHP e o PLP para salvar e restaurar


respectivamente os flags. Nesse programa em particular, são totalmente desnecessários.

Eles seriam necessários em um programa no qual fosse importante salvar os flags antes de
alterar qualquer um deles (no caso o Decimal Mode). Daí quando terminássemos nossos
cálculos, restauraríamos os flags para o status que eles eram antes dos cálculos.

Veja também que o status dos flags após os cálculos é totalmente descartável, ou seja,
podemos restaurar como eles eram antes porque não dependemos deles fora dos cálculos.

E:\Atari\SDK\doc\Tutorial.doc Página 194 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

wNumeros
1 byte 1 byte
1 nibble (milhar) 1 nibble (centena) 1 nibble (dezena) 1 nibble (unidade)
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0

0 1
X

Depois de conhecermos 2 formas de incrementar números, passamos para a parte em que


montamos os gráficos.

Agora que a variável wNumeros tem o novo valor, precisamos fazer com que esse valor seja
exibido na tela. A exibição se dá de forma gráfica, então temos uma matriz de bits que
compõe cada número (de 0 a 9). Por exemplo:

dois .byte %01111110 ;matriz de bits do número 2


.byte %01100000 ;
.byte %00110000 ;
.byte %00011000 ;
.byte %00001100 ;
.byte %00000110 ;
.byte %01100110 ;
.byte %00111100 ;

é o mesmo que

.byte %01111110 X X X X X X
.byte %01100000 X X
.byte %00110000 X X
.byte %00011000 X X
.byte %00001100 X X
.byte %00000110 X X
.byte %01100110 X X X X
.byte %00111100 X X X X

Cada número é identificado por um label. Esse label está se referindo ao offset do último
byte da matriz de bits que compõe cada número (contando de baixo para cima). No exemplo
do número 2 acima, o label dois está referenciando o primeiro byte (de cima para baixo) da
matriz de bits (ou último byte que representa a base do desenho do número 2, vai da leitura
e interpretação de cada um).

O que importa é que esse label estará indicando um offset e esse offset nos dá a posição na
ROM de onde está nosso número 2. Então para chegarmos até ele, basta colocar em algum
registrador esse offset (veremos logo).

O procedimento (que vai ser detalhado mais adiante) é o seguinte: lemos o valor de
wNumeros, pegamos um dos dígitos, localizamos na matriz de bits o offset desse número,
colocamos em um ponteiro e desenhamos o gráfico a partir desse ponteiro. Básico.

E:\Atari\SDK\doc\Tutorial.doc Página 195 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Então, voltando ao programa, depois de incrementar wNumeros, chamamos a rotina que


setará os ponteiros de acordo com os dígitos em wNumeros.

jsr .setaPonteiros ;muda de banco

Essa rotina é:

.setaPonteiros ;
ldx #$01 ;X = 4 dígitos
ldy #$06 ;Y = 16 bits cada ponteiro
;
.setaNumeros ;
txa ;A = X
pha ;salva
lda wNumeros,x ;
;
.segundoDigito ;
pha ;salva número lido
and #$0F ;mascara high nibble
tax ;X = dígito
lda numTable,x ;lê na tabela de números o dígito X
sta wNumerosPtr,y ;salva o endereço no ponteiro (low order)
lda #>numTable ;obtém high order
sta wNumerosPtr+1,y ;salva high order
pla ;restaura número lido
lsr ;divide por 16 para
lsr ;fazer com que o nibble
lsr ;superior passe para
lsr ;o nibble inferior
dey ;
dey ;decrementa ponteiro
cpy #$04 ;já fez a dezena?
beq .segundoDigito ;não. calcule a dezena
cpy #$00 ;já fez o milhar?
beq .segundoDigito ;não. calcule o milhar
pla ;restaura
tax ;X = A
dex ;segundo dígito já montado?
bpl .setaNumeros ;não. recalcule novamente para o segundo dígito
rts ;retorna para a rotina principal

Mais uma vez utilizamos X para determinar quem está sendo lido. Isso é feito quando
setamos X com 01h (LDX #$01) e depois lemos wNumeros (LDA wNumeros,x).

Quando o 6502 entra em .segundoDigito, X é 1 então ele fará os cálculos para a unidade.
Primeiro ele salva o byte (com PHA) pois vai precisar usar esse número 2 vezes. Então, ele
extrai o nibble inferior, ou seja, zera o nibble superior do número lido e o transfere para X.
Agora através da linha LDA numTable,X ele coloca em A o offset do número que está em X
e em seguida armazena esse offset em wNumerosPtr, que é o ponteiro para a matriz de bits.
Depois com LDA #>numTable, carrega em A a página de memória onde numTable está e
em seguida armazena em wNumerosPtr+1. Com isso, monta em wNumerosPtr o
offset+página de memória, ou seja, o endereço de 16 bits para a matriz de bits.

Feito isso com o nibble inferior, ele restaura o valor lido (com PLA) e desloca os 4 bits
superiores (nibble superior) para os 4 bits inferiores (nibble inferior). Daí decrementa Y 2
vezes, pois o ponteiro é formado de pares de bits. Se Y for 04h então ele calculará a
dezena, direcionando o fluxo para .segundoDigito com o A carregado com a dezena
deslocada 4 bits para a direita.

E:\Atari\SDK\doc\Tutorial.doc Página 196 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Daí quando ele decrementar Y 2 vezes novamente, Y agora terá o valor 02h. O desvio nos 2
BEQs não será satisfeito então ele restaura o valor de X (que era 1 inicialmente) e o
decrementa. Como X agora é 0, ele considera positivo e o desvio BPL ocorre, desviando o
fluxo para .setaNumeros. Agora o A é carregado com o byte de wNumeros que corresponde
a centena/milhar e o mesmo processo anterior é repetido. No final, ele terá montado um
ponteiro de 8 bytes (cada par representando um endereço de memória) e retorna ao
chamador através do RTS.

Depois do .aguardaVBLANK vem o posicinamento dos players. Não entraremos em detalhes


aqui pois já conhecemos o código. Que importa agora é dentro do .scanLoop.

.scanLoop ;
lax (wNumerosPtr+6),y ;5
lda (wNumerosPtr+4),y ;5
pha ;+3
;=8
lda (wNumerosPtr),y ;5
sta.w GRP1 ;+4 milhar
repeat 3 ;
nop ;+(2 * 3 = 6)
repend ;
;=15
lda (wNumerosPtr+2),y ;5 centena
sta GRP0 ;+3
;=8
pla ;4
sta GRP1 ;+3 unidade
;=7
stx GRP0 ;3
sta WSYNC ;0 próximo scanline
dey ;Y = Y-1
bne .scanLoop ;desenhou as 8 linhas? não. continua desenhando

Dentro do .scanLoop carregamos o valor endereçado em cada par de wNumerosPtr e


colocamos nos players.

A figura 100 mostra o resultado desse programa e a figura 101 mostra os números para
players com cores diferentes. Por essa figura, podemos observar que os players são
posicionados de forma intercalada.

Quando setamos o NUSIZx para criar 2 cópias próximas, a distância de uma cópia para a
outra é tal que, ao intercalar o outro player, tem-se a impressão de ser tudo uma coisa só,
devido às distâncias entre eles serem correspodentes a uma largura de player.

Nota: A numTable e a matriz de bits devem estar na mesma página de memória, pois
colocamos em wNumerosPtr+1 a página de memória de numTable e em wNumerosPtr o
valor que está em numTable,X e não seu offset. Na verdade, o valor que está em
numTable,X é o offset do número na matriz de bits, conforme construção abaixo:

numTable ;
.byte <zero-1, <um-1, <dois-1, <tres-1, <quatro-1
.byte <cinco-1, <seis-1, <sete-1, <oito-1, <nove-1

Dessa forma, podemos alterar a ordem dos números na matriz de bits (para compartilhar
bytes por exemplo (detalhes na próxima sessão)) que numTable se encarrega de “colocá-los
na ordem” para que o algoritmo os leia baseado no índice X.

E:\Atari\SDK\doc\Tutorial.doc Página 197 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 100

Figura 101

Sharing bytes
No programa anterior, utilizamos apenas 2 bytes para armazenar 4 dígitos. Isso acarretou
em mais código para “montar” e “desmontar” os dígitos, ou seja, mais lógica, mais bytes de
ROM, mais ciclos de máquina. Se utilizássemos 4 bytes, o código ficaria menor, acarretando
em menos bytes de ROM e talvez menos ciclos, entretanto estaríamos gastando mais 2
bytes de RAM.

Quando criamos um programa para o 2600 devemos levar em conta sobre o que podemos
nos dar ao luxo de gastar: ROM ou RAM? E de que forma os ciclos são afetados?

Tudo isso depende: Se nosso programa for muito grande, utilizamos a RAM. Se tivermos
muitas variáveis então economizamos a RAM e deixamos por conta do 6502 executar mais
código. Ainda assim, em um programa como o anterior (que exibe números) é possível
economizar bytes da ROM. Basta compartilhar (share) bytes.

E:\Atari\SDK\doc\Tutorial.doc Página 198 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Vamos supor que temos uma matriz de bits que representa números de 0 a 9 em um
formato mais “quadrado” (digital).

Os números acima foram desenhados com 4 colunas (4 bits) cada só para termos uma
melhor visualização. No programa de exibição de números descrito na sessão anterior,
utilizamos os 8 bits dos players.

De posse dessa matriz, nos perguntamos: quais números podem compartilhar a primeira e
última linha com outro(s) número(s)?

Quanto mais quadrado for o desenho, mais compartilhamento de bytes será possível
realizar.

Assim, basta reescrevermos os números verticalmente, compartilhando suas primeiras e


últimas linhas.

E:\Atari\SDK\doc\Tutorial.doc Página 199 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Os números são (de cima para baixo): 6, 2, 3, 5, 8, 0 e 9. O 1, 4 e 7 serão escritos sem


compartilhamento. As setas indicam onde os labels dos números deverão ser escritos, ou
seja, com os labels nessas posições, o programa montará os números normalmente.

Para quem ainda não entendeu, veja a figura 102, que representa o número 6 e o número 2
com bytes compartilhados:

E:\Atari\SDK\doc\Tutorial.doc Página 200 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 102

.dois .byte %11110000 ; |XXXX |


.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %11110000 ; |XXXX |
.byte %00010000 ; | X |
.byte %00010000 ; | X |
.byte %00010000 ; | X |
.seis .byte %11110000 ; |XXXX |
.byte %10010000 ; |X X |
.byte %10010000 ; |X X |
.byte %11110000 ; |XXXX |
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %10000000 ; |X |

No caso desses 2 números, economizamos 1 byte, ou seja, para cada par de números
economizamos 1 byte.

Com isso encerramos nossos tópicos de como criar um programa para o 2600. Não
exploramos tudo nesse tutorial, pois, ele é bem básico. Só serve de introdução. É só para
dar uma idéia da coisa.

Nos próximos tópicos veremos alguma coisa sobre bankswitching e vamos hackear uma
ROM utilizando o debugger do Stella.

E:\Atari\SDK\doc\Tutorial.doc Página 201 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Bankswitching
Você já está expert na programação de jogos para o 2600 e 4K é pouco para o jogaço que
está escrevendo. Vamos mostrar uma das várias formas de conseguir mais “espaço” na
ROM para programar jogos mais complexos e até mesmo mais de um jogo por cartucho
(lembra-se daqueles cartuchos com 2, 4, 32, etc. jogos?). Mas antes, um pouco de teoria...

É claro que o que vamos descrever aqui está de forma (muito) simplificada, pois se
entrarmos no mérito técnico da coisa, o entendimento seria mais complicado.

Cada byte de um programa é gravado na ROM em uma célula. De forma geral (e para
simplificar o entendimento) vamos dizer que essas células estão enfileiradas. Cada célula
então tem um número de ordem (ou endereço). Esse endereço é acessado pelo 6502
através de seu registrador PC. Uma ROM típica tem 4096 bytes de espaço para
armazenamento, ou seja, 4096 células, cada uma identificada por seu endereço e que pode
ser acessada através do regitrador PC. Lembrando que 4096 bytes = 4K. Então um grupo de
células (4K) é chamado de banco de memória, ou simplesmente banco daqui em diante.

Observemos a figura 103. Nela podemos ver, da direita para a esquerda, a coluna do
programa, a coluna com os opcodes de cada instrução e a coluna de offset. Essa coluna de
offset (com números hexadecimais, ou seja, iniciados por $) é o endereço de cada byte
(instrução) do programa na ROM. São esses números que o registrador PC do 6502
percorrerá e se orientará nos desvios e chamadas de subrotinas.

Podemos observar que a primeira instrução, SEI, tem somente 1 byte (78) e está no
endereço F000h. Como ela só tem 1 byte, então ela ocupa somente 1 célula da ROM e a
próxima instrução então estará no endereço F001h (que no caso da figura é o CLD e seu
opcode é D8). Pela figura, observamos as instruções e quantos bytes elas ocupam. O offset
muda de acordo com o tamanho de cada instrução.

Figura 103

E:\Atari\SDK\doc\Tutorial.doc Página 202 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Agora, observemos a figura 104.

Figura 104

Apesar de serem parecidos, os códigos são diferentes a partir do offset F007h. O que
queremos mostrar aqui é que ambos começam no offset F000h.

Vamos fazer uma analogia: Você está assistindo a um canal de TV. Daí você muda de
canal, ou seja, o que está sendo exibido é diferente, mas a janela de visão (por onde você
está olhando) continua a mesma.

Em relação às 2 figuras (103 e 104) o 6502 “vê” o que é colocado em sua janela de visão, ou
seja, você coloca um cartucho e ele vê o quem dentro dele (o programa), mas sempre no
mesmo lugar: a partir do offset F000h.

Então, no 2600, quando você trocava de cartucho o 6502 “via” o novo programa no mesmo
lugar do anterior.

No bankswitching não há a troca física do cartucho. O mesmo cartucho possui mais de um


banco e a troca é feita via hardware (no caso de cartuchos com aquelas chaves) ou
software.

No nosso caso, como estamos programando para rodar em um emulador, obviamente


faremos o bankswitching por software.

O bankswitching consiste então em substituir um programa por outro na “visão” do 6502.

E:\Atari\SDK\doc\Tutorial.doc Página 203 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Uma ROM de 2 bancos


Vamos começar com um exemplo básico. Uma ROM com 2 bancos, ou seja, 8K. Devemos
digitar o programa, compilar e rodar no modo debug para melhor visualização.

Processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
;=================================================================================
;Código
;=================================================================================
Seg Bank0 ;
org $E000 ;alocado 4K abaixo
rorg $F000 ;realoca para F000h
;
.banco0 ;
nop ;1 NOP para o SEI do banco #1
nop ;1 NOP para o CLD do banco #1
nop ;2 NOPs para o LAX $00 do banco #1
nop ;
nop ;2 NOPs para o STA $00,x do banco #1
nop ;
nop ;1 NOP para o DEX do banco #1
nop ;2 NOPs para o BNE .limpaMemoria do banco #1
nop ;
;
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
nop ;3 NOPs para o LDA $1FF8 do banco #1
nop ;
nop ;
lda #$01 ;<<<< executa daqui quando vem do banco #0
ldx #$02 ;
ldy #$03 ;
lda $1FF9 ;volta para o banco #0
;
org $EFFA ;alocado 4K abaixo
rorg $FFFA ;realoca para FFFAh
.word banco0 ;NMI
.word banco0 ;Reset
.word banco0 ;IRQ
;
;********
;ATENÇÃO!
;********
;
;Deve-se digitar o código abaixo dentro do mesmo arquivo do código acima, como se
;tivéssemos 2 programas dentro de um mesmo arquivo.
;
;=================================================================================
;Código
;=================================================================================
Seg Bank1 ;
org $F000 ;
;
.banco1 ;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;

E:\Atari\SDK\doc\Tutorial.doc Página 204 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;---------------------------------- ;
;Inicializações ;
;---------------------------------- ;
lda $1FF8,1 ;vai para o banco #0
nop ;2 NOPS para o LDA #$01 do banco #0
nop ;
nop ;2 NOPS para o LDX #$02 do banco #0
nop ;
nop ;2 NOPS para o LDY #$03 do banco #0
nop ;
nop ;3 NOPs para LDA $1FF9,0 do banco #0
nop ;
nop ;
lda #$AA ; <<<< executa daqui quando vem do banco #0
ldx #$BB ;
ldy #$CC ;
jmp .banco1 ;loop infinito
;
org $FFFA ;
.word banco1 ;NMI
.word banco1 ;Reset
.word banco1 ;IRQ

Ao rodar no modo debug, o Stella inicia a execução do banco #1, conforme a figura 105.

Figura 105

Observe na mesma figura, o 1 do Current bank, indicando o banco atual e o total de bancos,
Total banks, igual a 2, ou seja, nossa ROM tem 2 bancos.

E:\Atari\SDK\doc\Tutorial.doc Página 205 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O início do código já conhecemos. É aquela parte que zera toda a RAM. Para que não
precisemos executar passo a passo essa parte (um loop de 255 passos, o que demoraria
muito), colocamos um breakpoint em $F009. Para isso, basta clicar no lado esquerdo do
offset $F009 e um quadrado vermelho aparece, conforme a figura 106.

Figura 106

Agora clicamos em Exit (figura 107) para sairmos do modo debug e executarmos o
programa normalmente. Quando o 6502 atingir o offset $F009, o Stella retornará ao modo
debug automaticamente.

Figura 107

Agora que chegamos no breakpoint, observe a figura 108. O registrador PC aponta para o
offset que será executado, no nosso caso o $F009. Então vamos executar passo a passo o
programa. Para isso, clique no botão Step (figura 107). Ao clicar nesse botão, a instrução
que está no offset $F009 será executada. Essa instrução lê um endereço especial que diz
para mudar de banco. Na verdade, é um strobe, pois tanto faz ler ou escrever. O que
importa é: acessou o endereço, alguma coisa acontece. Essa é a natureza dos strobes.

E:\Atari\SDK\doc\Tutorial.doc Página 206 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 108

Então, ao clicar em Step, o Stella executa a instrução que diz para ir para o banco #0. Com
isso, a tela fica conforme a figura 109.

Note que o breakpoint continua em $F009 apesar de termos mudado de banco. Isso reforça
o que já dissemos: o 6502 agora “vê” outro programa, mas utiliza a mesma “janela de visão”
para isso.

Observe também na figura que o Current bank agora é 0 e o código do banco #0 está
carregado.

Figura 109

Quando ainda estávamos no banco #1, no offset $F009 onde está nosso breakpoint, o
registrador PC era $F009, ou seja, o registrador PC apontava para o offset que seria
executado, certo? Depois que clicamos em Step, o 6502 executou a instrução e passou para
o próximo offset, ou seja, o registrador PC agora aponta para $F00C (pois a instrução LDA
$1FF8 gasta 3 bytes). Entretanto, a instrução diz para mudar de banco. Com isso, o outro
banco (#0) agora é o banco atual e seu código é colocado no lugar do código do banco #1.
Porém, o registrador PC continua apontando para $F00C, e o 6502 vai continuar executando
o programa a partir desse offset. Imagine que o 6502 fica lendo os endereços não
importando o que esteja neles. Se você troca o conteúdo, ele continua fazendo o trabalho. É
como se você estivesse com os olhos vendados e almoçando. Seu processo é pegar a
E:\Atari\SDK\doc\Tutorial.doc Página 207 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

comida e levá-la até a boca. Várias vezes repetindo esse movimento. Se alguém troca o
prato, você continuará fazendo a mesma coisa, porém o conteúdo é diferente.

Por isso, o que queremos executar no banco #0 deve estar depois do offset $F009. Para
tornar isso possível, colocamos vários NOPs antes do código que queremos executar no
banco #0. Esses NOPs ocupam o mesmo lugar do código do banco #1. Quando o PC chega
em $F009, já gastamos 9 bytes, por isso temos 9 NOPs. Entretanto, a própria instrução em
$F009, que é LDA $1FF8, gasta 3 bytes. Daí quando o PC lê essa instrução ele vai para
$F00C, totalizando 12 bytes. No banco #0, colocamos então, além dos 9 NOPs, mais 3
NOPs correspondentes ao LDA $1FF8 do banco #1. Assim, quando o PC ler o LDA $1FF8
no offset $F009 do banco #1, o Stella carrega o banco #0 e o PC vai para $F00C (figura
110). O código útil do banco #0 inicia-se em $F00C devido aos 12 NOPs anteriores,
conforme a figura 109. Então, ao mudarmos de banco, a execução continua normalmente.

Figura 110

Continuamos a clicar em Step e executamos as 3 linhas do banco #0, que simplesmente


carregam valores para A, X e Y, e estão aí só para ilustrar, ou seja, só para visualizarmos
que o código do banco mudou, sem nenhuma outra utilidade.

Quando o PC chegar em $F012, ele encontrará a instrução LDA $1FF9 que diz ao Stella
carregar o banco #1 (figura 111).

Figura 111

E:\Atari\SDK\doc\Tutorial.doc Página 208 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Ao executar essa instrução o PC vai para o offset $F015 mas o banco #1 já terá sido
carregado, e o que estiver no offset $F015 do banco #1 será executado. Veja a figura 112.

Figura 112

De acordo com a figura 112, voltamos para o banco #1 e as instruções a partir do offset
$F015 estão lá só para efeito de ilustração também. Entretanto, observe os NOPs. Eles
ocupam o mesmo espaço do código útil do banco #0. Vamos colocar os códigos lado a lado
para melhor visualização.

Conte os bytes que cada instrução utiliza da ROM e confronte com a quantidade de NOPs.
As 2 quantidades devem ser iguais.

Os 12 NOPs do banco #0 são referentes ao código do banco #1, inclusive a instrução de


mudança de banco e os 9 NOPs do banco #1 são referentes ao código do banco #0,
inclusive a instrução de mudança de banco.

E:\Atari\SDK\doc\Tutorial.doc Página 209 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Com essas ilustrações começamos a ter idéia de como funciona o bankswitching. Agora
vamos ir direto para uma ROM de 8 bancos e ver como mudamos de banco nessa ROM.

E:\Atari\SDK\doc\Tutorial.doc Página 210 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Uma ROM de 8 bancos


Com essa ROM além de vermos como mudar de banco da forma já explicada, veremos
também uma outra forma de mudança: a que é possível mudar de banco e ir direto para um
determinado offset dentro desse banco. Assim não temos que ficar colocando NOPs, ou
seja, os programas dentro dos bancos não têm que ter o mesmo tamanho, não precisam ser
simétricos (um banco não precisa ser preenchido com NOPs onde, no outro banco, temos
instruções úteis), e a execução não é seqüencial em função do offset onde ocorreu a
mudança.

O código a seguir é mais ou menos um padrão para a estrutura, exceto pela macro. A macro
startBank é utilizada para garantir que, ao rodar o programa, o banco #0 será sempre
carregado primeiro. Assim saberemos qual é a porta de entrada para o nosso programa
(como o método main do Java ou função main do C). Se quiser fazer sem a macro também
pode. Entretanto, para garantir que o start se dará sempre por um determinado banco, deve-
se colocar o código que muda de banco no início da cada banco (essa frase ficou cheia de
palavras “banco”). A macro é só uma forma de facilitar as coisas na hora da programação. O
programa fonte fica mais enxuto. Entretanto é indiferente para o compilador. Onde ele
encontrar a macro, colocará o código dela. É como se automatizássemos a digitação de
códigos repetidos.

A colocação de um código que diz por qual banco queremos iniciar nosso programa se faz
necessária porque dependendo do emulador, coisas estranhas podem acontecer, como por
exemplo, o start se dar por qualquer banco. Como iniciaremos sempre pelo banco #0, no
próprio banco não precisamos colocar a macro.

Se mudarmos o valor de Y para 01h, por exemplo, o start se dará sempre pelo banco #1 e
assim por diante (até o máximo de 7, totalizando 8 bancos).

Toda essa estrutura pode ficar dentro do mesmo arquivo .ASM para ser compilada.

Observe que temos apenas um único segmento para as variáveis. Isso mesmo. Cada banco
é um programa, porém, compartilham um único segmento de memória.

Em cada mudança de banco, apenas o código do programa é substituído para ser


executado. A RAM continua a mesma para todos eles.

Processor 6502 ;processador


include ..\includes\vcs.h ;include
include ..\includes\macro.h ;include
;
;=================================================================================
;Macros
;=================================================================================
MAC startBank ;colocar no início de cada banco (exceto no #0)
;
inicioDoBanco ;
ldy #$00 ;
lda $1FF4,y ;startup sempre pelo banco #0
ENDM ;
;
;-----------------------------------;
;

E:\Atari\SDK\doc\Tutorial.doc Página 211 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;=================================================================================
;Constantes
;=================================================================================
;
;=================================================================================
;Variáveis
;=================================================================================
Seg.U Variaveis ;
org $80 ;
;
;=================================================================================
;Bank #0
;=================================================================================
Seg Bank0 ;
org $8000 ;aloca 28K abaixo
rorg $F000 ;realoca para F000h
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;----------------------------------;
;Seu código aqui ;
;----------------------------------;
;
org $8FFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;
;=================================================================================
;Bank #1
;=================================================================================
Seg Bank1 ;
org $9000 ;aloca 24K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
;
org $9FFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #2
;=================================================================================

E:\Atari\SDK\doc\Tutorial.doc Página 212 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Seg Bank2 ;
org $A000 ;aloca 20K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
;
org $AFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;
;=================================================================================
;Bank #3
;=================================================================================
Seg Bank3 ;
org $B000 ;aloca 16K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
;
org $BFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;
;=================================================================================
;Bank #4
;=================================================================================
Seg Bank4 ;
org $C000 ;aloca 12K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
;
org $CFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;
;=================================================================================
;Bank #5
;=================================================================================
Seg Bank5 ;
org $D000 ;aloca 8K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;

E:\Atari\SDK\doc\Tutorial.doc Página 213 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;----------------------------------;
; Seu código aqui ;
;----------------------------------;
;
org $DFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;
;=================================================================================
;Bank #6
;=================================================================================
Seg Bank6 ;
org $E000 ;aloca 4K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
;
org $EFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;
;=================================================================================
;Bank #7
;=================================================================================
Seg Bank7 ;
org $F000 ;
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
;
org $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

Agora vamos ver como mudamos de um banco para o outro. Vamos fazer um teste bem
rápido. Do banco #0 vamos mudar para o banco #4 e do banco #4 vamos mudar para o
banco #6. Basta alterar os seguintes bancos:

;=================================================================================
;Bank #0
;=================================================================================
Seg Bank0 ;
org $8000 ;aloca 28K abaixo
rorg $F000 ;realoca para F000h
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;

E:\Atari\SDK\doc\Tutorial.doc Página 214 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sei ;seta disable interrupt


cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;----------------------------------;
;Seu código aqui ;
;----------------------------------;
ldy #$04 ;banco #4
lda $1FF4,y ;muda de banco
;
org $8FFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #4
;=================================================================================
Seg Bank4 ;
org $C000 ;aloca 12K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;esses NOPs se referem ao código do banco #0
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
ldy #$06 ;banco #6
lda $1FF4,y ;muda de banco
;
org $CFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

E:\Atari\SDK\doc\Tutorial.doc Página 215 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;=================================================================================
;Bank #6
;=================================================================================
Seg Bank6 ;
org $E000 ;aloca 4K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;esses NOPs se referem ao código do banco #0
nop ;
nop ;mais o código do banco #4
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
lda #$01 ;código do banco #6
;
org $EFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

Ao executar esse código no modo debug, podemos notar a mudança de banco conforme as
figuras 113, 114 e 115. Observe que o código de um banco deve iniciar no offset seguinte ao
offset onde termina o código do banco chamador (inclusive a instrução de mudança de
banco). Há várias formas de fazer bankswitching. Criando tabelas de vetores, alterando o
ponteiro do vetor de IRQ e executando o BRK, colocando o destino na pilha, etc. Vamos ver,
nesse tutorial, a última forma elencada: colocando o destino na pilha.

Como vimos no exemplo anterior, conforme necessitamos mudar de banco, devemos


colocar NOPs correspondentes à quantidade de bytes gasta pelo código do banco
chamador, uma vez que, quando esse banco executa a instrução de mudança de banco, o
registrador PC está em determinado offset e continuará a partir desse offset no banco
chamado. Para contornar isso, colocamos o destino na pilha, mudamos de banco e
recuperamos o destino da pilha. Assim o registrador PC é obrigado a mudar para onde
quisermos.

E:\Atari\SDK\doc\Tutorial.doc Página 216 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 113

Figura 114

E:\Atari\SDK\doc\Tutorial.doc Página 217 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 115

Vamos alterar o exemplo anterior para eliminarmos a necessidade nos NOPs e executar a
partir de determinado ponto dentro do banco de destino.

;=================================================================================
;Bank #0
;=================================================================================
Seg Bank0 ;
org $8000 ;aloca 28K abaixo
rorg $F000 ;realoca para F000h
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;----------------------------------;
;Seu código aqui ;
;----------------------------------;
lda #>(.rotinaBanco4-1) ;colocamos a página de memória da rotina
pha ;na pilha (high order)
lda #<(.rotinaBanco4-1) ;colocamos o offset da rotina
pha ;na pilha (low order)
jmp .mudaBanco ;vamos para a rotina de mudar de banco

E:\Atari\SDK\doc\Tutorial.doc Página 218 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

org $8F01 ;alocamos a rotina para mudar de banco em 8F01h


rorg $FF01 ;realocamos para FF01h
;só para não ter que preencher com NOPs
.mudaBanco ;
ldy #$04 ;banco #4
lda $1FF4,y ;mudamos de banco
;
org $8FFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #4
;=================================================================================
Seg Bank4 ;
org $C000 ;aloca 12K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
ldy #$06 ;banco #6
lda $1FF4,y ;muda de banco
;
org $CF06 ;alocamos em CF06h
rorg $FF06 ;realocamos para FF06h
rts ;retornamos da chamada de subrotina???
;
org $CFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

Antes de fazermos também a alteração para mudar do banco #4 para o banco #6, vamos
verificar o que foi feito. No banco #0, o código:

lda #>(.rotinaBanco4-1) ;colocamos a página de memória da rotina


pha ;na pilha (high order)
lda #<(.rotinaBanco4-1) ;colocamos o offset da rotina
pha ;na pilha (low order)
jmp .mudaBanco ;vamos para a rotina de mudar de banco

Faz o seguinte: o compilador coloca em A (no primeiro LDA) o byte alto do endereço onde
está a .rotinaBanco4. Lembramos que um endereço, no 6502, é composto de 16 bits, ou 2
bytes. Daí colocamos esse valor na pilha. Depois, pegamos (com o segundo LDA), o byte
baixo do endereço onde está a .rotinaBanco4 e colocamos também na pilha. Fizemos nessa
ordem porque, lembrando novamente, o endereçamento é na forma little endian, ou seja, o
menor vem primeiro.

Como pegamos um endereço e colocamos na pilha, é o mesmo que se tivéssemos feito um


JSR, vide página 33 desse tutorial. Então, o JSR salva um endereço na pilha. O que fizemos
foi salvar um endereço na pilha também. Só que o JSR salva o endereço atual (o que está
em PC) + 1. Nós salvamos o endereço da .rotinaBanco4. Fizemos isso sem usar JSR uma

E:\Atari\SDK\doc\Tutorial.doc Página 219 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

vez que não queremos ir para essa subrotina agora, pois ainda estamos no banco #0 e ela
não existe nesse banco. Agora vem a parte do banco #4.

org $CF06 ;alocamos em CF06h


rorg $FF06 ;realocamos para FF06h
rts ;retornamos da chamada de subrotina???

Quando o 6502 executar a instrução LDA $1FF4,y, o registrador PC estará em FF05h.


Alocamos a rotina de mudança de banco em FF01h. A instrução LDY #$04 gasta 2 bytes e a
instrução LDA $1FF4,y gasta 3 bytes então: (FF01 + 2 + 3) - 1 = FF05h que é onde está o 3º
byte da instrução LDA $1FF4,y. Então no banco #4, alocamos nossa rotina em FF06h que é
onde o registrador PC estará apontando ao executar a instrução LDA $1FF4,y do banco #0.

Ao chegar no banco #4, no offset FF06h, o 6502 encontra a instrução RTS. Essa instrução
significa ReTurn from Subroutine, ou seja, retorne da subrotina. Ainda: retorne da subrotina
para o lugar de onde veio. E de onde que ele veio? Do lugar que está na pilha! E qual o
lugar que está na pilha? O endereço da .rotinaBanco4. Então, quando ele executar o RTS,
vai pegar o endereço que está guardado na pilha e setar o registrador PC. Assim nossa
execução vai direto para a .rotinaBanco4. Muito simples.

Veja as figuras 116, 117, 118 e 119. Elas mostram a execução. Quando chegamos no banco
#4, há o código para mudar para o banco #6 (do primeiro exemplo) e então paramos por aí.
Se executarmos o código do banco #4, vamos para o banco #6 com o PC apontando para
um offset diferente de onde começa o código útil do banco #6, ou seja, vamos entrar no
banco #6 e executar alguns NOPs antes de chegar no código útil propriamente dito. De
qualquer forma, do jeito que está podemos ver as 2 formas de mudança de banco
funcionando.

Figura 116

E:\Atari\SDK\doc\Tutorial.doc Página 220 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 117

A figura 117 ainda mostra o banco #0, porém no offset FF01h, pronto para mudar de banco.

Figura 118

A figura 118 mostra quando executamos a instrução LDA $1FF4 do banco #0. O Stella muda
para o banco #4 (vide Current bank destacado em vermelho) mas não exibe o código do
banco #4. Daí para sabermos o que vai ser executado, basta digitarmos o comando disasm
na parte esquerda da interface do debug do Stella. Como o banco agora é o #4, ele
disassembla o código do banco #4 a partir do offset dado por PC (que é FF06h). Daí
verificamos que a próxima instrução a ser executada no banco #4 é o RTS. Clicamos
novamente em Step para que essa instrução seja executada.

Ao executá-la, a tela da figura 119 é apresentada.

E:\Atari\SDK\doc\Tutorial.doc Página 221 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 119

O RTS pegou o endereço que estava na pilha e fez o PC apontar para ele. Agora estamos
no código útil do banco #4. Se executarmos essas instruções, vamos mudar para o banco #6
e a tela é apresentada conforme a figura 120.

Figura 120

E:\Atari\SDK\doc\Tutorial.doc Página 222 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

O PC foi para o banco #6 apontado para F008h. Como não fizemos nenhuma alteração no
banco #6 do primeiro exemplo, ele continuou com os NOPs referentes ao antigo código do
banco #4.

Com isso concluimos que, para mudarmos de banco, devemos colocar instruções em cada
banco dizendo para mudar para o banco #X e no banco #X devemos ter instruções que, ou
já são o código útil, ou fazem o fluxo ir para determinado offset. Para que não tenhamos que
escrever em cada banco essas instruções, podemos criar uma macro. A seguir, um exemplo
de mudança de banco com macro e com o recurso de dizermos para onde queremos que o
PC vá no banco de destino.

;=================================================================================
;Macros
;=================================================================================
MAC startBank ;colocar no início de cada banco (exceto no #0)
;
inicioDoBanco ;
ldy #$00 ;
lda $1FF4,y ;startup sempre pelo banco #0
ENDM ;
;
;----------------------------------;
MAC mudaBancos ;colocar no final de cada banco
pha ;salva A
txa ;A = X
pha ;salva A
lda $1FF4,y ;muda de banco
rts ;fake return
ENDM ;
;----------------------------------;
;=================================================================================
;Bank #0
;=================================================================================
Seg Bank0 ;
org $8000 ;aloca 28K abaixo
rorg $F000 ;realoca para F000h
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;----------------------------------;
;Seu código aqui ;
;----------------------------------;
.rotinaBanco0 ;
lda #>(.rotinaBanco4-1) ;A = high order do endereço da .rotinaBanco4
ldx #<(.rotinaBanco4-1) ;X = low order do endereço da .rotinaBanco4
ldy #$04 ;Y = banco #4
jmp .mudaBanco ;vai para .mudaBanco

org $8F01 ;alocamos a rotina para mudar de banco em 8F01h


rorg $FF01 ;realocamos para FF01h

E:\Atari\SDK\doc\Tutorial.doc Página 223 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;só para não ter que preencher com NOPs


.mudaBanco ;
mudaBancos ;macro
;
org $8FFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #4
;=================================================================================
Seg Bank4 ;
org $C000 ;aloca 12K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
.rotinaBanco4 ;
ldy #$06 ;banco #6
lda $1FF4,y ;muda de banco
;
org $CF06 ;alocamos em CF06h
rorg $FF06 ;realocamos para FF06h
;
mudaBancos ;
;
org $CFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

Com a mudança anterior, simplesmente colocamos o código em uma macro. Daí, setamos A
com o byte alto do endereço da rotina a ser chamada, X com o byte baixo e Y com o número
do banco de destino. No momento da execução, o programa é interpretado como:

lda #>(.rotinaBanco4-1) ;A = high order do endereço da .rotinaBanco4


ldx #<(.rotinaBanco4-1) ;X = low order do endereço da .rotinaBanco4
ldy #$04 ;Y = banco #4
jmp .mudaBanco ;vai para .mudaBanco
;
.mudaBanco ;
pha ;salva A
txa ;A = X
pha ;salva A
lda $1FF4,y ;muda de banco
rts ;fake return

A macro deve estar em todos os bancos. No nosso exemplo, ela está no banco #0 e no
banco #4. Assim já é possível notar que, quando estivermos no banco #0 e executarmos a
instrução LDA $1FF4,y o 6502 mudará para o banco #4 e o registrador PC apontará para a
instrução RTS. Ao executar o RTS, o 6502 lê a pilha e vai para a .rotinaBanco4. Escrevendo
o programa dessa forma, podemos agora implementar facilmente no banco #4 o código para
mudar para o banco #6. Assim.

E:\Atari\SDK\doc\Tutorial.doc Página 224 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;=================================================================================
;Bank #4
;=================================================================================
Seg Bank4 ;
org $C000 ;aloca 12K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
.rotinaBanco4 ;
lda #>(.rotinaBanco6-1) ;A = high order do endereço da .rotinaBanco6
ldx #<(.rotinaBanco6-1) ;X = low order do endereço da .rotinaBanco6
ldy #$06 ;Y = banco #6
jmp .mudaBanco ;vai para .mudaBanco
;
org $CF06 ;alocamos em CF06h
rorg $FF06 ;realocamos para FF06h
;
mudaBancos ;
;
org $CFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #6
;=================================================================================
Seg Bank6 ;
org $E000 ;aloca 4K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
.rotinaBanco6 ;
lda #>(.rotinaBanco0-1) ;A = high order do endereço da .rotinaBanco0
ldx #<(.rotinaBanco0-1) ;X = low order do endereço da .rotinaBanco0
ldy #$00 ;Y = banco #0
jmp .mudaBanco ;vai para .mudaBanco
;
org $EF06 ;alocamos em EF06h
rorg $FF06 ;realocamos para FF06h
;
mudaBancos ;
;
org $EFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

Colocamos no banco #6 um código para chamar de volta o banco #0. Loop infinito.

E:\Atari\SDK\doc\Tutorial.doc Página 225 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Utilizando bancos como uma coleção de subrotinas


Na sessão anterior vimos como mudar de banco e executar rotinas dentro dos mesmos. Mas
com o que foi apresentado podemos notar que, se o banco #1 (por exemplo) chama uma
rotina do banco #5 e nossa intenção é voltar para o banco #1, então devemos colocar uma
rotina de mudança de banco no banco #5 chamando o banco #1.

O processo de chamar uma rotina e depois retornar para o chamador é análogo à função da
instrução JSR, ou seja, eu preciso apenas saber quem vou chamar... quanto ao retorno, o
próprio programa se encarrega de saber para onde voltar.

Nos exemplos apresentados, teríamos que nos preocupar não somente em saber qual rotina
chamar, mas também saber para onde voltar. Em outras palavras um banco chamador
chama uma rotina e, depois que a rotina termina sua execução, o banco chamado chama
novamente o banco chamador, passando assim a ser um banco chamador também (essa
frase ficou pior que a frase que tem um monte de palavras “banco”). É um retorno falso.

Vamos apresentar agora uma forma de contornar isso, ou seja, vamos nos preocupar em
saber somente quem queremos chamar. O programa se encarregará de retornar para onde
ocorreu a mudança de banco no banco chamador.

;=================================================================================
;Macros
;=================================================================================
MAC startBank ;colocar no início de cada banco (exceto no #0)
;
inicioDoBanco ;
ldy #$00 ;
lda $1FF4,y ;startup sempre pelo banco #0
ENDM ;
;----------------------------------;
mac mudaBancos ;colocar no fim de cada banco
pha ;salva bancos de origem e destino
tya ;A = high order do endereço da rotina
pha ;salva
txa ;A = low order do endereço da rotina
pha ;salva
tsx ;X = Stack Pointer
inx ;incrementa X para que
inx ;ele aponte para o local da pilha onde
inx ;salvamos os bancos de origem e destino
lda $00,x ;A = bancos de origem e destino (lê da pilha)
and #$0F ;mascara high order
tay ;Y = banco de destino
lda $1FF4,y ;muda de banco
rts ;fake return
endm ;
;----------------------------------;
mac retornaBancos ;
pla ;restaura bancos de origem e destino
lsr ;desloca o nibble que
lsr ;representa o banco de origem
lsr ;para a direita até que ele
lsr ;seja o low nibble
tay ;Y = banco de origem
lda $1FF4,y ;muda de banco
rts ;fake return
endm ;
;----------------------------------;

E:\Atari\SDK\doc\Tutorial.doc Página 226 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;=================================================================================
;Bank #0
;=================================================================================
Seg Bank0 ;
org $8000 ;aloca 28K abaixo
rorg $F000 ;realoca para F000h
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;----------------------------------;
;Seu código aqui ;
;----------------------------------;
.rotinaBanco0 ;
lda #$04 ;A = bancos de origem e destino
ldy #>(.rotinaBanco4-1) ;Y = high order do endereço da .rotinaBanco4
ldx #<(.rotinaBanco4-1) ;X = low order do endereço da .rotinaBanco4
jsr .mudaBanco ;muda de banco
lda #$01 ;essas 3 linhas são apenas para
ldx #$02 ;visualizarmos melhor a execução, não tendo
ldy #$03 ;nenhum efeito na rotina de mudança de banco
jmp .rotinaBanco0 ;loop infinito

org $8F01 ;alocamos a rotina para mudar de banco em 8F01h


rorg $FF01 ;realocamos para FF01h
;só para não ter que preencher com NOPs
.mudaBanco ;
mudaBancos ;macro
;
.retornaBanco ;
retornaBancos ;macro
;
org $8FFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #4
;=================================================================================
Seg Bank4 ;
org $C000 ;aloca 12K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
.rotinaBanco4 ;
lda #$46 ;A = bancos de origem e destino

E:\Atari\SDK\doc\Tutorial.doc Página 227 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

ldy #>(.rotinaBanco6-1) ;Y = high order do endereço da .rotinaBanco6


ldx #<(.rotinaBanco6-1) ;X = low order do endereço da .rotinaBanco6
jsr .mudaBanco ;muda de banco
lda #$04 ;essas 3 linhas são apenas para
ldx #$05 ;visualizarmos melhor a execução, não tendo
ldy #$06 ;nenhum efeito na rotina de mudança de banco
jmp .retornaBanco ;retorna ao banco chamador
;
org $CF06 ;alocamos em CF06h
rorg $FF06 ;realocamos para FF06h
;
.mudaBanco ;
mudaBancos ;macro
;
.retornaBanco ;
retornaBancos ;macro
;
org $CFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #6
;=================================================================================
Seg Bank6 ;
org $E000 ;aloca 4K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
.rotinaBanco6 ;
lda #$07 ;essas 3 linhas são apenas para
ldx #$08 ;visualizarmos melhor a execução, não tendo
ldy #$09 ;nenhum efeito na rotina de mudança de banco
jmp .retornaBanco ;retorna ao banco chamador
;
org $EF06 ;alocamos em EF06h
rorg $FF06 ;realocamos para FF06h
;
.mudaBanco ;
mudaBancos ;macro
;
.retornaBanco ;
retornaBancos ;macro
;
org $EFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

Ocorreram mudanças grandes agora. A primeira delas é na macro mudaBancos. Essa


macro agora recebe os bancos de origem e destino, entretanto trabalha somente com o de
destino. Antes utilizávamos 1 byte para dizer que era o banco de destino. Agora utilizamos 1
nibble. O outro nibble indica o banco de origem (chamador). Detalhes do código:

E:\Atari\SDK\doc\Tutorial.doc Página 228 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

pha ;salva bancos de origem e destino


tya ;A = high order do endereço da rotina
pha ;salva
txa ;A = low order do endereço da rotina
pha ;salva
tsx ;X = Stack Pointer
inx ;incrementa X para que
inx ;ele aponte para o local da pilha onde
inx ;salvamos os bancos de origem e destino
lda $00,x ;A = bancos de origem e destino (lê da pilha)
and #$0F ;mascara high order
tay ;Y = banco de destino
lda $1FF4,y ;muda de banco
rts ;fake return

Colocamos em A os valores dos bancos de origem e destino, nessa ordem. A macro salva
esse byte no primeiro PHA. Depois, o processo é como nos exemplos anteriores: pegamos o
endereço da rotina a ser executada no banco de destino e salvamos na pilha. A partir do
TSX, vem nova mudança. A instrução TSX coloca em X o valor do registrador S (Stack
Pointer). Supondo que S fosse FFh, após os 3 PHAs, ele estaria com FCh. Esse valor seria
então colocado em X. Depois incrementamos X 3 vezes, ou seja, X agora volta a ser FFh
(lembrando que esses valores são hipotéticos, estamos dando um exemplo aqui). Agora,
através de um modo de endereçamento já visto, acessamos o valor que está em 00h + X.
Ora, se X é FFh então 00h + FFh = FFh. E assim colocamos em A o valor que está em FFh.
Esse valor é o que foi salvo no primeiro PHA da macro, ou seja, o byte que representa os
bancos de origem e destino.

Por fim, zeramos o high nibble (o banco de origem) desse valor em A, utilizando o AND, e
ficamos somente com o low nibble. Depois transferimos esse valor para Y (TAY) e
realizamos a mudança de banco normalmente com LDA $1FF4,y. O RTS seguinte é
executado já no banco de destino, conforme já explicado nos exemplos anterioes. Mas
lembrando… o RTS recupera da pilha o endereço da rotina a ser executada. Esse endereço
foi salvo pela macro através dos 2 PHAs seguintes ao primeiro PHA que salva os bancos de
origem e destino.

A segunda mudança é a criação da macro retornaBancos. O código é:

pla ;restaura bancos de origem e destino


lsr ;desloca o nibble que
lsr ;representa o banco de origem
lsr ;para a direita até que ele
lsr ;seja o low nibble
tay ;Y = banco de origem
lda $1FF4,y ;muda de banco
rts ;fake return

A macro mudaBancos salva 3 valores na pilha, sendo que o primeiro o byte corresponde aos
bancos de origem e destino, o segundo byte corresponde à parte alta do endereço da rotina
a ser executada e o terceiro byte corresponde à parte baixa desse endereço. Quando a
macro mudaBancos muda de banco, o banco de destino é carregado e o PC aponta para o
offset seguinte à instrução LDA $1FF4,y, que no caso é um RTS. Ao executar esse RTS, o
6502 é desviado para a rotina, como queríamos. O que ocorre também é que, ao executar o
RTS, a pilha é descarregada em 2 bytes, que correspondem ao endereço da rotina. Então, a
pilha agora tem só o byte que corresponde aos bancos de origem e destino e o registrador S
(Stack Pointer) está apontando para esse byte.

E:\Atari\SDK\doc\Tutorial.doc Página 229 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Ao executarmos a macro retornaBanco, a primeira instrução dela é um PLA. Com isso,


recuperamos esse byte que está sendo apontado por S. Assim, obtemos novamente o valor
dos bancos de origem e destino. Os 4 LSRs seguintes fazem os bits se deslocarem para a
direita e assim, o high nibble vai para o low nibble. Para relembrar sobre o LSR, consulte a
página 26 desse tutorial. Depois, transferimos o valor para Y (TAY) e mudamos de banco.

Daí vem a pregunta: e o RTS? Para que serve? Não salvamos nada na pilha que justifique
usá-lo!

Para responder, vamos ao código do banco #0, por exemplo.

lda #$04 ;A = bancos de origem e destino


ldy #>(.rotinaBanco4-1) ;Y = high order do endereço da .rotinaBanco4
ldx #<(.rotinaBanco4-1) ;X = low order do endereço da .rotinaBanco4
jsr .mudaBanco ;muda de banco
lda #$01 ;essas 3 linhas são apenas para
ldx #$02 ;visualizarmos melhor a execução, não tendo
ldy #$03 ;nenhum efeito na rotina de mudança de banco
jmp .rotinaBanco0 ;loop infinito

Carregamos A com 04h, indicando o banco de origem #0 e o banco de destino #4.


Carregamos em Y e X os bytes do endereço da rotina a ser executada no banco #4. Depois
chamamos a rotina de mudança de banco… utlizando nada mais, nada menos que o …JSR.

Ao chamarmos a rotina de mudança de banco dessa forma, já guardamos 2 bytes na pilha


correspondentes ao endereço da próxima instrução após esse JSR, que no caso é LDA
#$01. Lembrando que as linhas LDA…, LDX… e LDY… após os JSR .mudaBanco estão lá
só para efeito de ilustração.

Assim, quando a macro retornaBancos é executada, ao mudar de banco (voltar para o


banco de origem), o PC aponta para o RTS da macro. Quando o RTS é executado, o 6502
continua sua execução após a chamada JSR desse banco. Com isso podemos chamar um
banco/rotina de qualquer lugar de um banco/rotina e quando retornarmos o 6502 sabe
exatamente onde ocorreu a chamada e volta para a instrução seguinte a essa chamada.

Tudo isso tem um preço: gasta pilha e ciclos de máquina. Cada chamada gasta 5 bytes da
pilha e 45 ciclos de máquina desde o JSR do banco de origem até o RTS já no banco de
destino. Deve-se levar em conta em um programa funcional, uma vez que temos pouca
memória disponível e, dependendo do lugar, esses ciclos podem representar um STA
WSYNC a mais que teremos que fazer e com isso perder 1 linha de resolução vertical.

Outra coisa sobre os exemplos apresentados: destruímos A, X e Y na chamada e retorno, ou


seja, tivemos que setar A, X e Y para que pudéssemos mudar de banco e chamar a rotina.
Se A, X e/ou Y tivessem valores que deveriam ser mantidos, teríamos que salvá-los
primeiro. Ou utilizaríamos a pilha, ou variáveis.

Quando retornamos do banco de destino, A, X e Y também tiveram seus valores destruídos.

Vamos apresentar agora uma variação da macro que mantém os valores de A, X e Y antes
da mudança/chamada. Quando retornamos ao banco chamador, temos A, X e Y com seus
valores de antes da mudança de banco. Essa macro, como se pode presumir, gasta mais
bytes da ROM, gasta mais RAM e conseqüentemente mais ciclos de máquina.

Há também uma mudança na forma como ela é chamda. Vejamos.

E:\Atari\SDK\doc\Tutorial.doc Página 230 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

;=================================================================================
;Macros
;=================================================================================
MAC startBank ;colocar no início de cada banco (exceto no #0)
;
inicioDoBanco ;
ldy #$00 ;
lda $1FF4,y ;startup sempre pelo banco #0
ENDM ;
;----------------------------------;
mac mudaBancos ;colocar no fim de cada banco
pha ;salva A
txa ;
pha ;salva X
tya ;
pha ;salva Y
tsx ;X = stack pointer
inx ;
inx ;
inx ;
inx ;X = offset do JSR chamador
inc $00,x ;offset seguinte
lda ($00,x) ;pega byte que é o banco de origem e destino
pha ;coloca na pilha
and #$0F ;mascara high nibble
tay ;Y = banco de destino
inc $00,x ;offset seguinte
lda ($00,x) ;pega high order do endereço da subrotina
pha ;coloca na pilha
inc $00,x ;offset seguinte
lda ($00,x) ;pega low order do endereço da subrotina
pha ;coloca na pilha
lda $1FF4,y ;muda de banco
rts ;fake return
endm ;
;----------------------------------;
mac retornaBancos ;
pla ;restaura banco de origem
lsr ;desloca o nibble que
lsr ;representa o banco de origem
lsr ;para a direita até que ele
lsr ;seja o low nibble
tay ;Y = banco de origem
lda $1FF4,y ;muda de banco
pla ;
tay ;restaura y
pla ;
tax ;restaura X
pla ;restaura A
rts ;fake return
endm ;
;----------------------------------;
;
;=================================================================================
;Bank #0
;=================================================================================
Seg Bank0 ;
org $8000 ;aloca 28K abaixo
rorg $F000 ;realoca para F000h
;
sei ;seta disable interrupt
cld ;limpa modo decimal
lax $00 ;X = A = 0
;

E:\Atari\SDK\doc\Tutorial.doc Página 231 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

sei ;seta disable interrupt


cld ;limpa modo decimal
lax $00 ;X = A = 0
;
.limpaMemoria ;
sta $00,x ;zera memória
dex ;X = X - 1
bne .limpaMemoria ;X = 0? não. continua limpando a memória
;
;----------------------------------;
;Seu código aqui ;
;----------------------------------;
.rotinaBanco0 ;
jsr .mudaBanco ;muda de banco
.byte #$04 ;do banco #0 para o banco #4
.byte #>(.rotinaBanco4-1) ;high order do endereço da .rotinaBanco4
.byte #<(.rotinaBanco4-1) ;low order do endereço da .rotinaBanco4
;
lda #$01 ;essas 3 linhas são apenas para
ldx #$02 ;visualizarmos melhor a execução, não tendo
ldy #$03 ;nenhum efeito na rotina de mudança de banco
jmp .rotinaBanco0 ;loop infinito

org $8F01 ;alocamos a rotina para mudar de banco em 8F01h


rorg $FF01 ;realocamos para FF01h
;só para não ter que preencher com NOPs
.mudaBanco ;
mudaBancos ;macro
;
.retornaBanco ;
retornaBancos ;macro
;
org $8FFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

;=================================================================================
;Bank #4
;=================================================================================
Seg Bank4 ;
org $C000 ;aloca 12K abaixo
rorg $F000 ;realoca para F000h
;
startBank ;assegura startup pelo banco #0
;
;----------------------------------;
; Seu código aqui ;
;----------------------------------;
.rotinaBanco4 ;
lda #$04 ;essas 3 linhas são apenas para
ldx #$05 ;visualizarmos melhor a execução, não tendo
ldy #$06 ;nenhum efeito na rotina de mudança de banco
;
jsr .mudaBanco ;muda de banco
.byte #$46 ;do banco #4 para o banco #6
.byte #>(.rotinaBanco6-1) ;high order do endereço da .rotinaBanco6
.byte #<(.rotinaBanco6-1) ;low order do endereço da .rotinaBanco6
;
jmp .retornaBanco ;retorna ao banco chamador
;

E:\Atari\SDK\doc\Tutorial.doc Página 232 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

org $CF06 ;alocamos em CF06h


rorg $FF06 ;realocamos para FF06h
;
.mudaBanco ;
mudaBancos ;macro
;
.retornaBanco ;
retornaBancos ;macro
;
org $CFFA ;
rorg $FFFA ;
.word inicioDoBanco ;NMI
.word inicioDoBanco ;Reset
.word inicioDoBanco ;IRQ
;

O banco #6 não sofre alterações nesse exemplo. Execute o programa no modo debug e veja
como os valores de A, X e Y são preservados, ou seja, A, X e Y têm determinados valores
antes da mudança de banco e, quando ocorre o retorno, eles têm seus valores restaurados.

Isso implicou em mais gasto de pilha, bytes da ROM e ciclos de máquina. Para verificar
quantos ciclos essas rotinas (de mudança e retorno) estão gastando, basta colocar um STA
WSYNC no início de cada macro. Então no debug, quando executar o STA WSYNC, o
contador de ciclos é zerado (figura 121). Daí basta acompanhar seu valor até o término da
rotina. Por exemplo:

mac mudaBancos ;colocar no fim de cada banco


sta WSYNC ;zera contagem de ciclos
pha ;salva A
txa ;
pha ;salva X
tya ;
pha ;salva Y
tsx ;X = stack pointer
inx ;
inx ;
...

Se executarmos a macro veremos o contador de ciclos incrementar de acordo com os ciclos


que cada instrução gasta. Então se clicarmos em Step até que o PC atinja a instrução LDA
$1FF4,y terão passados 72 ciclos (figura 121). A instrução LDA $1FF4,y gasta 4 ciclos
(figura 122). Então 72 + 4 = 76 ciclos de máquina, que é o máximo de ciclos por resolução
horizontal do TIA. Ao atingir 76 ciclos, o contador volta para 0. Mas ainda não terminamos a
rotina. Falta o RTS.

Figura 121

Figura 122

E:\Atari\SDK\doc\Tutorial.doc Página 233 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Quando executamos o RTS o contador mostra que foram gastos mais 6 ciclos. Então na
verdade temos o total de 76 + 6 = 82 ciclos que essa rotina de mudança de banco gasta.

Se ela for executada (se uma mudança de banco ocorrer) dentro do .scanLoop (tempo em
que desenhamos na tela), com certeza ela gastará mais de 1 linha de resolução horizontal,
ficando assim uma certa deformidade na imagem. Conforme o caso, seria necessário usar
um STA WSYNC logo após a mudança de banco ocorrer.

A forma de como chamamos a rotina também mudou. Veja o código:

jsr .mudaBanco ;muda de banco


.byte #$46 ;do banco #4 para o banco #6
.byte #>(.rotinaBanco6-1) ;high order do endereço da .rotinaBanco6
.byte #<(.rotinaBanco6-1) ;low order do endereço da .rotinaBanco6

Diferentemente dos exemplos anteriores, que usávamos os 3 registradores para guardar


dados do banco e endereço de destino, aqui nós montamos essas informações na ROM.
Explicando melhor: Após a linha JSR .mudaBanco, dizemos ao compilador o seguinte: “ao
compilar, coloque aqui (.byte) o byte 46h, depois o byte que representa a high order do
endereço da .rotinaBanco6 e depois o byte que representa a low order desse endereço”.
Veja a figura 123. Depois do JSR… temos os bytes montados na ROM.

Figura 123

Nota: O Stella disassembla como instruções normais. Como o JSR está no seu formato
correto, ou seja, JSR $aaaa (veja os opcodes: 20h é o JSR e 01h FFh é o endereço no
formato little endian), para ele, o próximo byte deve ser uma instrução. Como montamos o
byte 46h, que na nossa intenção são os valores do banco de origem e destino, ele interpreta
46h como o opcode do LSR (vide página 21 desse tutorial). Ora, LSR de opcode 46h espera
1 byte como endereçamento de zero page. Então ele assume que F0h é esse byte (e que na
verdade é a high order do endereço da rotina no banco de destino). Daí restou o 3º byte que
montamos, que é a low order da rotina. O disassembler do Stella interpretou esse byte como
sendo DC $02.

Apesar disso, sabemos que não dará problema, pois esse código não será executado, ou
seja, ele não precisa fazer sentido porque o 6502 não vai executá-lo.

Ao executar o JSR, o 6502 salva na pilha o offset seguinte ao do JSR (que no caso da figura
123 seria salvo o F00Ch – 1 = F00Bh) e é desviado para a rotina de mudança de banco e
quando voltar, vai direto para o JMP seguinte aos bytes montados em vez de voltar para o
F00Ch (F00Bh + 1).

A própria rotina de mudança de bancos seta o retorno para o JMP. Para ela funcionar, deve
fazer isso e aproveitamos para retornar para o lugar certo.

A rotina de mudança de banco agora faz o seguinte:


E:\Atari\SDK\doc\Tutorial.doc Página 234 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007
pha ;salva A
txa ;
pha ;salva X
tya ;
pha ;salva Y
tsx ;X = stack pointer
inx ;
inx ;
inx ;
inx ;X = offset do JSR chamador
inc $00,x ;offset seguinte
lda ($00,x) ;pega byte que é o banco de origem e destino
pha ;coloca na pilha
and #$0F ;mascara high nibble
tay ;Y = banco de destino
inc $00,x ;offset seguinte
lda ($00,x) ;pega high order do endereço da subrotina
pha ;coloca na pilha
inc $00,x ;offset seguinte
lda ($00,x) ;pega low order do endereço da subrotina
pha ;coloca na pilha
lda $1FF4,y ;muda de banco
rts ;fake return

Os 3 primeiros PHAs salvam os registradores para que não tenham seus valores destruídos
quando do retorno do banco de destino para o banco chamador (preservamos seus valores
antes da mudança de banco).

O TSX já vimos. Coloca em X o Stack Pointer. Daí incrementamos X até apontar para o
lugar na pilha onde foi salvo o offset que o JSR .mudaBanco colocou lá. Agora é que vem a
parte interessante da coisa. Utilizando modos de endereçamento (por isso é importante),
fazemos um INC $00,x e um LDA ($00,x) que é diferente do outro exemplo que usávamos
LDA $00,x (sem parêntesis).

O INC $00,x incrementa a low order do offset salvo na pilha quando executamos o JSR
.mudaBanco. Daí, quando fazemos o LDA ($00,x), o 6502 pegará o byte que está no
endereço offset salvo + 1. No nosso programa, depois do JSR temos o .byte #$46. Então A
vai pegar o valor 46h. Daí salvamos esse valor com PHA, pois vamos precisar dele quando
retornarmos do banco. Mascaramos esse valor para ficar somente com o nibble do banco de
destino usando o AND e transferimos para Y (TAY).

Agora fazemos outro INC $00,x. Antes estávamos apontando para o byte dos valores dos
bancos de origem e destino. Agora, estamos apontando para o byte que representa a high
order do endereço da rotina a ser executada no banco de destino. Pegamos o byte com o
LDA ($00,x) e salvamos com PHA. Fazemos novamente o incremento de X para apontar
para a low order do endereço da rotina. Pegamos o byte novamente e salvamos na pilha
com PHA. A seguir, resta-nos mudar de banco e o RTS seguinte faz o 6502 ir para o
endereço salvo na pilha com os 2 últimos PHAs.

O retorno começa com um PLA. Lembra que havíamos salvado o byte com os valores dos
bancos de origem e destino? O PLA recupera esse byte.

O processo que segue é análogo aos outros retornos. Com a diferença que, antes do RTS
final, restauramos da pilha os valores de A, X e Y de antes da mudança de banco.

E:\Atari\SDK\doc\Tutorial.doc Página 235 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

pla ;restaura banco de origem


lsr ;desloca o nibble que
lsr ;representa o banco de origem
lsr ;para a direita até que ele
lsr ;seja o low nibble
tay ;Y = banco de origem
lda $1FF4,y ;muda de banco
pla ;
tay ;restaura y
pla ;
tax ;restaura X
pla ;restaura A
rts ;fake return

Daí fica na pilha somente o offset do JSR, porém incrementado pelos INC $00,x. Como os
INC $00,x fizeram ele apontar para o byte da low order do endereço da rotina, quando o
RTS ocorrer, o 6502 vai para esse offset + 1. O offset + 1 é a instrução seguinte àqueles
.byte que colocamos. Por isso o 6502 “pula” esses .byte no programa.

Com isso terminamos aqui nossa pequena lição sobre bankswitching. O que foi apresentado
é só o básico e apenas 1 ou 2 técnicas simples e não otimizadas. Há outras formas de fazer
bankswitching conforme já comentado.

Com o que foi exposto, podemos criar ROMs de mais de 4K para, por exemplo, termos
ROMs com mais de 1 jogo ou um jogo que utilize mais de 1 banco (1 banco para a lógica e
outro para o Kernel, por exemplo).

Vai da criatividade e necessidade de cada um.

E:\Atari\SDK\doc\Tutorial.doc Página 236 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Hacking
Aqui veremos como hackear uma ROM e com isso conhecer melhor o debugger do Stella. O
jogo será o Beam Rider (figura 124). O objetivo desse hack é ficar com vidas infinitas, ou
seja, os inimigos podem atingir nossa nave que não perderemos vida.

Figura 124

Para começarmos, carregue o jogo no emulador, tecle F2 (default do Stella para o reset) e
observe a quantidade inicial de vidas: 2 no caso (vide figura 125). Antes de começar a jogar
pressione a tecla de aspas simples. Em alguns teclados ela fica do lado do número 1 e
sobre a tecla Tab. O Stella entra no modo debug (figura 126).

Figura 125

E:\Atari\SDK\doc\Tutorial.doc Página 237 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Figura 126

Figura 127

Vamos destacar a RAM (figura 127). O que está em vermelho significa que sofreu
alterações, mas isso não é importante nesse caso. Agora, clique em Srch (de search), digite
02 (que é o número de vidas atual) e clique em Ok. Com isso procuramos na RAM todos os
endereços que têm valor 02h. Veja na figura 128 que esses valores ficam destacados
(endereços 85h e FCh).

Figura 128

E:\Atari\SDK\doc\Tutorial.doc Página 238 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Agora vamos sair do modo debug (clique em Exit ou tecle a aspas simples novamente ou
ainda digite run no lado esquerdo da tela do debugger do Stella). Comece a jogar (nesse
jogo basta movimentar o joystick ou teclar uma das setas de direção do teclado) e deixe que
a nave seja atingida (pode demorar um pouco até isso acontecer). Não dispare tiros nem
atinja naves inimigas. Afinal, queremos que aconteça o mínimo de mudanças na RAM.

Quando nossa nave for atingida, perderemos 1 vida. Daí o jogo volta a ficar aguardando que
pressionemos uma das teclas de direção (ou movimentemos o joystick) conforme a figura
129 (observe que agora temos 1 só vida). Nesse momento tecle novamente a aspas simples
para entrarmos no modo debug.

Figura 129

Agora é só comparar. Começamos o jogo com 2 vidas. Daí perdemos 1 vida e agora resta 1.
Então procuramos na RAM o valor 01h. Para isso, clique em Cmp (de compare), digite 01 e
clique em Ok. O Stella vai comparar somente os endereços encontrados anteriormente com
search e comparar seus valores com o 01h. Conforme a figura 130, somente 1 dos
endereços ficou destacado. Esse endereço, o 85h (linha 80h e coluna 05h) é um forte
candidato a ser o byte que contém o número de vidas.

Figura 130

Para termos certeza de que é esse o byte do número de vidas, vamos alterar seu valor.
Podemos fazer isso de 3 formas:

• dê um duplo clique no byte, digite um novo valor (por exemplo 09) e tecle enter (tem
que ser o do bloco alfabético, pois o enter do bloco numérico não funciona)
• digite (no lado esquerdo do debugger) o seguinte comando: ram $85 $09
• digite (no lado esquerdo do debugger) o seguinte comando: poke $85 $09

Qualquer uma das 3 formas apresentadas altera o valor da RAM no endereço 85h colocando
lá o valor que queremos (no caso 09h).

E:\Atari\SDK\doc\Tutorial.doc Página 239 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Agora, saia do modo debug (procedendo da forma já explicada) e observe a quantidade de


vidas na tela (figura 131). Encontramos o byte.

Figura 131

Bom… o que pretendemos fazer? Colocar vidas infinitas, ou seja, a quantidade de vidas não
deve chegar a zero. Se pararmos para pensar, um programa faz o seguinte (tipicamente):

Seta a variável com o número de vidas inicial

decrementarVida
Decrementa essa variável

Testa testa essa variável

Se for > 0 vai para decrementarVida caso contrário termina

Portanto, temos 2 opções: ou não deixamos a variável ser decrementada ou alteramos o


resultado do teste para nunca resultar na condição que termina o jogo. Temos que escolher
uma das duas opções.

Isso se deve ao seguinte fato: quando ele decrementa uma variável ele faz uma operação de
escrita na RAM. Quando ele testa uma variável ele faz uma operação de leitura da RAM.

Vamos escolher não deixar que ele decremente a variável.

O próximo passo é saber onde esse byte tem seu valor alterado. Para isso, setamos uma
trap que interceptará toda e qualquer operação de escrita nesse local da RAM. Se
tivéssemos escolhido burlar o teste, setaríamos uma trap de leitura. Como escolhemos
burlar o decremento, setaremos uma trap de escrita.

Para setar a trap de escrita, digitamos o seguinte no debugger do Stella (entre no modo
debugger novamente): trapwrite $85

O $85 é o local da RAM que queremos interceptar uma escrita (óbvio). Então, saia do modo
debug e deixe sua nave ser atingida novamente. O Stella volta automaticamente para o
debugger e pára depois da instrução que gerou a trap.

Nota: Quando setamos um breakpoint, o Stella pára no breakpoint, antes de executar a


instrução. É diferente com as traps. Para que uma trap seja gerada, a instrução que a gere
precisa ser executada, então o Stella sempre pára depois da instrução que gerou a trap.

E:\Atari\SDK\doc\Tutorial.doc Página 240 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Basta então voltarmos o código (fazer um scroll e ver o offset anterior ao PC) e verificar qual
instrução escreveu em 85h (figura 132). É um STA $85. Poderia ser um DEC $85, que nesse
caso faz mais sentido, ou seja, decrementa o número de vidas. Entretanto, esbarramos em
um STA $85. Pela lógica, concluímos que o número de vidas em 85h da RAM é lido em
algum momento, manipulado e depois atualizado (colocado novo valor em 85h). Portanto,
concluímos que não importa onde e quando esse valor é lido e nem o que é feito com ele. O
que nos importa é: não devemos deixar que o endereço 85h seja atualizado, ou seja, não
devemos deixar o STA $85 ser executado.

Figura 132

Isso é fácil de ser conseguido. Basta colocarmos NOP no lugar. NOP quer dizer No
OPeration (não faz nada). Como STA $85 tem 2 bytes e o NOP só tem 1 byte, devemos
então colocar 2 NOPs. Mas primeiro temos que saber qual é o opcode do NOP. É EAh (vide
página 22 desse tutorial). Devemos saber isso porque o Stella não tem um assembler. Não
podemos entrar com o mnemônico NOP e sim com seu opcode. Temos 2 formas de fazer
isso:

• dê um duplo clique na linha do offset $F671 e digite o opcode do NOP duas vezes e
tecle enter (o do bloco alfabético, não o do bloco numérico) conforme a figura 133.
• digite (no lado esquerdo do debugger) o seguinte comando: rom $f671 $ea $ea

Qualquer uma das 2 formas apresentadas altera o programa no emulador.

Figura 133

E:\Atari\SDK\doc\Tutorial.doc Página 241 de 243


Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Com o programa alterado (figura 134), saia do modo debug e deixe a nave ser atingida
novamente. Veja que o número de vidas se mantém. Agora temos um Beam Rider com
vidas infinitas.

Figura 134

Por fim, resta-nos salvar as alterações. Isso mesmo. O que fizemos foi no emulador, não na
ROM (arquivo .BIN). Temos que gravar nossas alterações em um novo arquivo .BIN. Para
isso, temos 2 formas:

• clique com o botão direito do mouse sobre o código da ROM e clique em Save ROM
(vide figura 135) e digite o nome da nova ROM.
• digite (no lado esquerdo do debugger) o comando: saverom nome_da_rom.bin

Figura 135

Com esse exemplo podemos ter noção de como patchear qualquer ROM. Por exemplo: em
vez de não deixarmos que o número de vidas fosse atualizado, poderíamos descobrir onde é
feito o teste de colisão entre os objetos. Afinal, após uma colisão o número de vidas é
decrementado. Se não deixarmos que o teste de colisão ocorra, nossa nave poderia colidir a
vontade com quaisquer objetos da tela e o jogo sempre continuaria ininterruptamente.No
caso apresentado, quando há colisão o jogo pára e volta para a tela que aguarda um
movimento do joystick mas o número de vidas não é decrementado. Se optássemos por
burlar o teste de colisão, o número de vidas também não seria decrementado e o jogo não
teria as paradas características de quando há colisão.
E:\Atari\SDK\doc\Tutorial.doc Página 242 de 243
Atari 2600 – Programação em Assembly para o processador 6502 20/8/2007

Conclusão
Esse pequeno tutorial teve o objetivo de reunir em um só lugar o máximo de informação
sobre a programação do 2600. Ainda faltam algumas coisas como técnicas e tópicos
avançados.

Por exemplo: programas com múltiplos sprites, outras formas de bankswitching, contagem
de frames, elaboração de som, scroll horizontal e vertical do playfield, matemática no
assembly, etc.etc.etc.

Esse tutorial é o básico do básico.

Por isso não nos preocupamos quanto à otimização de código e retirada de bugs (apenas
mostramos como detectar e tentar corrigir no nosso primeiro programa que mostra alguma
imagem).

Agora, cabe a cada um correr atrás das técnicas e otimizações para criar seu próprio
programa sem bugs.

A noção foi dada.

E:\Atari\SDK\doc\Tutorial.doc Página 243 de 243

Você também pode gostar