Z80Ardu – Soundkarte mit SN76489 (gestoppt)
Dieses Projekt habe ich gestoppt. Das Prinzip funktioniert. Der abgebildete Prototyp tut, was er soll, nämlich Sound wiedergeben. Ich wollte aber eigentlich immer eine Soundqualität haben, die sich mit dem SID des C64 messen kann.
Deshalb gibt es das Projekt „Z80Ardu – DoubleSID Soundkarte„
Der Baustein SN76489 wurde von Texas Instruments gebaut und in vielen frühen Homecomputern und Konsolen eingebaut. Bei dem Baustein handelt es sich um einen „Digital Complex Sound Generator“ oder auch PSG (programmable sound generator) genannt.
Die hier gezeigte „Soundkarte“ ist aus der Sicht der Elektronik wirklich einfach aufgebaut. Aus der Sicht der Programmierung ist es ein wenig komplizierter.
Hier ist auch noch ein kleines Musikpröbchen. Abgespielt über den oben gezeigten Prototyp. Der Verstärker ist allerdings ein LM386 mit 200-facher Verstärkung, was zu starkem Rauschen und Knacken führt. In dem unten gezeigten Schaltplan für die Soundkarte ist die Verstärkung auf 20-fach begrenzt.
Am Anfang der Hörprobe ist leider noch ein Hup-Ton zu hören. Das liegt an meinem schlecht programmierten Player 😀
Um den Prototyp testen zu können, habe ich ein Programm für den Z80Ardu geschrieben. Basis dafür ist ein VGM-Player, der mal für MSX Computer erstellt wurde. Ich selbst bin mit der genauen Programmierung des SN76489 nicht ganz so vertraut (Aber das wird sicher noch).
Da das Programm für den Z80Ardu geschrieben wurde, passt es zunächst auch nur für diesen kleinen Computer. Die Wiedergabe wird per Interrupt gesteuert. Beim Z80Ardu wird am Prozessoreingang /INT über die Grafikkarte (mit dem MC6847-Chip) bei jedem Vertical Sync Pulse ein Interrupt ausgelöst. Der Grafikchip wird aktuell mit 3,57 Mhz betrieben, was der Color Burst Frequenz von NTSC entspricht (60 Hz).
Unter Berücksichtigung dieser Bedingungen, kann das Kernmodul im Programm auch in anderen Computern genutzt werden. Bitte berücksichtigt, dass das Programm ein Testprogramm ist. Die einzelnen Programmteile enthalten so einiges an Overhead, da ich es immer wieder für unterschiedliche Testszenarien verwende.
Der eigentliche Player ist ein eigener Programmteil, der für das Abspielen an sich genügt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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 120 121 122 123 124 125 126 127 128 |
;************************************************************************** ;** Z80Ardu-Header ;** (c) 2019 by Martin de Hoogh ;** ;** Variable und Konstanten ;************************************************************************** DEBUG: equ 1 ; IO VMODEOUT: equ 040h ; I/O-Adresse des Video-Mode-Latches KEYBIN: equ 0E0h ; I/O-Adresse des Keyboards IDE: equ 060h ; I/O-Adresse der IDE-Schnittstelle CFBASE: equ 060h PSG: equ 0A0h ; I/O-Adresse SPI PSGREG: equ 0A0h ;VIDRAM: equ 07000h ; Start des VideoRAM im Speicher (7000h ist lediglich für Testzwecke) VIDRAM: equ 0E000h ; Start des VideoRAM im Speicher VIDRAM_HI: equ HI vidram ; Highbyte der VideoRAM-Adresse (Textmodus) VIDRAM_LAST: equ HI vidram +2 ; Highbyte der VideoRAM-Adresse (Textmodus) VIDRAM_SLINE: equ vidram + 020h ; Zweite Zeile am Bildschirm VIDRAM_N2LAST: equ vidram + 1e0h ; Vorletzte Zeile am (Text)Bildschirm ROM_START: equ 0h ;ROM_END: equ 1000h ;****************************************************************** ; INIT_GRAPHICMODE ( GRAPHICS = IO-Address $40) ; ; 64 x 64 Dots, 4 Colors (Color Graphics One) ; 128 x 64 Dots, 2 Colors (Resolution Graphics One) ; 128 x 64 Dots, 4 Colors (Color Graphics Two) ; 128 x 96 Dots, 2 Colors (Resolution Graphics Two) ; 128 x 96 Dots, 4 Colors (Color Graphics Three) ; 128 x 192 Dots, 2 Colors (Resolution Graphics Three) ; 128 x 192 Dots, 4 Colors (Color Graphics Six) ; 192 x 256 Dots, 2 Colors (Resolution Graphics Six) ; ; ; INIT VIRTUAL SCREENs ( GRAPHICS = IO-Address $40) ; SCR0 -- Screen 0 -- Adresse 00H ; SCR1 -- Screen 1 -- Adresse 10H ; SCR2 -- Screen 2 -- Adresse 20H ; SCR3 -- Screen 3 -- Adresse 30H ; ; Can be combined with a graphic mode ; ; -- Graphicmodes Adr. A/G CG0: equ 000H ; Textmode CG1: equ 000H + 08H ; 64 x 64, 4 Colors RG1: equ 001H + 08H ; 128 x 64, 2 Colors CG2: equ 002H + 08H ; 128 x 64, 4 Colors RG2: equ 003H + 08H ; 128 x 96, 2 Colors CG3: equ 004H + 08H ; 128 x 96, 4 Colors RG3: equ 005H + 08H ; 128 x 192, 2 Colors CG6: equ 006H + 08H ; 128 x 192, 4 Colors RG6: equ 007H + 08H ; 256 x 192, 2 Colors CSS: equ 020h ; -- Screenpages im Videoram... Für den Prozessor bleibt der Videospeicher ; jedoch immer an der Adresse E000h SCR0: equ 00H ; 0000H - 1FFFH SCR1: equ 10H ; 2000H - 3FFFH SCR2: equ 20H ; 4000H - 5FFFH SCR3: equ 30H ; 6000H - 7FFFH ; Systemvariable RAM_END: equ 0dfffh CURS_BLINK: equ 0ffh var_curposy: equ RAM_END - 1 ; Cursorspalte (Y) var_curposx: equ var_curposy - 1 ; Cursorzeile (X) var_curpos: equ var_curposy var_txtcolor: equ var_curposx - 1 var_keybcode: equ var_txtcolor - 1 ; Zuletzt betätigte Taste var_curadr: equ var_keybcode - 2 ; Aktuelle Cursoradresse 16 Bit var_blink: equ var_curadr - 1 ; Blinkzähler var_Flag1: equ var_blink - 1 ; Flag 1 ; - Bit 0 - Cursor blinkt = 1, Cursor schweigt = 0 ; - Bit 1 ; - Bit 2 ; - Bit 3 ; - Bit 4 ; - Bit 5 ; - Bit 6 ; - Bit 7 var_Flag2: equ var_Flag1 - 1 ; Flag 2 ; - Bit 0 - Cursor blinkt = 1, Cursor schweigt = 0 ; - Bit 1 ; - Bit 2 ; - Bit 3 ; - Bit 4 ; - Bit 5 ; - Bit 6 ; - Bit 7 var_curblink: equ var_Flag2 -1 ; Cursor-Blinkgeschwindigkeit var_save_sp: equ var_curblink - 2 ; Zwischenspeicher für Stackpointer var_AllowNMI: equ var_save_sp - 1 ; NMI durchlaufen? 0 = nein, <>0 = ja var_VideoMode: equ var_AllowNMI-1 ; Status des Video-Latch var_IOBuffer: equ var_VideoMode-180 ; 64 Byte IO-Puffer var_DataFlag: equ var_IOBuffer-1 ; DATA-Flag var_pixcolor: equ var_DataFlag-1 ; Pixelfarbe für SET/RESET var_xcord: equ var_pixcolor-1 ; X-Koordinate für SET/RESET var_ycord: equ var_xcord-1 ; Y-Koordinate für SET/RESET var_takeKey: equ var_ycord-1 STACK: EQU var_takeKey - 2 RAM_FREE_END: equ RAM_END - 4 ; Ende freier Speicher ; Sonstige Variable eos: equ 00h ; End of string cr: equ 0dh ; Carriage return lf: equ 0ah ; Line feed space: equ 20h ; Space tab: equ 20h ; Tabulator |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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 120 121 122 123 124 125 126 127 128 129 130 131 |
;*********************************** ;* Z80Ardu PSG Player * ;* * ;*********************************** ORG 0000H include "Header.asm" ;****************************************************************** ; Main Program ; ;****************************************************************** di im 1 jp _start org 0008h _RST8: ret nop nop org 0010h _RST10: ret nop nop org 0018h _RST18: ret nop nop org 0038h ; ********************************************* ; Einsprung ISR (/INT) ; Der MC6847 der Grafikkarte löst alle 54 µS einen Interrupt aus ; ********************************************* rst_38: push af push bc push de push hl di call inthndl ld bc,900 loopi: dec bc ld a,b or c jr nz, loopi nope: pop hl pop de pop bc pop af ei reti ; ********************************************* ; Einsprung ISR (/NMI) ; Beim Z80Ardu für Tastatureingaben reserviert ; ********************************************* org 0066h rst_66: retn ; ------------------------------------------------------------------------------------------------------------------------------------ ; Hauptprogramm _start: ld sp,STACK ; Stackpointer setzen ld a,CG0 + SCR0 ; ...komplett zurücksetzen call setVMode call pause ld a,20h ; Bildschirm löschen call cls call pause ld a,20h ; Bildschirm löschen call cls ld hl,_txt_hello ; Begrüssungstext adressieren call print ; und ausgeben call pause ; Päuschen call vgmplay ei call pause play: jr play ; Und ab in die Schleife include "Utilities.asm" include "Output.asm" include "PSGmod.asm" include "Txt_and_Tables.asm" ; --< Texte >--------------------------------------------------------- _txt_hello: defm "Z80ARDU HOMEBREW COMPUTER " defm "64K BYTE RAM. " defm " " defm "(C) 2022 M. DE HOOGH " defm " " defm " SN76489 PSG PLAYER " defb 0 _txt_pos: defm 1,7,"POSITION ",0 vgmdata: includebinary "soundfile.vgm" vgmend: defb 0 intallowed: defb 0x00 RAM_START: defb 00h |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 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 |
;************************************************************************** ;** Z80Ardu-Output ;** (c) 2019 by Martin de Hoogh ;** ;** Diese Routinensammlung benötigt die Datei "Header.asm" ;** Diese Routinensammlung benötigt die Datei "Txt_and_Tables.asm" ;************************************************************************** ; ; ;** Routine Parameter Ergebnis Beschreibung ; print_at HL = Anfangsadr Textausgabe Ausgabe eines Strings an vorgegebener Position, die ersten Bytes enthalten die Position ; ; print HL = Anfangsadr Textausgabe Ausgabe einer Zeichenkette ; ; printchar Zeichen in A Ein Zeichenausgeben Ausgabe eines Zeichens unter Berücksichtung der MC6847-Zeichentabellen ; ; calc_pos b=x, c=y Bildschirmadresse Berechnung einer Bildschirmadress aus X- und Y-Position ; ************************************************************************* ; print_at ; Ausgabe einer Zeichenkette an X- und Y-Position ; X- und Y-Position werden als zwei Bytes am Anfang des Strings erwartet ; Das Ende der Zeichekette wird mit 00h abgeschlossen ; ; print_at ruft calc_pos auf, um die Bildschirmadresse für die Ausgabe ; zu berechnen . ; Eingabe: HL = Anfangsadresse der Position und Zeichenkette ; ************************************************************************* print_at: ld a,(hl) ; X-Position in A ld (var_curposx),a ld b,a ; und nach B inc hl ; HL auf Y-Position ld a,(hl) ; Y-Position in A ld (var_curposy),a ld c,a ; und nach C call calc_pos ; Position auf dem Schirm kalkulieren _print_nextchar: inc hl ; HL zegt auf den Text print: ld a,(hl) ; Ein Zeichen des Strings lesen cp 00h jr z, _print_endputs ; Stringabschluss gefunden call printchar ; Zeichen ausgeben jr _print_nextchar ; Nächstes Zeichen im String _print_endputs: ret ; Ein Zeichen auf dem Bildschirm ausgeben (Ohne Positionsangabe) printchar: push hl ; Alle Register retten push de push bc push af ; Auszugebenden Zeichen sichern ld a,(var_Flag1) res 0,a ; Bit 0 = 0 = Cursor blinkt nicht ld (var_Flag1),a pop af ; Zeichen wieder herstellen ; Zeichen an MC6847-Zeichentabelle anpassen und Sonder- ; funktionen ausführen cp 20h ; Leerzeichen? jr z, _printchar_stchr ; Ja, dann Bit 6 lassen wie es ist cp 0dh ; Carriage Return jr z, _printchar_cr cp 80h jr nc, _printchar_stchr ; Semi-Graphics cp 70h jr nc,_printchar_stchr cp 60h jr nc, _printchar_inverse ; Inverse (Bei Kleinbuchstaben) res 6,a ; Bit 6 löschen = Code - 64 jr _printchar_stchr _printchar_inverse: sub a,20h set 6,a _printchar_stchr: ld hl,(var_curadr) ; Neue Cursorposition laden ld (hl),a inc hl ld (var_curadr),hl ; Prüfen ob letzte Zeile erreicht _printchar_lastline: push af ; Zeichen wieder auf den Stack ld a,h ; Highbyte der aktuellen Position nach a cp VIDRAM_LAST jr nz, end_putc ; Bildschirm scrollen ld hl, VIDRAM_SLINE ld de, VIDRAM ld bc, 0200h -20h ldir ; Letzte Zeile leeren ld b, 020h ; Zeilenlänge 32 Zeichen ld hl,VIDRAM_N2LAST ld a, 020h ; Füllzeichen = Leerzeichen _clrline: ld (hl),a inc hl djnz _clrline ; Cursor-zeiger auf Anfang der letzten Zeile ld hl, VIDRAM_N2LAST ld (var_curadr),hl end_putc: ld a,(var_Flag1) set 0,a ; Bit 0 = 1, Cursor blinkt wieder ld (var_Flag1),a pop af pop bc pop de pop hl ret _printchar_cr: ld a,00h ; X=0 ld (var_curposx),a ; Speichern ld b,a ; und nach B ld a,(var_curposy) ; aktuelle Y-Position nach a inc a ; Y +1 ld c,a ; Und in C ld (var_curposy),a ; Speichern call calc_pos ; Bildschirnadresse errechnen jr _printchar_lastline ; ************************************************************************* ; Cursorposition berechnen aus X (0 - 31) ; und Y (0 - 15) ; X-Position nach b ; Y-Position nach c ; HL enthält die Bildschirmposition als 16-Bit adresse ; HL wird außerdem in var_curadr abgelegt (=Bildschirmadresse) ; ************************************************************************* calc_pos: push hl push bc ; X und Y auf den Stack ld b, 08h ld d, 00h ld e, 20h ld hl, 0000h pos_loop: add hl,hl sla c jr nc, pos_zero add hl,de pos_zero: djnz pos_loop pop bc ; X-Wert vom Stack holen ld c,b ld b,00h add hl,bc ld bc, VIDRAM add hl,bc ld (var_curadr),hl pop hl ret ; ************************************************************************* ; Bildschirm löschen (volle 6 KB) ; ; A enthält das Füllzeichen (20h für Textbildschimr, 00h für ; Grafikbildschirm) ; ************************************************************************* cls: push hl ; HL auf den Stack push de ; DE auf den Stack push bc ; bc auf den Stack ld hl,VIDRAM ld b,0 ; 256 * 24 Bytes = 6144 Bytes ld d,24 _cls_clearit: ld (hl),a ; den Inhalt von A in den Speicher prügeln inc hl djnz _cls_clearit dec d jp nz,_cls_clearit ld hl,VIDRAM ld (var_curadr),hl ld hl,0000h ; Bildschirmpositionen zurücksetzen ld (var_curpos),hl pop bc ; BC vom Stack holen pop de ; DE vom Stack holen pop hl ; HL vom Stack holen ret ; ************************************************************************* ; Cursor ausgeben und Blinken lassen ; ; Zeichen an der Cursorposition wird invertiert ; Läuft innerhalb der Interrrupt-Service-Routine ; ************************************************************************* cursor: ld a,(var_Flag1) ; Flag 1 laden bit 0,a ; Blink-Bit gesetzt? jr z,cursor_noblink ; Nö, dann nicht rum blinken ld a,(var_Flag2) ; Flag 2 laden bit 0,a ; ist das CR-Bit gesetzt ret nz ; ja, dann fertig cursor_blink: ld hl,var_blink ; Blinkzähler laden dec (hl) ; - 1 ret nz ; nicht null, fertig ld a,CURS_BLINK ; Blinkzähler neu setzen ld (var_blink),a ; und speichern ld hl,(var_curadr) ; Cursorposition laden ld a,040h ; Inverse-Bit nach A xor (hl) ; Zeichen invertieren ld (hl),a ret cursor_noblink: ld hl,(var_curadr) ; Cursoradresse laden ld a,(hl) ; Zeichen an Cursoradresse laden and 0ffh-40h ld (hl),a ret ; Fertig ; ************************************************************************* ; Videomodus und Memorypage setzen ; ; Eingabe: A = Mode + Screen ; Ausgabe: Keine ; ************************************************************************* setVMode: di push bc ld b,08h ; 255 mal schreiben (einbrennen) _setVMode_loop: out (VMODEOUT),a ; Ausgabe auf Videoport djnz _setVMode_loop ; Wiederholen bis b=0 ld (var_VideoMode),a ; Latch-Status speichern pop bc ei ret ; ************************************************************************* ; Eingegebenes Zeichen auf dem Bildschirm ausgeben ; ; Eingabe: A = Zeichen aus var_keybcode (gefüllt durch NMI) ; Ausgabe: eingegebenes Zeichen ; ************************************************************************* scr_output: push hl ; HL auf den stack ld a,(var_keybcode) ; Zuletzt eingegebenes Zeichen laden cp 0 ; Keine Eingabe? jr z,scr_output_end ; Dann Ende call printchar ; Zeichen ausgeben ld a,00h ; A löschen ld (var_keybcode),a ; Keycode auf null und wegschreiben scr_output_end: pop hl ret ; ************************************************************************* ; SET/RESET/POINT für Grafik ; Aus x,y Bildadresse und Bitmaske ermitteln ; Sollte bei 128 x 64 und 128 x 192 in 4 Farben funktionieren ; ; Eingabe: A= Flag (80h für SET, 01h für RESET ) ; ************************************************************************* plotpixel: ld a,80h ; Maske für SET push af ld a,(var_ycord) ; Y-Koordinate laden ld e,a xor a ; A=0 ld d,a ; D=0 ex de,hl ; y * Zeilenlänge (32) add hl,hl ; x2 add hl,hl ; x4 add hl,hl ; x8 add hl,hl ; x16 add hl,hl ; x32 ex de,hl ; = rel. Zeilenanf.adresse ld a,(var_xcord) ; X-Wert laden push af ; und wieder auf den Stack srl a ; X-Koordinate / 4 srl a add a,e ; + relative Zeilenanfangsadresse ld e,a ld a,d ; + Bildanfangsadresse or 0E0H ; (Beim Laser ist das 7000H und in meiner Welt E000H) ld d,a ; DE = Bildadresse pop af ; X-Koordinate laden and 3 ; Die letzten 2 Bits maskieren (0,1,2,3) add a,a ; x2 (0,2,4,6) ld b,a ; in b als Verschiebezähler ; Für SET und RESET Bits in Reg. A und C maskieren ld c, 3FH ; Grundwert in C laden ld a,(var_pixcolor) ; Farbcode als Grundwert in A sla a ; in die oberen 2 Bits schieben sla a _pixshift: rrc a ; mit b als Schiebezähler Grundwerte in erf. Position rrc c ; schieben djnz _pixshift ; A für SET (OR), C für RESET (AND) ld b,a ; SET-Maske in B übertragen ld a,(de) ; Byte aus Bildspeicher laden and c ; Bits für adressiertes Pixel löschen ld (de),a ; Byte zurückschreiben pop af ; Funktionsflag laden or a ; RESET -Anweisung? jp p, _pixend ; Ja, dann fertig ld a,(de) ; SET --> Byte wieder laden or b ; Bits für adressiertes Pixel setzen ld (de),a _pixend: ret |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
; Init player vgmplay: ld hl,vgmdata + 0x40 ; Start of VGM, skipping header. ld (vgmpos),hl ld hl, 735 ; VGM Delay (60hz) ld (vgmdly),hl ld a,09Fh ; mute all channels on psg out (psgreg),a ld a,0BFh out (psgreg),a ld a,0DFh out (psgreg),a ld a,0FFh out (psgreg),a ret ;------------------------------------------------------------------------------ ; Interrupt Handler. Called every 1/60 second. ;------------------------------------------------------------------------------ inthndl: push hl ; Save registers push bc push de push af ; in a,(tmsreg) ; Check if VDP int set and reset it. ; bit 7, a ; jr z, exit ld hl,(vgmdly) ; Check if delay has gone past ld de,735 and 0xff sbc hl,de ld (vgmdly),hl jr nz, exit ld hl,(vgmpos) ; Start processing VGM commands next: ld a, (hl) inc hl ld (vgmpos), hl cp 0x66 ; Restart VGM cmd jr nz, next1 ld hl,vgmdata + 0x40 ; Start of VGM, skipping header. ld (vgmpos),hl ld hl, 735 ; VGM Delay ld (vgmdly),hl jr exit next1: cp 0x4f ; Game Gear SN76489 stereo. Ignored jr nz, sn76 inc hl jr next sn76: cp 0x50 ; Write byte to SN76489. jr nz, waitnn ld a, (hl) inc hl out (psgreg), a jr next waitnn: cp 0x61 ; Wait nn samples jr nz, wait60 ld a, (hl) inc hl ld d, (hl) inc hl ld (vgmpos), hl ld l, a ld h, d ld (vgmdly), hl jr exit wait60: cp 0x62 ; Wait 735 samples (60Hz) jr nz, exit ld (vgmpos),hl ld hl, 735 ld (vgmdly), hl exit: pop af ; Restore registers pop de pop bc pop hl ret ;------------------------------------------------------------------------------ ; Variables ;------------------------------------------------------------------------------ vgmpos: defw 0 vgmdly: defw 0 ;------------------------------------------------------------------------------ ; VGM data ;------------------------------------------------------------------------------ ;vgmdata: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 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 |
;************************************************************************** ;** Z80Ardu-Utilities ;** (c) 2019 by Martin de Hoogh ;** ;** Diese Routinensammlung benötigt "output.asm" für die Datenausgabe ;** Diese Routinensammlung benötigt die Datei "Header.asm" ;** Diese Routinensammlung benötigt die Datei "Txt_and_Tables.asm" ;************************************************************************** ; ; ;** Routine Parameter Ergebnis Beschreibung ; pause Keine ./. Pausiert ca. 2 Sekungen bei 4MHz ; prthex_nibble A=Nibble (4 Bit) Hex-Ausgabe Ausgabe eines Hex-Nibbles an der Cursorposition ; prthex_byte A=Byte Hex-Ausgabe Ausgabe eines Hex-Bytes an der Cursorposition ; prthex_word HL=Word Hex-Ausgabe Ausgabe eines Hey-Worts an der Cursorposition ; prt16dec HL=Word Dec-Ausgabe Ausgabe einer 16Bit Dezimalzahl an der Cursorposition ; prt8dec A=Byte Dec-Ausgabe Ausgabe einer 8Bit Dezimalzahl an der Cursorposition ; checkRAM Keine Bei Fehler: Meldung Ausgabe einer Meldung bei fehlerhaftem Speicher ; strcmp HL,DE Strings vergleichen Z=1, wenn Stings gleich ; strncmp HL,DE,BC Strings vergleichen Z=1 wenn Teilstrings gleich ; strcpy HL, DE String kopieren Kopiert einen String von (HL) nach (DE) ; strncpy HL,DE,BC Teilstring kopieren Kopiert n (BC) Zeichen von (HL) nach (DE) ; strlen HL=Stringadresse Stringlänge feststellen Stringlänge des Strings an (HL) in BC ;************************************************************************** ; Pausenschleife ; Darauf achten, dass immer eine gerade Anzahl von ex (sp),ix vorhanden ; ist, sonst wird der Stack zerlegt und ein Programm wird sich auf- ; hängen ;************************************************************************** pause: push bc ; BC retten ld bc,7000h ; 65535 Durchläufe _pause_ploop: dec bc ld a,b or c ex (sp),ix ; dieser Befehl dauert einfach recht lange :-) ex (sp),ix ; ist also ein Dummy ex (sp),ix ex (sp),ix ex (sp),ix ex (sp),ix jr nz,_pause_ploop ; So lange wiederholen, bis BC = 0 pop bc ; BC vom Stack holen ret ; Und Ende ;************************************************************************** ; Ein Byte Hexadezimal auf dem Bildschirm ausgeben ; ; Eingabe: A = Byte ; Ausgabe: Hexadezimale Ausgabe an Cursorposition ;************************************************************************** prthex_byte: push af ; Save the contents of the registers push bc ld b, a rrca rrca rrca rrca call prthex_nibble ; Print high nibble ld a, b call prthex_nibble ; Print low nibble pop bc ; Restore original register contents pop af ret ;************************************************************************** ; Ein Nibble Hexadezimal auf dem Bildschirm ausgeben ; ; Eingabe: A = Nibble (die unteren 4 Bit) ; Ausgabe: Hexadezimale Ausgabe an Cursorposition ;************************************************************************** prthex_nibble: push af ; Der Inhalt von A bleibt erhalten -> Stack and 0fh ; Nur für den Fall.... add a,'0' ; Wenn es 0 ist, sind wir fertig. cp 03ah ; Ergebnis > "9"? jr c, _prthex_nibb_1 add a,'A' - '0' - 0ah ; "A" - "F" beachten _prthex_nibb_1: call printchar ; Das Nibble ausgeben pop af ; und Register A wiederherstellen ret ; Fertig ;************************************************************************** ; Ein 16bit Wort Hexadezimal auf dem Bildschirm ausgeben ; ; Eingabe: HL = 16 Bit-Wort ; Ausgabe: Hexadezimale Ausgabe an Cursorposition ;************************************************************************** prthex_word: push hl ; HL auf den Stack push af ; A (und F) auf den Stack ld a, h ; Highbyte nach A call prthex_byte ; Byte ausgeben ld a, l ; Lowbyte nach A call prthex_byte ; Byte ausgeben pop af ; AF wiederherstellen pop hl ; HL wiederherstellen ret ; Fertig ;************************************************************************** ; Ein 32bit Wort Hexadezimal auf dem Bildschirm ausgeben ; ; Eingabe: BC = 16 Bit-Wort, DE 16 Bit Wort ; Ausgabe: Hexadezimale Ausgabe an Cursorposition ;************************************************************************** prthex_dword: push hl ; HL auf den stack push bc ; BC auf den Stack pop hl ; BC nach HL call prthex_word ; 16 Bit ausgeben push de ; DE auf den Stack pop hl ; DE nach HL call prthex_word ; 16 Bit ausgeben pop hl ret ;************************************************************************** ; Ein 16bit Wort dezimal auf dem Bildschirm ausgeben ; ; Eingabe: HL = 16 Bit-Wort ; Ausgabe: Dezimale Ausgabe an Cursorposition ; Zerstört: af, bc, hl, de used ;************************************************************************** prt16dec: ld bc,-10000 call prt16dec_num1 ld bc,-1000 call prt16dec_num1 ld bc,-100 call prt16dec_num1 ld c,-10 call prt16dec_num1 ld c,-1 prt16dec_num1: ld a,"0"-1 prt16dec_num2: inc a add hl,bc jr c,prt16dec_num2 sbc hl,bc call printchar ret ;************************************************************************** ; Ein Byte dezimal auf dem Bildschirm ausgeben ; ; Eingabe: A = 8 Bit ; Ausgabe: Dezimale Ausgabe an Cursorposition ; Zerstört: af, bc ;************************************************************************** prt8dec: ld c,-100 call prt8dec_num1 ld c,-10 call prt8dec_num1 ld c,-1 prt8dec_num1: ld b,'0'-1 prt8dec_num2: inc b add a,c jr c,prt8dec_num2 sub c ;works as add 100/10/1 push af ;safer than ld c,a ld a,b ;char is in b CALL printchar ;plot a char. Replace with bcall(_PutC) or similar. prt8dec_end: pop af ;safer than ld a,c ret ;************************************************************************** ; RAM-Check ; Der gesamte Speicher wird durchlaufen (ab RAM_START). Es wird ein Byte ; gelesen. Das Byte wird gesichert, dann verändert, dann geschrieben, dann ; geprüft, ob der Inhalt stimmt. Wenn der Inhalt stimmt, wird der ursprüng- ; liche Wert wiederhergestellt. ; Im Fehlerfall stoppt die Routine und gibt einen Fehlertext aus. ;************************************************************************** checkRAM: push hl push bc push de ld hl,txtMemChk call print ld hl,RAM_START ; Prüfung beginnt ab dem Ende des "Betriebssystems" ld bc,0dfffh - RAM_START ; Und geht bis zum Bildschirmspeicher checkLoop: ld a,(hl) ; Byte aus der Speicherstelle laden ld e,a ; und in E sichern ld a,r ; Bit 7 setzen ld d,a ; und in D sichern ld (hl),a ; Byte in den Speicher schreiben ld a,(hl) ; und wieder lesen cp d ; mit D vergleichen call nz,checkFail ; keine Übereinstimmung ld a,e ; gesichertes Byte zurückholen ld (hl),a ; und ins RAM schreiben inc hl ; Adresse hochzählen dec bc ; bis BC = 0 ld a,b or c ; ist BC denn schon 0? jr nz, checkLoop ; Nö ld hl,txtOK call print jr checkDone checkFail: push hl ld hl,txtMemFail ; Fehler im Speicher call print_at pop hl call prtHex_Word ; an Adresse HL checkDone: pop de pop bc pop hl ret ;************************************************************************** ; Name: strcmp ; Funktion: Stringvergleich ; Eingabe: HL, DE = addresses of strings to compare ; Ausgabe: Zero flag gesetzt, wenn Strings gleich ;************************************************************************** strcmp: push bc ld bc,0xffff ; max out counter call strncmp pop bc ret ;************************************************************************** ; Name: strncmp ; Funktion: Compare first n characters of strings ; Eingabe: HL, DE = Adressen der zu vergleichenden Strings ; BC = Anzahl der zu vergleichenden Zeichen (n) ; Ausgabe: Zero flag gesetzt, wenn Strings gleich ;************************************************************************** strncmp: ld a,b ; check if counter is 0 or c jp z,strncmp_end ld a,(de) ; compare bytes cp (hl) jp nz,strncmp_end cp 0 ; end of first string? jp z,strncmp_check inc de inc hl dec bc jp strncmp strncmp_check: ; check end of second string or (hl) ; has been reached too cp 0 strncmp_end: ret ;************************************************************************** ; Name: strncpy ; Funktion: Copy a string ; Eingabe: HL = address of source string ; DE = address of destination buffer ; Ausgabe: keine ;************************************************************************** strcpy: push bc ld bc,0xffff ; max out counter call strncpy pop bc ret ;************************************************************************** ; Name: strncpy ; Funktion: Copy first n characters of a string ; Eingabe: HL = address of source string ; DE = address of destination buffer ; BC = number of characters to copy (n) ; Ausgabe: Keine ;************************************************************************** strncpy: ld a,b or c jp z,strncpy_end ld a,(hl) ld (de),a inc hl inc de dec bc cp 0 jp nz,strncpy strncpy_end: ret ;************************************************************************** ; Name: strlen ; Funktion: Find length of string (excluding terminating \0) ; Eingabe: HL = address of string ; Ausgabe: BC = length of string ;************************************************************************** strlen: ld bc,0 strlen_loop: ld a,(hl) cp 0 jp z,strlen_end inc hl inc bc jp strlen_loop strlen_end: ret |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 65 |
;;************************************************************************** ;** Z80Ardu-Texte und Tabellen ;** (c) 2019 by Martin de Hoogh ;** ;************************************************************************** ; ; Willkommenstext "Z80Ardu" in Blockgrafik txtwelcome: defb 00h,00h ; X- und Y-Position defm 0a0h,0a3h,0a3h,0a3h,0a3h,0a3h,0a0h,0a1h,0a3h,0a2h,0a0h,0a1h,0a3h,0a2h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0dh defm 090h,090h,090h,090h,090h,096h,090h,09ah,090h,095h,090h,09ah,090h,095h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,0dh defm 0a0h,0a0h,0a0h,0a0h,0a6h,0a0h,0a0h,0aah,0a0h,0a5h,0a0h,0aah,0a0h,0a5h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a5h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0a0h,0dh defm 090h,090h,090h,096h,090h,090h,090h,099h,093h,096h,090h,09ah,090h,095h,090h,090h,090h,090h,090h,090h,090h,090h,090h,090h,095h,090h,090h,090h,090h,090h,090h,090h,0dh defm 0a0h,0a0h,0a6h,0a0h,0a0h,0a0h,0a0h,0aah,0a0h,0a5h,0a0h,0aah,0a0h,0a5h,0a0h,0a6h,0ach,0a9h,0a1h,0ach,0ach,0a2h,0a6h,0ach,0adh,0a5h,0a0h,0a0h,0aah,0a0h,0a0h,0a0h,0dh defm 090h,096h,090h,090h,090h,090h,090h,09ah,090h,095h,090h,09ah,090h,095h,090h,09ah,093h,095h,095h,090h,090h,090h,09ah,090h,095h,095h,090h,090h,09ah,090h,090h,090h,0dh defm 0a0h,0ach,0ach,0ach,0ach,0ach,0a0h,0a4h,0ach,0a8h,0a0h,0a4h,0ach,0a8h,0a0h,0a8h,0a0h,0a4h,0a4h,0a0h,0a0h,0a0h,0a4h,0ach,0a8h,0a0h,0ach,0ach,0a0h,0a0h,0a0h,0a0h,0dh defm "(C) 2016-20 BY M. DE HOOGH",0dh,0 ; Free Memory txtFreeMem: defb 00h,08h ; X- und Y-Position defm "FREE MEMORY: ",0 ; Memory Failure txtMemFail: defm 0,0,"MEMORY CHECK FAILED AT: ",0 ; Memory Check txtMemChk: defm 0,0,"MEMORY CHECK ",0 ; Bytes txtBytes: defm " BYTES",0 ; OK txtOK: defm " OK",cr,0 ; LetsSay txtLetsSay: defm 00h,0fh,"SAY HELLO TO THE WORLD!",0 ; Texte für die Registerausgabe txt_a: defm "A = ",0 txt_f: defm " - F = ",0 txt_r: defm " - R = ",0 txt_hl: defm 0dh,"HL= ",0 txt_de: defm " - DE= ",0 txt_bc: defm " - BC= ",0 txt_ix: defm 0dh,"IX= ",0 txt_iy: defm " - IY= ",0 txt_sp: defm " - SP= ",0 ; ------------------------------------------------------------------------- ; Schlüsselworttabelle tbl_Keywords: defm 80h+"M","ARTIN" defm 80h+"R","EGINA" defm 80h+"M","IRNA" defm 80h+"A","NKE" defm 80h+"R","ALF" defm 80h+"F","RITZ" defb 80h,00h ; Ende txt_MusicPointer: defm 0,8,"MUSICPOINTER:",0 txt_Substring: defm 0,9,"SUBSTR RETURN:",0 ende: defb 0ffh |
Die oben gezeigte Version FUNKTIONIERT LEIDER NICHT! Die Verstärkerstufen rauschen viel zu stark!!
Im Juni 2022 habe ich eine neue Version ohne Verstärker entworfen. Der SN76489 funktioniert ganz hervorragend, wenn man am Audioausgang einen Kondensator anschließt. Das Audiosignal hat dann einen ordentlichen Pegel und es rauscht auch nicht.
Meine neuen Platinen haben dennoch nicht funktioniert, da sich im Gerberfile zwei Fehler eingeschlichen hatten. Naja, dann auf ein Neues!