Sous DOS, il existe différents timings :
http://retrocomputing.stackexchange.com ... ect=1&lq=1
Le plus pratique pour l'affichage vidéo est celui du retour de balayage d'écran :
http://programmersheaven.com/discussion ... in-the-vga
Il s'agit de l'interruption provoquée par beaucoup de cartes EGA ou VGA lors de ce retour. Elle signale que l'image a été rafraîchie et que la mémoire vidéo n'est plus lue. En effet, il y a plusieurs raisons de vouloir en être averti par la carte vidéo :
- cadencer une animation ;
- synchroniser l'écriture en mémoire vidéo avec ce laps de temps (tampon simple) ;
- savoir quand permuter deux pages vidéo alors qu'on écrit dans une troisième (triple-tampon).
http://www.vcfed.org/forum/showthread.p ... on-EGA-VGA
http://www.vogons.org/viewtopic.php?t=58445
Hier, j'ai décidé de m'y remettre.
Voici un programme d'animation vidéo déjà présenté sur ce forum, il y a longtemps :
viewtopic.php?f=24&t=5047&start=31
Il s'agit d'une démonstration du double-tampon sous BASICA (EGA 64 Ko). Elle tourne plus rapidement sous QBasic mais garde un gros inconvénient : elle ne fait strictement rien pendant l'attente due à l'instruction WAIT &H3DA,8 (ligne 3130). Ce temps perdu pourrait avancer l'écriture du second tampon. L'utilisation du timer PC est possible mais reste inférieure à celle de l'interruption existante sur la plupart des cartes EGA et VGA.
La carte EGA d'IBM utilisait l'IRQ2. J'avais corrigé le programme d'exemple proposé ici pour le mode MS-DOS (inopérant dans une fenêtre DOS) :
viewtopic.php?f=16&t=26369
De nos jours, c'est le BIOS qui choisit pour nos cartes VGA. C'est pourquoi j'ai ajouté une ligne IRQ au début du fichier en assembleur : l'utilisateur n'a qu'à changer avec le numéro attribué à sa carte. Le programme appelant est en C. Problème : nous autres du peuple préférons l'EDI de QuickBASIC. Il va donc falloir imiter 12-2.C et adapter 12-1.ASM que nous pouvons visualiser au lien précédent.
Explications
L'interface entre le C Microsoft et l'assembleur est très souple. L'environnement QuickBASIC semble plus fermé, le BASIC ne partageant pas facilement ses procédures et données. Cependant, depuis la version 4.0b, il est permis à une routine externe de demander une action au BASIC. Il s'agit de la scrutation UEVENT :
http://jeffpar.github.io/kbarchive/kb/033/Q33488/
Il suffit alors que la routine appelle la procédure lointaine SetUEvent au moment venu. Si l'application doit être autonome, c'est-à-dire, sans nécessiter le run-time après compilation, la routine doit être liée au segment nommé CODE. Il est bon de savoir que cet appel modifie les registres AX et BX car un handler d'interruption (ISR) doit préserver tous les registres.
Pour l'exemple, nous utiliserons QuickBASIC 4.5, comportant la bibliothèque BCOM45, le run-time BRUN45 (.LIB et .EXE) et la QuickLibrary BQLB45. Nous avons vu que le programmeur C sauvait les registres BP, SI et DI à l'entrée de toute routine appelé par le programme C, puis les restituait à la sortie. De même pour le segment de données DS lorsqu'il était modifié. Et bien, le programmeur QuickBASIC doit aussi se soucier du segment extra ES. En outre, l'emploi d'un assembleur compatible MASM est requis (autre que la version IBM 1.0).
Nous savons que les procédures de BASIC n'ont pas de code de retour, alors que C emploie le registre AX pour cela. Nous enverrons donc une variable entière comme paramètre à notre routine d'initialisation. Elle renverra ainsi son succès ou son échec :
Code: Select all L23: mov bx,[bp+6] mov [bx],ax |
Pour tester nos routines dans l'environnement QuickBASIC, nous aurons à les incorporer dans une QuickLib. Supposant que notre fichier source s'appelle VREGA.ASM, nous emploierons la QuickLib par défaut et le paramètre QUICKLIBRARY de LINK :
Code: Select all MASM VREGA; LINK VREGA,,,BQLB45/Q; |
Code: Select all QB TEST /L VREGA |
Code: Select all MASM VREGA; BC TEST/V/O; LINK TEST+VREGA; |
Code: Select all DECLARE SUB Bye ALIAS "_exit" (BYVAL ErrorLevel%) REM (...) Bye Level% ' ERRORLEVEL = Level% AND 255 |
Fichiers
Exemple TEST.BAS à compiler puis à lier avec VREGA.OBJ pour obtenir l'exécutable TEST.EXE :
SPOILER Disabled
VREGA.ASM à assembler pour lier à TEST.OBJ ou afin de créer la QuickLibrary VREGA.QLB :
Code: Select all DECLARE SUB Bye ALIAS "_exit" (BYVAL ErrorLevel%) ON UEVENT GOSUB Increment UEVENT ON CALL EnableISR0A(VRcount%) IF VRcount% THEN PRINT "Impossible d'activer le handler d'interruptions verticales" Bye 1 END IF WHILE VRcount% < 600 PRINT "Nombre d'interruptions verticales :"; STR$(VRcount%) PRINT CHR$(30); WEND UEVENT OFF CALL DisableISR0A END Increment: LET VRcount% = VRcount% + 1 RETURN |
SPOILER Disabled
Le numéro IRQ doit correspondre à celui de la carte vidéo du programmeur.Code: Select all TITLE 'Listing 12.1' NAME VREGA PAGE 55,132 ; ; Nom : VREGA ; ; Fonction : Routine d'interruption verticale pour EGA et VGA ; ; Appelant : QuickBASIC 4.0b ; ; EnableISR0A (integer%) 'retourne 0 si bien installée ; ; DisableISR0A ; ; QuickLib : LINK VREGA,,,BQLB??/Q; ; ; Une carte VGA PnP peut se voir attribuer une ligne IRQ autre que 2/9 : IRQ EQU 2 ; le PC/AT redirige la ligne IRQ2 sur 9. if IRQ lt 2 or IRQ eq 6 or IRQ eq 8 or IRQ eq 13 ; PIT/KBD/FDC/RTC/FPU if1 %out Ligne IRQ réservée ! %out Assemblage interromptu. endif else CRT_MODE EQU 49h ; adresses de la zone des informations ADDR_6845 EQU 63h ; vidéo du BIOS DGROUP GROUP _DATA CODE SEGMENT byte public 'CODE' ; seg BASIC requis pour BC /O ASSUME cs:CODE,ds:DGROUP EXTRN Setuevent:far ; UEVENT dans le pgm QB qui appelle ISR0A PROC far ; handler d'interruption pour l'INT 0Ah push ax ; préservation registres push bx push dx push ds mov ax,seg DGROUP mov ds,ax ; DS -> DGROUP if IRQ ge 8 and IRQ ne 9 mov al,20h out 0A0h,al ; EOI au PIC esclave du PC/AT endif ; Voir si c'est bien une interruption verticale mov dx,3C2h ; DX := port d'E/S pour le ; registre 0 d'état des entrées in al,dx test al,80h ; tester le bit 7 du reg. d'état jnz L10 ; saut si interruption verticale ; Ce n'est pas une interruption verticale. On chaîne donc le traitement ; vers l'ancien handler if IRQ lt 8 and IRQ ne 2 mov al,20h out 20h,al ; EOI au PIC maître du PC/AT endif pushf ; simuler une INT call ds:PrevISR0A ; vers l'ancien handler de l'Int 0Ah jmp short Lexit ; Traiter une interruption verticale L10: mov dx,Port3x4 ; DX := 3B4h or 3D4h in al,dx ; AL := valeur du reg. adresse du CRTC push ax ; la sauvegarder mov ax,DefaultVREnd ; AH := valeur par défaut pour reg. VR End ; AL := 11h (numéro du registre) and ah,11101111b ; AH bit 4 := 0 (RAZ latch d'interruption) out dx,ax ; modifier le registre VR End jmp $+2 ; laisser un peu de temps au CRTC sti ; activer les interruptions ; Faire quelque chose d'utile... call Setuevent ; attention : modifie AX et BX ; Envoyer un signal de fin d'interruption au PIC Intel 8259A pour ; autoriser les interruptions sur IRQ2 suivantes cli ; désactiver les interruptions mov al,20h ; port du 8259A out 20h,al ; envoyer un EOI non spécifique au 8259A jmp $+2 ; attendre sa réponse ; Permettre au CRTC de produire d'autres interruptions mov ax,DefaultVREnd ; AH := valeur par défaut pour le reg. VR End ; AL := 11h (numéro du registre) and ah,11011111b ; AH bit 5 := 0 (autoriser int. verticale) or ah,00010000b ; AH bit 4 := 1 (activer le latch out dx,ax ; d'interruption) jmp $+2 pop ax out dx,al ; restaurer précédente adresse registre Lexit: pop ds ; restauration registres et retour pop dx pop bx pop ax iret ISR0A ENDP ; ; EnableISR0A -- Activer un handler d'Interruption ; PUBLIC EnableISR0A EnableISR0A PROC far push bp ; préservation registres mov bp,sp push es mov ax,40h mov es,ax ; ES -> zone des infos vidéo du BIOS ; Sauver les valeurs des registres du CRTC mov dx,es:[ADDR_6845] ; DX := adresse du port CRTC mov Port3x4,dx ; sauvegarder l'adresse mov ax,1A00h ; AH := 1AH (numéro fonction INT 10H) ; AL := 0 (lire combinaison d'affichage) int 10h ; AL := 1AH si fonction 1AH supportée ; BL := sous-système vidéo actif cmp al,1Ah jne L20 ; saut si pas VGA cmp bl,7 je L21 ; saut si VGA cmp bl,8 je L21 ; saut si VGA mov ax,0FFFFh ; renvoyer 0FFFFh si ni EGA ni VGA jmp short L23 ; Obtenir la valeur par défaut pour le registre EGA Vertical Retrace End L20: mov al,es:[CRT_MODE] ; AL := numéro du mode video du BIOS mov bx,offset DGROUP:EGADefaultVals xlat ; AL := valeur par défaut pour le reg. VR End jmp short L22 ; Obtenir la valeur par défaut pour le registre VGA Vertical Retrace End L21: mov al,VREndReg ; AL := numéro du registre VR End out dx,al inc dx ; DX := 3B5H or 3D5H in al,dx ; AL := valeur courante de ce registre L22: mov VREndValue,al ; la sauvegarder ; Sauvegarder l'ancien vecteur d'interruption 0Ah if IRQ eq 9 mov ax,350Ah ; AH := 35H (numéro de la fonction INT 21h) ; AL := 0AH (numéro de l'interruption) else if IRQ ge 8 mov ax,3570h+IRQ-8 else mov ax,3508h+IRQ endif endif int 21h ; ES:BX := précédent vecteur de l'Int 0AH mov word ptr PrevISR0A,bx mov word ptr PrevISR0A+2,es ; le sauvegarder ; Modifier le vecteur d'interruption avec l'adresse du présent handler push ds ; préserver DS mov dx,offset ISR0A push cs pop ds ; DS:DX -> ISR0A if IRQ eq 9 mov ax,250Ah ; AH := 25H (numéro de fonction de l'Int 21H) ; AL := 0AH (numéro interruption) else if IRQ ge 8 mov ax,2570h+IRQ-8 else mov ax,2508h+IRQ endif endif int 21h ; modifier le vecteur d'interruption Int 0AH pop ds ; restaurer DS ; Réactiver IRQ2 en mettant à 0 le bit 2 du registre de masque du 8259A cli ; verrouiller les interruptions if IRQ eq 9 mov dx,21h ; DX := registre de masque du 8259A in al,dx ; AL := valeur présente and al,11111011b ; RAZ bit 2 else if IRQ ge 8 mov dx,0A1h in al,dx and al,not(1 shl (IRQ-8)) else mov dx,21h in al,dx and al,not(1 shl IRQ) endif endif out dx,al ; Réactiver les interruptions verticales mov dx,Port3x4 ; DX := 3B4H or 3D4H mov ax,DefaultVREnd and ah,11001111b out dx,ax ; RAZ bits 4 et 5 du registre VR End jmp $+2 ; laisser le temps au CRTC or ah,00010000b out dx,ax ; mise à 1 du bit 4 jmp $+2 sti ; réactiver les interruptions xor ax,ax ; AX := 0 (valeur de retour) L23: mov bx,[bp+6] mov [bx],ax pop es ; restauration registres et retour mov sp,bp pop bp ret 2 EnableISR0A ENDP ; ; DisableISR0A -- Désactiver le handler d'interruptions verticales ; PUBLIC DisableISR0A DisableISR0A PROC far push bp mov bp,sp push ds ; Désactiver les interruptions verticales cli ; verrouiller les interruptions mov dx,Port3x4 mov ax,DefaultVREnd out dx,ax ; restaurer le reg. Vertical Retrace End jmp $+2 sti ; réactiver les interruptions ; Restaurer le précédent handler du vecteur d'interruption 0Ah lds dx,PrevISR0A ; DS:DX := précédent vecteur Int 0AH if IRQ eq 9 mov ax,250Ah ; AH := 25H (numéro fonction INT 21H) ; AL := 0AH (numéro interruption) else if IRQ ge 8 mov ax,2570h+IRQ-8 else mov ax,2508h+IRQ endif endif int 21h pop ds ; restauration registres et retour mov sp,bp pop bp ret DisableISR0A ENDP CODE ENDS _DATA SEGMENT word public 'DATA' PrevISR0A DD ? ; zone de sauvegarde pour l'ancien ; vecteur d'interruption 0Ah Port3x4 DW ? ; 3B4h ou 3D4h DefaultVREnd LABEL word VREndReg DB 11h ; numéro registre Vertical Retrace End VREndValue DB ? ; valeur par défaut pour reg.VR End EGADefaultVals DB 2Bh,2Bh,2Bh,2Bh,24h,24h,23h,2Eh ; valeur par défaut DB 00h,00h,00h,00h,00h,24h,23h,2Eh ; pour registre EGA DB 2Bh ; EGA VR End _DATA ENDS endif END |
Conclusion
Ce que nous avons appris :
- retourner un ERRORLEVEL avec une application compilée par QB 4.5 et non PDS 7.0 ;
- créer une QuickLib pour tester une routine assembleur à partir de l'EDI BASIC ;
- répondre à un appel externe en permettant à UEVENT de scruter ;
- traiter une interruption EGA/VGA lorsqu'elle se produit.
P.S. : pour certaines cartes compatibles EGA, JNZ L10 serait à remplacer par JZ L10.
Michael Abrash et Dan Illowsky sont plus précis sur ce point (polarité du bit 7 du registre Input Status 0) :
http://archive.org/stream/PC_Tech_Journ ... 3/mode/2up
Constat de début 2021, le lien aux fichiers 12-?.* est mort !