UPDATE:
Nachdem ich eine saubere Lösung für Gerds Problem mit dem music-Aufruf wollte, habe ich mich das Wochenende mal etwas genauer damit auseinandergesetzt.
Folgende Erkenntnisse hat das Ganze gebracht:
1) Das Ergebnis dieser Schleife am Anfang des Programms scheint wirklich nur für music() verwendet zu werden (wenn wer was anderes entdeckt bitte melden!).
2) Die Erkenntnisse, die bereits in meinem vorhergehenden Post erwähnt wurde:
Die Schleife frisst 100% CPU-Zeit, das lastet den Prozessor unnötig aus und ist unter Multitasking-Systemen inakkurat (v.A. wenn man mit PAUSE+Enter abbricht).
Der hier nun vorgestellte Patch hat folgende Vorteile:
1) Er lastet die CPU nicht zu 100% aus, die VDM wird freigegeben.
2) Das timing ist in Multitasking-Umgebungen um einiges exakter als mit der OA-eigenen Methode
3) Er beseitigt natürlich - was ja die ursprüngliche Intention war - die lästige Warteroutine beim OA4-Start.
Zur Funktionsweise:
Nachdem Gerd das Problem gemeldet hat, habe ich mir einmal den originalen Code der music-Routine angesehen und auch gesehen wo er im Speicher liegt (die relevanten Teile habe ich kommentiert):
Code:
cs:1B00 8AC4 mov al,ah
cs:1B02 E642 out 42,al ; Timer 3 für Frequenz Speaker setzen
cs:1B04 E461 in al,61
cs:1B06 8AE0 mov ah,al ; Alten Wert sichern
cs:1B08 0C03 or al,03
cs:1B0A E661 out 61,al ; Speaker ein
cs:1B0C 368B163219 mov dx,ss:[1932] ; Delayzyklen laden
cs:1B11 B9D80E mov cx,0ED8
cs:1B14 E2FE loop 1B14
cs:1B16 4A dec dx ; .. und solange loopen bis
cs:1B17 75F8 jne 1B11 ; verbraucht
cs:1B19 4B dec bx
cs:1B1A 75F0 jne 1B0C ; und das für alle 10ms intervalle
cs:1B1C 8AC4 mov al,ah
cs:1B1E E661 out 61,al ; Speaker aus
cs:1B20 CA0400 retf 0004 ; bye
cs:1B23 0000 add [bx+si],al ; Start Müll...
cs:1B25 00FF add bh,bh
cs:1B27 FF db FF ; ..Ende Müll
cs:1B28 BB0100 mov bx,0001 ; Einsprungpunkt Timerloop
cs:1B2B 32E4 xor ah,ah ; Den Rest kennen wir ja schon...
cs:1B2D CD1A int 1A
cs:1B2F 52 push dx
cs:1B30 8BC3 mov ax,bx
cs:1B32 B9F10A mov cx,0AF1
cs:1B35 E2FE loop 1B35
cs:1B37 48 dec ax
cs:1B38 75F8 jne 1B32
cs:1B3A 32E4 xor ah,ah
cs:1B3C CD1A int 1A
cs:1B3E 59 pop cx
cs:1B3F 3D0100 cmp ax,0001
cs:1B42 74E4 je 1B28
cs:1B44 2BD1 sub dx,cx
cs:1B46 83FA02 cmp dx,0002
cs:1B49 7307 jnb 1B52
cs:1B4B F8 clc
cs:1B4C 43 inc bx
cs:1B4D 73DC jnb 1B2B
cs:1B4F BBFFFF mov bx,FFFF
cs:1B52 36891E3219 mov ss:[1932],bx
cs:1B57 CB retf
Es wird also gewartet, indem die Anzahl der beim Programmstart errechneten Zyklen geloopt, die CPU also solange mit einer Schleife beschäftigt wird, bis die jeweilige zehntelsekunde abgelaufen ist.
Außerdem habe ich entdeckt, dass derselbe Code nicht nur in OA4.SPI sondern auch noch in APP.SPI zu finden ist, wir müssen also beide Dateien patchen.
So, nun stellt sich die Frage nach einer vernünftigen Lösung fürs Warten.
Mal sehen, ob es DOS/BIOS-Funktionen hierfür gibt.
Grundsätzlich gibt es sie:
Einmal die
Die BIOS-Wait funktion des INT15h und einmal die
MS-DOS 4 SLEEP-Funktion.
Also gleich einmal ausprobieren, ob sie funktionieren. Es stellt sich jedoch bald Ernüchterung ein: Sie funktionieren problemlos in "echtem" DOS, die NTVDM zeigt sich aber ziemlich unbeeindruckt und scheint keine der beiden Funktionen zu unterstützen. Das war's dann also mit der Idee vom relativ einfachen Patch.
Doch es muss ja auch noch andere Möglichkeiten geben. Wenn es nicht so komfortabel geht, muss man sich wohl seine eigene Warteschleife bauen.
Wie stellt man das nun am Besten an?
Hier kommt einem der programmierbare Zeitgeberbaustein 8253 in den Sinn, welcher für die Echtzeituhr zuständig ist.
Dieser erhält die Schwingungen des Oszillators 8284, welcher 1.193.180 Impulse pro Sekunde erzeugt. Dieser Zeitgeber wird übrgens auch zur Tonerzeugung mit dem PC-Speaker verwendet. Der dritte der 3 verfügbaren Timer ist mit dem PC-Speaker verschaltet und gibt dann in entsprechender Frequenz Signale, sodass ein Ton entsteht. Der erste Timer wird für die Systemzeit verwendet und schwingt 18.2mal pro Sekunde. Dem 8253er Baustein kann man nun mittels eines Divisors mitteilen, dass dieser seinen internen Zähler nur dann um 1 erhöhen soll, wenn entsprechend viele Durchläufe vergangen sind. Den Wert des Systemzeitgebers kann man aus dem BIOS-Datenbereich an 46Ch auslesen.
Die Idee ist daher folgende: Man stelle den Sytemzeitgeber so um, dass er mit 10Hz läuft und bei jedem Inkrementieren des Zählers ist eine der Zehntelsekunden, die der User als Dealy-Wert angibt, vergangen. Der Divisor für ein 10Hz-Signal wäre somit 119318. Aber halt! Der Wert ist für ein 16bit-Register zu groß! Bleibt uns also nur der Divisor 11931, welcher dann ein 100Hz-Signal erzeugt.. Reicht ja auch zum Messen, warten wir halt die entsprechende Anzahl an Durchläufen ab.
Während man nun wartet, bis der Timer entsprechend hochgezählt hat, sollte man nun zwecks Vermeidung von hoher CPU-Auslastung den Timeslot der VDM freigeben. Dies geschieht mit bereits oben vorgestellter Funktion
Release Current Virtual Machine Time-Slice.
Nach getanener Arbeit muss man natürlich den Timer wieder auf seinen ursprünglichen Wert zurücksetzen.
Soviel zur Theorie. In der Praxis klingt das zwar recht nett, aber eine Routine, die das alles bewerkstelligt braucht natürlich entsprechend viel Platz. Nie und nimmer bekommt man die in dem durch das eliminieren der Schleife freigewordenen Platz unter. Was also tun?
Wer sich das oben angegebene Codestück genauer angesehen hat wird folgendes feststellen: Direkt nach der music()-Routine befindet sich unsere altbekannte Berechnungsroutine vom Programmstart. Nun ja, die brauchen wir ja eignetlich nicht mehr, die nervt uns sowieso nur. Es erscheint also naheliegend, den Code durch unsere neuen Warte-Routine zu ersetzen und mittels CALL anzuspringen. Natürlich dürfen wir nicht den Anfangscode der Routine überschreiben, sonst würde es uns beim Programmstart zerbröseln. Stattdessen lassen wir ihn das Register BX beschreiben, fügen danach einen Sprung ans Ende der originalen Routine ein, wo die Stackvariable entsprechend gesetzt wird und lukrieren den dazwischen freigewordenen Platz für uns.
Aber das Ganze ist immer noch relativ wenig Platz, weshalb man durch trickreiche Programmierung das Ganze irgendwie reinbekommen muss. Wie ich das Ganze dann gelöst habe ist dem unten stehenden Code zu entnehmen, ich werde darauf garnicht weiter eingehen, kann sich jeder selbst ansehen. 1 Byte ist sogar noch frei geblieben und wurde mit einem NOP aufgefüllt
Hier ist der Code, durch den ich den originalen ersetzt habe:
Code:
cs:1B06 8AE8 mov ch, al ; In ch-Register statt ah, cx bleibt ja unberührt
cs:1B08 0C03 or al,03
cs:1B0A E661 out 61,al ; PC-Lautsprecher ein
cs:1B0C 90 nop ; 1 Byte blieb übrig ;)
cs:1B0D 1E push ds ; DS sichern
cs:1B0E 33D2 xor dx,dx
cs:1B10 8EDA mov ds,dx ; DS auf 0 legen fürs Lesen von BIOS-Dataarea
cs:1B12 BA9B2E mov dx,2E9B ; 100hz Timerauflösung für 8523-5 Baustein
cs:1B15 E82D00 call 1B45 ; Divisor setzen
cs:1B18 E81200 call 1B2D ; Warteroutine aufrufen
cs:1B1B 1F pop ds ; DS wiederherstellen
cs:1B1C 8AC5 mov al,ch ; Aus ch statt ah-Reg. holen
cs:1B1E E661 out 61,al
cs:1B20 CA0400 retf 0004
cs:1B23 0000 add [bx+si],al ; Start Müll
cs:1B25 00FF add bh,bh
cs:1B27 FF db FF ; Ende Müll
cs:1B28 BB0100 mov bx,0001 ; Loopwert mit 1 init (unbrauchbar!)
cs:1B2B EB25 jmp 1B52 ; Zur Initialisierung d. Speicherstelle und ret
cs:1B2D BA0800 mov dx,0008 ; -- Hier beginnt unsere Delayroutine --
cs:1B30 03166C04 add dx,[046C] ; Endzeit in dx
cs:1B34 B88016 mov ax,1680 ; Release current VM timeslice
cs:1B37 CD2F int 2F ; CPU in VM entlasten!
cs:1B39 3B166C04 cmp dx,[046C] ; Loop abgelaufen?
cs:1B3D 79F5 jns 1B34 ; Nein -> Warte weiter
cs:1B3F 4B dec bx ; Warteschleife bis zum Ablauf der Zeit
cs:1B40 75EB jne 1B2D
cs:1B42 BAFFFF mov dx,FFFF ; Timer 0 des 8523-5 wiederherstellen
cs:1B45 B036 mov al,36 ; Setze Timeraufl.
cs:1B47 E643 out 43,al
cs:1B49 8BC2 mov ax,dx
cs:1B4B E640 out 40,al
cs:1B4D 8AC4 mov al,ah
cs:1B4F E640 out 40,al
cs:1B51 C3 ret ; Fertig
Für all jene, die die Routine mit dem Hexeditor in ihre OA4.SPI, APP.SPI und CMP.SPI hineinpatchen wollen: In den Zeilen mit Assemblercode steht - wie unschwer zu erkennen ist - links neben den Mnemonics die Bytesequenz.
Zum Download
Nachdems doch ganz schön viel zu patchen ist, empfehle ich, besser die
neue Version meines Patchers zu verwenden, welcher wieder am
1. Beitrag oben angehängt ist. Bitte vor dem Patchen die originale OA4.SPI wieder zurückkopieren, um sicherzugehen, dass der Patch sauber funktioniert.
..und dann soll noch einer sagen, wir Assembler-Programmierer würden nicht mehr gebraucht werden

Feedback, ob es bei euch funktioniert ist erwünscht!
Schönes verlängertes Wochenende!