Das hier vorgestellte Listing zeigt Ihnen, wie Sie eine Uhr in der Sprache C selbst programmieren und die selbst überwachte Uhrzeit auf dem Bildschirm anzeigen können, ohne auf vorgefertigte Uhr- und Ausgabefunktionen zurückzugreifen. Neben anderen nützlichen Informationen erfahren Sie außerdem, wie eigene Funktionen in die VBL-Slots eingebunden und wie Systemvariablen benutzt werden.
Noch ‘ne Uhr, werden einige von Ihnen sagen, denn schon in der Ausgabe 10/86 der ST-Computer-Zeitschrift wurde eine Bildschirmuhr als Listing vorgestellt. Aber warum nicht? Denn wie sich auch Armbanduhren, Wanduhren und Digitaluhren von ihrem Innenleben her unterscheiden, so können sich auch Bildschirmuhren von Ihrer Funktionsweise her unterscheiden.
Daß das hier vorgestellte Listing nicht nur eine interessante Alternative im Hinblick auf die programmtechnische Realisierung einer vom eigenen Programm überwachten Uhrzeit und deren Darstellung auf dem Bildschirm zeigt, sondern auch gleichzeitig einen kleinen Einblick in die Benutzung von Systemvariablen und Interrupts ermöglicht und darüber hinaus auch ohne Schwierigkeiten in eigene Programme eingebunden werden kann, möchte ich Ihnen im folgenden erläutern.
Eine Möglichkeit, die Uhrzeit auf dem Bildschirm darzustellen, wurde schon in der Ausgabe 10/86 vorgestellt, wobei ein Desk-Accessory programmiert wurde, um die quasiparallele Abarbeitung der Uhranzeige zu gewährleisten.
Doch leider wurde der Programmteil zur Berechnung und Darstellung der Systemzeit mit in die evnt_multi()-Schleife eingebaut, was den Nachteil mit sich brachte, daß bei Diskettenzugriffen, die Zeit nicht mehr angezeigt wurde, und somit der ebenfalls programmierte Wecker nicht lief, und manchmal nach Beendigung des Schreib- oder Lesezugriffs die Zeitanzeige auf dem Bildschirm erst dann wieder funktionierte, wenn man das Desk-Accessory erneut mit der Maus aktivierte.
Der Anzeigetakt der dort realisierten Uhr konnte im geringsten Fall zwei Sekunden betragen, die Uhrzeit wurde nur bei Benutzung des Desktops angezeigt. Die Ursachen dieser Probleme liegen bei der Verwaltung der Accessories im Betriebssystem des ST.
Ich möchte mich an dieser Stelle aber nicht über die Accessoryverwaltung auslassen, das könnte vielleicht das Thema eines anderen Artikels werden, sondern nur soviel dazu sagen, daß die Accessories nicht in regelmäßigen Abständen aufgerufen werden und sind daher für die periodische Darstellung einer Uhrzeit nicht unbedingt geeignet.
Wo könnte dann aber eine Uhranzeige eingebunden werden, die die besagten Nachteile nicht mit sich bringt und die die Uhrzeit auch bei Diskettenzugriffen und auch in einem Kommandointerpreter, wie z.B. der DOS-Shell anzeigt?
Einem/einer aufmerksamen Leser/in der Rubriken ‘Software’ und ‘Programmier-Praxis' in der ST-Computer-Zeitschrift wird eine Antwort dazu nicht schwerfallen:
“Natürlich kann ich Funktionen als Interruptroutinen vom Betriebssystem aufrufen lassen, z.B. wenn ich sie in die Liste der VBL-Interrupt-Routinen einfüge!”
Gut, aber das ist, wenn überhaupt, nur die halbe Miete. Denn, wie bekomme ich mein Programm dazu, die Zeit in Sekundentakten anzuzeigen, wenn sogar die XBIOS-Funktion 23 namens ‘Gettime()’ nur alle zwei Sekunden eine veränderte Zeit ausgibt?
Nachdenken.... irgendwo muß es eine Variable geben, die in einem bestimmten Takt verändert wird, denn wie sollte das Betriebssystem sonst seine periodischen Aufgaben durchführen, wenn es nicht wüßte, wann eine Periode vorüber ist?
Also Suchen bei den Systemvariablen ... such, such, such - aha, da ist sie. _HZ_200 wird sie genannt, steht an der Speicherstelle $4ba und wird 200 Mal in der Sekunde um den Wert 1 erhöht. Also immer wenn _HZ_200 um 200 erhöht wurde, ist eine Sekunde vergangen und eine neue Zeitangabe sollte auf dem Bildschirm erscheinen.
Wir könnten nun zu Anfang des Programms, in der Funktion ‘main()’, einmal die Zeit mit einer üblichen Funktion wie ‘Gettime()\ ‘getclk()’ beim Lattice C-Compiler oder in einer anderen Sprache mit einer anderen Funktion holen, und diese nach jeder vergangenen Sekunde erhöhen. Damit wäre die selbstverwaltete Uhrzeit realisiert.
So ist es auch im Listing geschehen, welches übrigens mit dem Lattice C-Compiler V 3.04 geschrieben wurde. Die Ausgabe unserer internen Programmzeit auf den Bildschirm stellt uns vor ein weiteres, aber auch nicht unlösbares Problem.
Die GEM-Funktion v_gtext() kann nicht benutzt werden, woher soll das ‘handle’ kommen, welches sie als Parameter benötigt? Ein Versuch, in der Interruptroutine ein neues ‘handle’ mit der Funktion graf_handle() zu erhalten, endet mit Bombenhagel.
Auch die C-Standard-Bibliotheks-Funktion printf() verhält sich da nicht freundlicher. Abhilfe schafft eine eigene Ausgaberoutine, die uns die Ziffern der Systemzeit direkt in den Bildschirmspeicher schreibt.
Dazu benötigen wir noch die Bytes, aus denen die Ziffern zusammengestellt sind, im Listing ist das der Buffer ‘clfont[]’, in dem der Reihe nach die Ziffern ‘0’ bis ‘9’ und ein (Doppelpunkt) definiert sind. Eine Ziffer besteht hier aus jeweils 16 Bytes, die, untereinander auf dem Bildschirm ausgegeben, das entsprechende Bild der Ziffer ergeben. So, das Grundprinzip des Programms sollte nun klar sein, begeben wir uns also zum Listing.
Das Listing ist für den Lattice C-Compiler, Version 3.04, geschrieben und sollte sich ohne große Änderungen auch mit anderen C-Compilern compilieren lassen. Beachten Sie, daß die im Listing benutzten Funktionen ‘getclkO’ und ‘stch_i()’ der Lattice C-Bibliothek entnommen sind, und bei anderen Compilern wahrscheinlich andere Namen haben.
Zu Anfang werden, wie üblich, die Includedateien und Definitionen für den Präprozessor aufgeführt. Die Bedeutung der darauf folgenden Variablen entnehmen Sie bitte dem Listing.
Hier werden zunächst die an das Programm übergebenen Koordinaten geprüft und die y-Koordinate in Bildschirmzeilen umgerechnet. Dann wird die Adresse des Bildschirms geholt und die tatsächliche Ausgabestelle auf dem Bildschirm berechnet. Mit der Funktion getclk() aus der Lattice C-Bibliothek schreibt man die aktuelle Systemzeit in den Buffer ‘clock’. In ‘clock[4]’ befinden sich die Stunden, in ‘clock[5]’ die Minuten und in ‘clock[6]’ die Sekunden. Danach wird der Inhalt von _HZ_200 der Variablen ‘start’ zugeordnet, anschließend die Adresse der Funktion ‘ do_it()’ mit der Funktion 'to_vbl_list()’ in die Liste der Routinen eingefügt, die bei einem VBL (Vertical Blank) ausgeführt werden sollen.
‘to_vbl_list()’ holt sich dazu die Anzahl der auszuführenden Funktionen aus der Systemvariablen ‘NVBLS’ an der Adresse $454 und einen Zeiger auf die Liste dieser Funktionen aus der Systemvariablen ‘_VBLQUEUE’ mit der Adresse $4ce.
Vom Ende der Liste bis zu deren Anfang wird nach einem Wert ‘0L’ gesucht, welches einen freien Slot kennzeichnet, an dem ein Zeiger au: eine eigene Routine eingetragen werden kann. Nach dem Auffinden vor. ‘0L’ wird die Adresse von ‘do_it’ eingetragen und die Funktion mit dem Rückgabewert ‘TRUE’ beendet. Die Benutzung dieser Funktion gewährleistet, daß schon vorhandene Zeiger nicht überschrieben werden. Am Ende der Funktion ‘main()’ wird das Programm verlassen, aber im Speicher behalten. Von jetzt an wird nach jedem VBL unsere Funktion ‘do_it()’ ausgeführt, ‘main()’ hat keine Aufgaben mehr.
In der Funktion ‘do_it()’ wird zuerst überprüft, ob mindestens eine Sekunde seit der letzten Überprüfung vergangen ist, indem der Inhalt der Variablen ‘start’ (ist ja der Inhalt der Systemvariablen _HZ_200 vom letzten vollen Durchlaufen der Funktion ‘do_it()’) mit dem aktuellen Inhalt von _HZ_200 verglichen wird.
Wenn weniger als eine Sekunde vergangen ist, wird ‘do_it()’ nicht weiter ausgeführt, ansonsten addiert man die Anzahl der ganzen vergangenen Sekunden mit Hilfe der Funktion ‘add_second()’ ebenfalls zum Buffer ‘clock[4]' und gibt mit der Funktion ‘put_to_screen()’ die Uhrzeit auf dem Bildschirm aus. Den Vergleichswert für den nächsten Aufruf von ‘do_it()’ in ‘start’ korrigiert man mit den vergangenen Sekundenbruchteilen ‘diff’, da sonst unsere Uhr nach- oder vorgehen würde.
put_to_screen() zerlegt die ersten drei Bytes des Buffers ‘clock’ in Zehner- und Einerstelle und übergibt diese als Zeiger auf die zugehörige Bytefolge im Buffer ‘clfontf]’, der hier durch den Zeiger ‘dig’ repräsentiert wird, an die Funktion ‘dig_copy()’. Diese kopiert nun 16 Bytes ab der Adresse, auf die ‘dig’ zeigt, an die Adresse, auf die ‘schirm’ zeigt, und addiert zwischen jedem Byte 80 (Anzahl der Zeichen pro Zeile) zu ‘schirm’. Dadurch werden die zusammengehörigen Bytes untereinander dargestellt.
Die Linkdatei und das Makefile für die Lattice C-Umgebung sind mitabgedruckt. Das fertige Programm sollte die Endung ‘.TTP’ erhalten, damit Parameter vom Desktop aus übergeben werden können. Möchten Sie die Routinen in eigene Programme einbinden. sollten Sie der Funktion 'main()’ einen anderen Namen geben, z.B. ‘uhr()’ Compilieren Sie dann das Programm und linken Sie es mit Ihren eigenen Routinen zusammen.
Kopieren Sie das Programm in den Auto-Ordner, wird die Uhranzeige bei jedem Rechnerstart automatisch aktiviert. Wollen Sie das Programm vom Desktop aus starten, sollte es die Endung ‘.TOS’ haben, damit Sie als ersten Parameter die x- Koordinate (zwischen 0 und 71) und als zweiten Parameter die y- Koordinate (zwischen 0 und 24) eingeben können. Übergeben Sie aber keine Koordinaten, so benutzt das Programm die Defaultwerte, die Sie im Quellcode natürlich nach Belieben ändern können.
So, nun bleibt mir noch Ihnen viel Spaß beim Abtippen zu wünschen.
/****************************************************/
/* clock for autostart-ordner, 1/88, v1.0 */
/* (c) uwe gohlke */
/* burgwedeler str. 2G */
/* 3804 Isernhagen 2 */
/* für st-computer Zeitschrift */
/****************************************************/
#include "dos.h"
#include "osbind.h"
typedef unsigned char UBYTE; /* unsigned 8 bit int */
typedef short WORD; /* signed 16 bit int */
typedef long LONG; /* signed 32 bit int */
typedef void VOID;
#define TRUE 1
#define FALSE 0
#define _VBLQUEUE 0x456L
#define _HZ_200 0x4baL
#deflne NVBLS 0x454L
#define FONTSIZE 16
#define CH_PER_LINE 80
#define RPS 199
#define OKARGS 3
#define NORMXPOS 71
#define NORMYPOS 0
#define MAXXPOS 71
#define MAXYPOS 24
/****************************************************/
/* */
/****************************************************/
extern LONG _32K; /* nur für lattice c-compiler */
LONG _32K = 0x8000; /* v 3.04 benötigt. */
UBYTE clock[0], /* buffer für lokale zeit */
*schirm; /* pointer auf bildschirmspeicher*/
LONG start; /* zeit der letzten abfrage */
int posx,posy; /* clockposition */
UBYTE clfont[]={ /* fontdaten */
0,0,0x3c,0x7e,0x66,0x66,0x66,0x6e,0x76,0x66,0x66,0x66,0x7e,0x3c,0,0,
0,0,0x18,0x18,0x38,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x7e,0x7e,0,0,
0,0,0x3c,0x7e,0x66,0x66,0x0c,0x0c,0x18,0x18,0x30,0x30,0x7e,0x7e,0,0,
0,0,0x7e,0x7e,0x0c,0x0c,0x18,0x18,0x0c,0x0c,0x66,0x66,0x7e,0x3c,0,0,
0,0,0x0c,0x0c,0x1c,0x1c,0x3c,0x3c,0x6c,0x6c,0x7e,0x7e,0x0c,0x0c,0,0,
0,0,0x7e,0x7e,0x60,0x60,0x7c,0x7e,0x86,0x06,0x06,0x66,0x7e,0x3c,0,0,
0,0,0x1c,0x3c,0x70,0x60,0x60,0x7c,0x7e,0x66,0x66,0x66,0x7e,0x3c,0,0,
0,0,0x7e,0x7e,0x06,0x06,0x0c,0x0c,0x18,0x18,0x30,0x30,0x30,0x30,0,0,
0,0,0x3c,0x7e,0x66,0x66,0x3c,0x3c,0x66,0x66,0x66,0x66,0x7e,0x3c,0,0,
0,0,0x3c,0x7e,0x66,0x66,0x7e,0x3e,0x06,0x06,0x06, 0x0e,0x3c,0x38,0,0,
0,0,0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x18,0x18,0,0
};
VOID do_it(),put_to_screen(),dig_copy(), add_second();
WORD to_vbl_list();
/***************************************************/
/* installiere die funktion 'do_it' in die vbl- */
/* funktionenliste, setze koordinaten für Uhren- */
/* ausgabe, hole die systemzeit & ordne sie dem */
/* buffer 'clock' zu, verlasse die funktion (das */
/* Programm), aber behalte es im Speicher */
/***************************************************/
main(argc,argv)
UBYTE **argv;
int argc;
{
LONG oldstack;
if(argc==OKARGS) { /* koordinaten setzen */
stch_i(argv[1],&posx); /* wandle string ->int */
stch_i(argv[2],&posy);
if(posx<0) /* teste koordinaten */
posx*=-1; /* auf gültigkeit */
if(posy<0)
posy*=-1;
if(posx>MAXXPOS)
posx=MAXXPOS; /* defaultwert */
if(posy>MAXYPOS)
posy=MAXYPOS;
}
else { /* defaultposition */
posx=NORMXPOS;
posy=NORMYPOS;
}
posy*=CH_PER_LINE*FONTSIZE; /* konvertiere zu 1inienhöhe*/
printf('Uhranzeige aktiv! (c) uwe gohlke 1/88\n");
schirm=Logbase()+posx+posy; /* bildschirmadresse */
getclk(clock); /* hole systemzeit */
oldstack=Super(0L); /* supervisormodus an */
start=*(LONG *) _HZ_200; /* 208 hz zaehler */
Super(oldstack); /* supervisormodus aus */
to_vbl_list(&do_it); /* in vbl-liste einordnen*/
Ptermres(15000L,0); /* Programm verlassen & */
/* im Speicher behalten */
}
/****************************************************/
/* */
/****************************************************/
VOID
do_it()
{
register diff,
noch, neu;
if((diff=((neu**(LONG *)_HZ_200))-start)>=RPS)
{ /* prüfe, ob */
/* mindestens 1 Sekunde */
/* seit dem letzten */
/* aufruf vergangen ist.*/
/* aufruf. */
noch=diff/RPS; /* anzahl der vergan- */
/* genen Sekunden, */
diff-=noch*RPS; /* sekundenbruchtei1. */
start=neu-diff+1; /* Start für naechsten */
while(noch--) /* addiere Sekunden zur */
add_second(&clock[4]); /* lokalen zeit, */
put_to_screen(schirm,&clock[4],clfont);
/* ausgabe auf schirm */
}
}
/*******************************************************/
/* addiere 1 Sekunde zum zeitbuffer 'clock' und */
/* aktualisiere gegebenenfalls die minuten und stunden */
/*******************************************************/
VOID
add_second(clock)
UBYTE *clock;
{
if(++*(clock*2)==60) { /* Sekunden */
*(clock*2)=0;
if(++*(clock+1)==60) { /* minuten */
*(clock+1)=0;
if(++*clock==24) { /* stunden */
*clock=0;
}
}
}
}
/*******************************************************/
/* schreibe 'clock' zur bildschirmposition */
/* 'schirm' mit den zeichen */
/*******************************************************/
VOID
put_to_screen(schirm,clock,dig)
UBYTE *clock,
*schirm,
*dig;
{
register i,
ten,
one;
for(i = 0; i<3; i++,clock++) { /* 3: minuten, */
/* Sekunden, stunden */
ten=(*clock)/10; /* dezimalstelle */
dig_copy(schirm,dig+ten*FONTSIZE);
/* auf schirm */
schirm++;
one=(*clock)-ten*10;
dig_copy(schirm,dig+one*FONTSIZE);
schirm++;
if(i<2) { /* schreibe ':' dazwischen*/
dig_copy(schirm,dig+10*FONTSIZE);
schirm++;
}
}
}
/****************************************************************/
/* kopiere FONTSIZE bytes vom buffer dig' zur adresse 'schirm' */
/* addiere zwischen jedem byte CH_PER_LINE zu 'schirm' */
/****************************************************************/
VOID
dig_copy(schirm,dig)
UBYTE *schirm, *dig;
{
register i;
for(i=0; i<FONTSIZE; i++,dig++) {
*(schirm+i*CH_PER_LINE)=*dig;
}
}
/****************************************************************/
/* schreibe den wert 'rein' in die vbl-routinenliste an eine */
/* stelle, an der eine 'BL' steht und gib TRUE(1) zurück */
/* wenn keine '0L' vorhanden, gib FALSE (0) zurück */
/****************************************************************/
WORD
to_vbl_list(rein)
register rein;
{
register i;
register oldstack;
LONG *inh;
oldstack=Super(0L); /* supervisor-mode an */
inh=*(LONG *) _VBLQUEUE! /* zeigen auf vblqueue */
for(i=0,inh+=(*(WORD *) NVBLS); i< *(WORD *) NVBLS; i++,inh--) {
if(!*inh) { /* wenn Inhalt 0 */
*inh=rein; /* neuer inhalt rein */
Super(oldstack);
/* supervisormode aus */
return(TRUE);
}
}
Super(oldstack)i /* usermode aus */
return(FALSE);
}