Forum: Mikrocontroller und Digitale Elektronik Z180 Programmierung (Timer)


von Marcel A. (dl1ekm)


Lesenswert?

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
von Safari (Gast)


Lesenswert?

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).

von Marcel (Gast)


Lesenswert?

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

von S. R. (svenska)


Lesenswert?

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 Safari (Gast)


Lesenswert?

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...

von Marcel A. (dl1ekm)


Lesenswert?

Ja, ich weiß...
Mir geht es aber im Moment darum, die Z180 Programmierung für den Timer 
hinzubekommen. Welche Register, wo liegt die ISR...

von c-hater (Gast)


Lesenswert?

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.

von Marcel (Gast)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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).

von Marcel A. (dl1ekm)


Lesenswert?

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 :-)

von Leo C. (rapid)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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. ;-)

von Marcel (Gast)


Lesenswert?

Hi Sven,

es gibt für die CP/M Sticks mit AVR auch eine Weiterentwicklung mit Z80 
statt 8080. brauchst du die Links?

von Holm T. (Gast)


Lesenswert?

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

von Marcel A. (dl1ekm)


Lesenswert?

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?

von Tom (Gast)


Lesenswert?

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).

von Leo C. (rapid)


Lesenswert?

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.

von Marcel A. (dl1ekm)


Lesenswert?

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 :-)

von S. R. (svenska)


Lesenswert?

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
Noch kein Account? Hier anmelden.