|
|
entwickelt von Ronald Daleske |
|
|
|
z-meic = (z)80 - (m)odular (e)rweiterbare e(i)nplatinen (c)omputer
Beschreibung der Arbeitsweise der Firmware (inklusive CP/M)
Die Firmware des z-meic wird durch das Pascal-Programm "z-meic.PAS" mit Hilfe des RONPAS-Compilers generiert.
Die Z80-CPU verbleibt nach dem Start zunächst im Ruhezustand (keine Taktversorgung), da zu diesem Zeitpunkt noch kein Programmcode für die Z80-CPU geladen wurde.
Nach dem die Firmware des ATMEGA 32A den Z80-Urloader mit dem CP/M-BIOS aus dem PASCAL-Array (MEIC_TXT.INC) in den SRAM geladen hat (auf die Adresse 0000H), übernimmt die Z80-CPU den Master-Betrieb (zwischen den beiden Rechnersystemen). Der Microcontroller ATMEGA 32A fungiert als Taktgenetators für die Z80-CPU sowie nach Bedarf auch als Z80 I/O (Ein- und Ausgabeeinheit). Siehe dazu 7.2.1. Master-Slave-Betrieb des Mikrocontrollers ATMEGA32A.
1. Master-Slave-Betrieb des Mikrocontrollers ATMEGA32A
Die Firmware des z-meic hat zwei grundsätzlich unterschiedliche Betriebsarten für den Mikrocontroller ATMEGA 32A (und damit auch für die Z80-CPU).
1.1. Master-Betrieb
Der Start des z-meic erfolgt durch Starten des ATMEGA32A nach dem Anlegen der Spannung (Power on RESET) oder nach dem Drücken des "RESET-Buttons" . Der ATMEGA32A ist nach dem Start der Master-Controller. Die Z80 CPU erhält vom ATMEGA32A noch keine Taktsignale und ist derzeit inaktiv.
Quelltext: RONPAS-Compiler, Quelldatei:
z-meic.PAS |
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
|
InitPorts;
Init_I2C;
Init_SPI;
if Test_I2C_vorhanden then
INIT_FLASH_Manager;
I2C_vorhanden:=true;
else
I2C_vorhanden:=false;
endif;
Init_ITP3;
INIT_Laufwerke_Verteiler;
ConsoleIn:=0;
ConsoleOut:=0;
SoundOut:=0;
Steuer_String:=';
ConsoleIn_gelesen:=false;
DISKNO:=0;
// Laden des Z80-Bios
Z80_RESET_und_URLOAD;
|
|
Die ersten Aktivitäten des ATMEGA32A nach dem Start sind:
- Initialisierung des ATMEGA32A und der hardwarenahen Softwareroutinen (Zeilen 294 bis 315)
- Laden des Z80-Bios durch "Z80_RESET_und_URLOAD" (Zeilen 327) - ausführliche Beschreibung siehe "7.2.2. Beschreibung des SRAM-Loaders"
Es folgen diverse Initialisierungs- und Abfrageroutinen für die Hardware zum Betrieb des z-meic (bis zur Zeile 433).
1.2. Slave-Betrieb
Nach dem Übergang in die Endlosschleife (Zeilen 439 bis 450) für den Takt des Z80 geht der ATMEGA 32A vom Master- in den Slave-Betrieb über. Das bedeutet, dass der ATMEGA 32A nicht mehr direkt Routinen abarbeitet.
Inzwischen ist der SRAM ab Adresse 0000H mit dem Boot-Code gefüllt, die Z80-CPU ist zurückgesetzt (RESET) und die Z80-CPU beginnt (angetrieben durch die Taktgenerierung) mit der Abarbeitung des Boot-Codes.
Endlosschleife fuer Takterzeugung aus dem Quelltext des z-meic
Quelltext: RONPAS-Compiler, Quelldatei:
z-meic.PAS |
437
438
439
440
441
442
443
444
445
446
447
448
449
450
|
// Endlosschleife fuer Takterzeugung
// Taktfrequenz etwa 3 MHz
LOOP
ClearBit(CLK_Z80); // 2 Takte
ASM;
SBIS PinB, 1
CALL AUSWERTEN_IORQ
ENDASM;
SetBit(CLK_Z80); // 2 Takte
ENDLOOP;
|
|
die gleiche Endlosschleife fuer Takterzeugung in ATMEGA-Assembler (übersetzt durch den RONPAS-Compiler)
Quelltext: AVR-Assembler, Quelldatei:
z-meic.asm |
22499
22500
22501
22502
22503
22504
22505
22506
22507
22508
22509
22510
22511
22512
22513
22514
|
; LOOP - Start_Marke
M_LOOP_01324:
; ClearBit(CLK_Z80)
CBI PORTD, 7
; ASM
SBIS PinB, 1
CALL AUSWERTEN_IORQ
; SetBit(CLK_Z80)
SBI PORTD, 7
; LOOP - Ende
RJMP M_LOOP_01324
|
|
Die Takterzeugung für den Z80 und das CP/M erfolgt bei anderen Systemen mit einem festen Takt aus einem Quarzgenerator oder einem Takt generiert über den Timer des ATMEGA (so auch bei allen Versionen von Z80-MBC und Z80-MBC2). Für die Reaktion auf eine IO-Operation (Z80 IN und OUT-Befehl) wird dann eine spezielle Hardwarelösung benötigt.
Beim z-meic wird die Hardwarelösung eingespart und der Takt in einer kleinen Assemblerschleife erzeugt.
Berechnung der Taktfrequenz für die Z80 CPU: 14,7 MHz / 9 Takte = 1,6 MHz
Unterbrechung der Takterzeugung bei einem I/O-Befehl (IN oder OUT)
Nach jeder steigenden Flanke des Taktes für die Z80-CPU wird die IORQ-Leitung abgefragt (Zeile 444 in "z-meic.PAS") und bei deren Aktivierung (IORQ=LOW) verzweigt die Assemblerroutine zur RONPAS-Prozedur "AUSWERTEN_IORQ". Nun erwacht der ATMEGA 32A aus seinem Dornröschenschlaf und arbeitet die I/O-Anforderung ab.
Danach wird die Endlosschleife wieder fortgesetzt, bis die nächste I/O-Anforderung erkannt wird.
2. Beschreibung des SRAM-Loaders
In vielen klassischen CP/M-Computern wird der Befehlscode für den Z80 Prozessor in einen EPROM gebrannt. Nach dem Start der Z80-Prozessors wird dieser dann nach dem RESET ab Adresse 0000H abgearbeitet. Oft wird der adressierbare Speicherbereich in 2 Teile unterteilt. Im unteren Teil wird der EPROM aressiert und im oberen Bereich (z.B. ab Adresse 8000H) wird der RAM adressiert. So kann der Stardcode (BOOT-Loader) einfach vom EPROM in den RAM kopiert werden. Danach wird der EPROM ausgeblendet und der gesamte Speicherbereich kann nun als SRAM genutzt werden.
So weit, so gut. Aber die Neuprogrammierung eines EPROMs ist recht aufwendig. Er muss aus der Fassung genommen werden, mit UV-Licht gelöscht werden (dauert etwa 20 Minuten) und anschließend wieder mit einem speziellem EPROM-Programmer neu programmiert weden.
Bein z-meic werden Daten mit einem kleinen "Trick" vom ATMEGA 32A an eine beliebiger Stelle in den SRAM geladen. Dazu wird die Z80-CPU als Adressgenerator "missbraucht", ohne das diese ein besonderes Programm abarbeiten muss.
Die Grundidee dieses Verfahrens wurde vom "Mini80 Overview":
Mini80 Overview
übernommen.
Der Z80-Prozessor hat einen interessanten Befehl den NOP (No OPeration). Dieser Befehl hat den Befehlscode 00H und macht eben nichts. Es werden keine Register verändert. Nach dem Befehl wird der Befehlszähler um ein Bit erhöht und der Z80-Prozessor lädt den nächsten Befehl. Wenn der nächste Befehl wieder ein NOP-Befehl ist, dann läuft der Adresszähler (und die Adressleitungen) von 0000H nach dem RESET solange bis FFFFH weier, oder man ihn vorher unterbricht.
|
Bild 7.2.2b: Befehlscodelesezyklus (M1-Zyklus) des Z80-Prozessors |
Mit diesem Trick fungiert der Z80-Prozessor als Adressgenerator. Der Ablauf ist im Bild 2b dargestellt und läuft so ab:
- Aktivieren der RESET-Leitung des Z80-Prozessors durch setzen auf Low-Pegel.
- Senden einiger Takte, damit der Z80-Prozessor intern das RESET abarbeiten kann.
- Die Steuersignale "/RD" und "/WR" des Z80-Prozessors werden durch den Baustein 74HC244 mit der Steuerleitung "/RAM_SW" des ATMEGA32A für den SRAM ausgeblendet.
- Der ATMEGA32A überwacht aber über die Leitungen "/Z80_RD" und "/Z80_WR" die Steuersignale "/RD" und "/WR" des Z80-Prozessors.
- Nun wird die RESET-Leitung des Z80-Prozessors wieder deaktiviert (auf High-Pegel gesetzt) und es werden nacheinander Taktsignale vom ATMEGA32A Anschluß Z80_CLK an den Z80-Prozessor gesendet, bis dieser den ersten Befehl ab der aktuellen Adresse des Programmzählers (0000H) lesen möchte.
- Die Adresse 0000H liegt mun sauber auf dem Adressbus und die Datenleitungen des Z80-Prozessors sind auf Eingang geschaltet (es soll ja eigentlich ein Byte gelesen werden), siehe dazu den Markierung A im Bild 2b.
- Jetzt kann der ATMEGA32A über seine Datenleitungen D0..D7 auf den Adressbus das erste Byte für den SRAM bereitstellen.
Quelltext: RONPAS-Compiler, Quelldatei:
MEIC_TXT.INC |
|
Laenge_MEIC_Array : Word = $0328;
MEIC_ARR : array[0..Laenge_MEIC_Array] of Byte = (
$F3,$31,$80,$FE,$21,$12,$00,$11,$00,$F0,$01,$9D,$08,$ED,$B0,$C3, // 0000H
$00,$F0,$C3,$5B,$F1,$C3,$7D,$F1,$C3,$A7,$F1,$C3,$B4,$F1,$C3,$BF, // 0010H
$F1,$C3,$C7,$F1,$C3,$CF,$F1,$C3,$D7,$F1,$C3,$D8,$F1,$C3,$DE,$F1, // 0020H
$C3,$F4,$F1,$C3,$F9,$F1,$C3,$FE,$F1,$C3,$03,$F2,$C3,$27,$F2,$C3, // 0030H
$4B,$F2,$C3,$4E,$F2,$00,$02,$00,$00,$80,$00,$00,$00,$00,$00,$00, // 0040H
|
|
- Dieses Byte wird aus dem CONST-Array "MEIC_ARR" in der INCLUDE-Datei "MEIC_TXT.INC" gelesen. Konkret ist es F3H (auf Adresse 0000H im Array).
- Mit der Aktivierung der "/WE"-Leitung über die Leitung "/RAM_WE" des ATMEGA32A (kurz auf Low und dann wieder auf High) wird dieses erste Byte F3H in den SRAM geschrieben.
- Es werden anschliessend so lange Takte generiert, bis der Z80-Prozessor das /RD-Signal auf High-Pegel legt (Markierung B im Bild 7.2.2b).
- Es werden weitere Takte generiert und der Z80-Prozessor arbeitet den NOP-Befehl weiter ab (senden eines REFRESH-Bytes für ein eventuell angeschlossenen DRAM), verändert dabei aber keine Register.
- Damit ist dieser Befehlszyklus abgeschlossen.
- Werden nun weiter Takte generiert, beginnt der Z80-Prozessor den nächsten Befehlsholezyklus.
- Dies wird so lange fortgesetzt, bis das CONST-Array "MEIC_ARR" mit der Länge Laenge_MEIC_Array in den SRAM geschrieben wurde.
Nach dem Beschreiben des SRAMs aus dem CONST-Array "MEIC_ARR" kann die Steuerleitung "/RAM_SW" des ATMEGA32A den Baustein 74HC244 wieder deaktivieren (die Steuersignale "/RD" und "/WR" des Z80-Prozessors werden wieder an den SRAM geleitet).
Quelltext: RONPAS-Compiler, Quelldatei:
SRAM.INC |
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
|
// Kopieren des MEIC_ARR in den SRAM
procedure lade_meic_Array_in_RAM;
begin
// 3. Abschalten der /RD und /WR Signale der Z80 an den RAM
SetBit(G_RAMSW);
// OE und WE aktivieren
SetBit(OE_RAM_A);
SetBit(WE_RAM_A);
// OE und WE DIR Ausgabe
SetBit(OE_RAM_D);
SetBit(WE_RAM_D);
// OE und WE aktivieren
SetBit(OE_RAM_A);
SetBit(WE_RAM_A);
for Array_Pos:=0 to Laenge_MEIC_Array do
warte_solange_RD_High;
// 5. Einschreiben der Daten vom CPD5_ARR in den RAM
// an der aktuellen Adresse (entspricht Array_Pos)
// Daten-Port auf Schreiben
DDRC := %11111111;
// Anlegen der Z80-Daten
PORTC := MEIC_ARR[Array_Pos];
NOP;
NOP;
NOP;
ClearBit(WE_RAM_A);
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
SetBit(WE_RAM_A);
// 6. Anlegen von NOP an den Datenport fuer die Z80
PORTC := 0; // NOP
warte_solange_RD_Low;
// Daten-Port auf Lesen
DDRC := %00000000;
// 8. Wiederholen bis das ganze ARRAY in den RAM geladen wurde
endfor; { for Array_Pos:=0 to Laenge_CPD6_Array do }
// OE und WE aktivieren
SetBit(OE_RAM_A);
SetBit(WE_RAM_A);
// OE und WE DIR Eingabe
ClearBit(OE_RAM_D);
ClearBit(WE_RAM_D);
// 11. Zuschalten der /RD und /WR Signale der Z80 an den RAM
ClearBit(G_RAMSW);
end lade_meic_Array_in_RAM;
|
|
Dieser hier beschriebene Ablauf wird durch die Prozedur "lade_meic_Array_in_RAM" in der INCLUDE-Datei "SRAM.INC" realisiert.
3. Starten des CP/M durch den BOOT-Loader (Kaltstart)
Quelltext: RONPAS-Compiler, Quelldatei:
Z80.INC |
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
// laedt das Z80-BIOS in den RAM
// und startet es durch RESET
procedure Z80_RESET_und_URLOAD;
begin
// 0. /RESET auf High setzen
SetBit(RESET_Z80);
sende_50_Takte;
// 1. /RESET auf LOW setzen
ClearBit(RESET_Z80);
// 2a. Senden von min. 3 Takten (10 Takte)
sende_50_Takte;
// 2b. /RESET auf High setzen
SetBit(RESET_Z80);
lade_meic_Array_in_RAM;
// 9. /RESET auf LOW setzen
ClearBit(RESET_Z80);
// 10a. Senden von min. 3 Takten (10 Takte)
sende_50_Takte;
// 10b. /RESET auf High setzen
SetBit(RESET_Z80);
end Z80_RESET_und_URLOAD;
|
|
Während der Initialisierungsphase wird die Prozedur "Z80_RESET_und_URLOAD" in der INCLUDE-Datei "Z80.INC" aufgerufen.
In dieser Prozedur wird die Z80-CPU zurückgesetzt (RESET) und es wird die Prozedur "lade_meic_Array_in_RAM" in der INCLUDE-Datei "SRAM.INC" aufgerufen.
Unter "2. Beschreibung des SRAM-Loaders" wurde beschrieben, wie der die Prozedur "lade_meic_Array_in_RAM" den Z80-BOOT-Loader Code aus dem CONST-Array "MEIC_ARR" in der INCLUDE-Datei "MEIC_TXT.INC" in den SRAM lädt.
Damit ist folgender Grundzustand vorhanden:
- Die Z80-CPU ist zurückgesetzt und bereit zum lesen des Codes ab Adresse 0000H.
- Der SRAM ist mit dem Boot-Loader und dem CP/M-BIOS (aus dem CONST-Array "MEIC_ARR" ab Adresse 0000H beschrieben.
Nach der Übergabe des Hauptprogramms an die Endlosschleife für die Takterzeugung der Z80-CPU (siehe 1.2. Slave-Betrieb) wird der Urloader gestartet.
Quelltext: Z80-Assembler, Quelldatei:
MEICULDR.INC |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
; Loader fuer ZMEIC
TITLE MEICLDR
.Z80
; ***** Programmbeginn *****
START:
DI
LD SP,BIOSSTACK
;
LD HL,UPPER_MEM1
LD DE,BIOSBASE
LD BC,1*(UPPER_MEM2-UPPER_MEM1) ;Laenge der Uebertragungsdateien
LDIR
;
JP BIOSBASE
|
|
Der Urloader kopiert den BIOS-Code (er liegt zu diesem Zeitpunkt kurz hinter dem Urloader) in den SRAM-Bereich ab Adresse F000H und startet anschliessend das BIOS (genauer das Cold-BIOS - CBIOS) durch Sprung auf die Adresse F000H.
4. Lade des BDOS und des CCP in den SRAM
Das BDOS und das CCP werden nicht direkt von der Firmware des ATMEGA32A aus kopiert.
Vielmehr werden im BIOS des CP/M die beiden Z80-Unterprogramme "COPY_BDOS_TO_RAM" und "COPY_CCP_TO_RAM" aufgerufen.
Quelltext: Z80-Assembler, Quelldatei:
MEICBIOS.INC |
195
196
197
198
199
200
201
202
|
; *** Kopieren des BDOS in den RAM ***
CALL COPY_BDOS_TO_RAM
; *** Kopieren des CCP in den RAM ***
CALL COPY_CCP_TO_RAM
|
|
Das Z80-Unterprogramm "COPY_BDOS_TO_RAM" in der Z80-INCLUDE-Datei "MEICIORQ.INC" lädt das HL-Register mit der Adresse an die das BDOS kopiert werden soll ("BDOSBASE EQU 0E200H").
Das A-Register wird mit dem Code für "IORQ_BDOS2RAM" geladen ("IORQ_BDOS2RAM EQU 11") und an die Adress-Konstante "AUSGANG" ("AUSGANG EQU 0") ausgegeben.
Hinweis: Für alle IN und OUT Operationen wird das Adressbyte "AUSGANG" genutzt, das aber im RONPAS-Compiler nicht weiter ausgewertet wird, da es nur diese eine Adresse gibt. Wichtig ist nur der IN- oder OUT-befehl, der den IORQ-Ausgang aktiviert.
Quelltext: Z80-Assembler, Quelldatei:
MEICIORQ.INC |
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
; *** COPY_BDOS_TO_RAM ***
;
; Aufruf: COPY_BDOS_TO_RAM
;
; Funktion:
; Dies ist die Service-Routine fuer das Kopieren des BDOS in den RAM.
; Das eigentliche Kopieren wird durch den Mikrocontroller nach dem
; Aufruf dieser Funktion vorgenommen. Das Kopieren beginnt an der Adresse BDOSBASE.
;
; Uebergabeparameter: keine
; Rueckgabeparameter: keine
;
COPY_BDOS_TO_RAM:
LD HL,BDOSBASE
LD A,IORQ_BDOS2RAM
OUT (AUSGANG),A
JP (HL)
NOP
NOP
|
|
Nach dem Befehl "OUT (AUSGANG),A" erkennt die Endlosschleife für die Takterzeugung die Aktivierung des IORQ-Ausganges (wurde auf Low gesetzt) und ruft dadurch die Prozedur "AUSWERTEN_IORQ" in der Include-Datei "CPM_IO.INC" (unten) auf.
Dort wird die Prozedur "START_BDOS2RAM" in der Include-Datei "CPM_IO.INC" aufgerufen.
Quelltext: RONPAS-Compiler, Quelldatei:
CPM_IO.INC |
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
|
procedure Auswerten_IORQ;
var
IO_Wert : Byte;
begin
if BitLow(WR_Z80_E) then
// Daten-Port auf Lesen
DDRC := %00000000;
IO_Wert := PinC;
case IO_Wert of
// CP/M Device Routinen
IORQ_CONOUT : CONOUT_to_Device;
|
IORQ_CONIN : CONIN_to_Device;
|
IORQ_CONST : CONST_to_Device;
|
IORQ_LIST : LIST_to_Device;
|
IORQ_SOUND : SOUND_to_Device;
|
// interne RAM-Floppy auf dem Board (LW A:)
IORQ_RAM2ARR : START_RAM2ARR;
|
IORQ_ARR2RAM : START_ARR2RAM;
|
// CP/M Hilfsroutinen
IORQ_BDOS2RAM : START_BDOS2RAM;
|
IORQ_CCP2RAM : START_CCP2RAM;
|
// Sektor, Track und Disk an den uC senden
IORQ_STD2UC : STD_an_uC;
|
// Ist das aktuelle Laufwerk vorhanden?
IORQ_LW_ok : LW_vorhanden;
|
IORQ_LW_CONOUT : Geraete_anzeigen;
Laufwerke_Verteiler_anzeigen;
IORQ_Ende;
|
// je nach Laufwerk wird nun READ oder WRITE
// ausgefuehrt
IORQ_RD_DISK2ARR : Laufwerke_Verteiler_READ;
|
IORQ_WR_ARR2DISK : Laufwerke_Verteiler_WRITE;
|
else
SER0_String:='x80';
WriteLn_SER0;
endcase; { case IO_Wert of }
endif; // if BitLow(WR_Z80) then
end Auswerten_IORQ;
|
|
Das kopieren kann aber noch nicht beginnen, da der Programmzähler (PC) des Z80 beim Befehl "OUT (AUSGANG),A" angehalten wurde.
Daher müssen in der Prozedur "START_BDOS2RAM" solange Takte generiert werden, bis der Befehl "JP (HL)" abgearbeitet ist.
Quelltext: RONPAS-Compiler, Quelldatei:
SRAM.INC |
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
|
// Kopieren des BDOS in den RAM
procedure START_BDOS2RAM;
var
SP_Zaehler : Word;
RAM_Wert : Byte;
RAM_ADR : Byte;
begin
// warten bis /IORQ inaktiv wird (High)
warte_solange_IORQ_Low;
// Der naechste M1-Zyklus ist der "JMP (HL)"-Befehl
warte_solange_RD_High;
warte_solange_RD_Low;
// Der naechste M1-Zyklus wird abgefangen
// *** nun wird in den RAM geschrieben ***
// Abschalten der /RD und /WR Signale der Z80 an den RAM
SetBit(G_RAMSW);
// OE und WE aktivieren
SetBit(OE_RAM_A);
SetBit(WE_RAM_A);
// OE und WE DIR Ausgabe
SetBit(OE_RAM_D);
SetBit(WE_RAM_D);
// OE und WE aktivieren
SetBit(OE_RAM_A);
SetBit(WE_RAM_A);
for SP_Zaehler:=0 to Laenge_BDOS_Array do
warte_solange_RD_High;
// Daten-Port auf Schreiben
DDRC := %11111111;
RAM_Wert := BDOS_ARR[SP_Zaehler];
PortC := RAM_Wert;
ClearBit(WE_RAM_A);
NOP;
NOP;
NOP;
SetBit(WE_RAM_A);
// Anlegen von NOP an den Datenport fuer die Z80
PortC := 0; // NOP
warte_solange_RD_Low;
// Wiederholen bis das ganze ARRAY in den RAM geladen wurde
endfor; { for SP_Zaehler:=0 to Laenge_BDOS_Array do }
// *** Einschreiben von RET ($C9) an Z80
warte_solange_RD_High;
// Daten-Port auf Schreiben
DDRC := %11111111;
// Anlegen von RET an den Datenport fuer die Z80
PORTC := $C9; // RET
warte_solange_RD_Low;
// Daten-Port auf Lesen
DDRC := %00000000;
// OE und WE aktivieren
SetBit(OE_RAM_A);
SetBit(WE_RAM_A);
// OE und WE DIR Eingabe
ClearBit(OE_RAM_D);
ClearBit(WE_RAM_D);
// Zuschalten der /RD und /WR Signale der Z80 an den RAM
ClearBit(G_RAMSW);
end START_BDOS2RAM;
|
|
Nach der Abarbeitung des Befehls "JP (HL)" steht der Programmzähler (PC) des Z80 auf der Adress-Konstante "BDOSBASE" (EQU 0E200H).
Jetzt kann das Kopieren der Daten aus dem CONST-Array "BDOS_ARR" (mit der Länge: "Laenge_BDOS_Array") in den RAM beginnen.
Dabei wird prinzipiell der gleiche Ablauf wie unter 7.2.2. Beschreibung des SRAM-Loaders beschrieben genutzt:
- Die Adresse E200H liegt auf dem Adressbus und die Datenleitungen des Z80-Prozessors sind auf Eingang geschaltet (es soll ja eigentlich ein Byte gelesen werden), siehe dazu den Markierung A im Bild 7.2.2b.
- Jetzt kann der ATMEGA32A über seine Datenleitungen D0..D7 auf den Adressbus das erste Byte für den SRAM bereitstellen.
Quelltext: RONPAS-Compiler, Quelldatei:
BDOS_TXT.INC |
|
Laenge_BDOS_Array : Word = $0DFF;
BDOS_ARR : array[0..Laenge_BDOS_Array] of Byte = (
$00,$00,$00,$00,$00,$00,$C3,$11,$E2,$99,$E2,$A5,$E2,$AB,$E2,$B1, // 0000H
$E2,$EB,$22,$43,$E5,$EB,$7B,$32,$D6,$EF,$21,$00,$00,$22,$45,$E5, // 0010H
$39,$22,$0F,$E5,$31,$41,$E5,$AF,$32,$E0,$EF,$32,$DE,$EF,$21,$74, // 0020H
$EF,$E5,$79,$FE,$29,$D0,$4B,$21,$47,$E2,$5F,$16,$00,$19,$19,$5E, // 0030H
$23,$56,$2A,$43,$E5,$EB,$E9,$03,$F0,$C8,$E4,$90,$E3,$CE,$E4,$12, // 0040H
|
|
-
Dieses Byte wird aus dem CONST-Array "BDOS_ARR" in der INCLUDE-Datei "BDOS_TXT.INC" gelesen. Konkret ist es 00H (auf Adresse 0000H im Array).
-
Mit der Aktivierung der "/WE"-Leitung über die Leitung "/RAM_WE" des ATMEGA32A (kurz auf Low und dann wieder auf High) wird dieses erste Byte 00H in den SRAM geschrieben.
-
Es werden anschliessend so lange Takte generiert, bis der Z80-Prozessor das /RD-Signal auf High-Pegel legt (Markierung B im Bild 7.2.2b).
-
Es werden weitere Takte generiert und der Z80-Prozessor arbeitet den NOP-Befehl weiter ab (senden eines REFRESH-Bytes für ein eventuell angeschlossenen DRAM), verändert dabei aber keine Register.
-
Damit ist dieser Befehlszyklus abgeschlossen.
-
Werden nun weiter Takte generiert, beginnt der Z80-Prozessor den nächsten Befehlsholezyklus.
-
Dies wird so lange fortgesetzt, bis das CONST-Array "BDOS_ARR" mit der Länge Laenge_BDOS_Array in den SRAM geschrieben wurde.
Was passiert aber nachdem der SRAM mit dem BDOS beschrieben ist? Der Programmzähler (PC) steht nun auf der Adresse "BDOSBASE + Laenge_BDOS_Array = EFFFH". Würde der Z80 hier weitermachen, würde ab der nächsten Adresse das BIOS mit den CBIOS aufgerufen werden. Das soll aber nicht so sein.
Daher wird nach dem Kopieren des BDOS in der Prozedur "START_BDOS2RAM" der Befehl "RET" (C9H) an die Z80-CPU gesendet.
Dieser Befehl lädt den Programmzähler (PC) mit dem abgespeicherten Wert auf dem Stack. Hier wurde zuletzt die Rücksprungadresse beim Aufruf der Prozedur "CALL COPY_BDOS_TO_RAM" im BIOS des CP/M abgelegt. Und genau dort soll die Abarbeitung des Codes weitergeführt werden.
Das CCP wird auf die gleiche Art wie das BDOS kopiert. Es werden nur die entsprechenden CCP-Routinen aufgerufen, die das CCP auf die Adresse DA00H kopieren.
5. Beschreibung der Schnittstellen zwischen dem ATMEGA32A (RONPAS-Compiler) und der Z80-CPU (BIOS in Z80-Assembler)
5.1. Wie kommunizieren der ATMEGA32A und der Z80 miteinander?
Im letzten Abschnitt wurde das schon am Beispiel des Kopierens des BDOS in den SRAM beschrieben.
Das Prinzip soll in diesem Abschnitt noch einmal an einem einfachen Beispiel beschrieben werden.
Wie unter "1.2. Slave-Betrieb" beschrieben fungiert der ATMEGA32A als komfortable Ein- und Ausgabeeinheit (I/O).
Diese Ein- und Ausgabeeinheit wird demzufolge auch mit den dafür vorgesehenen IO-Befehlen des Z80 "IN" und "OUT" angesprochen.
Werden diese Befehle vom Z80-Assembler aufgerufen werden dabei einer der folgenden Parameter an den RONPAS-Compiler übergeben:
IORQ-Befehlscodetabelle im Z80-Assembler
Quelltext: Z80-Assembler, Quelldatei:
MEICDEF.INC |
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
; Befehlscode fuer IORQ
IORQ_CONOUT EQU 01
IORQ_CONIN EQU 02
IORQ_CONST EQU 03
IORQ_START_PROG EQU 04
IORQ_RAM2ARR EQU 05
IORQ_ARR2RAM EQU 06
IORQ_STD2UC EQU 07
IORQ_BDOS2RAM EQU 08
IORQ_CCP2RAM EQU 09
IORQ_LIST EQU 10
IORQ_SOUND EQU 11
IORQ_RD_DISK2ARR EQU 12
IORQ_WR_ARR2DISK EQU 14
IORQ_LW_ok EQU 15
IORQ_LW_CONOUT EQU 16
|
|
IORQ-Befehlscodetabelle im RONPAS-Compiler
Quelltext: RONPAS-Compiler, Quelldatei:
z-meic.PAS |
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
// Befehlscode fuer IORQ
IORQ_CONOUT : Byte = 01;
IORQ_CONIN : Byte = 02;
IORQ_CONST : Byte = 03;
IORQ_START_PROG : Byte = 04;
IORQ_RAM2ARR : Byte = 05;
IORQ_ARR2RAM : Byte = 06;
IORQ_STD2UC : Byte = 07;
IORQ_BDOS2RAM : Byte = 08;
IORQ_CCP2RAM : Byte = 09;
IORQ_LIST : Byte = 10;
IORQ_SOUND : Byte = 11;
IORQ_RD_DISK2ARR : Byte = 12;
IORQ_WR_ARR2DISK : Byte = 14;
IORQ_LW_ok : Byte = 15;
IORQ_LW_CONOUT : Byte = 16;
|
|
5.2. Beispiel für die Nutzung des OUT-Befehls mit einem IORQ-Befehlscode
Quelltext: Z80-Assembler, Quelldatei:
MEICBIOS.INC |
362
363
364
365
366
367
368
369
370
371
|
CONOUT:
LD A,IORQ_CONOUT
OUT (AUSGANG),A
LD A, C
OUT (AUSGANG),A
; Ruecksprung
;
RET
|
|
Wird der Assembler-Befehl "OUT (AUSGANG),A" von der Z80-CPU ausgeführt, wird dazu die IORQ-Leitung aktiviert (auf LOW gesetzt) und der Inhalt des "Register A" auf den Datenbus gelegt.
Innerhalb der unter "7.2.1.2. Slave-Betrieb" beschriebenen Taktschleife wird die IORQ-Leitung nach jeder steigenden Flanke des Taktes abgefragt. Ist die IORQ-Leitung auf LOW, so wird die RONPAS-Prozedur "AUSWERTEN_IORQ" aufgerufen.
Innerhalb der Prozedur "AUSWERTEN_IORQ" (in der Include-Datei "CPM_IO.INC") wird der IORQ-Befehlscode vom Datenbus gelesen (siehe IORQ-Befehlscodetabelle) und auf die entsprechenden Ausführungsprozeduren verzweigt.
Quelltext: RONPAS-Compiler, Quelldatei:
CPM_IO.INC |
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
|
procedure Auswerten_IORQ;
var
IO_Wert : Byte;
begin
if BitLow(WR_Z80_E) then
// Daten-Port auf Lesen
DDRC := %00000000;
IO_Wert := PinC;
case IO_Wert of
// CP/M Device Routinen
IORQ_CONOUT : CONOUT_to_Device;
|
IORQ_CONIN : CONIN_to_Device;
|
IORQ_CONST : CONST_to_Device;
|
IORQ_LIST : LIST_to_Device;
|
IORQ_SOUND : SOUND_to_Device;
|
// interne RAM-Floppy auf dem Board (LW A:)
IORQ_RAM2ARR : START_RAM2ARR;
|
IORQ_ARR2RAM : START_ARR2RAM;
|
// CP/M Hilfsroutinen
IORQ_BDOS2RAM : START_BDOS2RAM;
|
IORQ_CCP2RAM : START_CCP2RAM;
|
// Sektor, Track und Disk an den uC senden
IORQ_STD2UC : STD_an_uC;
|
// Ist das aktuelle Laufwerk vorhanden?
IORQ_LW_ok : LW_vorhanden;
|
IORQ_LW_CONOUT : Geraete_anzeigen;
Laufwerke_Verteiler_anzeigen;
IORQ_Ende;
|
// je nach Laufwerk wird nun READ oder WRITE
// ausgefuehrt
IORQ_RD_DISK2ARR : Laufwerke_Verteiler_READ;
|
IORQ_WR_ARR2DISK : Laufwerke_Verteiler_WRITE;
|
else
SER0_String:='x80';
WriteLn_SER0;
endcase; { case IO_Wert of }
endif; // if BitLow(WR_Z80) then
end Auswerten_IORQ;
|
|
In diesem Beispiel verzweigt der IORQ-Befehlscode "IORQ_CONOUT" auf die Prozedur "CONOUT_to_Device" (aus der Include-Datei "CPM_IO.INC").
Wichtiger Hinweis:
Damit ist die Abarbeitung des OUT-Befehls mit dem IORQ-Befehlscode "IORQ_CONOUT" noch nicht beendet! Innerhalb der BIOS-Funktion "CONOUT" muss noch das auszugebende Zeichen an das CONOUT-Gerät weitergegeben werden.
Dazu muss nach dem Einleitungsbefehl:
Das erfolgt in der Prozedur "CONOUT_to_Device" mit der Prozedur "Z80_CONOUT" (aus der Include-Datei "Z80.INC").
Quelltext: RONPAS-Compiler, Quelldatei:
CPM_IO.INC |
96
97
98
99
100
101
102
103
104
105
|
procedure CONOUT_to_Device;
begin
// einlesen des auszugebenden Bytes
Z80_CONOUT;
direkt_CONOUT_to_Device;
end CONOUT_to_Device;
|
|
Hier wird auf das nächste Zeichen (das durch das CP/M-BIOS über eine OUT-Befehl an die IO-Einheit gesendete wird) gewartet.
Quelltext: RONPAS-Compiler, Quelldatei:
Z80.INC |
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
procedure Z80_CONOUT;
begin
// warten bis /IORQ inaktiv wird (High)
warte_solange_IORQ_Low;
// Das naechste Byte ist das auszugebende Zeichen
warte_solange_IORQ_High;
ConsoleOut := PinC;
IORQ_Ende;
end Z80_CONOUT;
|
|
Nach der Abarbeitung aller Prozeduren wird mit der Taktgenerierung innerhalb der Endlosschleife fuer Takterzeugung weitergearbeitet (siehe "7.2.1.2. Slave-Betrieb").
6. Erläuterungen zum CP/M-BIOS
Das CP/M-BIOS des z-meic ist bewußt minimal gehalten. Der größte Teil der Aufgaben wurde an den Microcontroller ATMEGA32A übergeben oder noch weiter an die ITP3-Module.
6.1. Laufwerksverwaltung
DW
128 |
SPT
- 128 bytes sectors per track |
DB
5 |
BSH
- block shift factor |
DB
31 |
BLM
- block mask |
DB
1 |
EXM
- Extent mask |
DW
2047 |
DSM
- Storage size (blocks - 1) |
DW
511 |
DRM
- Number of directory entries - 1 |
DB
240 |
AL0
- 1 bit set per directory block |
DB
0 |
AL1
- |
DW
0 |
CKS
- DIR check vector size (DRM+1)/4 (0=fixed disk) |
DB
0 |
OFF
- Reserved tracks |
Sektoren
pro Spur: 128 |
Blockgroesse:
4096 Byte |
Anzahl
der Verzeichniseintraege: 512 |
Groesse
des Laufwerks: 8.388.608 Byte = 8MB |