Hallo, ausgehend von dem Aufbau des absolut genialen Z180 (genauer Z8S180 ) Testsystems "STAMP Z180" [[Beitrag "Z180-Stamp Modul" ]] habe ich angefangen, mich ein wenig mit der Programmierung zu beschäftigen Neben ein paar einfachen Übungen aus dem Zaks wollte ich nun einmal ein Lauflicht programmieren. Dazu habe ich eine 8-Bit Parallel-Ausgabe-Karte aus einem MFA-Computer umverdrahtet. Die Ansteuerung über OUT (Adresse CC) geht super. Da der Rechner aber mit über 18MHz läuft, sind sämtliche Schleifen-Verzögerungen immer noch zu wenig - daher wollte ich es einmal mit den Timern versuchen. Dazu habe das user guide verwendet [[https://cdn.hackaday.io/files/9907423861088/Z180.pdf]]. Leider steh ich da noch ziemlich auf dem Schlauch - immerhin komme ich aus einer Zeit weit nach Assembler :-) Ich habe zwei kleine Anleitungen für Z80 und "echte" CTCs gefunden [[www.blunk-electronic.de/train-z/pdf/howto_program_the_Z80-CTC.pdf]], die kaskadiert und an die Interupt-Leitung angeschlossen werden, da habe ich einige Ideen gewonnen, aber der Z180 hat ja stattdessen 2 16-bit Programmable Reload Timer im Bauch. Laut Leo nutzt das CPM 3 den PRT0, so bliebe noch der PRT1. Sein Ausgang liegt auf A18 legen, aber wir brauchen den für die volle Speicheradressierung. Laut Z8S180 user guide kann man den Ausgang (TOUT1) auch ganz abschalten. Dazu im Time Control Register (TCR) die Bits TOC0 und TOC1 auf 0 setzen. Habe ich richtig gelesen - das kann man einfach über IO-Adrese 10H beieinflussen? Dann müsste man noch das Bit TIE1 (Timer Interupt Enable) auf 1 setzen, damit ein Interrupt beim Erreichen von 0 ausgelöst wird? Im Bit TIF1 (Timer Interupt Flag) kann man prüfen, ob ein Interupt ausgelöst wurde (Counter auf 0). Die beiden 8-Bit-Teile des PRT1 (Timer Reload Register Channel 1 - RLDR1) finde ich unter 16H und 17H? Zum Setzen muss im TCR noch Bit TDE1 (Timer DownCount Enable) auf 0 gesetzt werden (falls man etwas anderes als FFFF einschreiben will). Um den Zähler zu aktivieren, muss TDE1 dann auf wieder auf 1 gesetzt werden? Wenn ich das richtig verstehe wird bei jedem 20. Takt eins nach unten gezählt. Damit hätte ich einen Zähltakt von ungefähr 915khz. Bei vollen Zählern würde also nach etwa 1/14 Sekunde ein Interrupt ausgelöst. Nun stellen sich mir einige Fragen: - Was ist der beste Weg, das noch weiter zu reduzieren? Meine Idee wäre, eine Speicherstelle als Zähler zu nehmen und hochzuzählen. Bei 14 wäre 1 Sekunde um - Laut Leo muss die Interrupt-Routine im Bereich F000 - F300 (common) liegen. Was ich nicht gefunden habe ist, wo man die Interupt-Routine (Adresse) festlegt, die aufgerufen wird, wenn PRT1 auf 0 geht? Beim CTC finde ich Hinweise, dass dies über einige Bits im in einem Register geht (z.B. CTC3 holt sich je nach Programmierung die Zieladresse bei 16h). - Muss ich den Z180 im IM 2 fahren? - Wie packe ich das alles in ein Programm? Init Timer, Init Interupt (IM, EI), Definition Ziele, ISR, .... Besten Gruß Marcel
:
Bearbeitet durch User
Ich mache sowas mit einer "Software-System-Uhr". Den Timer z.B. auf 1mS. Die ISR zählt nur eine Speicherstelle (32 oder mehr bit von Vorteil) hoch. Dann ein paar Helfer-Routinen: get_ticks - liefert Inhalt der Speicherstelle reset_ticks get_uptime_s Ein Delay funktioniert dann so:
1 | void systick_delay(int ticks) { |
2 | uint32_t ticks_start = systick_counter; |
3 | |
4 | while(systick_counter < (ticks_start + ticks)); |
5 | |
6 | return; |
7 | }
|
Solange der Counter kleiner ist, als sein Wert bei Eintritt+die Wartezeit, blockiert diese Funktion. (while ... tue nichts).
Danke dir, ja, so mache ich das in etwa mit C unter einem Arduino. Hier soll es aber Z80 Assembler sein und Speicherstellen sind 8Bit, viel Kompfort (Schleifen, Variablen...) gibts da nicht :-) Mir geht es vor allem um die spezielle Initialisierung der Z180 Timer und die Auswertung/Steuerung der Interrupts. Gruß Marcel
Marcel schrieb: > Hier soll es aber Z80 Assembler sein und Speicherstellen sind 8Bit, viel > Kompfort (Schleifen, Variablen...) gibts da nicht :-) Du darfst auch zwei davon benutzen. ;-) Der Z80 kann auch 16 Bit-Addition.
Von Hand in ASM mit vier Bytes zu hantieren, ist auch kein Hexenwerk. C ist auch nur ein aufgebohrter Makro-Assembler oder Pseudocode-Interpreter. Wenn die Logik in kleine Häppchen aufgeteilt ist, kann das 1:1 in die andere Sprache (ASM) übertragen werden. Wenn ein Byte überläuft, wird ein Overflow Flag (ich nenne es mal OVF) gesetzt. Pseudo-Code:
1 | .db b0, b1, b2, b3; |
2 | |
3 | increment_systick: |
4 | inc b0 |
5 | if OVF==0 jmp outhere ; wenn nicht übergelaufen, fertig |
6 | inc b1 |
7 | if OVF==0 jmp outhere ; ... |
8 | ...
|
9 | outhere: |
In einer ISR ist dafür auf jeden Fall noch Zeit...
Ja, ich weiß... Mir geht es aber im Moment darum, die Z180 Programmierung für den Timer hinzubekommen. Welche Register, wo liegt die ISR...
Marcel A. schrieb: > Leider steh ich da noch ziemlich auf dem Schlauch - immerhin komme ich > aus einer Zeit weit nach Assembler Also aus einer unendlich weit entfernten Zukunft? Weil: Assembler und Assemblerprogrammierer muss es immer geben, denn: wer schreibt denn sonst den Compilernutzern ihre Werkzeuge? > Mir geht es aber im Moment darum, die Z180 Programmierung für den Timer > hinzubekommen. Welche Register, wo liegt die ISR... Normalerweise läuft das bei Hardwareprogrammierung IMMER so (also egal ob Assembler, C oder sonstwas): man besorgt sich das Datenblatt der Hardware, liest was da drin steht, versteht es und wendet es dann an. > genauer Z8S180 Na also, du weisst immerhin schon, womit du hantierst. Also sollte es kein großes Problem sein, das entsprechende DB aufzutreiben. Lesen, verstehen und anwenden kann dir niemand abnehmen, das musst du schon selber tun.
Ja, natürlich habe ich (wie oben geschrieben) das Datenblatt (und vieles mehr). Ich bin aber eher der Mensch, der (auch) aus Beispielen lernt. So erarbeite ich mir einiges zum Z80 - denn da gibt es zum Glück viele Unterlagen. Ich raffe halt beim Z180 Timer nicht, wo da Startadresse der ISR für den Interrupt bei 0-Durchlauf hinterlegt werden muss. Oder ich fahren den Z180 im IM 1, dann müsste er bei einem Interruft 038H anspringen - so mein Verständnis.
Wenn ich jetzt keinen Stuss erzähle, ist das ganz einfach. Dein Timer-Handler liegt bei irgendeiner Adresse. Der Timer 0 ist INT 4, damit ist der 4. Eintrag der Tabelle für diesen Interrupt notwendig. Du schreibst also deine Handler-Adresse in die Bytes 8 (low)/9 (high) der IVT. Die IVT selbst hat auch eine Adresse, denn sie liegt irgendwo im Speicher (mit 32 Byte-Alignment). Das High-Byte dieser Adresse kommt in's I-Register, das Low-Byte an I/O-Adresse 33h (durch das Alignment sind die unteren 5 Bit null).
Hallo Sven SUPER! Das waren die entscheidenden Hinweise, mit denen ich die Doku durchforsten konnte. Nun habe ich ein klares Bild und kann experimentieren. Bei den Interrupts ist ja beschrieben, wie die Tabelle aufgebaut wird, etwas weiter unten die Stelle, an welcher Stelle PRT1 (hier 5) seine Zieladresse sucht usw. So macht es Spaß - auch wenn ich als Anfänger hier noch viel probieren muss. Über Sinnhaftigkeit müssen wir nicht reden - in TP3.0 waren das 8 Zeilen Code :-)
Marcel A. schrieb: > Laut Leo nutzt das CPM 3 den PRT0, Ja. > so bliebe noch der PRT1. > Sein Ausgang > liegt auf A18 legen, aber wir brauchen den für die volle > Speicheradressierung. > Laut Z8S180 user guide kann man den Ausgang (TOUT1) auch ganz > abschalten. Dazu im Time Control Register (TCR) die Bits TOC0 und TOC1 > auf 0 setzen. Habe ich richtig gelesen - das kann man einfach über > IO-Adrese 10H beieinflussen? Genau. > Dann müsste man noch das Bit TIE1 (Timer Interupt Enable) auf 1 setzen, > damit ein Interrupt beim Erreichen von 0 ausgelöst wird? Ja, aber vorher den Timer (und ggf. Vektor) initialisieren. > Im Bit TIF1 (Timer Interupt Flag) kann man prüfen, ob ein Interupt > ausgelöst wurde (Counter auf 0). Ja, aber da der Timerkanal einen eigenen Int-Vektor hat, kann man sich das sparen. > Die beiden 8-Bit-Teile des PRT1 (Timer Reload Register Channel 1 - > RLDR1) finde ich unter 16H und 17H? Zum Setzen muss im TCR noch Bit TDE1 > (Timer DownCount Enable) auf 0 gesetzt werden (falls man etwas anderes > als FFFF einschreiben will). > Um den Zähler zu aktivieren, muss TDE1 dann auf wieder auf 1 gesetzt > werden? Die Timer Datenregister (TMDR) können jederzeit gelesen werden, wenn man die richtige Reihenfolge beim Lesen von Low- und High-Byte beachtet. Zum Schreiben der Reload- und Datenregister sollte der entsprechende Timer gestoppt werden (TDEx auf 0). Siehe dazu den Abschnitt "PRT Operation Notes" im von Dir verlinkten User Manual. > Wenn ich das richtig verstehe wird bei jedem 20. Takt eins nach unten > gezählt. Damit hätte ich einen Zähltakt von ungefähr 915khz. Bei vollen > Zählern würde also nach etwa 1/14 Sekunde ein Interrupt ausgelöst. > > Nun stellen sich mir einige Fragen: > - Was ist der beste Weg, das noch weiter zu reduzieren? Meine Idee wäre, > eine Speicherstelle als Zähler zu nehmen und hochzuzählen. Bei 14 wäre 1 > Sekunde um > - Laut Leo muss die Interrupt-Routine im Bereich F000 - F300 (common) > liegen. Was ich nicht gefunden habe ist, wo man die Interupt-Routine > (Adresse) festlegt, die aufgerufen wird, wenn PRT1 auf 0 geht? Beim CTC > finde ich Hinweise, dass dies über einige Bits im in einem Register geht > (z.B. CTC3 holt sich je nach Programmierung die Zieladresse bei 16h). > - Muss ich den Z180 im IM 2 fahren? > - Wie packe ich das alles in ein Programm? Init Timer, Init Interupt > (IM, EI), Definition Ziele, ISR, .... Grundsätzlich könntest Du Dir natürlich einen beliebigen Interrupt-Mode aussuchen. Die Z180-interne Peripherie funktioniert aber nur sinnvoll mit IM2. Außerdem benutzt das BIOS wie erwähnt bereits Interrupts für Timer0 sowie die beiden seriellen Schnittstellen. Wenn Dein Programm also unter CP/M laufen soll, müssen die bestehenden Vektoren erhalten bleiben oder von Dir passend ersetzt werden. Du kannst Dir aber auch einiges vom Z180-Stamp-BIOS abgucken. Die Vektortabelle wird in "intinit" (misc.180) mit Dummyvektoren vorbelegt. Die Timer0-Funktionen sind in der Datei time.180: prt0ini: Der Vektor für die Timer0-ISV wird gesetzt und der Timer für eine Periode von 1,25ms initialisiert. (1,0ms geht leider nicht) isvprt0: Die Timer0 ISV. Der Stackpointer wird auf einen lokalen Stack umgeschaltet, da der BDOS-Stack leider sehr klein ist. TIF0 wird durch Lesen der Timer-Register zurückgesetzt. Danach wird ein lokaler (laufender) Timeout Counter auf 0 herunter gezählt und der 32 Bit Uptime Counter um 1 erhöht. In der Datei sind auch noch die beiden Funktionen gtimer (32 Bit) und gstimer (16 Bit) interessant. Sie liefern den aktuellen Uptime-Wert wenn mit 0 als Parameter aufgerufen, bzw. die Differenz zu einem (vorher gelieferten) Wert. Die Routinen können nicht aus der TPA (Anwenderprogramm) aufgerufen werden. Man kann sie aber kopieren oder als Vorlage für eigene Funktionen verwenden.
Marcel A. schrieb: > Über Sinnhaftigkeit müssen wir nicht reden - in TP3.0 waren das 8 Zeilen > Code :-) Ich habe CP/M auf einem Atmega8515 (16 MHz) und 32 KB SRAM am Laufen. Der emulierte i8080 schafft etwa 2 MHz für NOP. ;-)
Hi Sven, es gibt für die CP/M Sticks mit AVR auch eine Weiterentwicklung mit Z80 statt 8080. brauchst du die Links?
S. R. schrieb: > Marcel A. schrieb: >> Über Sinnhaftigkeit müssen wir nicht reden - in TP3.0 waren das 8 Zeilen >> Code :-) > > Ich habe CP/M auf einem Atmega8515 (16 MHz) und 32 KB SRAM am Laufen. > Der emulierte i8080 schafft etwa 2 MHz für NOP. ;-) Die Z180 Stamp schafft aber 18Mhz..ohne Emulation, das ist auf andere Weise auch wieder problematisch, man findet keine "Standardperipherie" die man da anschließen könnte. Ich habe 2 Stück PLCC 16Mhz PIOs da liegen..mal sehen ob die das mitmachen. 32Kbyte mit CP/M ist nicht gerade die Erfüllung, da läuft doch kein CP/M Programm richtig. Gruß, Holm
Hallo Leo, wow, das ist schon Code der Extraklasse... den muss man als Anfänger erst mal verstehen :-) Frage 1: Du verwendest eine ganze Menge der SLR180-Pseudos :-) Warum nutzt du bei intinit out0 (il),l statt einem normalen OUT auf 33H? OUT0 MACRO ?P,?R DB 0EDH,1+(??&?R AND 7) SHL 3,?P ENDM Ich "vermute" mal: ED steht für OUTD, aber was passiert dann...? Frage 2: Wenn ich die Timer unter CPM nutze, dass gibt es doch schon die IVT, die hast du ab FFC0 abgelegt und da kann ich doch meine Sprungadresse für PRT1 ablegen?
Marcel A. schrieb: > Wenn ich die Timer unter CPM nutze, dass gibt es doch schon die IVT, die > hast du ab FFC0 abgelegt und da kann ich doch meine Sprungadresse für > PRT1 ablegen? Ja. Der saubere Weg ist es, die IVT zu kopieren, abzuändern und dann den Basisvektor umzustellen. Bei Programmende brauchst Du nur den Vektor zurückstellen und alles ist sauber (solange zwischendrin nicht jemand anders an der Vektortabelle gedreht hat).
Marcel A. schrieb: > Frage 1: Das sind aber schon 2 Fragen. :) > Du verwendest eine ganze Menge der SLR180-Pseudos :-) Welche denn (z.B.)? > Warum nutzt du bei intinit > out0 (il),l > statt einem normalen OUT auf 33H? Die Z180-interne Peripherie dekodiert volle 16 Bit vom Adressbus. D.h., das Highbyte muß 0 sein. Bei den Z80 I/O-Befehlen liegt aber etwas anderes auf der oberen Adresshälfte [1]. U.a. deshalb gibt es beim Z180 zusätzliche I/O-Befehle, bei denen die obere Adressbushälfte immer 0 ist. > OUT0 MACRO ?P,?R > DB 0EDH,1+(??&?R AND 7) SHL 3,?P > ENDM > Ich "vermute" mal: ED steht für OUTD, aber was passiert dann...? Diese Macros sind nicht von mir, und der SLR-Assembler braucht sie nicht. Die sind für reine Z80-Assembler, die die Z180-Befehle nicht kennen. OUT0 ist ein 2-Byte Befehl. ED ist dabei das erste Byte (Opcode Prefix), und das 2. Byte wird aus den Parametern zusammen gebastelt. Wenn Du Dir im User Manual die Opcode Map anschaust (Table 50. 2nd Op Code Map Instruction Format: ED XX), erkennst Du vielleicht die Systematik. > Frage 2: > Wenn ich die Timer unter CPM nutze, dass gibt es doch schon die IVT, die > hast du ab FFC0 abgelegt und da kann ich doch meine Sprungadresse für > PRT1 ablegen? So ist es. Mein letzter Post enthält (etwas indirekt) die Anleitung dazu. "prt0ini: Der Vektor für die Timer0-ISV wird gesetzt " [1] Bei IN und OUT liegt auf der oberen Hälfte der Akku, bei den Block-I/O-Befehlen, bzw. allen Befehlen, bei denen die I/O-Adresse in C ist, liegt auf der oberen Hälfte Register B.
Leo C. schrieb: > der SLR-Assembler braucht sie > nicht Ah - ich hatte im (PDF-Scan des) Handbuchs nach OUT0 gesucht und nichts gefunden - der Scan liefert OUTO - da ist es erklärt :-)
Holm T. schrieb: > 32Kbyte mit CP/M ist nicht gerade die Erfüllung, da läuft doch kein CP/M > Programm richtig. Das stimmt allerdings. Allerdings ist es so nur ein Drei-Chip-System (AVR, Latch, SRAM) und ich war zu faul, mehr zu löten.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.