♫ ♩ ♪ TSA ⚡ ☘ ⚡ Trzy zapałki ♪ ♩ ♫
https://youtu.be/OT-wW06LCqY
Temat komputerka CA80 jest u mnie teraz bardzo aktualny, a wyśmienicie działająca `Ładowarka Do Pamięci` sprawia, że w końcu mogę sobie używać i klecić programy dłuższe niż kilkaset bajtów, nie dostając mdłości na myśl o tysiącach kliknięć zleceniem *D. Translator SB-Assembler jest narzędziem całkiem OK, ale skoro już tyle napisano o SDCC, to może czas wykorzystać tę wiedzę w jakiś konkretny sposób.
Poniższa opowiastka powstała na podstawie lektury wypracowania Kompilator języka C dla Z80 , które popełnił gaweł w zeszłe,zeszłe wakacje. Zacznę od tego, że we wspomnianym artykule o SDCC znajdują się wszelkie konieczne do zabawy informacje i chcąc sensownie pożenić CA80 z pakietem SDCC należy przez owe przebrnąć i tu nie ma zlituj. Dodatkowe detale wyczytałam sobie z SDCC - Interfacing with Z80 assembler code , choć zanim osiągnęłam w miarę zadowalające rezultaty odbyła się seria skoków na bungee z analizą assemblerowych listingów w tle. A zatem...
Komputerek CA80 nie jest trywialny do melanżu z SDCC z kilku powodów. Pierwszy i podstawowy to fakt, że nasz program będzie uruchamiał się w dedykowanych dla aplikacji użytkownika lokalizacjach, w jednym z banków obsadzonych RAM (od 0x4000, 0x8000 lub 0xC000), to automatycznie oznacza konieczność podpowiedzenia linkerowi jak uplasować kod i dane. Po drugie - zupełna rezygnacja z kodu rozruchowego (crt0) jak proponują niektóre źródła oznacza jazdę ze stosem (czy raczej brakiem jego inicjacji na potrzeby CA) oraz konieczność własnoręcznego zadbania o wstępne poustawianie wartości zmiennych. O kodzie rozruchowym gaweł sporo napisał, więc aby się nie powtarzać - poniżej moja bojowa wersja:
crt0ca80_template.s pisze:Kod: Zaznacz cały
.module crt0
.globl _main
.area _HEADER (ABS)
;; Reset vector
.org __CA80_CODE_BASE__
init:
ld sp,#0xFF66 ; CA80 typical
call gsinit
call _main
1$: jp 1$
.area _HOME
.area _CODE
.area _INITIALIZER
.area _GSINIT
.area _GSFINAL
.area _DATA
.area _INITIALIZED
.area _BSEG
.area _BSS
.area _HEAP
.area _GSINIT
gsinit::
ld bc, #l__INITIALIZER
ld a, b
or a, c
jr Z, gsinit_next
ld de, #s__INITIALIZED
ld hl, #s__INITIALIZER
ldir
gsinit_next:
.area _GSFINAL
ret
I tu pierwsza modernizacja względem Andrzejowego tutoriala - to jest plik-szablon. Właściwy plik do assemblacji powstaje w locie, na skutek wykonania skryptu, który przy okazji zamienia mi symbol `__CA80_CODE_BASE__` na ustawioną zawczasu wartość liczbową. Mam tym prostym sposobem bootstrap ruszający od 4000, od 8000 czy C000, zależnie od kaprysu.
W tutorialu wspominano o problemach z assemblacją kodu rozruchowego, no fakt - sdasz80 płacze komunikatem Error: <u> undefined symbol encountered during assembly bo nie wie co to #l__INITIALIZER i reszta. Znacząco pomaga mu dodanie opcji `-g` Undefined symbols made global. Całość preparująca własny rozrusznik wygląda tak:
Kod: Zaznacz cały
CODE_ORG="0x8000"
cat crt0ca80_template.s | sed -e "s/__CA80_CODE_BASE__/$CODE_ORG/" > crt0ca80.s
sdasz80 -g -l -o crt0ca80.rel crt0ca80.s
Tym oto prostym sposobem mam starter naturalnie osadzony od adresu 8000, z kodem inicjującym zmienne globalne w RAM (od wskazanego później adresu), przy okazji kod rozruchowy wyczyściłam ze zbędnych zaślepek na programowe przerwania RSTxx, bo to przecież mam w systemowym EPROM.
A teraz programik w C, jak łatwo zgadnąć będzie to migadełko do ledów i to symulowanych wyświetlaczem z WaveForms i pudełkiem AD2. Program robi co następuje: z nieskończonej pętli wysyła na port PA układu 8255 wybrany z tabeli wzorek, wartość wzorka oraz jego adres (tzn. adres bieżącego elementu tabeli) pokazuje na systemowym wyświetlaczu i przy pomocy opakowanych w C procedur systemowych z EPROM-u MIK, no właśnie.
Z takich sympatyczniejszych kawałków - przepisane kiedyś z sieci makro BIN(), przydaje się do definiowania stałych-wzorców (np. fontów), aby były czytelne dla człowieka:
simple_leds_1.c pisze:Kod: Zaznacz cały
define BIN(x) \
( ((0x##x##L & 0x00000001L) ? 0x01 : 0) \
| ((0x##x##L & 0x00000010L) ? 0x02 : 0) \
| ((0x##x##L & 0x00000100L) ? 0x04 : 0) \
| ((0x##x##L & 0x00001000L) ? 0x08 : 0) \
| ((0x##x##L & 0x00010000L) ? 0x10 : 0) \
| ((0x##x##L & 0x00100000L) ? 0x20 : 0) \
| ((0x##x##L & 0x01000000L) ? 0x40 : 0) \
| ((0x##x##L & 0x10000000L) ? 0x80 : 0))
Jego wykorzystanie:
simple_leds_1.c pisze:Kod: Zaznacz cały
/*const*/ unsigned char ledPattern[ 8 ] = {
BIN( 00000001 ),
BIN( 00000010 ),
BIN( 00000100 ),
BIN( 00001000 ),
BIN( 00010000 ),
BIN( 00100000 ),
BIN( 01000000 ),
BIN( 10000000 )
};
O `const` w komentarzu powiem dalej.
Jeden z wrapperów na systemową procedurę, tu LADR - pokazuje zawartość HL w/g zadanej pozycji wyświetlacza.
simple_leds_1.c pisze:Kod: Zaznacz cały
void mikLADR( unsigned char pwys, unsigned short value ) __naked {
__asm
ld IY,#2
add IY,SP
ld A,0(IY) ; mamy PWYS
;
ld HL,#0xFFF6 ; APWYS
ld (HL),A ; ustaw!
;
ld L,1(IY) ;value (low)
ld H,2(IY) ;value (high)
;
call 0x0021 ; systemowa LADR1
;
ret
__endasm;
}
I tu może spostrzegawcze oko (co zerkało na pisaninę o CA80 na bienata) zauważy - wołam nie LADR a LADR1, co oznacza, że parametr PWYS musi zostać ustalony osobno, przed wywołaniem procedury systemowej. Tak więc zostaje on wprowadzony jako pierwszy do funkcji-wrappera. Parametry SDCC przekazuje przez stos, przed wywołaniem funkcji warstwa wyższa upycha kolanem wartości parametrów na stosie, wewnątrz funkcji trzeba to sobie pozbierać i to tak aby nie naruszyć adresu powrotu, który też na stosie leży. I stąd te wygibasy z rejestrem indeksowym IY, przy okazji musimy polubić taki dziwny zapis np.`LD A,9(IY)` zamiast typowo spotykanego `LD A,(IY+9)`. Ja choć to formalnie zbędne dopisuje sobie dla zerowego offsetu właśnie 0 (widać przy PWYS), mam na otarcie łez przynajmniej jakąś konsekwencję w tych dziwnych numerkach. Magiczny parametr PWYS, o którym napisano kilka kartek w MIK05 ubrałam sobie w makro:
simple_leds_1.c pisze:Kod: Zaznacz cały
#define PWYS(digitsused/*8-1*/,startfrom/*7-0*/) ((digitsused<<4)|startfrom)
I dzięki temu już mi się nie merda, który nibble tego parametru jest do czego i jakie ma zalecane wartości.
Pętla główna tego prostego demka wygląda następująco:
simple_leds_1.c pisze:Kod: Zaznacz cały
void main( void ) {
unsigned char cntr = 0;
USER8255_CTRL = 0x80; // same wyjscia
while ( 1 ) {
for ( cntr = 0; cntr < sizeof( ledPattern ); cntr++ ) {
USER8255_PA = ledPattern[ cntr ]; // wzorek na wyjścia
mikCLR( PWYS(8,0) ); // skasuj wyswietlacz
mikLBYTE( PWYS(2,6), ledPattern[ cntr ] ); // pokaz daną (lewo)
mikLADR( PWYS(4,0), (unsigned short)&ledPattern[ cntr ] ); // i adres (prawo)
delay();
}
}
}
Wywołania systemowe są chyba dobrze wyeksponowane, ale odnośnie nazw to mam rozterki - mikLADR() czy może sysLADR(), a może sys_LADR() a może inaczej, powinny przecież nawiązywać do literałów, którymi operują MIK-i. Póki co opakowałam pisanie po wyświetlaczu, kolejne atrakcje to będą funkcje czytające klawiaturę i pobierające parametry. Podobnie z identyfikacją peryferiów w CA80, choćby tych podstawowych CTC i 8255...
Do kompilacji i linkowania prosty skrypcik, jego pierwsze linie już pokazywałam, tu dalej:
Kod: Zaznacz cały
CODE_LOC="0x8010"
DATA_LOC="0xC000"
sdcc --verbose -mz80 -c simple_leds_1.c
sdcc --verbose -mz80 --code-loc ${CODE_LOC} --data-loc ${DATA_LOC} --no-std-crt0 -o simple_leds_1.ihx crt0ca80.rel simple_leds_1.rel
Cóż więcej, aha - ten `const` przy deklaracji wzorka:
Kod: Zaznacz cały
const unsigned char ledPattern [8]
Powyższa deklaracja spowoduje, że wzorek będzie pobierany z segmentu programu, na filmiku będzie widać zmieniające się adresy 80xx.
Kod: Zaznacz cały
unsigned char ledPattern [8]
Bez const-a, jak wyżej, będzie to zwykła zmienna globalna w RAM, w segmencie danych wskazanym adresem C000, zawartość na podstawie wzorca z segmentu kodu ustali gsinit podczas rozruchu aplikacji i to też można zobaczyć na wyświetlaczu po rekompilacji programiku (adresy C0xx).
W podsumowaniu - na CA80 da się klecić w C, choć wymaga to zapoznania się ze specyfiką SDCC, szczęściem jest z czego się uczyć. W nieco dalszej perspektywie zostaje dobranie się do przerwań NMI oraz wektorowego systemu przerwań maskowalnych w trybie IM2, no tu może być całkiem ciekawie.
Na dobranoc filmik:
https://youtu.be/7aGTgmdL3xo
#slowanawiatr