entwickelt von Ronald Daleske

Startseite Impressum

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

Bild 7.2.1a: Master-Slave-Betrieb

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:

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.

Bild 2a: Laden von Daten in den SRAM

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:

Quelltext: RONPAS-Compiler, Quelldatei: MEIC_TXT.INC

1
2
3
4
5
6
7
8
9
  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 

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.

Bild 3a: lade BIOS

Damit ist folgender Grundzustand vorhanden:

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.

Bild 3b: kopiere BIOS

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.

Bild 4a: lade BDOS und CCP

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:

Quelltext: RONPAS-Compiler, Quelldatei: BDOS_TXT.INC

1
2
3
4
5
6
7
8
9
  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 

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

Startseite Impressum