Einzelnen Beitrag anzeigen
Alt 07.12.2008, 13:44   #8
leecher
Moderator
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!
leecher ist offline   Mit Zitat antworten