Compilador:
-> Declaração de variáveis
-> Atribuindo valores
-> Laços de Repetição
-> Teste Condicional
-> Equal
-> Sub Rotinas
-> Funções
-> Importando arquivos Externos
-> Incluindo Assembly no Source Code
-> Manipulando Bits e Posições de Memoria
-> Interrupções, Exceptions e Trap Vectors
Biblioteca Genesis_std:
-> genesis_header.asm
-> Std_init
-> Vdp_set_config
-> Joypad6B_read
-> Wait_vblank
-> Load_tiles_DMA
-> Load_tiles_DMA_128ksafe
-> Load_cram_DMA
-> Load_cram_DMA_128ksafe
-> Draw_tile
-> Draw_tilemap
-> Set_sprite_gfx
-> Set_sprite_size
-> Set_sprite_position
-> Update_sprite_table
-> Set_VerticalScroll_position
-> Set_HorizontalScroll_position
-> Hscroll_strip8
-> Hscroll_line
-> Update_Hscroll_table
-> Enable_global_int
-> Disable_global_int
-> Enable_V_int
-> Disable_V_int
-> Enable_H_int
-> Disable_H_int
-> Enable_Ext_int
-> Disable_Ext_int
-> Enable_Display
-> Disable_Display
-> Set_Hint_counter
-> Direct_color_DMA
<- Declaração de Variáveis ->
Uma Varivel pode ser declarada formalmente por meio do comando "Dim",
seguindo a estrutura:
Dim Nome_Da_Variável as "Tipo"
Dim var1 as integer 'Por exemplo
Sendo que o tipo pode ser:
-> Byte - Unsigned de 8 Bits
-> Word/Integer - Unsigned de 16 Bits
-> Fixed - (variavel unsigned de 16 bits e ponto fixo
que varia de 0.00 até 511.99)
-> Long - Unsigned 32 Bits
-> String - 32 Bits (funciona como um ponteiro
armazenando o endereço da cadeia de caracteres)
Variáveis Signed são suportadas, bastando adicionar "signed" logo
após o tipo da variável, exceto para os tipos string e fixed.
Também é possível declarar Matrizes usando os símbolos "[" e "]"
para armazenar os valores correspondentes ao tamanho da Matriz.
Dim matriz[10] as word ' Cria uma Matriz de 10 elementos (0-9 elementos) do tamanho Word Dim matriz[10,10] as word ' Cria uma Matriz bidimensional de 100 elementos do tamanho Word
Também podemos criar Matrizes cujo os elementos podem ser de tipos
diferentes e são indexados por um identificador, para isso usamos a estrutura
"Enum" para nomear e definir a estrutura da matriz.
Enum matriz_indexada x as integer y as integer z as long end Enum
E na hora de fazer a declaração formal adicionamos a tag "new" antes de
especificar o tipo da matriz, e o acesso a cada elemento é feito usando "." +
Nome do identificador de cada elemento
Dim nova_matriz as new Matriz_indexada nova_matriz.x = 50
Essa estrutura também comporta vetores (matrizes unidimensionais) na
sua declaração:
Dim nova_matriz[100] as new Matriz_indexada
No Menu Ferramentas-> Opções é possível Ligar/Desligar a declaração
automática de variáveis.
Com a opção de declaração automática de variáveis Ligada, caso o
programa faça referencia a um identificador que não tenha sido previamente
declarado, ele é declarado automaticamente como uma variável do tipo Word
Unsigned.
No menu de opções também é possível especificar o intervalo de
endereços da memoria RAM onde será alocado o Heap, por padrão temos os endereços do Mega Drive
&HFF0000 &HFFFFFF).
O NextBasic suporta tanto variáveis Globais quanto Locais, sendo que
toda variável declarada dentro de uma função/Sub Rotina será local a menos que usemos a Tag "Global" ao invéz de "Dim" em sua declaração, variáveis locais são alocadas no Stack e serão destruídas assim que função/subrotina se encerrar. Qualquer variável
declarada fora de uma Função/Subrotina será visível em qualquer parte do
código fonte a partir do ponto onde ela é declarada.
Para fazer com que uma variável seja visível em qualquer parte do
código fonte (inclusive em arquivos de código fonte externos) é necessário
declara-la como global, substituindo a tag "dim" por "Global" na linha onde
ocorre a declaração da variável.
O registrador usado como Frame Pointer para alocar memoria no Stack é o
Registrador A6.
Além disso ainda é possível declarar variáveis e "linkar-las" a um
endereço especifico da memoria, esse recurso foi incluído para permitir ao
programador manipular registradores do sistema de maneira mais cômoda, isso
pode ser feito usando a tag "Sysreg".
Sysreg VDP_CONTROL_REG as integer at &hC00004
<- Atribuição de valores ->
A atribuição de valores é feita especificando uma variável, um operador
de atribuição e uma expressão matemática.
Dentro das expressões matemáticas temos os seguintes operadores
matemáticos/booleanos:
+ - Soma
- - Subtração
* - Multiplicação
/ - Divisão com maior precedência que a multiplicação (é
realizada antes da multiplicação)
\ - Divisão com menor precedência que a multiplicação (é
realizada depois da multiplicação)
~ - Not Bitwise
<< - Deslocamento para a esquerda
>> - Deslocamento para a direita
and - Operador bitwise and
or - Operador bitwise or
xor - Operador bitwise xor (or exclusiva)
not - Operador 'Not' retorna um valor verdadeiro caso o valor testado seja
falso
> - Operador maior (retorna verdadeiro se o termo da esquerda
for maior que o da direita)
< - Operador menor (retorna verdadeiro se o termo da esquerda
for menor que o da direita)
<> - Operador diferente (retorna verdadeiro se o termo da esquerda
for diferente ao da direita)
= - Operador igual (retorna verdadeiro se o termo da esquerda for
igual o da direita)
>= - Operador maior/igual (retorna verdadeiro se o termo da esquerda
for maior/igual o da direita)
<= - Operador menor/igual (retorna verdadeiro se o termo da esquerda
for menor/igual o da direita)
E os operadores de atribuição podem ser:
= Igual, resolve a expressão matemática e atribui o valor a
variável
+= Mais Igual, resolve a expressão matemática e soma ao valor a variável
-= Menos Igual, resolve a expressão matemática e subtrai do
valor a variável
*= Mult. Igual, resolve a expressão matemática e multiplica pelo valor a
variável
/= Div Igual, resolve a expressão matemática e divide com o valor
a variável
\= Div Igual, resolve a expressão matemática e divide com o valor
a variável
|= OR igual, resolve a expressão matemática e realiza a operação
Or com o valor a variável
&= AND igual, resolve a expressão matemática e realiza a operação and com
o valor a variável
^= XOR Igual, resolve a expressão matemática e realiza a operação xor
com o valor a variável
~= NOT Igual, resolve a expressão matemática, inverte o valor e salva na
variável.
<<= LShift Igual, resolve a expressão matemática e desloca para a
esquerda o valor da variável
>>= RShift Igual, resolve a expressão matemática e desloca para a
direita o valor da variável
Exemplo de uso:
var <<= 2 + 3 Equivalente à var = var
<< (2 + 3)
x &= 256 Equivalente à
x = x and 256
A precedência de operadores existe, sendo que as quatro operações
matemática sempre tem a maior precedência em comparação com os outros
operadores. Então na expressão
x = y AND x+2
x será somado com 2 e então será realizada a operação AND com Y. Já na
expressão:
x = ~ x + y
x será somado com y e o resultado sera invertido.
A precedência exata dos operadores é dada pela ordem: OR, XOR, AND, NOT(), ~,
'operadores de comparação', operações de shift '<<' '>>', +, -,
MOD, \, *, /.
(sendo que os últimos operadores dessa lista tem a maior precedência, então
são executados primeiro).
O uso de parênteses para estabelecer a ordem de precedência dos
cálculos é incentivado, já que isso não afeta o desempenho do código gerado
(na verdade é até bom, pois evita a saturação dos registradores em calculos com multiplos operadores associativos).
Quando uma operação de atribuição é iniciada o compilador avalia o data type
da variável onde será salvo o resultado, e utilizara esse data type como
referencia para resolver a expressão matemática.
Logo, se misturarmos variáveis de data types diferentes na expressão o
compilador fara a conversão entre eles automaticamente, então variáveis com um
data wide menor serão estendidas (ao salvar uma variável byte de 8 bits em uma
variável word de 16 bits por exemplo), e variáveis com um data wide maior
terão sua parte excedente descartada, então ao salvar uma variável long de
32bits em uma variável word de 16 bits os 16 bits mais significativo da
variável long serão descartados.
O mesmo se sucede para variáveis Fixed, ao tentar salvar uma variável
fixed em uma variável word, por exemplo, o valor dela será convertido para
inteiro automaticamente.
O inverso também é valido, ao tentar salvar uma variável inteira em uma
variável fixed a conversão é feita automaticamente.
A conversão é feita sempre descartando o valor após o ponto no Momento
em que a variavel é acessada
Ex.:
1 2 3 4 |
dim _fixed_1 as fixed = 3.5 dim _fixed_2 as fixed = 8.5 dim var_int as integer = _fixed_1 + _fixed_2 'Neste caso o valor salvo em var_int é 11, resultado de 8 + 3 |
Para preservar o
calculo usando o data type original das variáveis que compões a equação,
neste caso, pode se usar a função _fixed(), assim o compilador vai calcular
a equação usando a matemática de ponto fixo e converterá o Resultado
retornado pela função para inteiro antes de salvar na variável de destino.
1 |
dim var_int as integer = _fixed( _fixed_1 + _fixed_2 ) ' Neste caso o valor salvo em var_int é 12, resultado de 8.5 + 3.5 |
A conversão automatica de
valores Não é aplicada a operadores Bitwise (AND, OR, XOR, ~, <<,
>>, etc.) apenas a operadores matematicos (+, -, *, /) e de comparação
(= , > , <> , >=, etc.)
<- Laços de repetição ->
Existem três estruturas com laços de repetição, a estrutura FOR e a
estrutura WHILE e a Estrutura DO.
A Estrutura DO consiste num laço de repetição infinito.
Do '[...] Laço de repetição infinito Loop
A unica maneira de sair desse laço de repetição é por meio do comando
"Exit Do"
A estrutura While( ) consiste num laço de repetição que se mantem
enquanto a expressão matemática dentro dos parenteses for verdadeira
(verdadeiro é sempre qualquer coisa diferente de zero).
Ela pode ser utilizada para executar um bloco de código:
While() '[...] Laço de repetição wend
Ou em sua maneira "in_line" que executa apenas um comando inserido na
mesma linha logo apos a estrutura while():
while( <expressão_matematica> ) <comando_a_executar>
É possível sair do laço de repetição While a qualquer momento usando o
comando "Exit While"
O laço de repetição For atribui um valor a uma variável por meio de uma
expressão matemática e especifica um valor de referencia, a cada execução do
laço a variável sera incrementada e o laço se repetira enquanto o valor
armazenado na variável for diferente do valor de referencia.
For i=0 to 30
'[...] O código sera executado enquanto i for diferente de 30
'[...] Ao termino da execução do laço a variável 'i' sera incrementada
Next i
Também é possível usar uma expressão matemática para especificar que
valor sera incrementado a variável de teste utilizando a tag 'step'.
For i=0 to -10 step -1
'[...]O valor -1 sera somado a variável 'i'
'[...] O laço se repetirá enquanto i for diferente de -10
Next i
É possível sair do laço for a qualquer momento usando o comando "Exit
For".
<- Teste Condicional ->
O compilador Next Basic possui duas estruturas de teste condicional, a
estrutura Select e a Estrutura If.
A estrutura Select serve para comparar o resultado de uma expressão
matemática com vários valores especificados numa lista:
Select <expressao_avaliada> case <expressao 1> '[...]Código a Executar caso o resultado da expressão 1 seja igual o resultado da expressão avaliada case <expressao 2> '[...]Código a Executar caso o resultado da expressao 2 seja igual o resultado da expressao avaliada ... case <expressao n> '[...]Código a Executar caso o resultado da expressão n seja igual o resultado da expressão avaliada case else '[...]Código a executar caso nenhum resultado das expr. anteriores seja igual ao da expr. avaliada End Select
A estrutura "Case Else" é opcional e caso um dos resultados seja
verdadeiro a estrutura executa o bloco de código equivalente e depois se
encerra sem testar os valores posteriores. Exemplo:
select x case 1 '... case 2 '... case 5 '... case else '... end select
Se o valor de X for igual a 2, o valor 5 não é nem sequer testado, se o
valor de X não for igual a nenhum dos valores testados o bloco de código
correspondente ao "case else" sera executado.
A Estrutura IF resolve a expressão matemática e testa se o valor é
verdadeiro (verdadeiro é igual a Diferente de Zero), se o valor for verdadeiro
então o bloco de código é executado, se não, o programa segue em frente ( e
executa o bloco de código depois do "Else" caso ele exista), é possivel usar
essa estrutura de maneira "in_line" tambem.
' Usando a estrutura If if x > y then '[...] end if ' Usando a estrutura If In_line If x>y then <um comando apenas> ' Usando a estrutura If / Else if x > y then '[...] else ' [...] end if ' Usando a estrutura If / Else in_line If x>y then <um comando apenas> else <um comando apenas>
Também é possível aninhar vários testes de condição de maneira que caso
um teste seja falso, ele pule automaticamente para o próximo, assim que uma
condição verdadeira for encontrada o bloco se encerra sem testar as demais, a
estrutura "Else" é opcional em ambos os casos.
if x = 1 then '[...] elseif x= 2 then '[...] elseif x= 3 then '[...] elseif x= 4 then '[...] else '[...] end if
<- Equal ->
O comando Equal serve como uma "Alias" permitindo especificar um
identificador que sera substituído por uma string na hora da compilação, isso
é útil para nomear valores constante. Exemplo
Equal Gravidade "10" Direcao_y += Gravidade
Neste caso acima o compilador ira enxergar
Direcao_y += 10
<- Sub Rotinas ->
Sub Rotinas são trechos de código para o qual podemos pular
arbitrariamente e retornar depois, geralmente são utilizadas para automatizar
tarefas que precisam ser feitas varias vezes, de maneira que ao invés de ficar
reescrevendo os procedimento é possível apenas "pular" para a subrotina quando
necessário.
Uma Subrotina pode receber parâmetros, porem não pode retornar nenhum
valor. Para definir uma subrotina no Next Basic usamos a seguinte
sintaxe:
Sub Nome_subrotina( <parametros> ) '[...] Código a executar end sub
É possível sair de uma subrotina em duas ocasiões, usando o comando
"Return" ou então quando ela chegar ao fim.
Os parâmetros são passados via Stack, e são especificados da seguinte
forma
Byval Nome_do_parametro as <tipo>
Sendo que o tipo pode ser byte, Word/Integer, Long ou String.
Depois de especificados os parâmetros eles podem ser acessados como variáveis
dentro da subrotina, caso sejam especificados mais de 1 parâmetro eles devem
ser separados por virgula, Zero parâmetros também são permitidos.
Também é possível passar parâmetros por referencia caso o valor esteja
contido em uma variável (similar aos ponteiros da linguagem C), dessa maneira
podemos acessar essa variável e ler ou alterar seu valor dentro da subrotina.
Parâmetros passados por referencia também podem ser tratados como Matrizes
Unidimensionais, o que permite passar matrizes como parâmetros também.
Exemplo: Uma subrotina de soma dois valores word (x1 e x2) e salva
numa variável global "k"
sub somar_valores(byval X1 as integer, Byval x2 as integer) k = X1 + X2 end sub
O mesmo exemplo, usando um parâmetro por referencia para salvar o resultado na
variável Global K:
1 2 3 4 5 6 7 8 |
dim k as integer 'Ao termino da execução dessa subrotina a variavel K tera valor = 4 soma(2,2,k) sub somar_valores(byval X1 as integer, byval X2 as integer, Byref _result_ as integer) _result_ = X1 + X2 end sub |
Parâmetros por referencia também podem ser acessados como um vetor
unidimensional, o que permite passar matrizes inteira como parâmetros para
funções e subrotinas.
dim _m[10] as integer ' Declara uma Matriz -m com 10 elementos unsigned integer subrotina(_m) 'Apos a execução dessa linha o elemento 5 da matriz _m tera o valor 10 salvo sub subrotina(byref vec_ as integer) ' Recebe um valor inteiro por Referencia vec_[5] = 10 ' Salva o valor 10 no Quinto Elemento da matriz recebida por referencia end sub ' Fim da subrotina
Subrotinas são chamadas da mesma maneira que as funções, porem sempre fora de
uma expressão matemática, já que elas não retornam nenhum valor.
<- Funções ->
As funções são muito similares as subrotinas, porem com o diferencial
de que elas podem retornar valor. Neste caso é necessário especificar no
código que se trata de uma função e definir o tipo de valor que essa função
retorna(que pode ser Byte, Word/Integer, Long, String ou Fixed).
Exemplo, uma função que soma dois valores word e retorna o resultado ficaria
assim:
Function soma_valores(byval X1 as integer, Byval x2 as integer) as integer return V1 + V2 End Function
Como ocorre um retorno de valor geralmente as chamadas de função
acorrem de dentro de uma expressão matemática. Exemplo:
var1 = soma_valores(2,2)
Neste caso o resultado armazenado na variável var1 sera de 4, já que a
função soma_valores() retorna a soma dos parâmetros passados na chamada de
função.
<- Importando Arquivos Externos ->
O Next Basic é capaz de ler arquivos de código fonte externos, para
arquivos de código fonte existem 3 formatos reconhecidos.
Arquivos .asm -> Arquivos de código fonte em assembly, estes são
lidos e adicionados ao ASM gerado pelo NextBasic antes da compilação... Eles
são inseridos no ponto em que são "importados" no código em Basic.
Arquivos .nbs (ou . vb)-> Arquivos NextBasic Source São arquivos que contem
código fonte em Basic, estes arquivos são inseridos no código principal na
posição em que são importados na hora da compilação.
Arquivos .nbh -> Arquivos NextBasic Header, estes arquivos são
adicionados sempre ao inicio do código principal (usem com cautela), estes
arquivos são bons para armazenar definições usando o comando Equal e
declaração formais de variáveis globais (sem atribuição de valor) que são
visíveis em qualquer parte do código.
Qualquer outro arquivo adicionado ao código será lido como um arquivo
Raw binary.
Para incluir um arquivo no source code usamos o comando "import", sendo
que o NextBasic é capaz de enxergar arquivos na pasta do source code que
estamos editando ou na pasta "system" dentro do diretório do compilador.
exemplo:
Import "\system\genesis_std.nbs" ' Importa a biblioteca Genesis Estandard na pasta system do compilador Import "\sprites.bin" ' Importa um arquivo binario chamado "Sprites" no diretorio do source code
Para e importação de arquivos Raw Binary é possivel passar diretivas
para o compilador com parâmetros que especificam como esse arquivo deve ser
inserido no binario final resultante da compilação. Esses paremetros podem ser
referentes ao alinhamento com a memoria, um endereço absoluto ou a posição do
arquivo em relação ao source code.
Para inserir os parametros basta colocar uma virgula após o endereço/nome do arquivo a ser importando e inserir os parametros desejados, o parametros podem ser:
Para inserir os parametros basta colocar uma virgula após o endereço/nome do arquivo a ser importando e inserir os parametros desejados, o parametros podem ser:
-o Para inserir o arquivo
importado num endereço absoluto de memoria especificado em Hexadecimal
-e Força o alinhamento do
arquivo importado com um endereço par de memoria
-u Força o alinhamento dos
dados após o arquivo importado com um endereço par de memoria
-f Envia o arquivo importado
para o final do source file
-a alinha o arquivo importado
com um valor especificado pelo programador
Caso os parâmetros sejam omitidos, o compilador ira importar o arquivo
e alinha-lo com um endereço par da memoria automaticamente.
Exemplo de uso:
1 |
import "\data.bin , -o 4FFFF , -u " ' Importa o arquivo data.bin no endereço absoluto $4ffff e força o alinhamento dos dados apos esse arquivo com um endereço par da memoria |
<- Incluindo Assembly no Source code ->
Existem diversas maneira de se fazer isso, usar o comando import para
incluir um arquivo de código fonte em assembly externo é uma delas (como
especificado no tópico Anterior), também podemos usar a Tag "_asm" para
incluir uma linha em assembly entre aspas, exemplo:
_asm(" move.l D0,D1")
Para incluir mais de uma linha devemos usar as Tag "_asm_block #__"
para inicializar o bloco de código em assembly e as Tags " __# _asm_block_end"
para finalizar o Bloco, tudo que estiver entre essas tag's sera copiado e
adicionado direto ao assembly gerado pelo NextBasic.
Alem disso ainda temos diversas outras rotinas para nos ajudar a
integrar funções em Basic com funções em assembly, dentre elas os comando Push
e Pop. sendo que o comando Push serva para gravar um valor ou expressão
matemática em um Registrador do Sistema e o comando Pop serve para ler um
valor de um registrador do sistema. Em ambos os casos devemos
especificar o tamanho dos dados que estamos lendo (byte, Word ou Long) e o
registrador que estamos acessando deve ser referenciado entre " aspas ".
Por exemplo, imagine que queremos integrar uma função em assembly no
nosso código e essa função recebe como parâmetros um valor v1 no registrador
D0 e v2 no registrador D1, o resultado dessa função é salvo no registrador D5.
Para integrar essa função em assembly no nosso código em Basic bastaria
utilizar os seguintes comandos:
Function funcao_asm(Byval v1 as word, Byval v2 as word) as word push( v1 as word, "D0") 'Salva o valor de V1 em D0 push( v2 as word, "D1") 'Salva o valor de V2 em D1 _asm_block #__ ;[...] Codigo em Assembly __# _asm_block_end return Pop( "D5" as Word) 'Retorna o resultado da função em ASM salvo em D5 End Function
Também existem outros fatores que podem facilitar a integração do Basic
com o Assembly, por exemplo, um código em assembly pode enxergar todas as
variáveis e labels do código em Basic, porem o contrario não é valido (o
código em Basic não consegue enxergar variáveis e labels definidas no código
em assembly).
Para acessar variáveis do Basic, basta saber o nome da variável e se
ela é Global ou Local.
Se ela for global basta adicionar "_global_" ao nome da Variável, para
variáveis locais adicionamos "_local_" porem, variáveis locais são acessadas
sempre por meio do Frame Pointer (registrador A6), Exemplo:
Para salvar uma variável Global 'x' no Registrador D0:
move.w _global_x,D0
Para uma variável local 'y':
move.w _local_y(A6),D0
Para pular para qualquer trecho do código em Basic, basta usar o nome
da Label/Função/Subrotina (já que o NextBasic não altera nem adiciona nada aos
nomes das labels).
jmp Nome_Label
<- Manipulando Bits e Posições de Memoria ->
O Next Basic conta com alguns recursos que permitem verificar/controlar
o estado dos bits individualmente em uma variável.
Podemos fazer isso por meio da função bit_test:
Bit_Test(variável , n_bit)
Sendo que o primeiro parâmetro é uma expressão matemática, o segundo
parâmetro pode ser um numero literal ou uma expressão matemática (inclusive
pode ser outra variável servindo como índice), essa função retorna verdadeiro
caso o Bit testado seja 1.
Para o caso especifico de variáveis podemos ler o valor de cada bit
individualmente usando "." seguindo do endereço do bit que queremos ler.
variavel.5
-> Retorna verdadeiro caso o bit 5 seja 1.
Para controlar o estado de um bit em uma variável podemos usar a função
bit_set para forçar o estado 1 ou bit_clear para forçar um estado 0.
Essas duas funções seguem o mesmo molde dos parâmetros da função anterior.
Bit_Set(<variável> , <n_bit>)
Bit_Clear(<variável> , <n_bit>)
Também podemos gravar ou ler posições de memoria usando as funções Peek
e poke.
A função peek lê um valor de memoria absoluta de um tamanho
especificado no parâmetro <tipo> (que pode ser Byte, Word, Long).
Acessar valores Word e Long deve ser feitos com cautela, pois esse
tipo de valor só pode ser acedido a partir de posições PARES de memoria, se
você tentar ler ou gravar um valor Word/Long em uma posição de memoria Impar,
o MC68000 ira gerar uma expection de Address Error! Apenas lembrando que
valores usados como endereços de memoria devem ser do tipo Long.
Peek (<Endereço> as <tipo>)
A função Poke serve para gravar um determinado valor numa posição de
memoria absoluta.
Poke (<dados> as <tipo> , <Endereço>)
Os dados a serem gravados tem um tamanho especificado no parâmetro
<tipo> (Byte, Word, ou Long), e serão gravados no Endereço (cujo tamanho
é calculado como long), só lembrando que tentar gravar um valor Word ou Long
em um endereço de memoria ímpar também gera uma exception de Address
Error. Por tanto certifique-se que o endereço de memoria seja SEMPRE par
caso esteja tentando gravar mais de 1 byte por vez.
A função Addressof( ) retorna um valor long equivalente ao endereço de
uma Label, Variável, Matriz, Subrotina, ou Função. Muito útil para
integrar rotinas em assembly, calcular offsets de memoria e atribuir valores a
ponteiros.
<- Interrupções, Exceptions e Trap Vectors ->
O NextBasic gera a Vector Table (caso essa opção esteja marcada no menu
Ferramentas->Opções), O Stack Pointer é definido para o ultimo endereço Par
da memoria RAM e o inicio do código é definido para depois da label
"inicio_src:" Essa label foi inserida dentro do arquivo "genesis_header.asm",
pois quando compilamos para o Mega Drive obrigatoriamente temos que colocar no
inicio da ROM o Header contendo os dados da ROM, bem como a verificação de
segurança de Trademark da Sega (tudo isso é feito automaticamente no arquivo
genesis_header.asm). Porem caso você não utilize o arquivo
genesis_header.asm sera necessário inserir a label "inicio_src" manualmente
para marcar o inicio do source code.
Para definir um ponto para onde o programa deve saltar ao ocorrer uma
exception, basta criar uma subrotina sem parâmetros, com o mesmo nome
referenciado na vector table.
Exemplo: para criar uma subrotina para onde o programa deve saltar
sempre que ocorrer uma Intrrupção de nivel 7 (Interrupção Vertical no casso do
Mega Drive) podemos usar esse código:
sub isr_07_vector() '[...] end sub
Caso os vetores não sejam especificados no código eles serão inseridos
ao final do source code, sendo que as interrupções são seguidas da instrução
RTE, já as demais exception's e Errors Trap convergem para um Loop infinito
caso ocorram!
Exception_list:
isr_buserror isr_addreserror isr_illegalinstruction isr_divisionbyzero isr_chk isr_trapv isr_privilegeviolation isr_Errortrap isr_01_vector isr_02_vector isr_03_vector isr_04_vector isr_05_vector isr_06_vector isr_07_vector trap_00_vector trap_01_vector trap_02_vector trap_03_vector trap_04_vector trap_05_vector trap_06_vector trap_07_vector trap_08_vector trap_09_vector trap_10_vector trap_11_vector trap_12_vector trap_13_vector trap_14_vector trap_15_vector
..:: - Biblioteca Genesis STD - ::..
Genesis_Header.asm
Arquivo contendo o Header necessário para inicializar uma ROM de Mega
Drive, esse header faz a verificação de Trade Mark da Sega e limpa
completamente a RAM do MC68000 e do Z80. É importante que ele fique sempre
no topo.
Std_Init()
Subrotina que inicializa o VDP com as configurações padrão
(Windows Plane Desativada, PlaneA e PlaneB com tamanho igual 64x32 tiles,
interrupções Horizontais, Externas e Globais desligadas e interrupção Vertical
Ligada), alem de inicializar os valores do buffer usado para construir os
comandos do DMA e de zerar a sprite Table.
vdp_set_config( config_table )
Subrotina que carrega os valores de configuração do VDP a partir de
uma tabela, recebe como parâmetro um valor Long contendo o endereço da
tabela. A tabela pode ser gerada pela ferramenta auxiliar VDP Config. Tool
disponível no menu Ferramentas.
Joypad6B_read( Joystick )
Lê o estado dos botões do Joystick especificado pelo valor passado
parâmetro (0 para joystick 1 e 1 para o joystick 2), e retorna um valor
Word contendo o status de cada botão, sendo 1 para pressionado e 0 para não
pressionado, nessa ordem:
Mode - X - Y - Z - Start - A - C - B - Right - Left - Down Up
Um conjunto de Equal's foi inserido na biblioteca para facilitar a
leitura dos botões, associando uma ID ao valor correspondente a cada Bit na
variável que armazena o resultado da leitura do Joystick:
' Bits referentes a cada botão no Joystick Equal btn_up "0" Equal btn_down "1" Equal btn_left "2" Equal btn_right "3" Equal btn_b "4" Equal btn_c "5" Equal btn_a "6" Equal btn_start "7" Equal btn_z "8" Equal btn_y "9" Equal btn_z "10" Equal btn_mode "11"
Dessa maneira podemos usar um comando de bit_test + uma estrutura IF
para detectar quando um botão foi pressionado. Exemplo:
J = Joypad6B_read(0) ' Le o Joystick 1 e salva na variavel J If bit_test(j, btn_Start) then '[...] Executa o que esta dentro desse If caso o botão Start seja Pressionado end if
Wait_Vblank()
Essa função prende o processador em um Loop até que o próximo intervalo
de Vertical Blank se incie, isso é feitos monitorando o Bit 3 do Registrador
Status do VDP. Essa função não recebe nenhum valor como parâmetro. Exemplo:
do '[...] O que esta dentro desse laço sera executado uma vez a cada frame wait_Vblank() loop
Load_tiles_DMA(Data_Address, N_tile, Vram_Addr)
Essa rotina copia um certo numero de tiles para a Vram usando DMA
(Direct Memory Access), o primeiro parâmetro é um Long contendo o endereço a
partir do qual vamos começar a copiar esses tiles, a segundo parametro é um
Word contendo o numero de tiles que vamos copiar, e o ultimo parâmetro é um
long contendo o endereço final na Vram / 32 ( ou seja o endereço em
tiles) isso por que cada tile consiste num conjunto de 32 bytes.
Esse método é muito veloz para copiar grandes quantidades de memoria,
principalmente durante os intervalos de V_blank. Exemplo de uso:
load_tiles_DMA(addressof(fonte_0), 1, 1) Fonte_0: datalong &h11100111 datalong &h11100111 datalong &h11000011 datalong &h00011000 datalong &h00011000 datalong &h11000011 datalong &h11100111 datalong &h11100111
O programa acima copia 1 tile a partir da label "Fonte_0" e salva na
posição 1 da memoria de video, a posição Zero é utilizada para armazenar o
tile que o VDP usa para preencher os espaços "vazios" da tela, então é
importante não alterar o conteúdo desse tile, sendo mais seguro realizar todas
as copias para a Vram a partir da posição '1' .
Load_tiles_DMA_128kSafe(Data_Address, N_tile, Vram_Addr)
Faz exatamente o mesmo que subrotina anterior porem NÃO verifica
se o endereço é seguro para a transferência, isso por que o VDP do Mega Drive
enxerga a memoria em Chunks de 128Kb, se os dados carregados estiverem entre
um chunk e outro parte dos dados sera corrompida no processo.
A Subrotina anterior verifica se o endereço é seguro antes de fazer a
copia e caso não seja ela automaticamente divide a transferência em duas
operações, porem esse processo consome mais ciclos de processamento ja que é
necessário fazer os cálculos de verificação do endereço.
Caso você tenha certeza que o endereço é seguro e que ele não revaza de
um chunk para o outro, você pode usar essa rotina (Load_tiles_DMA_128kSafe)
que é ainda mais veloz!
Load_cram_DMA(Data_Address, N_cores, Palet_)
Essa subrotina serve para carregar dados para a Cram (Color Ram, a
memoria que armazena as paletas de cores do Mega Drive), O primeiro parâmetro
é um Long contendo o Endereço dos dados que vamos copiar, o segundo parâmetro
é um Word contendo a quantidade de cores que vamos copiar, e o ultimo
parâmetro é um word contendo o numero da palheta a partir da qual vamos
iniciar a copia, esse numero pode ir de 0 a 3 (dado que o Mega
Drive possui 4 palhetas de cores). Exemplo:
load_cram_dma(addressof(paleta_cores),16,0) ' Carrega 16 cores na paleta 0 paleta_cores: dataint &h0000,&h0EEE,&h0EEE,&h0EEE,&h0EEE,&h0EEE,&h0EEE,&h0EEE dataint &h0EEE,&h0EEE,&h0EEE,&h0EEE,&h0EEE,&h0EEE,&h0EEE,&h0EEE
Load_cram_DMA_128ksafe(Data_Address, N_cores, Palet_)
Faz exatamente o que a subrotina anterior porem sem verificar se o
endereço não ultrapassa os limites dos Chunks de 128Kb, é bem mais veloz que a
subrotina anterior porem só deve ser usada caso se tenha certeza que o
endereço é seguro (para mais esclarecimentos verifique o paragrafo da
subrotina Load_tiles_DMA_128ksafe ).
Draw_tile( Tile, pos_x, pos_y, Plane )
Desenha um tile na Tela na posição e no plano indicado, essa função
recebe como parâmetro um valor indicando que tile vamos desenhar na tela, a
posição em X, a posição em Y e o plano (que pode ser Plane_A ou Plane_B), a
coordenada é dada em tiles e todos os parâmetros são do tipo
word/integer.
Por padrão o tile é desenhado usando a paleta Zero, porem é possível
definir qualquer outra paleta realizando a operação "or" entre o valor que
especifica o índice do tile usado e as tags palette_1, palette_2
ou palette_3 sendo também possível aplicar efeitos de Flip
Horizontal e vertical, ou definir a prioridade do Tile usando a operação "or"
com as tags V_Flip, H_Flip ou Priority_H
Exemplo:
draw_tile(1,0,0,Plane_A) ' Desenha o tile 1 na posição 0,0 (canto superior esquerdo) do plano_A
Draw_tilemap( tilemap, Size_x, size_y, px , py, Plane , offset )
Desenha um tilemap na tela, recebe como paremtro um Long contendo o endereço
do tilemap, um word contendo o tamanho do mapa em X, um word contendo o
tamanho do mapa em Y, um word contendo a coordenada em X, um word contendo a
coordenada em Y, um word contendo o Plano onde o mapa sera desenhado e um Word
contendo o offset
Set_sprite_gfx(sprite_number, Gfx, Palet_)
Essa subrotina define os gráficos e a paleta de cores para um
determinado sprite, ela recebe como parametro um Word contendo o indice
correspondente ao Sprite que vamos alterar, o numero do tile na Vram que vamos
atribuir a esse sprite e por ultimo um word contendo o numero da paleta que
que vamos usar para colorir esse sprite, também é possível aplicar os efeitos
de de Flip Horizontal e vertical, ou definir a prioridade do sprite, para isso
basta realizar a operação "OR" entre o parâmetro Gfx e o identificador do
efeito desejado (H_Flip, V_Flip, Priority_H).
set_sprite_gfx(0,1,0) ' Linka o Sprite Zero da tabela com o primeiro tile da Vram e com a paleta Zero
Set_sprite_size(sprite_number, Widht, Height)
Essa subrotina define o tamanho de um sprite e recebe como parâmetros
um word contendo o índice equivalente ao sprite que queremos modificar, um
word contendo o tamanho que sera atribuído ao sprite na horizontal e um word
que sera o tamanho atribuído ao sprite na vertical.
Sendo que o Mega Drive consegue renderizar sprites de até 4x4 tiles e
cada tamanho intermediário é representado por um numero de 0-3 sendo 0
equivalente a 1 Tile e 3 equivalente a 4 tiles. Exemplo:
set_sprite_size(0,0,0) ' Define o sprite Zero da Sprite Table como tendo 1x1 tile de tamanho
Set_sprite_position(sprite_number, pos_x, pos_y)
Essa subrotina define a posição de um sprite na tela, sendo que ela
recebe como parâmetros um Word contendo o índice do sprite que vamos mover, um
word contendo a posição em X para qual vamos mover-lo e por ultimo um word
contendo a posição em Y, ambas em pixels!
Só lembrando que os Sprites podem se mover numa área de 512 x 512
pixels e a área visível para o jogador começa na posição 128x128.
set_sprite_position(0,128,128) ' Move o Sprite Zero da Tabela para o canto superior esquerdo da tela
Update_sprite_table( )
Essa Subrotina, atualiza a Sprite Table, aplicando todas as
modificações que foram feitas nos Sprites desde a ultimas vez em que ela foi
chamada, ela se faz necessária por que essa biblioteca mantem um buffer na RAM
e todas as modificações feitas nos sprites são feitas primeiramente nesse
buffer, ao chamar a rotina de Update o buffer é transmitido para a Vram usando
DMA.
Essa subrotina não recebe nenhum valor como parâmetro. Exemplo:
Do '[...] Código que modifica a posição ou animação dos sprites Update_sprite_table( ) ' Aplica as modificações Wait_Vblank( ) ' Espera o inicio do Vblank Loop
Set_VerticalScroll_position(cam_V, Plane)
Essa função serve para definir o valor do scroll Vertical, por padrão
ele vem configurado para o modo "Whole Screen", essa função recebe como
parâmetro um word contendo a posição para qual a Scroll Cam sera enviada e um
Word contendo o numero do plano ao qual aplicaremos o scroll (sendo 0 para o
Plane_A e 1 Para o Plane_B)
do x+=1 Set_VerticalScroll_position(x,Scroll_A) Wait_Vblank() loop
Set_HorizontalScroll_position(cam_H, Plane)
Essa função serve para definir o valor do scroll Horizontal, por
padrão ele também vem configurado para o modo "Whole Screen" e essa função
recebe como parâmetro um word contendo contendo a posição para qual a Scroll
Cam Horizontal sera enviada e um Word contendo o numero do plano ao qual
aplicaremos o scroll, sendo 0 para o Plano A e 1 Para o Plano B, foi
adicionado um Equal na biblioteca para facilitar a utilização desses
parâmetros, sendo possível usar os identificados Scroll_A e Scroll_B para
seleciona o plano de Scrolling.
do y+=1 Set_HorizontalScroll_position(y,Scroll_A) Wait_Vblank() loop
Hscroll_strip8( camera, strip, plane )
Esse subrotina deve ser usada para setar a posição da camera Horizontal para
cada faixa de 8 Pixels individualmente (quando o modo de scroll Horizontal
estiver configurado dessa maneira no VDP), ela recebe como parâmetro um Word
contendo a posição que sera atribuída a câmera, um word contendo o numero da
faixa (strip) e um word contendo o plano ao qual essa strip pertence. A
mudanças só são aplicadas apos chamar a subrotina update_Hscroll_table()
Hscroll_line( camera, line, plane )
Esse subrotina deve ser usada para setar a posição da camera Horizontal
para cada linha individualmente (quando o modo de scroll Horizontal
estiver configurado dessa maneira no VDP), ela recebe como parâmetro um
Word contendo a posição que sera atribuída a câmera, um word contendo o
numero da linha e um word contendo o plano ao qual essa linha pertence. A
mudanças só são aplicadas apos chamar a subrotina update_Hscroll_table()
Update_Hscroll_table()
Atualiza a Scroll Table na Vram via DMA.
Enable_global_int()
Ativa interrupções Globais
Disable_global_int()
Desativa as Interrupções Globais
Enable_V_int()
Ativa Interrupções Verticais
Disable_V_int()
Desativa Interrupções Verticais
Enable_H_int()
Ativa Interrupções Horizontais
Disable_H_int()
Desativa Interrupções Horizontais
Enable_Ext_int()
Ativa Interrupções Externas.
Disable_Ext_int()
Desativa Interrupções Externas.
Enable_display()
Liga Display.
Disable_display()
Desliga o Display (acelera transferências por DMA fora do Vblank).
Set_Hint_counter( count )
Seta o valor de contagem de scanlines entre as interrupções horizontais
(valor salvo no registrador Horizontal Interrupt Counter do VDP), recebe
como parâmetro um word contendo o numero de scanlines entre uma interrupção
e outra.
Direct_color_DMA( bmp , frames)
Exibe uma imagem de 192x224 na tela usando a tecnica de Direct Color DMA,
recebe como parâmetro um long contendo o endereço da imagem e um word
contendo o numero de frames em que a imagem sera exibida (sendo 60 frames =
1 segundo).
Nenhum comentário:
Postar um comentário