Programování pro CP/M-68K
Operační systém CP/M-68K poskytuje běžícím programům sadu služeb zvanou BDOS (Basic Disk Operating System). Díky nim program v jazyce C nemusí znát fyzické detaily hardwaru (např. na kterém pinu mikrokontroléru PIC je sériová linka). Místo toho program požádá CP/M o standardizovanou službu – například “vypiš znak” nebo “přečti klávesu”.
Na procesorech řady Motorola 68000 se tyto služby volají softwarovým
přerušením pomocí instrukce trap #2.
Předávání parametrů
Když chceš zavolat funkci BDOS z assembleru, CP/M-68K očekává následující konvenci:
-
Registr
d0(spodní word): Číslo funkce BDOS (např. 2 pro výpis znaku, 9 pro výpis řetězce). -
Registr
d1(neboa0pro adresy): Parametr funkce (např. jaký znak vypsat, nebo adresa řetězce v paměti). -
Instrukce
trap #2: Předání řízení operačnímu systému. -
Registr
d0: Po návratu ztrap #2obsahujed0výsledek operace (např. přečtený znak nebo návratový kód).
Seznam nejdůležitějších funkcí BDOS
Zde je přehled základních funkcí, které se ti budou při programování retro her a utilit na 68k-MBC nejvíce hodit:
Číslo v d0 |
Název funkce | Popis a parametry |
| 0 | System Reset | Ukončí program a vrátí uživatele do příkazové řádky (A>). |
| 1 | Console Input | Čeká na stisk klávesy a její znak zobrazí na obrazovce (echo). Výsledek v d0. |
| 2 | Console Output | Vypíše znak zadaný v registru d1 na terminál. |
| 6 | Direct Console I/O | Univerzální funkce. Pokud je v d1 hodnota 255 (0xFF), pokusí se přečíst klávesu bez zobrazení (no echo). Pokud klávesa není stisknuta, vrátí v d0 nulu. Vhodné pro neblokující čtení. |
| 9 | Print String | Vypíše na obrazovku textový řetězec. V registru d1 (nebo a0) musí být adresa řetězce ukončeného znakem dolaru ($). |
| 11 | Console Status | Zkontroluje buffer klávesnice. Pokud je klávesa připravena, vrátí 0xFF v d0. Pokud ne, vrátí 0x00. |
Příklad 1: Neblokující čtení klávesnice
Protože staré kompilátory jazyka C (jako Alcyon C) často nepodporují snadné vkládání assembleru přímo do C kódu (inline assembly), řeší se systémová volání tak, že se napíše malá funkce v assembleru, která se v C pouze deklaruje a následně s ním slinkuje.
Klíčové pravidlo: Funkce volané z jazyka C musí mít v assembleru na
začátku názvu podtržítko (např. _getch_noblock).
1. Kód v Assembleru (KEY.S)
Zde je onen brilantní a optimalizovaný kód využívající BDOS funkci 11 (Status) a 6 (Direct I/O):
.text
.globl _getch_noblock ; Export pro linker (C kompilátor přidává podtržítka)
_getch_noblock:
; Zjištění stavu klávesnice (BDOS 11)
move.w #11,d0
trap #2
tst.w d0 ; Je klávesa stisknuta?
beq.s L_empty ; Pokud ne (d0 == 0), skoč na L_empty
; Přečtení znaku bez echa (BDOS 6)
move.w #6,d0
move.l #255,d1 ; 255 znamená "čti" (bez blokování a zobrazení)
trap #2
andi.l #255,d0 ; Ořízni případné smetí, ponech jen 8 bitů (ASCII)
rts ; Návrat do C (výsledek je v d0)
L_empty:
moveq #0,d0 ; Vrať 0 (žádný znak)
rts
.end
2. Použití v jazyce C (K&R Standard)
Historický dialekt C (tzv. K&R) před příchodem ANSI C (C89) neznal
klíčové slovo void, návratový typ byl implicitně int a do prototypů
funkcí se nepsaly datové typy parametrů.
/* Soubor: MAIN.C */
/* Deklarace naší funkce z KEY.S */
int getch_noblock();
main()
{
int c;
printf("Stiskni klavesu 'q' pro ukonceni...\n");
while(1) {
c = getch_noblock(); /* Volame nas ASM stub */
if (c != 0) {
printf("Stisknuto: %c (ASCII: %d)\n", c, c);
if (c == 'q' || c == 'Q') {
break;
}
}
/* Zde muze bezet herni smycka (pohyb ufounu, hada, atd.) */
}
return 0;
}
Příklad 2: Vlastní výpis řetězce
Který se obejde bez těžkopádné funkce printf.
Procesor MC68008 na 68k-MBC je při parsování funkce printf docela
vytížený (zabírá cenné takty). Pokud chceme vypsat čistý text (např.
ANSI sekvenci) co nejrychleji, můžeme obalit BDOS funkci 9 (Print
String).
1. Kód v Assembleru (PRINT.S)
.text
.globl _print_cpm
_print_cpm:
; Starý K&R C předává parametry přes zásobník.
; Návratová adresa je na 4(sp), první parametr (adresa textu) je na 8(sp).
move.l 8(sp), d1 ; Načti adresu řetězce do d1 pro BDOS
move.w #9, d0 ; BDOS funkce 9 (Print String)
trap #2
rts
.end
2. Použití v jazyce C
U BDOS funkce 9 musíme pamatovat na to, že CP/M ukončuje řetězce znakem
$, nikoliv nulou \0 jako jazyk C!
/* Deklarace nasi ASM funkce */
int print_cpm();
main()
{
/* Retezec musi byt ukonceny znakem dolaru! */
char *clear_screen = "\033[2J\033[1;1H$";
char *message = "Tento text se vypsal bleskove pres BDOS volani!$";
/* Rychle smazani obrazovky a presun kurzoru */
print_cpm(clear_screen);
/* Vypsani hlašky */
print_cpm(message);
return 0;
}
Jak se to celé překládá na 68k-MBC?
Když píšešeme programy na reálném stroji nebo v historickém toolchainu přímo pro operační systém CP/M-68K, proces probíhá následovně:
- Překlad C kódu (Alcyon C): Kompilátor vezme
MAIN.Ca přeloží ho do objektového souboru. Zpracování zaštiťuje dávkový příkaz (přes preprocesor, C kompilátor a optimalizátor).
- Překlad Assembleru:
Systémový assembler převede .s do objektových kódů.
A> AS68 KEY.S A> AS68 PRINT.S
- Slinkování (CLINK):
Linker spojí objektový soubor Céčka, tvoje zkompilované ASM knihovny a
systémovou céčkovou knihovnu (clib) do spustitelného souboru s příponou
.68K.
A> CLINK MAIN KEY PRINT
- Spuštění:
Na obrazovce napíšeš jen jméno vzniklého programu:
A> MAIN
Parametry z příkazové řádky
Aby program v C mohl přijímat parametry (argumenty) z příkazové řádky,
stačí lehce upravit definici hlavní funkce main. Místo prázdných
závorek přidáme dvě speciální proměnné: tradičně se nazývají argc a
argv.
Základní syntaxe
int main(int argc, char *argv[]) {
// tvůj kód
return 0;
}
Co tyto proměnné znamenají?
argc (Argument Count): Celočíselná proměnná, která systému říká,
kolik parametrů bylo programu předáno.
argv (Argument Vector): Pole textových řetězců (ukazatelů na
znaky), které obsahuje samotné předané parametry.
Důležité pravidlo: První parametr, tedy argv[0], je vždy název
samotného spuštěného programu (nebo cesta k němu). Skutečné argumenty,
které uživatel zadá, začínají až od indexu argv[1]. Proto je hodnota
argc vždy minimálně 1, i když programu nepředáš žádný dodatečný
parametr.
Příklad použití
Tady je jednoduchý kód, který projde všechny předané parametry a vypíše je na obrazovku:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Celkovy pocet parametru: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("Parametr %d: %s\n", i, argv[i]);
}
return 0;
}
Jak to vypadá po spuštění v terminálu:
Pokud tento program zkompilujeme (např. jako program) a spustíme s
parametry: ./program test 123
Výstup bude: Celkovy pocet parametru: 3 Parametr 0: ./program
Parametr 1: test Parametr 2: 123
Převod textu na čísla
Je důležité pamatovat na to, že všechno v poli argv je text
(string). I když do příkazové řádky napíšeme číslo 123, program ho
vidí jako text "123".
Pokud chceme parametr použít k matematickým výpočtům, musíme ho převést
pomocí vestavěných funkcí z knihovny <stdlib.h>:
atoi()(ASCII to Integer) – pro převod na celé číslo.
atof()(ASCII to Float) – pro převod na desetinné číslo.
Příklad převodu:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc > 1) {
int cislo = atoi(argv[1]);
printf("Dvojnasobek zadaneho cisla je: %d\n", cislo * 2);
}
return 0;
}