Hallo wobo, vorweg muss ich hierbei darauf hinweisen das ich noch nie auf einem PC Sound programmiert habe. Meine letzten Versuche ein paar Töne zu programmieren war auf dem C64er.
Aber ich habe hier "Das Soundblaster Buch" von Josha Munnik/Eric Oostendorp vom Sybex-Verlag vor mir liegen.
wobo hat geschrieben:Kennt sich einer von Euch etwas genauer mit dem IRQ-Acknowledgement (Port[$20] := $20) aus? Meines Wissens, muß dieses am Ende einer Hardware-Interrupt-Service-Routine ausgegeben werden, damit der IRQ-Controller weiß, dass er den nächsten IRQ für eben diesen IRQ auslösen darf, oder?
z.B. für einen Tastaturhandler (IRQ2=Int9);
Code: Alles auswählen
PROCEDURE TastaturInt; interrupt;
BEGIN
inkey := Port[$60];
{ .... }
Port[$20] := $20;
END;
Das IRQ-Acknowledgement ist hier am Ende der Interrupt-Service-Routine angeordnet, was ich nachvollziehen kann. Es soll ja - wenn ich das richtig verstanden habe - dem IRQ-Controller sagen: „Ich bin fertig, Du darfst jetzt einen neuen Int-9 auslösen.“
Ja genau, so kenne ich es auch.
Ganz anders meine Soundblaster-Beispiel-Routinen. Diese lösen das iRQ-Acknowledgement am Anfang der Interrupt-Service-Routine aus, wie es meines Wissens auch Creative verlangt.
Meine SB-Interrupt-Routine (f. SB IRQ5 u. IRQ7) sieht daher z.B. so aus:
Code: Alles auswählen
PROCEDURE SB_Int; interrupt;
VAR b : byte;
BEGIN
b := Port[$22E]; {1}
Port[$20] := $20; {2}
Vermix_ganz_zeitaufwendig_meine_Samples; {3}
END;
{1} sagt der SB (Basis=$220), dass der gemeldete IRQ angenommen wurde,
{2} IRQ-Acknowledgement, welches nach Creative direkt nach der SB-Entlastung ausgegeben werden muss,
{3} meine sehr zeitaufwendige Sound-Vermixungsroutine.
Eigentlich darf so eine IRQ-Routine nicht sehr lang sein. Das bedeutet das man dort nur wenige Dinge machen kann, aber keine zeitaufwendige Sound-Vermixungsroutine.
Diese lagert man am besten ins Hauptprogramm aus. Mit der IRQ-Routine und dafür spezifizierte Speicheradressen (die man als Flags verwendet kann) steuert man dann ob die Vermixungsroutine etwas tun soll, oder nicht.
In einem Beispielprogramm zur Interruptsteuerung aus dem Buch(Kapitel über DSP Programmieren) wird hier eine Speicher-Adresse verwendet wo die Samplelänge eingestellt wird.
Auch wird innerhalb der IRQ-Routine ein Befehle via Port an die SB übertragen. In dieser Unter-Routine wird dieses 200 mal
versucht und dabei geschaut, ob es erfolgreich war (mit "in al, dx" und "or al, al" Prüfe Bit 7)
und wenn das Übertragen geklappt hat, dann wird der eigentliche Befehlswert zur SB übertragen.
Zum Schluss wird erst EOI gesendet, die gepushten Register vom Stack gepoppt und mit "iret" beendet.
So wie ich das jetzt verstanden habe, könnte also die Situation auftreten, dass mein PC zu langsam ist, und ein erneuter SB-IRQ aufgerufen wird, obwohl mein alter ja noch gar nicht abgearbeitet ist. Denn ich habe ja sehr frühzeitig das IRQ-Acknowledgement gegeben.
Ich dachte daher, dass ich zwei globale Variablen benutze, die mir als Flag sagen, ob mein PC zu langsam ist, nämlich
CalculatingSamples : boolean = false;
CPUtooslow : boolean = false;
Meine SB-IRQ-Routine sähe dann so aus:
Code: Alles auswählen
PROCEDURE SB_Int; interrupt;
VAR b : byte;
BEGIN
b := Port[$22E]; {1}
Port[$20] := $20; {2}
If CalculatingSamples then CPUtooslow := true;
CalculatingSamples := true;
if Not CPUtooSlow then
Vermix_ganz_zeitaufwendig_meine_Samples; {3}
CalculatingSamples := false;
END;
Ich frage dann in meinem Hauptprogramm laufend ab, ob das Flag CPUtooslow gesetzt ist und beende dann ggf. das Hauptprogramm mit dem Hinweis, dass der PC (jedenfalls für die gewählte SB-Frequenz) zu langsam ist.
Jetzt habe ich genau diese Situation: mein PC (386sx16) vermixt bei 12 kHz die Samples tadellos, bei 16 kHz ist er aber schon zu langsam. Mein Rechner steht dann: es wird ausschließlich (total verzerrter) Sound ausgegeben. Mein Hauptprogramm hat irgendwie keine Chance, das Flag CPUtooslow abzufragen und wie gewünscht zu beenden. Auch Ctrl-Break etc. hilft nichts mehr. Auf NumLock oder andere Tasten reagiert das BIOS nicht mehr. Es hilft nur noch ein Reset, um dem Graus ein Ende zu setzen.
Irgendwie wird in der SB-IRQ-Routine auch mein Flag CPUtooslow nicht gesetzt. Denn würde es gesetzt, dürften ja überhaupt keine Samples mehr vermixt werden. Dies ist aber definitiv nicht der Fall: Das Vermixen geht einfach weiter, nur eben zu langsam.
Weiss jemand Rat? Oder – etwas verallgemeinert – kennt jemand eine Methode, wie per Software überprüft werden kann, ob eine CPU noch schnell genug ist, immer wieder hintereinander auftretende Hardware-IRQs abzuarbeiten?
Wie gesagt, ich würde auf keinen PC, egal wie schnell er ist, eine IRQ-Routine mit zu vielen Befehlen bestücken, sondern nur dafür verwenden um z.B. Flags zu setzen, die dann von der Hauptroutine ausgewertet werden, um eine zeitaufwendige Routine im Hauptprogramm anzustossen, bzw. zu beenden.
....
Um die Geschwindigkeitunterschiede von verschieden schnellen CPUs zu ermitteln habe ich eine Testschleife mit 64 NOPs verwendet, wo ein Zähler die jeweiligen Runden zählt. Die Testschleife läuft genau eine Sekunde lang und je noch CPU-Geschwindigkeit wird die Schleife mehr oder minder oft ausgeführt und dabei ein Zähler hochgezählt. Damit konnte ich die Geschwindigkeit eines von mir verwendeten Menüs, welches man über Cursortasten bedient, mit einer entsprechenden Verzögerungsschleife steuern, wobei der ermittelte Zähler hierbei mit eingerechnet wird und für eine jeweils angepasste Geschwindigkeit sorgt. Anders war das Menü auf schnelleren CPUs nicht zu bedienen, da sonst alle Menüpunkte zu schnell durchlaufen wurden und es dann eher zufällig war an welchem Menüpunkt angehalten wurde, auch wenn man die Taste nur ganz kurz drückt.
In der nun folgenden Routine wird zunächst überprüft ob nur ein 286er, oder bereits ein 386er+ vorhanden ist und dann zu einer von zwei Schleifen gesprungen. Die eine Schleife verwendet nur zwei 16Bit-Register, die andere Schleife ein 32Bit-Register als Zähler.
Am Ende wird eine Datei(SPEED.tic) mit 4 Bytes angelegt mit dem Inhalt des Schleifenzählers. Diese Datei kann dann von anderen Anwendungen eingeladen werden.
Code: Alles auswählen
.MODEL SMALL
.386P
.387
.CODE
org 100h
Cpu86 = 0 ; Wert als Index in der Takt-Tabelle
Cpu286 = 4
Cpu386 = 8
Cpu486 = 12
Cpu586 = 16
START: mov ax, @DATA
mov ds, ax ; DS auf Daten-Bereich
;-------------------------------------
call GETCPU ; Prozessor-Analyse: AX=CPU
;-------------------------------------
call GETSPEED ; Zählschleife
;-------------------------------------
call FILE ; create a new file
;-------------------------------------
mov ah, 4Ch ; Dos-Rücksprung, Programm-Ende
int 21h
;────────────────────────────────────────────────────────────────────────────
; U N T E R - R O U T I N E N
;────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16) + 16
;---------------------------------------------------------------------------
GETCPU: mov ax, Cpu86
xor bx, bx
push bx ; Null auf Stack
popf ; Null in Flagregister
pushf
pop bx ; zurück nach bx
and bh, 0F0h
cmp bh, 0F0h ; wenn gleich, dann 8086
je short CPUOK
;-------------------------------------
mov ax, Cpu286
push 7000h ; dasselbe mit 7000h
popf
pushf
pop bx
and bh, 70h
jz short CPUOK
;-------------------------------------
mov ax, Cpu386
mov edx, esp
and esp, 0FFFCh ; durch vier teilbare Adr.
pushfd
pop ebx
mov ecx, ebx
btc ebx, 18 ; Bit 18 umdrehen
push ebx
popfd
pushfd
pop ebx
push ecx ; alte Flaggen zurück
popfd
mov esp, edx ; Stack zurück
cmp ecx, ebx ; wenn gleich dann 386
jz short CPUOK
;-------------------------------------
mov ax, Cpu486
btc ecx, 21
push ecx
popfd
pushfd
pop ebx
cmp ebx, ecx ; wenn ungleich, dann 486
jnz short CPUOK
;-------------------------------------
mov ax, Cpu586 ; sonst Pentium
;----------------------------------------------------------------------------
CPUOK: mov bp, ax
ret ; AX=Cpu
;────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16) + 16
;---------------------------------------------------------------------------
GETSPEED: cli ; Interrupt's verbieten
xor ax, ax
mov es, ax ; ES auf Seg.: 0
;-------------------------------------
mov di, es:[20h] ; Vector von INT 8 retten (Offset)
mov si, es:[22h] ; (Segment)
;-------------------------------------
mov es:[20h], OFFSET NEUVEC ; Interrupt-Vector von INT 8
mov es:[22h], cs ; auf neue Routine legen
;-------------------------------------
mov al, 36h ; RUNDE auf 18,2 Hertz (Standart)
out 43h, al
xor al, al
out 40h, al ; low
out 40h, al ; high
;-------------------------------------
cmp bp, 8 ; 32-Bit-CPU ?
jb short M16
jmp M32
;---------------------------------------------------------------------------
org START + ((($-START)/16)*16) + 16
;---------------------------------------------------------------------------
M16: mov bp, 1 ; Aktiv-Flag = IRQ-Ende wenn 0
xor cx, cx ; Zähler low
xor dx, dx ; Zähler high
sti ; neuen Interrupt 8 starten
;-------------------------------------
S1: and bp, bp ; Auf IRQ-Ende warten
jnz S1
mov bp, 1 ; Für's Zählen IRQ Aktiv-Flag setzen
;-------------------------------------
S2: inc cx ; Hauptzählschleife
jnz short S3
inc dx
;-------------------------------------
S3: nop ; 64 Nop's
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
;-------------------------------------
and bp, bp ; auf Timer-IRQ-Ende abfragen
jnz S2 ; weiter, wenn Timer-IRQ noch aktiv
;-------------------------------
mov RUNDE, cx ; Zähler low retten
mov RUNDE+2, dx ; Zähler high retten
;---------------------------------------------------------------------------
ANALYS: cli ; Interrupts verbieten
mov es:[20h], di ; alten Interrupt-Vector
mov es:[22h], si ; von INT 8 wiederherstellen
sti ; Interrupts erlauben
ret
;────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16) + 16
;---------------------------------------------------------------------------
M32: mov bp, 1 ; Aktiv-Flag = IRQ-Ende wenn 0
xor ecx, ecx ; Zähler
sti ; neuen Interrupt 8 starten
;-------------------------------------
S4: and bp, bp ; Auf IRQ-Ende warten
jnz S4
;-------------------------------------
mov bp, 1 ; Für's Zählen IRQ Aktiv-Flag setzen
;-------------------------------------
S5: inc ecx ; Hauptzählschleife
nop ; 64 Nop's
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
;-------------------------------------
and bp, bp ; auf Timer-IRQ-Ende abfragen
jnz S5 ; weiter, wenn Timer-IRQ noch aktiv
;---------------------------------------------------------------------------
mov DWORD PTR[RUNDE], ecx ; Zähler retten
jmp ANALYS
;────────────────────────────────────────────────────────────────────────────
; neue I R Q - R o u t i n e für INT 8
;────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16) + 16
;---------------------------------------------------------------------------
NEUVEC: mov al, 20h ; neue IRQ-Routine für INT 8
dec bp ; Aktiv-Flag löschen
out 20h, al ; =EOI
iret
;────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/16)*16) + 16
;---------------------------------------------------------------------------
FILE: mov dx, OFFSET SPEEDNAM
xor cx, cx
mov ah, 3Ch ; Datei erstellen
int 21h
mov bx, ax
;---------------------------------------
mov dx, OFFSET RUNDE
mov cx, 4
mov ah, 40h ; Datei beschreiben
int 21h
;---------------------------------------
mov ah, 3Eh ; Datei schließen
int 21h
ret
;────────────────────────────────────────────────────────────────────────────
; D A T E N - B E R E I C H
;────────────────────────────────────────────────────────────────────────────
org START + ((($-START)/64)*64) + 64
;---------------------------------------------------------------------------
.DATA
org 0
;---------------------------------------------------------------------------
RUNDE DW 0, 0 ; Anzahl der Schleifen-Durchläufe
SPEEDNAM DB "SPEED.tic", 0
;────────────────────────────────────────────────────────────────────────────
.STACK 20h
end
Meine Anwendung mit den Menü habe ich auf einem 386er mit 40 Mhz programmiert. Nun habe ich diese Anwendung unter XP mit DOSBox 0.74 erneut auf meinem Core2Quad mit 2700 Mhz getestet und das Menü kann man immer noch bedienen.
Die Anzahl der Schleifendurchläufe(im Speed.tic) ist dort Hex: 78E536. Für die Cursortasten rechts/links multipliziere ich diesen Wert mit "Hori_Speed = 32h" und für die Cursortasten oben/unten multipliziere ich den Wert mit "Vert_Speed = 24h", um daraus eine Verzögerungsschleife für die Menübedienung zu berechnen.
Dirk