
 ----------------------------------------------------------------------------
             Tips und Tricks zum C-Control/BASIC Mikrokontroller             
 ----------------------------------------------------------------------------

 In dieser Textdatei stehen einige Tips und Tricks zur Programmierung des C-
 Control/BASIC-Mikrokontrollers in BASIC und/oder Assembler und zu anderen
 Themen.

 ----------------------------------------------------------------------------
                Assemblerprogramme auf den Rechenstack pushen                
 ----------------------------------------------------------------------------

 Selten benutze, kurze Assemblerprogramme muessen nicht unbedingt ins interne
 EEPROM programmiert werden. Viel effektiver ist es, diese einfach waehrend
 der Laufzeit des BASIC-Programms auf den Rechenstack zu pushen und
 anschliessend aufzurufen. Zu beachten ist hierbei, dass die
 Assemblerprogramme nicht laenger als 7 Words (14 Bytes) werden duerfen und
 das fuer das Laden des Rechenstacks natuerlich relativ viel Zeit benoetigt
 wird. Immerhin muss der Rechenstack VOR JEDEM Aufruf des Assemblerprogramms
 geladen werden.

 Falls doch einmal ein Assemblerprogramm benoetigt wird, das laenger als 14
 Byte ist, koennen die Datenstrukturen die im Speicher hinter dem Rechenstack
 folgen als Zwischenspeicher fuer das Assemblerprogramm missbraucht werden.
 Das ist einerseits ein Word das zum Generieren der Zufallszahlen benoetigt
 wird (kann mit RANDOMIZE geaendert werden) und andererseits das USER-RAM zum
 Abspeichern der USER-Variablen. In das USER-RAM kann einfach durch Aendern
 von Variableninhalten geschrieben werden. Wenn alle aufgefuehrten
 Moeglichkeiten ausgenutzt werden, koennen Assemblerprogramme bis 40 Byte
 Laenge (14+2+24) waehrend der Laufzeit des BASIC-Programms
 zwischengespeichert und aufgerufen werden!

            Hier ein einfaches Beispiel, das den SYS-Befehl nutzt:

   print asmaufruf
   end
   
   ' Der SYS-Befehl in der BASIC-Unterroutine ASMAUFRUF ruft folgendes
   ' Assemblerprogramm auf:
   
   '0091 =        org $91
   '0091 : a604   lda #$4
   '0093 : b791   sta $91
   '0095 : a6d2   lda #$d2
   '0097 : b792   sta $92
   '0099 : 81     rts
   '009a : 9d     nop
   
   #asmaufruf
     sys &h91 &h819d,&hb792,&ha6d2,&hb791,&ha604
   return


 Diese Art Assemblerprogramme in BASIC-Programme einzubinden erhoeht sogar
 die Lebensdauer des Mikrokontrollers, denn laut Motorola vertraegt das
 interne EEPROM nur 10000 Schreibzyklen!

 Allerdings sollte beachtet werden, dass diese Art der Stackbenutzung auch
 ihre Tuecken hat: Innerhalb einer komplexen Berechnung sollte kein
 Assemblerprogramm auf diese Weise aufgerufen werden. Z.B. bereitet der
 Ausdruck "2+3*asmaufruf" Probleme, da zuerst 2 und 3 auf den Stack gepusht,
 dann das Assemblerprogramm aufgerufen und erst anschliessend die
 Multiplikation und dann die Addition durchgefuehrt werden. Falls das
 Assemblerprogramm in diesem Fall aber mehr als 5 Words (10 Bytes) des Stacks
 benutzt, tritt ein Stackueberlauf auf, bei dem die Zahlen 2 und 3 verloren
 gehen.

 Auch innerhalb einer Interruptroutine sollte der Stack nicht
 ueberstrapaziert werden (siehe hierzu die Anmerkungen in der Datei
 BUGS.TXT).


 ----------------------------------------------------------------------------
                  Tabelleninhalte als BASIC-Token ausfuehren                 
 ----------------------------------------------------------------------------

 Wenn Tabellen inmitten von BASIC-Codebereichen stehen, werden die Daten in
 den Tabellen als BASIC-Token ausgefuehrt. Diesen Umstand kann man sich
 zunutze machen um Token zu erzeugen, die der BASIC-Compiler nicht
 unterstuetzt.

      Hier ein Beispiel, das die Laenge der EEPROM-Datendatei bestimmt:

   open# for write
   print filelen
   print# 12
   print filelen
   print# 13
   print filelen
   close#
   print filelen
   end
   
   #filelen
     table filelentab
       7180  '28*256+12
     tabend
   return


 ----------------------------------------------------------------------------
                 PEEK und POKE-Funktionen in BASIC-Programmen                
 ----------------------------------------------------------------------------

 In der Datei DUMP.BAS (liegt dieser Informationssammlung bei) ist
 beschrieben, wie mit Hilfe von kleinen Assemblerprogrammen, die innerhalb
 des USER-RAMs liegen, PEEK und POKE-Funktionen nachgebildet werden koennen.
 Die dem C-Control/BASIC-Chip zugrundeliegende CPU unterstuetzt keine 16-Bit
 Zeiger, so dass etwas getrickst werden muss, um Daten ausserhalb der
 Zeropage zu lesen oder zu schreiben.


 ----------------------------------------------------------------------------
                 Abspeichern ins interne EEPROM von BASIC aus                
 ----------------------------------------------------------------------------

 Auch Informationen zu diesem Thema stehen in der Datei DUMP.BAS.


 ----------------------------------------------------------------------------
               Eigene Assembler-Interruptroutinen programmieren              
 ----------------------------------------------------------------------------

 Das Betriebssystem unterstuetzt das Einbinden von eigenen Assembler-
 Interruptroutinen, die im internen EEPROM (Adresse $101 bis $1ff) liegen
 muessen. Auf Adresse $50 bis $53 des RAMs befinden sich Zeiger, in die die
 Adressen der eigenen Unterroutinen eingetragen werden muessen. Nach einem
 Reset werden diese Pointer mit dem Wert null (d.h.: keine User-
 Interruptroutine vereinbart) ueberschrieben.

 Als kleines Beispiel folgt ein Assemblerprogramm, das sich in den Timer
 Compare Interrupt einklinkt und etwa alle 20 Millisekunden den Zustand von
 Port A nach Port B kopiert. Wenn an Port B ein Oszilloskop oder LEDs
 angeschlossen werden, kann somit der Zustand von Port A ueberwacht werden.
 Das ist sehr sinnvoll bei einer BASIC M-Unit, denn hier ist Port A nicht
 vollstaendig herausgefuehrt. Zusaetzlich koennen (mit LEDs am Port B) die
 Zustandsaenderungen auf der SDA- und SCL-Leitung des I2C-Busses ueberwacht
 werden.


                           Hier das BASIC-Programm:
          (in der DOS-IDE eingeben und unter TIMERCMP.BAS speichern)

   sys &h101                     ' Interruptroutine in das System einbinden
   syscode "timercmp.s19"


                       Und hier das Assemblerprogramm:
   (in der DOS-IDE eingeben, unter TIMERCMP.ASM speichern und assemblieren)

     org $101
   
   initmytimercmp:
     lda #255                    ; Port B als Ausgang schalten
     sta $05
     lda #(mytimercmp-$100)      ; eigene Interruptroutine setzen
     sta $52
     rts                         ; Ruecksprung zu BASIC
   
   mytimercmp:
     lda $00                     ; Port A nach
     sta $01                     ;   Port B schreiben
     lda #1                      ; Betriebssystemroutine aufrufen
     rts                         ; Ruecksprung zum Betriebssystem


 Um eine eigene Interruptroutine in das System einzubinden muss einer der
 vier Userpointer (an Adresse $50, $51, $52 oder $53) mit der Adresse der
 entsprechenden Routine geladen werden. Da hierfuer nur ein Byte zur
 Verfuegung steht, wird die Adresse relativ zum Beginn des EEPROMs (Adresse
 $100) angegeben.

 Eine eigene Interruptroutine muss immer mit RTS (nicht RTI) beendet werden.
 Wird im Akkumulator als letzter Wert null gespeichert, dann wird die
 Betriebssystemroutine, die eigentlich fuer das Bedienen des Interrupts
 zustaendig ist, nicht mehr aufgerufen. Hiermit kann das Betriebssystem also
 voellig "ausgeschaltet" werden. Ich habe im obigen Beispiel als letzten Wert
 eine Eins in den Akkumulator gespeichert und so dem Betriebssystem
 signalisiert, dass die normale Routine weiterhin aufgerufen werden soll.

 Zu beachten ist auch, dass die eigene Interruptroutine nicht zu lang geraten
 darf, denn wenn ein weiterer Interrupt auftritt bevor die Interruptroutine
 abgearbeitet wurde fuehrt das zum "Verschlucken" von folgenden Interrupts.

       Diese vier Pointer stehen fuer Interruptroutinen zur Verfuegung:

              $50 = IRQPTR (Userpointer fuer IRQ-Interrupt)
              $51 = CAPPTR (Userpointer fuer TIMERCAP-Interrupt)
              $52 = CMPPTR (Userpointer fuer TIMERCMP-Interrupt)
              $53 = OFLPTR (Userpointer fuer TIMEROFL-Interrupt)

 Leider ist es nicht moeglich, eigene Routinen in den Serial Communications
 Interface Interrupt, in SWI oder RESET einzubinden.

 Genauere Informationen wie ein Interrupt arbeitet oder welche Register nach
 einem Interrupt wieder initialisiert werden muessen, kann dem
 Betriebssystemlisting (CCBASROM.TXT) oder dem MC68HC05B6-Manual der Firma
 Motorola entnommen werden. Eine genaue Beschreibung der verschiedenen
 Interrupts steht in der Textdatei INTERRUP.TXT.


 ----------------------------------------------------------------------------
                 Benutzung der A/D-Ports als Digitaleingaenge                
 ----------------------------------------------------------------------------

 Es scheint wenig bekannt zu sein, dass die A/D-Ports als zusaetzliche
 digitale Eingaenge benutzt werden koennen. Hierzu steht, zusaetzlich zu den
 Ports mit denen der A/D-Wert (0..255) ausgelesen werden kann, der Port D an
 Adresse $03 zur Verfuegung.

 Leider unterstuetzt das Betriebssystem der C-Control/BASIC-Unit nicht dieses
 zusaetzliche Feature des MC68HC05B6-Mikrokontrollers, so dass wieder einmal
 etwas getrickst werden muss.

                    Port D von BASIC aus byteweise lesen:

   print portd
   end
   
   #portd
     table portdtab
       2818  '11*256+2
     tabend
   return


 ----------------------------------------------------------------------------
            Lesen und Schreiben von beliebigen RAM-Speicherstellen           
 ----------------------------------------------------------------------------

 Es gibt ein Byte an der Adresse $dc, das offenbar von keiner
 Betriebssystemroutine verwendet wird. Wenn man den schon weiter oben
 beschriebenen Tabellentrick anwendet, kann auf dieses Byte ganz einfach -
 und sogar ohne Maschinensprache - zugegriffen werden.

         Die Speicherstelle $dc von BASIC aus lesen und beschreiben:

   define var byte
   var=123
   
   var=var                ' durch diesen Trick wird der Wert von
                          ' "var" auf den Stack gepusht
   gosub unbenutztwrite   ' dann den Stackinhalt auf Adresse $dc schreiben
   var=0                  ' Variable "var" loeschen und
   var=unbenutztread      ' den Wert an Adresse $dc wieder einlesen
   print var              ' zum Schluss den Inhalt der Variablen ausgeben
   end
   
   #unbenutztread
     table unbenutztreadtab
       3035 '11*256+$dc-1
     tabend
   return
   
   #unbenutztwrite
     table unbenutztwritetab
       4562 '17*256+$dc-$a
     tabend
   return

 Durch eine kleine Aenderung im Programm kann jedes beliebige Byte im RAM ab
 Adresse 1 abgefragt und jedes Byte ab Adresse $a veraendert werden. Dazu
 muessen nur die Werte in den Tabellen angepasst werden. So koennte man also
 fuer jede Adresse, auf die man zugreifen moechte, eine eigene BASIC-
 Subroutine erstellen.


 ----------------------------------------------------------------------------
                     Taktfrequenz bis auf 12 MHz erhoehen                    
 ----------------------------------------------------------------------------

 Wie das Conrad Electronic Technologie Centrum am 15. September 1998 ueber
 ihr Forum zur C-Control-Unit verlauten liess kann die Taktrate des
 Minicomputersystems problemlos bis auf 12 MHz erhoeht werden. Dazu muss ein
 anderer Quarz, bzw. Schwinger eingeloetet werden. Manche Exemplare sollen
 sogar mit 16 MHz noch funktionieren.

 Der Mikrokontroller koennte sogar noch weit ueber 20 MHz betrieben werden
 (obwohl er von Motorola nur bis 4 MHz spezifiziert wurde), doch das serielle
 EEPROM 24C65 ist zu langsam.

 Zu beachten ist aber, dass sich durch den Einbau eines anderen Quarzes die
 Baudrate der RS232-Schnittstelle aendert. Ausserdem laeuft die Uhr schneller
 und die Stromaufnahme der Unit wird erhoeht. Das durch das Einloeten eines
 neuen Quarzes (bzw. Schwingers) jeglicher Garantieanspruch verfaellt
 versteht sich hoffentlich von selbst.


 ----------------------------------------------------------------------------
                 Internes EEPROM nicht unnoetig strapazieren                 
 ----------------------------------------------------------------------------

 Wenn ein Programm geschrieben werden soll, das sowohl BASIC- als auch
 Assembler-Code enthaelt, sollte darauf geachtet werden, dass jedes
 Neuprogrammieren des internen EEPROMs (mit "Systembytes") schaedlich auf
 dieses wirkt.

 Falls die Assemblerroutinen des Programms fehlerfrei funktionieren und nur
 noch Aenderungen an dem BASIC-Code durchgefuehrt werden muessen, sollte vor
 dem Uebertragen des Programms zur C-Control Unit der SYSCODE-Befehl
 auskommentiert werden. In diesem Fall wird das Maschinenspracheprogramm (das
 sich ja im internen EEPROM befindet) nicht veraendert und so wertvolle
 Schreibzyklen gespart. Laut Motorola vertraegt das interne EEPROM nur 10000
 (in Worten: zehntausend) Programmierzyklen!


 ----------------------------------------------------------------------------
                 EEPROM-Datendatei nicht unnoetig schliessen                 
 ----------------------------------------------------------------------------

 Das Betriebssystem unterscheidet bei einem CLOSE#-Befehl nicht ob die im
 seriellen EEPROM befindliche Datendatei zum Lesen oder zum Schreiben
 geoeffnet wurde. Das hat die Konsequenz, dass der CLOSE#-Befehl
 grundsaetzlich von einem schreibenden Zugriff ausgeht und so auch nach dem
 Lesen den Zaehler fuer die Groesse der Datei ins EEPROM programmiert.
 Dadurch werden unnoetig Programmierzyklen des EEPROMs verschenkt. Da sich
 die Anzahl der Bytes in der Datei durch einen lesendem Zugriff natuerlich
 nicht aendert, wird der gleiche Wert in die entsprechende EEPROM-Adresse
 geschrieben, der schon auf der Adresse steht. Ob das 24C65-EEPROM dies
 erkennt, das Umprogrammieren als unnoetig ansieht und deshalb nicht
 durchfuehrt, ist aus dem Manual zum EEPROM von Microchip (meines Wissens)
 nicht ersichtlich.

 Auf diesem Grund sollte auf den CLOSE#-Befehl bei einem lesenden Zugriff auf
 die Daten im EEPROM (mit OPEN# FOR READ) verzichtet werden.


 ----------------------------------------------------------------------------
            High Endurance Block des seriellen EEPROMs verschieben           
 ----------------------------------------------------------------------------

 Jede Speicherstelle im seriellen 24C65'er EEPROM, das im C-Control-
 Minicomputer Verwendung findet, kann eine Million mal neu programmiert
 werden, danach ist sie hinueber. Es gibt aber einen verschiebbaren, 4096 Bit
 grossen Bereich, der bis zu zehn Millionen mal neu programmiert werden kann.
 Dieser Bereich heisst High Endurance Block und liegt von Werk aus
 (angeblich) am Ende des Speicherraums des EEPROMs.

 Eigentlich wollte ich meiner Informationssammlung auch ein getestetes
 Programm zum Verschieben des High Endurance Blocks beilegen. Doch selbst
 naechtelange Versuche meinem EEPROM mittels Oszilloskopkarte, Manual und
 unzaehligen Assemblerprogrammen das Verschieben des Blocks beizubringen
 blieben ohne Erfolg. Warum alle meine Versuche zum Scheitern verurteilt
 waren viel mir erst sehr viel spaeter beim Blick auf die Platine meiner M-
 Unit auf: Diese ist naemlich nicht mit dem 24C65'er EEPROM von Microchip,
 sondern mit dem 24C64 von der Firma SGS Thomson bestueckt. Und dieser
 Speicherchip hat gar keinen High Endurance Block!

 Der arme Chip war selbst durch den recht harmlosen Versuch, die Lage des
 High Endurance Block auszulesen so verwirrt, dass er das naechste Byte, das
 nach dem Leseversuch uebertragen wurde, nicht einmal mehr per Acknowledge-
 Signal bestaetigte.

 Aus diesem Grund ist es mir leider unmoeglich, die Programme, die ich zum
 Auslesen und Setzen der Lage des High Endurance Block geschrieben und dieser
 Informationssammlung beigelegt habe, richtig zu testen. Nach dem was laut
 meiner Scopekarte am I2C-Bus anliegt, sollten die Programme aber fehlerfrei
 arbeiten. Vielleicht kann sie ja jemand anderes (der ein 24C65 EEPROM
 besitzt) austesten?!

 Wie ein 24C65-Chip auf das Verschieben des High Endurance Blocks reagiert
 kann ich nicht genau (vorher-)sagen. Vermutlich muss der Mikrokontroller
 mitsamt EEPROM nach dem Verschieben resettet und das BASIC-Programm erneut
 ins serielle EEPROM programmiert werden.

 Das Assemblerprogramm zum Lesen und Schreiben der Lage des High Endurance
 Blocks heisst auf jeden Fall HIGHENDU.ASM und benoetigt nach dem
 Assemblieren das BASIC-Programm HIGHENDU.BAS zum Kompilieren und Ausfuehren.
 Die Signalverlaeufe auf dem I2C-Bus beim Setzen des Blocks auf Block Nummer
 14 sind in der Grafik HIGHENDU.GIF dargestellt.

 Genauere Infos zum EEPROM und wie man den High Endurance Block verschiebt,
 findet man uebrigens im 24C65-Manual der Firma Microchip (siehe
 INTERNET.TXT).


 ----------------------------------------------------------------------------
          Eingaenge der M-Unit auf ein definiertes Potential setzen          
 ----------------------------------------------------------------------------

 Falls eine M-Unit in einer Schaltung eingesetzt werden soll, muss beachtet
 werden, dass bei diesem Chip nicht alle Eingaenge ab Werk durch Pull-Up oder
 Pull-Down Widerstaende auf ein definiertes Potential gelegt wurden.

 Bei den diversen digitalen Ein- und Ausgaengen spielt das keine allzu grosse
 Rolle. Diese koennen einfach im BASIC- oder Assemblerprogramm als Ausgaenge
 definiert werden. Probleme koennten aber durch die unbeschalteten Eingaenge
 am PORTA und durch die Datenleitungen der seriellen Schnittstelle (TXD und
 RXD) entstehen. Wenn diese "in der Luft" liegen, kann bereits durch die
 blosse Beruehrung des Mikrokontrollergehauses mit der Hand ein
 Eingangssignal vorgegaukelt werden. Durch diese statischen Entladungen kann
 man den Chip sogar zerstoeren!

 Aus diesem Grund sollte der RXD-Eingang des Kontrollers mit einem Pull-Up
 oder Pull-Down Widerstand (z.B. 10 kOhm) auf VDD oder GND Potential gelegt
 werden. Die unbeschalteten Eingaenge von PORTA sollten in jedem BASIC-
 Programm durch Aufruf des untenstehenden Assemblerprogramms auf Ausgang
 geschaltet werden. Das sollte moeglich frueh, also beim Start des Programms,
 geschehen.

                Alle Eingaenge von PORTA auf Ausgang schalten:
             (sollte von jedem BASIC-Programm aufgerufen werden)

     org $101
   
   definiert:
     lda $04            ; DDRA holen (sollte %01011111 sein)
     ora #%11011110     ; CTS zusaetzlich als Ausgang schalten
     sta $04
   
     lda $01            ; PORTA holen
     and #%01111111     ; CTS auf 0 Volt (GND-Potential) setzen
     sta $01
   
     rts


 ----------------------------------------------------------------------------
      Maximale Verschachtelungstiefe bei Assemblerunterprogrammaufrufen      
 ----------------------------------------------------------------------------

 Bei jedem Aufruf einer Assemblerunterroutine wird die Adresse, zu der nach
 Abarbeitung der Unterroutine zurueckgekehrt werden soll, auf dem
 Hardwarestack zwischengespeichert. Nach Abarbeitung der Routine wird diese
 Adresse per "rts"-Befehl wieder vom Stack entfernt und hinter den Befehl,
 der die Assemblerroutine aufgerufen hat, gesprungen.

 Bei jeder 6805'er CPU wird dieser Stack automatisch von einem Stackpointer
 verwaltet, der den Speicherbereich von Adresse $c0 bis $ff ansprechen kann.
 Der Stack hat also eine Groesse von 64 Byte. Da das Betriebssystem der C-
 Control aber einen grossen Teil des Stackbereichs zum Zwischenspeichern von
 eigenen Daten benutzt, schrumpft die maximale Groesse auf nur noch 27 Bytes
 zusammen.

 In diesen 27 Byte werden aber nicht nur Ruecksprungadressen, sondern auch
 die aktuellen Inhalte des Akkumulators, des Index Registers und der Flags
 zwischengespeichert, bevor eine Interruptroutine aufgerufen wird. In diesem
 Fall werden die angesprochenen Register mittels "rti"-Befehl (inklusive
 Ruecksprungadresse) vom Stack entfernt. Bei einem Interrupt werden also
 fuenf Bytes des Stacks benutzt, waehrend es bei einem "normalen"
 Unterprogrammaufruf nur zwei Bytes sind.

 Es stellt sich die Frage, wieviele Ruecksprungadressen und Registerinhalte
 auf den Stack gespeichert werden koennen, ohne dass es zu einem
 Stackueberlauf (bzw. zum Ueberschreiben von Systemdaten) kommt. Ich moechte
 dazu an dieser Stelle zunaechst ausfuehrlich beschreiben, was mit dem
 Hardwarestack zwischen einem Reset und dem Aufruf eines eigenen
 Assemblerprogramms (mittels "SYS"-Befehls von BASIC aus) passiert.

 Der Stackpointer wird nach einem Reset auf den Wert $ff gesetzt.
 Anschliessend fuehrt das Betriebssystem die "IDLE-LOOP" (Adresse $969 im
 ROM-Listing) solange aus, bis der Start-Jumper gedrueckt und so die
 Betriebssystemroutine zum Starten des BASIC-Programms aufgerufen wird.

 Dieser Aufruf hinterlaesst natuerlich seine Spur auf dem Stack: Auf Adresse
 $fe wird der Wert $097e gespeichert. Wer das ROM-Listing zur Hand nimmt,
 wird erkennen, dass auf dem Stack die Adresse des Befehls "bra $0969"
 gespeichert wurde. Von Interesse ist aber weniger dieser Befehl, als der
 Befehl unmittelbar davor ("jsr $08ee"). Dieser "Jump To Subroutine"-Befehl
 steht auf Adresse $097b und ruft die bereits angesprochene Routine zum
 Starten des BASIC-Programms auf. Der Stackpointer zeigt nach dem Speichern
 dieses Words auf Adresse $fd.

 Jetzt wird also das BASIC-Programm ausgefuehrt. Dazu holt das Betriebssystem
 ein BASIC-Token nach dem anderen aus dem seriellen EEPROM und liest die
 Startadressen der zu den Token gehoerenden Unterroutinen aus einer
 Sprungtabelle. Anschliessend wird die entsprechende Routine mit Hilfe der
 Systemfunktionen fuer die Pseudo-Adressierungsmodi aufgerufen. Irgendwann
 trifft das Betriebssystem auch auf das "SYS"-Token und ruft die
 Systemroutine an Adresse $1cdb auf. Auch hierbei wird wiederum die
 Ruecksprungadresse auf dem Stack gespeichert und kann auf Adresse $fc des
 Stacks gefunden werden. Der Stackpointer zeigt auf $fb.

 Die Systemroutine fuer die Pseudo-Adressierungsmodi verzweigt ueber einige
 Umwege zur eigentlichen "SYS"-Routine, die an Adresse $170a beginnt. Dazu
 werden aber unbedingte Spruenge ("jmp" oder "bra") benutzt, so dass im
 Gegensatz zu den "jsr"- und "bsr"-Befehlen keine Adressen auf dem Stack
 gespeichert werden muessen.

 Die Routine fuer den "SYS"-Befehl holt nun ihrerseits ein Word aus dem
 seriellen EEPROM, das die Adresse der aufzurufenden Assemblerroutine
 enthaelt. Um dieses nun endlich aufzurufen, wird ein kleines Programm im
 Zeropage-RAM (ab Adresse $bd) generiert und aufgerufen. Da das kleine
 Hilfsprogramm ebenfalls per "jsr"-Befehl gestartet wird, hinterlaesst auch
 dieser Aufruf seine Spur auf dem Stack (Adresse $fa). Das Hilfsprogramm
 enthaelt nichts weiter als einen unbedingten Sprung zu unserem Programm, so
 dass dieser Aufruf wieder keinen Stackspeicher benoetigt.

 Jetzt endlich wird unser Assemblerprogramm ausgefuehrt. Der Stackpointer
 steht auf Adresse $f9 und so wurden bis jetzt drei Words (oder sechs Bytes)
 des Stacks benutzt.

       Belegung des Hardwarestacks bei Aufruf eines Assemblerprogramms:
                   (der Stackpointer zeigt auf Adresse $f9)

   Adr. Wert   Beschreibung
   $fa  $1713  User-Assemblerroutine starten ("jsr $bd" auf Adresse $1711)
   $fc  $093e  SYS-Hauptroutine starten ("jsr $1cdb" auf Adresse $093b)
   $fe  $097e  BASIC-Programm starten ("jsr $08ee" auf Adresse $097b)

 Es stehen auf dem Stack also noch 21 Bytes zur Verfuegung. Bei jedem Aufruf
 einer eigenen Assemblerroutine mittels "jsr"- oder "bsr"-Befehl ohne
 anschliessendes "rts" bewegt sich der Stack um zwei Bytes in Richtung
 Systemdaten und droht diese zu ueberschreiben. Um festzustellen, wieviele
 Unterprogrammverschachtelungen erlaubt sind, reicht es nun aber nicht aus,
 einfach 21 (bzw. 20) durch zwei zu teilen, als Ergebnis zehn
 herauszubekommen und daraus zu schliessen, dass Unterprogramme bis zu einer
 Verschachtelungstiefe von zehn verwendet werden koennen. Wie oben bereits
 erwaehnt, wird der Stack auch von den Interruptroutinen benutzt!

 So stellt sich also die Frage, wieviel Stackspeicher die Interruptroutinen
 benoetigen. Wenn in einen Interrupt eine eigene User-Assemblerinterrupt-
 routine eingebunden wurde, haengt es eventuell von dieser Routine ab,
 wieviel der kostbaren Stackeintraege maximal benutzt werden. Fuer die
 Standard-Interruptroutine gilt aber folgendes:

 Die Betriebssystemroutine fuer den 20-Millisekunden-Ticker und die Routinen
 zur Dekodierung des (DCF77-)Signals einer am FREQ1-, bzw. TCAP1-Eingang
 angeschlossenen Signalquelle haben den groessten Stackspeicherbedarf. Wie
 jede Interruptroutine, so benutzen auch diese fuenf Bytes des Stacks zum
 Speichern der Ruecksprungadresse und der Registerinhalte. Zusaetzlich finden
 innerhalb der Interruptroutinen zwei verschachtelte Unterprogrammaufrufe
 statt, so dass insgesamt neun Bytes des Stacks fuer die Interruptbedienung
 reserviert werden muessen.

 Und so schrumpft der freie Stackspeicher auf eine Groesse von 12 Bytes
 zusammen. In einem selbstgeschriebenen Assemblerprogramm duerfen also
 maximal sechs verschachtelte Unterprogrammaufrufe verwendet werden. Bei
 Programmen, die ausschliesslich eigene Unterroutinen aufrufen, sollte dieser
 Wert keine Probleme machen. Problematisch wird es aber, wenn ein Programm
 Betriebssystemroutinen verwendet, die ihrerseits weitere Systemroutinen
 aufrufen!

 Ein gutes Beispiel hierfuer ist die Betriebssystemroutine an Adresse $197c,
 die ueber die Adresse $197f (mit einer Zahl zwischen 1 und 6 im Akkumulator)
 aufgerufen werden sollte. Ueber die sechs Funktionen dieser Routine kann auf
 die Datendatei im seriellen EEPROM zugegriffen werden. Wenn z.B. der BASIC-
 Befehl "OPEN# FOR READ" in Assembler nachgebildet werden soll muss im
 Akkumulator der Wert 1 uebergeben werden.

 Leider ruft diese Betriebssystemroutine eigenstaendig relativ viele andere
 Subroutinen verschachtelt auf. Bei "OPEN# FOR READ" betraegt die
 Verschachtelungstiefe (inklusive erstem "jsr $197f"-Aufruf) insgesamt vier,
 so dass diese Routine hoechstens aus einer Unter-unterroutine eines eigenen
 Assemblerprogrammes heraus aufgerufen werden darf!

         Aufrufbaum fuer das Assemblerequivalent zu "OPEN# FOR READ":

   jsr $197f          Aufruf von ProcFileIO vom Assemblerprogramm aus
     jsr $1a24        Aufruf von OpenFileForRead (von Adresse $19ab aus)
       jsr $0de5      Aufruf von _24C65_BeginSequentialRead (von $1a2a aus)
         jsr $083c    Aufruf von I2C_Start (von Adresse $0def aus)
         jsr $0846    Aufruf von I2C_Write (von Adresse $0df4 aus)
       jsr $0e0d      Aufruf von _24C65_ReadWord (von Adresse $1a2d aus)
         bsr $0e04    Aufruf von _24C65_ReadNext (von Adresse $0e0d aus)

 Die Aufrufbaeume von weiteren Betriebssystemroutinen koennen der Datei
 CCBASIC.MAP entnommen werden. Diese Datei liegt dem ZIP-Archiv mit den
 Original-Quellcodes des Betriebssystems bei. Leider sind dort aber auch
 Unterprogrammaufrufe, die mit einem unbedingten Sprung ("jmp") durchgefuehrt
 wurden, aufgefuehrt.


 ----------------------------------------------------------------------------
        Unit ueber RS232 bei gesetztem Start-Jumper neu programmieren        
 ----------------------------------------------------------------------------

 Die meisten C-Control-Units werden in der Praxis wohl mit gesetztem Start-
 Jumper betrieben. Das macht das manuelle Setzen des Jumpers von Hand, wenn
 der Strom ausgefallen ist oder die Unit aus sonst einem Grund resettet
 wurde, ueberfluessig.

 Das Problem sind dann aber Programmupdates: Es waere eine gute Sache, wenn
 die Unit "aus der Ferne" ueber eine RS232-Verbindung mit einem neuen BASIC-
 oder Assemblerprogramm ausgestattet werden koennte - nur leider ist der
 Start-Jumper gesetzt und die Unit ignoriert saemtliche Programmierversuche.

 Doch auch hierfuer existiert eine Loesung: Man kann die Routinen des
 Betriebssystems, mit denen ein neues BASIC- oder Maschinenspracheprogramm
 zur Unit uebertragen wird, vom laufenden BASIC-Programm aus aufrufen und die
 Unit anschliessend resetten. Das kann ganz einfach durch ein paar
 Sprungbefehle in Assembler geschehen:


                      Hier ein passendes BASIC-Programm:

   
   ' Der folgende SYS-Befehl ruft dieses Assemblerprogramm auf:
   
   '0091 =         org $91
   '0091 : cd0917  jsr $0917    ; BASIC-Programmausfuehrung beenden
   '0094 : cd0aa4  jsr $0aa4    ; Befehlsbyte holen und ausfuehren
   '0097 : cc0800  jmp $0800    ; Betriebssystem neu starten
   '009a : 9d      nop          ; (muss per "jmp" aufgerufen werden!)
   
   #endlos
     if rxd then sys &h91 &h009d,&hcc08,&h0aa4,&h17cd,&hcd09
                                ' wenn ein Byte an der RS232-Schnittstelle
                                ' anliegt, wird dieses so interpretiert,
                                ' als ob es bei nichtgesetztem Startjumper
                                ' (waehrend der IDLE-Loop) empfangen wurde
   
     ...                        ' das restliche BASIC-Programm
   goto endlos


 Natuerlich kann dieses Programm noch verfeinert werden. So ist es z.B.
 denkbar, dass die Unit nur dann in den Programmiermodus geht, wenn der ueber
 RS232 angeschlossene PC zuvor ein bestimmtes Signalbyte sendet. Dann kann
 die RS232-Verbindung auch zum beiderseitigen Datenaustausch benutzt werden.

 Reine BASIC-Programme koennen mit dieser Methode relativ problemlos
 aktualisiert werden. Bei Programmen, die Assemblerroutinen im internen
 EEPROM benutzen, gibt es allerdings folgendes Problem: Die RS232-Kommandos
 werden vom Ladeprogramm bekanntlich sequentiell, d.h. Stueck fuer Stueck
 hintereinander, uebertragen und von der C-Control-Unit ausgefuehrt. Zwischen
 diesen Uebertragungen wird das BASIC-Programm immer wieder neu gestartet, da
 ja der Startjumper gesetzt ist. Wenn das BASIC-Programm nun auf
 Assemblerroutinen im internen EEPROM zugreifen will, kann es sein, dass das
 interne EEPROM zu diesem Zeitpunkt noch nicht die aktualisierte Version des
 Maschinenspracheprogramms enthaelt. Das BASIC-Programm muss auf diesen Fall
 vorbereitet sein und z.B. die Versionsnummer des Programms im internen
 EEPROM abfragen, bevor es eine Assemblerroutine aufruft.

 Es ist auch zu beachten, dass die serielle Schnittstelle von der C-Control
 waehrend der gesamten Update-Phase nicht benutzt werden darf, denn sonst
 wird das Ladeprogramm im PC vermutlich die Uebertragung abbrechen. Ausserdem
 muss die Unit auf jedes gesendete Befehlsbyte natuerlich ausreichend schnell
 reagieren.

 Wenn sich das BASIC-Programm aus irgendeinem Grund "aufhaengt" und deshalb
 nicht mehr die serielle Schnittstelle abfragt oder waehrend der Uebertragung
 des Programms ein Fehler auftritt, ist auf die beschriebene Weise natuerlich
 kein Softwareupdate mehr moeglich und die Unit muss resettet werden. Die
 sicherste Methode fuer ein Softwareupdate ist deshalb, einfach noch eine
 zusaetzliche Leitung mehr als fuer die RS232-Verbindung ohnehin erforderlich
 ist zur Unit zu legen und darueber ein Relais zu steuern. Mit diesem Relais
 kann dann sowohl der RESET-Pin kurz auf Masse gezogen, als auch der
 Startjumper waehrend der Neuprogrammierung unterbrochen werden.

 Es gibt natuerlich noch einige andere Moeglichkeiten, ein Softwareupdate bei
 einer Unit durchzufuehren, die sich ausser Reichweite befindet. Friedhelm
 Boehl hat mir seine auf Hardware basierende Realisierung als ZIP-Archiv
 zugesandt (befindet sich in der Datei NEUPROG.ZIP im WORD_TXT-Verzeichnis).
 Wenn jemand noch andere gute Idee verwirklicht hat, kann er sich ja bei mir
 oder im Forum melden.


 ----------------------------------------------------------------------------
                          Unit per Software resetten                         
 ----------------------------------------------------------------------------

 Manchmal ist es nuetzlich, die C-Control aus der laufenden Anwendung heraus
 zu resetten. Im vorherigen Abschnitt wurde davon beispielsweise Gebrauch
 gemacht. Mit dem "jmp $0800"-Befehl kann das Betriebssystem der Unit aus
 jedem laufenden Programm heraus neu gestartet werden.

 Es gibt aber noch die Moeglichkeit, den "Watchdog" (zu deutsch: Wachhund)
 fuer diesem Zweck zu missbrauchen. Er dient normalerweise zum Resetten des
 Mikrokontrollers und der eventuell am RESET-Pin angeschlossenen externen
 Baugruppen, wenn das laufende Programm abgestuerzt ist.

 Ein einmal aktivierter Watchdog kann aus Sicherheitsgruenden nicht mehr per
 Software deaktiviert werden. Das laufende Programm ist nun gezwungen, einen
 Zaehler in periodischen Zeitintervallen zu loeschen, damit dieser nicht
 ueberlaeuft. Bei einem Ueberlauf loest die Watchdog-Baugruppe einen Reset
 aus und das Programm startet von neuem. So ist eine weitgehende
 Funktionssicherheit des Mikrokontrollers auch bei nicht anwesendem Anwender
 gewaehrleistet.

 Wenn ein Programm den Watchdog zum Resetten der Unit benutzen moechte, muss
 es ihn durch Setzen eines Bits im "Miscellaneous register" aktivieren. In
 Assembler geschieht dies durch den Befehl "bset #0,$0c". Wenn dahinter
 gleich ein "stop"-Befehl folgt, wird sofort ein Reset durchgefuehrt. Nach
 dem Reset ist der Watchdog wieder deaktiviert.


 -----------------------------------------------------------------------------
 Dieser Text gehoert zur Informationssammlung "C-Control intern" von Dietmar
 Harlos zum C-Control-Mikrokontrollersystem. Die Fehlerfreiheit der Angaben
 kann nicht garantiert werden. Die kommerzielle Nutzung irgendeiner Informa-
 tion oder eines Verfahrens dieser Sammlung ist ausdruecklich untersagt!
 Eine aktuelle Version dieser Sammlung kann als ZIP-Archiv im Internet unter
 der Adresse "http://www.geocities.com/ccontrolintern" heruntergeladen werden.
 Weitere Informationen zum Copyright entnehmen Sie bitte der Datei INFO.TXT.
 -----------------------------------------------------------------------------
