VBXE + Action! Animowane sprite’y.

Choć w zasadzie powinienem napisać jeden sprite 🙂

set 14=$8000
set $491=$8000
include "H1:RUNTIME.ACT"

MODULE
int fno=[1], i=[0]
byte bltst=$D653
byte bltmv0=$A115, bltmv1=$A116, bltmv2=$A117

byte array bltsrc =
   [ 0 96 1
   128 96 1
     0 97 1
   128 97 1 ]

proc CIOVE=$E4DF(byte areg, xreg)

proc delay()
 card i
 for i=0 to 30000 do od
 return

proc bltme()
 if fno mod 10 = 0 then
  bltmv0=bltsrc(i*3)
  bltmv1=bltsrc(i*3+1)
  bltmv2=bltsrc(i*3+2)
  bltst=1 fno==+1 i==+1
  if i > 3 then
   fno=1 i=0
  fi
 [$4C $E462]
 fi
 fno==+1
[$4C $E462]

proc main()
 byte vc=$D640, clock=$14
 int i byte memcont=$D65E, membank=$D65F
 byte psel=$D645, csel=$D644, cr=$D646, cg=$D647, cb=$D648
 byte xdla0=$D641, xdla1=$D642, xdla2=$D643
 byte blt0=$D650, blt1=$D651, blt2=$D652 ; bltst=$D653
 byte bkg=710, curinh=752
 byte iocb1cmd=850
 card iocb1buf=852, iocb1len=856
 byte nmien=$D40E
 card vvblkd=$0224

 byte array xdl=[98 136 219 0 0 0 64 1 17 223]

 byte array clrscr=
   [ 0 0 0     ;src addr
       0 0     ;src step y
         0     ;src step x
     0 0 0     ;dst addr ($0000 up)
      64 1     ;dst step y (320)
         1     ;dst step x
      63 1     ;width (320-1)
       239     ;height (240-1)
         0     ;and mask
         0     ;xor mask
         0     ;collision and mask
         0     ;zoom
         0     ;pattern
         0 ]   ;control

 byte array bltmv=
  [ 0 96 1     ;src addr
       0 2     ;src step y
         1     ;src step x
  224 62 0     ;dst addr
      64 1     ;dst step y
         1     ;dst step x
     124 0     ;width
        53     ;height
       255     ;and mask
         0     ;xor mask
         0     ;collision and mask
         0     ;zoom
         0     ;pattern
         0 ]   ;control

 if vc<>$10 then
  printe("Brak VBXE FX")
  delay()
  [$4C $C2AA] ;reset
 fi

 memcont=$A8
 membank=128+20
 moveblock($A000, xdl, 10)
 xdla0=0 xdla1=64 xdla2=1

 moveblock($A100, clrscr, 21)
 blt0=0 blt1=65 blt2=1
 bltst=1 ;blitter's clear scr
 while bltst <> 0 do od
 moveblock($A115, bltmv, 21)
 blt0=21 blt1=65 blt2=1

 graphics(0)
 bkg=0 curinh=1 put(31)

 close(1)
 open(1,"D1:HEL.PAL",4,0)
 psel=1 csel=0

 for i=0 to 255 do
  cr=getd(1) cg=getd(1) cb=getd(1)
 od
 close(1)

 open(1,"D1:HEL.PIC",4,0)
 iocb1cmd=7
 iocb1buf=$A000
 iocb1len=$1000

 for i=22 to 29 do
  membank=128+i
  CIOVE(0,$10)
 od

 close(1)
 vc=3
 membank=128+20

 i=clock while clock=i do od
 nmien=0
 vvblkd=bltme
 nmien=$40
 do od
return

VBI finescroll + DLI rainbow (ASM)

Od piątku leżę w szpitalnym łóżku (spokojnie, to tylko kolejne badania moich szwankujących mięśni) i pomimo tego, że sobotę i niedzielę spędziłem z dzieciakami w domu (przypomina mi się seria „Wielkie ucieczki” na TVN), trochę mi się nudzi. Dzisiaj mnie biopsnęli w nogę, więc może trochę pod wpływem anestetyków postanowiłem dokończyć mój ulubiony (ad nauseam) scroll poziomy w VBI i „rainbow effect” w DLI, tym razem w assemblerze i tym razem w lewo.

hscrol = 54276
sdmctl = $022F
dlist = 560
nmien = $D40E
vvblkd = $0224
xitvbv = $E462
scount = $8000
colt = $D017
indx = $8002
wsync = $D40A
vdslist = $0200

      org $4000

init  ldy #0
      sty sdmctl                                ; wyłącz antic
      lda <ndl                                  ; ustaw adres tablicy ndl jako dliste
      sta dlist
      lda >ndl
      sta dlist+1
      lda #16
      sta scount                                ; wyzeruj liczniki
      sty chrno
      sty indx
      lda #64
      sta 88
      lda #156
      sta 89

      ; vbi+dli
      lda <scroll                               ; ustaw scroll w opóźnionym vblank interrupt
      sta vvblkd
      lda >scroll
      sta vvblkd+1
      lda <dli                                  ; ustaw adres procedury dli
      sta vdslist
      lda >dli
      sta vdslist+1
      lda #42
      sta sdmctl                                ; włącz antic
      lda #$c0                                  ; włącz przerwania dli
      sta nmien
      ; kolory i napisy
      lda #0
      sta 710
      posxy #2, #5
      putline #txt1
      lda #50
      sta 88
      lda #155
      sta 89
      posxy #0, #0
      putline #txt2

loop  jmp loop

scroll ldy scount
       dey
       sty hscrol
       beq @+
       sty scount
       jmp xitvbv
@      ldy chrno
       iny
       cpy #30
       bne chmem
       lda #25
       ldx #0
       stx chrno
       sta ndl[28],x
       lda #155
       sta ndl[29],x
       ldx #16
       stx scount
       jmp xitvbv
chmem
       clc
       sty chrno
       ldx #0
       lda ndl[28],x
       adc #2
       sta ndl[28],x
       lda ndl[29],x
       adc #0
       sta ndl[29],x
       ldy #15
       sty hscrol
       iny
       sty scount
       jmp xitvbv

dli    pha
       txa
       pha
       tya
       pha
       inc indx
       ldx #7
@      lda #1
       sta wsync
       lda indx
       cmp #30
       beq @+
       dex
       cpx #0
       beq ret
       clc
       adc vcount
       sta colt
       jmp @-
@      ldy #0
       sty indx
       jmp @-1
ret    pla
       tay
       pla
       tax
       pla
       rti

scount .by 0
chrno .by 0

.array ndl [33] .byte
 112, 112, 112, 66, 64, 156, 2, 2, 2, 2, 6, 2, 2
 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 130, 86, 30, 155
 65, 32, 156
.enda

txt1  .by 'test finescroll!' $9b
txt2  .by 'mads compiler!' $9b

.link 'putline.obx'
.link 'posxy.obx'

a2

Powyższy przykład przesuwa druga z linii w trybie Antic 6/GR. 1, ale sporo zabawy zajęło mi uzyskanie prawidłowego finescroll w Antic 2 / GR. 0. Z tego co udało mi się wyeksperymentować, w trybie GR. 0 jeden piksel potrzebuje pół cyklu koloru, skoro tych pikseli jest 8, do przesunięcia jednego znaku potrzeba 4 zmiany rejestru finescroll (1 cykl koloru = 1 finescroll). Analogicznie w trybie GR. 1 / Antic 6, jeden piksel potrzebuje jeden cykl koloru, znak ma 8 pikseli szerokości, więc mamy finescroll o 8 per znak.

Warto wspomnieć o jeszcze jednym zjawisku. W przykładach w Action! i assemblerze scroll działał prawidłowo, ale tylko w prawo i tylko o 16 punktów. Wydawało mi się to dziwne i długo nie dawało spokoju dlaczego tak się dzieje, że przy finescroll o 8 widać coś jakby szarpnięcie o jeden znak. Zjawisko brało się stąd, że zmieniałem adres pamięci w display liście i wychodziłem z procedury, a zmiana rejestru hscroll na odpowiadający nowemu położeniu znaków na ekranie, następowała dopiero przy następnym vblank interrupt (czyli hscroll był nadal 0, a ANTIC przez chwilę do następnego vbi wyświetlał znak przesunięty po zmianie adresu). Zmiana adresu pamięci i jednoczesne ustawienie hscroll przed jmp xitvbv usunęło problem i wszystko działa tak jak w książce…

Reasumując, Mads jest moim ulubionym cross-assemblerem i pewne jego cechy i funkcje bardzo mnie zachęcają do dalszych eksperymentów, jednak coraz większy widzę sens w programowaniu w Action! Przede wszystkim czytelność kodu jest o wiele większa i na pierdoły nie traci się wiele czasu (tutaj ten sam efekt w Action!). Ciekaw jestem jak sprawdzi się Action! + VBXE.

PS: putline i posxy pochodzą z LIBRARIES/stdio/lib mads. Możnaby jeszcze wyzerować pamięć obrazu, ale… X /C w SDX zrobi to za mnie ;).

Action! Jedno RUNTIME, wiele modułów.

To już chyba ostatni wpis  z „linkowaniem” za pomocą type lub cat, który jednak pokaże do czego może się przydać Symbol Table Lister.  Załóżmy, że sklejamy kilka kawałków/modułów w Action! Fajnie byłoby aby Runtime było dołączane tylko raz – wymyśliłem sobie, że skoro STL pokaże mi adresy procedur w Runtime, to można zrobić tak:

  • Uruchomić w monitorze R: „H1:STL.ACT”
  • Otworzyć w edytorze Ctrl+Shift+R H1:RUNTIME.ACT i dodać np. set 14=$8000 set $491=$8000
  • Przejść do monitora Ctrl+Shift+M, skompilować C, zapisać W „H1:RUNTIME.OBX”

W LISTING.TXT (patrz poprzedni wpis o STL) znajdziemy adresy:

PrintE……… $822C PROC(BYTE ARRAY)
PrintF……… $8430 PROC(BYTE ARRAY, CARD, CARD, CARD, CARD, CARD)

Więc w naszym programie możemy zdefiniować:

set 14=$8739
set $491=$8739

proc printf=$8430(byte array a, card b, card c,
                  card d, card e, card f)
proc printe=$822C(byte array a)

proc main()
   printf("%S%E", "Test")
   printe("Runtime")
   do od

Kompilujemy i zapisujemy np. jako TESTRT.OBX
Potem sklejamy cat RUNTIME.OBX TESTRT.OBX > TESTRT.XEX

Dzięki temu każdy z dołączanych kawałków binarnych będzie mniejszy, a dodatkowo kompilacja będzie trwać krócej, bo RT kompilowane będzie tylko raz. Szkoda, że kompilatora uruchomionego w emulatorze nie da się odpalać w skrypcie. Można by do tego napisać makefile albo skrypt w bashu…

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.

EnvisionPCreborn

EnvisionPCreborn to świetny program do edycji zestawów znaków i map do gier. Jak dla mnie interfejs tego programu to po prostu rewelacja i chciałbym aby więcej programów dla Atari na PC powstawało z takimi założeniami dla GUI.

epc

Pomimo, że program od dawna nie był aktualizowany, kompiluje się pod Linuksem bez większych problemów po małej modyfikacji Makefile.linux: trzeba dodać undo.c do listy plików w SOURCE.

No i jeszcze jeden brzydki przykład w Action!

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

card chrom, chram, start
card charp=756
int i
byte cur=752
byte bkg=710

byte array chrs1 = [0 0 0 24 24 0 0 0
0 0 0 24 24 0 0 0
0 0 0 0 31 63 96 192
0 0 0 0 248 252 6 3]

byte array chrs2 = [0 0 0 0 192 127 63 0
0 0 0 0 3 254 252 0
0 0 0 24 24 0 0 0
0 0 0 24 24 0 0 0]

graphics(0)

bkg=0
cur=1

chrom=charp*256
chram=16384
moveblock(chram, chrom, 1024)
chram=16384+1024
moveblock(chram, chrom, 1024)
start=16384+776
moveblock(start, chrs1, 32)
start=16384+776+1024
moveblock(start, chrs2, 32)

charp=64

for i=1 to 7 do
 printe("ab ab ab ab ab ab ab ab ab")
 printe("cd cd cd cd cd cd cd cd cd")
 printe("")
od

do
 for i=0 to 15000 do od
 charp=68
 for i=0 to 15000 do od
 charp=64
od

return

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.