Modularyzacja i linkowanie w Action!

Gdzieś tam jest i czai się Error 61, to tylko kwestia czasu kiedy zaatakuje :).

Postanowiłem sprawdzić, czy podzielenie programu w Action na mniejsze kawałki to duży problem. Okazuje się, że nie.

Ale najpierw dwa słowa o listowaniu tablicy symboli Action! Jest sobie program Symbol Table Lister. Kilka razy do niego już podchodziłem bez pozytywnych rezultatów, ale ostatecznie udało się. Wygląda na to, że instrukcja w powyższym linku jest błędna.

W pierwszym kroku, zmieniłem linię:

   Close(5) Open(5, „P:”, 8) na
   Close(5) Open(5, „H1:LISTING.TXT”, 8)

Dzięki temu wynik działania listera zapisany zostanie do pliku.

Następnie trzeba było:

  • załadować interesujący nas program do edytora
  • w Monitorze, Ctrl+Shift+M uruchomić lister za pomocą R „H1:STL.ACT”
  • skompilować interesujący nas program za pomocą C

W pliku LISTING.TXT zapisywany jest ładnie sformatowany listing z adresami funkcji, procedur i zmiennych programu z edytora.

Teraz prosty test. W pliku LIB.ACT, mamy dwie proste funkcje:

MODULE ; LIB.ACT

set 14=$8000
set $491=$8000

proc test1()
 byte bkg=710
 bkg=0
return

proc test2()
 byte bkg=710
 bkg=52
return

Za pomocą

set 14=$8000
set $491=$8000

ustawiamy aby kod umieszczony został pod adresem $8000.

Główny program wrzucamy dalej, od $8013:

include "H1:INCL.ACT"

set 14=$8013
set $491=$8013

proc delay()
 int i for i=0 to 10000 do od
return

proc main()
 do
  test1()
  delay()
  test2()
  delay()
 od

Plik INCL.ACT definiujący adresy procedur:

proc test1=$8000()
proc test2=$8009()

Po kompilacji MAIN.ACT i LIB.ACT „linkujemy” powstałe pliki binarne za pomocą cat lub type pod Windows:

type LIB.OBX MAIN.OBX > FINAL.XEX

I otrzymujemy plik wykonywalny.

Przykładowe wyjście z Symbol Table Lister wygląda tak:

Symbol Table for H1:LIB.ACT

Local declarations for test1:
bkg………… $02C6 BYTE

Local declarations for test2:
bkg………… $02C6 BYTE

Global declarations:

test1………. $8000 PROC()
test2………. $8009 PROC()

Code base = $8000, code size = 19

Prawda że przydatne? Przygotowanie w ten sposób nieco okrojonego RUNTIME oddala problem braku miejsca w tablicy symboli. Big Symbol Table jakoś dziwnie działa. Pozostaje sprawdzenie ładowania plików binarnych. Podzielenie dużego programu na mniejsze doczytywane kawałki wydaje się najbardziej sensowne.

Finescroll w VBI + tęcza w DLI

Zabawy z przesuwaniem tekstu chyba nie będzie końca. Tym razem do przesuwu typu finescroll realizowanego w ramach VBLANK interrupt dodamy ruchomą tęczę w ramach procedury obsługi przerwania DLI.

int i=[0], j=[0], k=[0]
byte wsync=$D40A, vcount=$D40B
byte colt=$D017, indx=[0], hscrol=$D404
card pointer pc
card tmp
include "H1:DEFINES.ACT"

proc dli()
[pha txa pha tya pha]
indx==+1
for i=0 to 7 do
 wsync=1
 if indx>30 then indx=0 fi
 colt=vcount+indx
od
[pla tay pla tax pla rti]

proc scroll()
 hscrol=j
 j==+1
 if j=17 then
  j=0 pc^==-2 k==+1
  if k=14 then
   pc^=tmp k=0
  fi
 fi
[jmp xitvbv]

proc main()
int i card savmsc=88
byte clock=$14
byte nmien=$D40E
byte array ndl = [112 112 112 66 64 156 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 130 86 216 159 65 32 156]
card dlist=560
card vvblkd=$0224
byte col0=708
byte col1=709
card vdslist=$0200
graphics(0)
dlist=ndl
savmsc=40000
col0=14 col1=14

for i=1 to 23 do
 printf("line: %i%E", i)
od
print("action!")

pc=dlist+28
tmp=pc^+6
i=clock
while clock=i do od
nmien=0
vvblkd=scroll
vdslist=dli
nmien=$C0
do od
return

a w pliku DEFINES.ACT:

define rti="$40",
pha="$48", pla="$68", txa="$8A",
tax="$AA", tya="$98", tay="$A8",
jmp="$4C", php="$08", plp="$28",
xitvbv="$E462" 

Najwięcej czasu (ze dwa niespokojne tygodnie :)) zajęło mi dojście do tego, dlaczego w dli muszę zmieniać sprzętowy rejestr koloru, a nie jego cień :). Wynika to z faktu, że jeżeli zmienię rejestr cień, to wartości rejestrów cieni są przepisywane do rejestrów sprzętowych GTIA dopiero w systemowej procedurze przerwania VBLANK, czyli po wyświetleniu ramki do końca.  Skutek jest taki, że wszystkie linie ekranu malowane są ostatnim kolorem z pętli „wsync=1… colt=”  w dli. Po zakończeniu procedury dli() w rejestrze cieniu siedzi sobie tenże ostatni kolor z pętli, bo kolor zmieniony w rejestrze cieniu nie trafia od razu do GTIA, układ graficzny maluje sobie dalej kolorem przepisanym podczas poprzedniego VBLANK. Kończy ramkę, wywołuje przerwanie VBLANK, przepisuje rejestr sprzętowy ostatnią wartością  rejestru systemowego i wszystkie linie od pierwszej do tej, dla której dli jest ustawione maluje w tym samym kolorze.

Jeżeli ustawimy rejestr sprzętowy ($D017 zamiast $2C5) to przy wsync=1 nowy kolor jest przepisywany do GTIA w momencie przerwania poziomego, następna linia do końca jest malowana tym kolorem, tak osiem razy bo w tej linii trybowej mamy osiem linii w wierszu, kończy się malowanie ramki, w VBLANK system operacyjny przepisuje rejestry sprzętowe wartościami z rejestrów cieni, czyli maluje od pierwszej linii trybowej, do tej w której ustawione jest dli kolorami z cieni. Potem startuje dli, zmienia rejestr sprzętowy osiem razy maluje w innym kolorze itd, itd.

Muszę jeszcze tylko przetestować binarkę na prawdziwym Atari… (update: działa).

Ciekawe ile jeszcze takich fascynujących „odkryć” na mojej drodze :). Pewnie sporo.

Action!: Finescroll w VBLKD.

Poprzednie przykłady z „Poradnika programisty” brzydko się rwały przy przesuwaniu, zwłaszcza na prawdziwym Atari. Na podstawie różnych „odkryć i znalezisk” powstał poniższy przykład w Action! wykorzystujący Vertical Blank Interrupt. A tutaj analogiczny przykład, który robi to samo w zwykłej pętli i synchronizuje się z VBLANK interrupt za pomocą zegara systemowego.

int j=[0], k=[0]
byte hscrol=54276
card pointer pc
card tmp

include "H1:RUNTIME.ACT"

proc scroll()
 hscrol=j
 j==+1
 if j=17 then
  j=0 pc^==-2 k==+1
  if k=14 then
   pc^=tmp k=0
  fi
 fi
[$4C $E462] ;jmp xitvbv

proc main()
 int i card savmsc=88
 byte clock=$14
 byte nmien=$D40E
 byte array ndl = [112 112 112 66 64 156 2 2 2 2 2 2 2 2 2 2
                   2 2 2 2 2 2 2 2 2 2 2 86 216 159 65 32 156]
 card dlist=560
 card vvblkd=$0224
 byte col0=708
 byte col1=709

 graphics(0)
 dlist=ndl
 savmsc=40000
 col0=14 col1=14

 for i=1 to 23 do
  printf("line: %i%E", i)
 od

 print("Action!")

 pc=dlist+28
 tmp=pc^+6
 i=clock
 while clock=i do od
 nmien=0
 vvblkd=scroll
 nmien=$40
 do od
return

scroll() dodałem jako część opóźnioną procedury obsługi przerwania. Wg Tajemnic Atari powinno się ustawiać vvblkd po „cyknięciu” zegara, żeby się nie okazało, że nie starczy cykli procesora na ustawienie adresu naszej procedury obsługi przerwania. Tak też zrobiłem. Nie jestem przekonany czy to jest konieczne.

Finescroll.

Czasem moje eksperymenty przenoszą się na godziny późno-nocne 🙂 Tym razem przemyślenia w temacie „finescroll poziomy”. Synchronizacja nie powinna uciekać, bo przed przesunięciem łapiemy ramkę sprawdzając czy zegar „tyknął”, a dzieje się tak podczas VBLANK interrupt 50 razy na sekundę. W tym wpisie analogiczny przykład z VBI, przy okazji którego pojawił się pomysł uzupełnienia tej procedury o i=time while time=i do od.


include "H1:RUNTIME.ACT"

proc main()
 int i=[0], j=[0], k=[0]
 card dlist=560
 byte hscrol=54276
 card pointer pc
 card savmsc=88
 card tmp
 byte col0=708
 byte col1=709
 byte time=$14

 byte array ndl = [112 112 112 66 64 156 2 2 2 2 2 2 2 2 2 2
                   2 2 2 2 2 2 2 2 2 2 2 86 216 159 65 32 156]
;                  64 + 6 (GR.1) + 16 (hscrol on) = 86
 graphics(0)
 savmsc=40000
 dlist=ndl
 col0=14
 col1=14

 for i=1 to 23 do
 printf("line: %i%E", i)
 od

 print("Action!")

 pc=dlist+28
 tmp=pc^+6
 do
  i=time while time=i do od
  hscrol=j
  j==+1
  if j=17 then
   j=0 pc^==-2 k==+1
   if k=14 then
    pc^=tmp k=0
   fi
  fi
od
return

Scroll poziomy.

Tamtego wieczoru się nie udało, ale dziś, jak wszyscy już śpią, mogę chwilę się pobawić. Przykłady w książkach Ziętary być może nie były bezpośrednio jego autorstwa, ale jednak były bardzo fajne.

Poniżej przykład scrollowania poziomego i coś podobnego w Action!

100 POKE 82,0:GRAPHICS 0:FOR I=1 TO 92:? I,"   ";:NEXT I
110 DL=PEEK(560)+256*PEEK(561)
120 POKE 54276,7
130 FOR I=DL+10 TO DL+20:POKE I,PEEK(I)+16:NEXT I
140 FOR K=1 TO 10:FOR J=7 TO 0 STEP -1
150 POKE 54276,J:GOSUB 300
160 NEXT J:NEXT K
170 FOR K=1 TO 20:FOR J=7 TO 0 STEP -1
180 POKE 54276,J
190 NEXT J:NEXT K:J=7
200 POKE 54276,J:J=J-1
210 IF J>=0 THEN 200
220 J=7:L=PEEK(DL+4):H=PEEK(DL+5):L=L+2
230 IF L>255 THEN L=L-256:H=H+1
240 IF H>255 THEN POKE 82,2:GRAPHICS 0:END
250 POKE DL+4,L:POKE DL+5,H:GOTO 200
300 FOR I=1 TO 100:NEXT I:RETURN

 

proc delay()
 int i for i=0 to 500 do od
 return

proc main()

byte lmarg=82
int i, j, k
card dl=560, hscrol=54276
byte pointer ptr
card pointer p

lmarg=0 graphics(0)

for i=10 to 92 do
 printf("%I         ", i)
od

hscrol=7
ptr=dl+10

for i=10 to 20 do
 ptr^==+16
 ptr==+1
od

for k=1 to 10 do
 for j=7 to 0 step -1 do
  hscrol=j delay()
 od
od

for k=1 to 20 do
 for j=7 to 0 step -1 do
  hscrol=j
 od
od

j=7 p=dl+4
do
 hscrol=j
 j==-1
 if j<0 then
  j=7
  p^==+2
  if p^ > 65530 then
   lmarg=2 graphics(0)
   main()
  fi
 fi
delay()
od
return

Action!zmy i Scrolle.

Grafikę graczy i pocisków na razie odpuszczam 🙂 ale tylko na chwilę, bo zawsze chciałem ogarnąć temat scrollowania. Przerabiam więc przykłady z „Poradnika Programisty” Ziętary na Action! Oto pierwszy z nich. Jak widać, tak jak chciałem display listę zmieniam wskaźnikiem, bez peeków i poków.

Listing w Basicu:

100 POKE 82,0:GRAPHICS 0:FOR I=1 TO 92:? I,:NEXT I
110 DL=PEEK(560)+256*PEEK(561)
120 POKE 54377,0
130 FOR I=DL+10 TO DL+27:POKE I,PEEK(I)+32:NEXT I
140 FOR K=1 TO 10:FOR J=0 TO 7
150 POKE 54277,J:GOSUB 300
160 NEXT J:NEXT K:J=0
170 FOR K=1 TO 20:FOR J=0 TO 7
180 POKE 54277,J
190 NEXT J:NEXT K
200 POKE 54277,J:J=J+1
210 IF J<8 THEN 200
220 J=0:L=PEEK(DL+4):H=PEEK(DL+5):L=L+40
230 IF L>255 THEN L=L-256:H=H+1
240 IF H>255 THEN POKE 82,2:GRAPHICS 0:END
250 POKE DL+4,L:POKE DL+5,H:GOTO 200
300 FOR I=1 TO 100:NEXT I:RETURN

I zbliżony kod w Action (zbliżony, bo pisany na szybko i nie chce mi się pisać
printa z tabulatorem):

include "H1:RUNTIME.ACT";

proc delay()
 int i for i=0 to 500 do od
 return

proc main()

byte i, j, k, lmarg=82, vscrol=54277
card dl=560
byte pointer ptr
card pointer p

lmarg=0 graphics(0)

for i=10 to 92 do
  printf("%I         ", i)
od

vscrol=0
ptr=dl+10
for i=0 to 17 do
 ptr^==+32
 ptr==+1
od

for k=1 to 10 do
 for j=0 to 7 do
  vscrol=j delay()
 od
od

for k=1 to 20 do
 for j=0 to 7 do
  vscrol=j
 od
od

do
 while j<8 do
   vscrol=j
   j==+1
   delay()
 od
 p=dl+4
 j=0 p^==+40

 if p>=65495 then
  lmarg=2 graphics(0)
  main()
 fi
 delay()
od

return

Dziś wieczorem eksperymenta ze scrollem poziomym.

Action! Własne Graphics(), Position(), Plot(), Drawto() i Print().

Nie znam asm, ale na podstawie tego co można znaleźć w kilku moich ulubionych książkach udało mi się stworzyć procedurki do rysowania i pisania na ekranie. Dumny jestem z printa, który wykorzystuje feature Actionowego „string constant”, czyli fakt, że element zerowy łańcucha napisu zawiera długość napisu, którą wydłubujemy w asm i podajemy w ICBLL, określając długość bufora.

MYGR.ACT

include "H1:ASMINC.ACT"
proc main()
int r
card savmsc=88
card textaddr=660
byte bkg=710
byte array dltb=[ 112 112 112 66 96 159 72
112 158 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
8 8 8 8 65 78 158 ]
card dlist=560
graphics(3)
bkg=0
dlist=dltb
textaddr=40800
savmsc=40560
print("ACTION! rules")
for r=0 to 18 step 2 do
 plot(0+r,0+r) drawto(0+r,10+r)
 plot(0+r,10+r) drawto(10+r,10+r)
 plot(10+r,10+r) drawto(10+r,0+r)
 plot(10+r,0+r) drawto(0+r,0+r)
od
do od

MYLIB.ASM

           org $600
graphics   pha
           ldx #$60
           lda #close
           sta iccom,x
           jsr ciov
           lda #open
           sta iccom,x
           lda <fname
           sta icbal,x
           lda >fname
           sta icbah,x
           pla
           sta icaux2,x
           and #$f0
           eor #$10
           ora #opnino
           sta icaux1,x
           jmp ciov
           rts

;          org $62b
position   sty colcrs
           stx colcrs+1
           sta rowcrs
           rts

;          org $632
plot       jsr position
           ldx #$60
           lda #putchr
           sta iccom,x
           lda #$0
           sta icbll,x
           sta icblh,x
           lda color
           jmp ciov
           rts

;          org $64b
drawto     jsr position
           lda color
           sta atachr
           ldx #$60
           lda #drawln
           sta iccom,x
           lda #opnino
           sta icaux1, x
           lda #$0
           sta icaux2, x
           jmp ciov
           rts

;          org $669
print      clc
           sta $d4
           stx $d5
           ldx #0
           adc #1      ;leave 0-th element
           sta icbal,x
           lda #putrec
           sta iccom,x
           lda $d5
           sta icbah,x
           lda #0
           sta icblh,x
           ldy #0
           lda ($d4),y  ;buflen from string
           sta icbll,x  ;constant's 0-th element
           jsr ciov
           rts

atachr     equ $2fb
drawln     equ $11
opnino     equ $c
icaux1     equ $34a
icaux2     equ $34b
fname      dta c 'S:'
colcrs     equ $55
rowcrs     equ $54
putchr     equ $0b
icbal      equ $344
icbah      equ $345
iccom      equ $342
icbll      equ $348
icblh      equ $349
color      .db 1
ciov       equ $e456
putrec     equ $9
getrec     equ $5
open       equ $03
close      equ $0c

Przy tego typu łączeniu kodu asm z Action! adresy procedur najbezpieczniej wziąć z listingu wygenerowanego madsem (opcja -l:ASMINC.lst).

ASMINC.ACT

proc graphics=$600(byte m)
proc position=$62B(card c, byte r)
proc plot=$632(card c, byte r)
proc drawto=$64B(card c, byte r)
proc print=$669(card msg)

Wiem że da się to zrobić lepiej ale i tak jestem z siebie dumny 🙂

Jak widać Action przekazuje parametry procedur przez akumulator i rejestry X i Y (a jeśli jest ich więcej, również przez stronę zerową). W print() adres napisu maglujemy w postaci LSB,MSB. MYLIB.ASM madsujemy i cat-ujemy MYLIB.OBX z binarką MGR.XEX.

Ciekawe jest to, że powyższy przykład w Action! po kompilacji z Runtime Action! albo uruchomieniu z cartem się wykrzacza, a działa po „zlinkowaniu” z ASMLIB.obx. Domyślam się, że chodzi o współrzędne podawane plot() i drawto(). Zapewne w przypadku procedur z carta i runtime generują one coś w stylu error 141, a procedurki w asm nie sprawdzają zakresu współrzędnych i jadą dalej poza pamięcią obrazu… Na chwilę obecną nie jestem w stanie tego lepiej wytłumaczyć. Ważne że działa.

Konwertować do bloków kodu [] mi się tego póki co nie chce, ale pewnie niebawem i to przetrenuje.

Kolejny, ważniejszy krok, to stworzenie procedur piszących bezpośrednio do pamięci ekranu danego trybu graficznego. JHusak kiedyś wspominał o szybkim plot(), jest nawet o tym wzmianka w FAQ o Action! atarionline.pl, ale od jakiegoś czasu ten link się zapętla.

Rozszerzenia pamięci Atari, czyli jak działa PORTB.

Okazuje się, że to wcale nie takie proste jak się Atari pamięta głównie z czasów dziecinnych, a pozostała dokumentacja jest często rozrzucona i niekompletna.

Najpierw co to ten PORTB. W skrócie to rejestr pod adresem 54017. Ustawiając odpowiednie bity tego rejestru możemy skonfigurować dodatkową pamięć.

Znaczenie poszczególnych bitów w 130XE (wg. Atari Magazine):

Bit Opis
0 1: włącz OS ROM, brak dostępu do RAM w obszarze
$C000-$FFFF (default)
0: wyłącz OS ROM włącz RAM w obszarze $C000-$FFFF
1 0: włącz BASIC ROM od obszarze $A000-$BFFF (default)
1: wyłącz BASIC ROM, dostęp do RAM w obszarze $A000-$BFFF
2 3 00: pierwszy 16K bank w obszarze $4000-$7FFF
01: drugi bank w obszarze $4000-$7FFF
10: trzeci bank w obszarze $4000-$7FFF
11: czwarty bank w obszarze $4000-$7FFF
4 1: 6502 ma dostęp do pamięci podstawowej (default)
0: 6502 ma dostęp do pamięci rozszerzonej
5 1: ANTIC ma dostęp do pamięci podstawowej (default)
0: ANTIC ma dostęp do pamięci rozszerzonej
6 Nie używane. Default = 1
7 1: wyłacz self-test, włącz RAM $5000-$57FF (default po włączeniu)
0: włącz  self-test wyłącz RAM pomiędzy $5000-$57FF

To w przypadku Atari 130XE, a co z innymi rozszerzeniami?

Znalazłem jedno sensowne wytłumaczenie tutaj (niestety serwer nie działa, ale od czego jest google cache):

PORT B – jest to grupa rozszerzeń opartych o rejestr sterujący $d301 (tzw. PORT B), przeznaczonych dla komputerów Atari 8-bit serii XL/XE. Rozszerzenia te opierają się na bankach 16kB RAM, przełączanych w obszarze adresowym $4000-$7fff, czyli w tym samym obszarze co rozszerzenie Axlon.

  • 130XE – standardowo Atari 130XE posiadało 4 dodatkowe banki, które wybierało się bitami 2 i 3 rejestru PORT B. Bit 4 sterował dostępem 6502 do dodatkowej pamięci, natomiast bit 5 dostępem Antica do dodatkowej pamięci – wartość 1 danego bitu ustawiała dostęp danego procesora (6502 lub Antica) do pamięci podstawowej, wartość 0 do dodatkowej, dzięki czemu istniała możliwość niezależnego dostępu 6502 i Antica.
  • 192kB – rozszerzenie analogiczne do 130XE, z tym, że do bitów 2 i 3 dochodził dodatkowo bit 6, dzięki czemu liczba kombinacji wzrosła do 8 banków dodatkowej pamięci (czyli +128kB dodatkowej pamięci)
  • TOMS 256kB, 256kB/320kB RAMBO – rozszerzenia polegały na tym, że bit 4 decydował o dostępie zarówno Antica jak i 6502 do dodatkowej pamięci, natomiast bit 5 był dodatkowym bitem umożliwiającym wybór banku. Dawało to możliwość wyboru 16 banków (czyli +256kB RAM) za pomocą bitów 2,3,5,6, jednak w niektórych rozszerzeniach z racji specyfiki konstrukcyjnej wykorzystywano tylko 12 dodatkowych banków.
  • 576kB RAMBO/320kB Compy Shop – rozszerzenia polegały na tym, że bit 7 portu B, który standardowo w Atari 8-bit odpowiadał za dostęp do pamięci ROM z Self Testem pełnił podwójną rolę. Przy bicie 4 = 1 pełnił swoją standardową rolę, natomiast w wypadku wartości 0 bit ten stanowił dodatkowy bit określający wybór banku. Zatem bity 2,3,6,7 pozwalały na zaadresowanie 16 banków. W wypadku rozszerzenia RAMBO 576kB wyboru banku dokonywał również bit 5 (wspólnie sterowany dostęp Antica i CPU przez bit 4), co dawało wybór 32 banków, natomiast w rozszerzeniu Compy Shop bit 5 decydował o niezależnym dostępie Antica.
  • 1088kB RAMBO/576kB Compy Shop – rozszerzenia analogiczne do 576kB RAMBO/320kB Compy Shop, jednak dochodził tam dodatkowy bit sterujący: bit 1, który standardowo decyduje o włączeniu/wyłączeniu ROM-u z interpreterem Basica. W tym wypadku, gdy bit 4 miał wartość 1, bit 1 pełnił swoją rolę „po staremu”, jednak w wypadku bitu 4 równego 0, bit 1 wraz z bitami 7,6,2,3,5 dawał możliwość wyboru 64 banków (+1 MB) w rozszerzeniu 1088kB RAMBO lub 32 banków (+512kB) z rozszerzeniem 576kB Compy Shop.

To teoria, a teraz prosty przykład w Action! Program zapisuje 10 bajtów z numerem banku do 10-ciu kolejnych banków w Rambo 320K.

; 320K Rambo
; 7 6 5 4 3 2 1 0  bit no
;
; 1 0 0 0 0 0 1 1  : 131
; 1 0 0 0 0 1 1 1  : 135
; 1 0 0 0 1 0 1 1  : 139
; 1 0 0 0 1 1 1 1  : 143
; 1 0 1 0 0 0 1 1  : 163
; 1 0 1 0 0 1 1 1  : 167
; 1 0 1 0 1 0 1 1  : 171
; 1 0 1 0 1 1 1 1  : 175
; 1 1 0 0 0 0 1 1  : 195
; 1 1 0 0 1 0 1 1  : 203

; bits 2356 define bank no
; bit 4 allows CPU and ANTIC access

include "H1:RUNTIME.ACT"
proc main()

byte array pb = [131 135 139 143 163
167 171 175 195 203]

card portb=54017
int i,j
byte pointer ptr

for i=0 to 9 do
 portb=pb(i)
 ptr=$4000
 for j=0 to 9 do
  ptr^=i
  ptr==+1
 od
od

for i=0 to 9 do
 portb=pb(i)
 ptr=$4000
 printf("Bank %I%E", i)
 for j=0 to 9 do
  printf("%I", ptr^)
  ptr==+1
 od
printe()
od
do od
return

I jeszcze analogiczny przykład dla 576K Compy Shop (ten typ rozszerzenia pozwala na niezależne ustawienie dostępu dla CPU i ANTIC (w U1MB jest zarówno 320K RAMBO jak i 576K Compy Shop).

; 576K Compy shop
;
; 7 6 5 4 3 2 1 0  bit no
;
; 0 0 0 0 0 0 0 1  : 1
; 0 0 0 0 0 0 1 1  : 3
; 0 0 0 0 0 1 0 1  : 5
; 0 0 0 0 0 1 1 1  : 7
; 0 0 0 0 1 0 0 1  : 9
; 0 0 0 0 1 0 1 1  : 11
; 0 0 0 0 1 1 0 1  : 13
; 0 0 0 0 1 1 1 1  : 15
; 0 1 0 0 0 0 0 1  : 65
; 0 1 0 0 0 0 1 1  : 67

; 76321 define bank no, bits 4 and 5 control
; CPU's and ANTIC's independent access

include "H1:RUNTIME.ACT"
proc main()

byte array pb = [1 3 5 7 9 11 13 15 65 67]

card portb=54017
int i,j
byte pointer ptr

for i=0 to 9 do
 portb=pb(i)
 ptr=$4000
 for j=0 to 9 do
 ptr^=i
 ptr==+1
 od
od

for i=0 to 9 do
 portb=pb(i)
 ptr=$4000
 printf("Bank %I%E", i)
 for j=0 to 9 do
  printf("%I", ptr^)
  ptr==+1
 od
 printe()
od

do od
return

A tutaj mój wątek na AtariAge. Może komuś angielskojęzycznemu się przyda.

Action! – Mixed display list.

I jeszcze jeden przykład mieszanych trybów na podstawie „Atari Graphics and Arcade Games Design”:

include "H1:RUNTIME.ACT"
proc main()
card dlist = 560
card savmsc = 88
byte dindex = 87
byte array dltb = [112 112 112 112 74 160
155 10 10 10 10 10 10 10 10 10 10 10
10 10 10 10 71 224 156 72 244 156
8 8 8 8 8 8 8 8 8 8 8 8 8 65 104 155]
graphics(5+16)
dlist=dltb
savmsc=39840
color=3 plot(10,0) drawto(60,15)
savmsc=40180
dindex=3
color=2 plot(19,0) drawto(0,8)
savmsc=40160
dindex=2
position(0,0)
printd(6," graphics 2 test")
do od
return

Atari Action! Displaylisty, wskaźniki i inne.

Moje nocne eksperymenty z Atari Action! mają się całkiem nieźle. Jak dotąd najlepsza zabawa polega na przepisywaniu przykładowego kodu w Basicu na Action. Dość często wartościowe pod względem merytorycznym książki mają przykłady tylko w Atari Basic. Ostatnio przy okazji eksperymentów z display listami naciąłem się na pewne zjawisko:

1. Zdefiniowane w display liście adresy grafiki i tekstu przy kompilacji z RUNTIME.ACT (binarka ma działać bez cartridge) muszą być jawnie podane Antic-owi:

Ten przykład w Basic:

20 GRAPHICS 3
30 DLIST=PEEK(560)+PEEK(561)*256
35 REM MODIFY DISPLAY LIST
40 FOR 1=0 TO 2:POKE DLIST+9+I,PEEK(DLIST+3+I):NEXT I
50 FOR 1=0 TO 5:POKE DLIST+3+I,PEEK(DLIST+25+I):NEXT I
60 FOR 1=0 TO 5:POKE DLIST+25+I,8:NEXT I
70 REM PLOT GR. 3
80 PRINT "HI TEXT IS UP HERE"
90 SETCOLOR 0,12,4:REM SET COLOR 1 TO GREEN
100 SETCOLOR 2,4,6:REM SET COLOR 3 TO PINK
110 COLOR l:PLOT O,O:DRAWTO 10,10
120 COLOR 3:PLOT 12,12:DRAI.JT0 19,0
1000 GOTO 1000 

Po małych modyfikacjach samej display listy (tylko jedna linia tekstu na górze, a dlist definiowany w tablicy, w całości) w Action! wygląda tak:

include "H1:RUNTIME.ACT"
proc main()
card savmsc=88
card textaddr=660
byte array dltb=[ 112 112 112 66 96
159 72 112 158 8
8 8 8 8 8 8 8 8
8 8 8 8 8 8 8 8
8 8 8 8 65 78 158 ]
card dlist=560
graphics(3)
dlist=dltb ; piękne, prawda?
textaddr=40800
printe("Action!")
savmsc=40560
setcolor(2,4,6)
color=1 plot(0,0) drawto(10,10)
color=3 plot(12,12) drawto(19,0)
do od
return
Jak wyżej wspomniałem: jeśli display list definiuje adresy pamięci obrazu i tekstu, trzeba je podać Antic-owi w programie w przeciwnym razie fukcje print(), printe(), printf(), plot() czy drawto() nie będą działać standalone. W przypadku uruchomienia z cartem musi to się odbywać jakoś inaczej, ale w jaki sposób? Czy jakieś procedury analizują dlist? Tego się chyba nie dowiemy.
card savmsc=88
card textaddr=660
Powyższe definicje bardzo mi się w Action! spodobały, jest to zresztą cecha Action! – zmienna może być umieszczona pod konkretnym adresem w pamięci (w tym przypadku są to rejestry które pokazują Anticowi gdzie jest pamięć ekranu i tekstu). Co więcej, typ card pozwala na przypisanie wartości w jednej instrukcji przypisania (bez stosowania notacji LSB, MSB).
Po takiej definicji, wystarczy przypisać tym dwóm adresom właściwe wartości:
graphics(3)
dlist=dltb ; ustawiamy pod adresem 560 adres tablicy, w której zdefiniowany jest nowy display list.
textaddr=40800 ; 159*256+96 tutaj ustawiamy adres pamięci tekstu na ekranie wg display list.
printe("Action!")
savmsc=40560 ; 158*256+112 a tu adres grafiki

2. Wskaźniki.

Kolejną ciekawostką są w Action! wskaźniki. Wydawały mi się idealne do zmiany dlisty zamiast peeków i poków, jednak zauważyłem pewien problem: Jeśli wskaźnik zdefiniuje się w ten sposób:
CARD DL=560
BYTE POINTER DLP
DLP=DL
to działa po kompilacji z RT.
BYTE POINTER DLP = DL
przypisuje wskaźnikowi adres, a nie wartość pod adresem, więc dostajemy nie to, o co nam chodzi.
 BYTE POINTER DLP = DL^
działa z cartem, nie działa standalone (wyświetlają się zera).
Wbrew pozorom, takie eksperymenty zabierają sporo czasu, ale mam cały czas nadzieję, że pewne braki dokumentacji czy błędy w Action! da się ogarnąć, jak wyżej. Przy okazji eksperymentów znalazłem procedury w asm do przetestowania pod kątem szybszej obsługi grafiki, ale do tego brak mi jeszcze podstawowej wiedzy, więc będę sobie dalej czytał mądre książki znalezione na pigwie i przerabiał podane w nich przykłady…