Man kann sogar die X/Y-Koordinaten fĂŒr die Bildschirmposition ĂŒbergehen.
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
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.
main()
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.
Compilieren und Linken
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.
Starten des Programms
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);
}