Win3x.Org

Windows & DOS Community

Implémenter une gestion d'interruption EGA/VGA en QuickBASIC

Répondre   Page 1 sur 1  [ 5 messages ]
Auteur Message
gm86
Sujet du message : Implémenter une gestion d'interruption EGA/VGA en QuickBASIC
Publié : 07 avr. 2020 15:32
Membre inscrit
Avatar de l’utilisateur
Hors-ligne
 
Messages : 1290
Inscription : 01 sept. 2008 19:07
 
Introduction

Sous DOS, il existe différents timings :
https://retrocomputing.stackexchange.co ... ect=1&lq=1
Le plus pratique pour l'affichage vidéo est celui du retour de balayage d'écran :
https://programmersheaven.com/discussio ... 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).
Ce sujet est revenu sur le Web récemment :
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 :
L23:	mov	bx,[bp+6]
	mov	[bx],ax
D'ailleurs, les procédures externes doivent être appelées par leur adresse complète. Elles seront donc déclarées FAR en plus de PUBLIC.


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 :
MASM VREGA;
LINK VREGA,,,BQLB45/Q;
Supposant que notre programme BASIC s'appelle TEST.BAS, nous l'exécuterons ainsi :
QB TEST /L VREGA
Une fois les tests concluants, nous passerons à la compilation. Vu que notre programme ne comporte pas de numéro de ligne, la scrutation d'événement se fera par la paramètre V et non W. De plus, si on souhaite ne pas dépendre de BRUN45.EXE et que notre routine appelant SetUEvent se trouve dans le segment CODE, nous compilerons une application EXE autonome (/O) :
MASM VREGA;
BC TEST/V/O;
LINK TEST+VREGA;
Dernière chose, Andrew Schulman dans les Coulisses du DOS "Undocumented DOS", chapitre 2, explique qu'une application compilée par QB45 ne peut pas renvoyer un code de retour à la ligne de commande (ERRORLEVEL). L'astuce d'appeler directement la fonction 4Ch de l'interruption 21h du DOS court-circuite le BASIC, empêchant ce dernier de restituer le curseur. Plus tard, on a su que la QuickLibrary par défaut comportait le module C _exit fonctionnant très bien à une condition : que l'application EXE ait été autonome. Attention toutefois à son emploi dans l'environnement QuickBASIC (QB /L), cela équivaudrait à exécuter l'instruction SYSTEM (quitter l'interpréteur). Exemple :
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 :
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
VREGA.ASM à assembler pour lier à TEST.OBJ ou afin de créer la QuickLibrary VREGA.QLB :
	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

Le numéro IRQ doit correspondre à celui de la carte vidéo du programmeur.


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) :
https://archive.org/stream/PC_Tech_Jour ... 3/mode/2up

Constat de début 2021, le lien aux fichiers 12-?.* est mort !

Dernière modification par gm86 le 16 avr. 2021 15:59, modifié 3 fois.

_________________

C:\ONGRTLNS.W95


Haut
Profil Citer
Deksor
Sujet du message : Re: Implémenter une gestion d'interruption EGA/VGA en QuickBASIC
Publié : 07 avr. 2020 19:12
Modérateur
Avatar de l’utilisateur
Hors-ligne
 
Messages : 10538
Inscription : 23 mai 2011 13:33
PC Rétro : 486DX 33 8mo SB Pro 2, P2 450 128mo Voodoo 2 SLI
 
Intéressant tout ça !
Je ne savais même pas qu'on pouvait faire ça avec une carte VGA peu importe le langage :lol:

C'est très très pratique !!

_________________

Laptop hater


Haut
Profil Citer
gm86
Sujet du message : Re: Implémenter une gestion d'interruption EGA/VGA en QuickBASIC
Publié : 07 avr. 2020 21:25
Membre inscrit
Avatar de l’utilisateur
Hors-ligne
 
Messages : 1290
Inscription : 01 sept. 2008 19:07
 
Il suffit que la puce VGA soit compatible, voire qu'un jumper ou une option du BIOS le lui permette (Assign IRQ For Vga dans le SETUP).

Il paraît que le bit 7 (port 3C2h au début de l'ISR) possède une polarité différente sur certains clones EGA. Ce qui est gênant vu qu'on traiterait alors l'interruption d'un périphérique autre que la carte vidéo.

L'identification de la source de l'interruption est différente sur la carte MCGA. On peut le voir au début du listing 12-3 de ma disquette. Néanmoins, à l'instar du listing EGA/VGA original, l'IRQ2 reçoit une EOI avant de « faire quelque chose de très utile » entre les instructions STI et CLI. Or je ne sais pas si cela suffit à faire planter un PS/2. Je laisse donc le soin d'une éventuelle correction au possesseur d'un modèle 25 ou 30.

Quant à la procédure d'une carte XGA, il faut se référer au PC Graphics Handbook (§14.4.5 The Vertical Retrace Interrupt, XGA Screen Blanking Interrupt).

La programmation vidéo était aussi un prétexte pour présenter SetUEvent donc voici la documentation en ligne de Microsoft :
Q31936: Example of Using SetUEvent, ON UEVENT to Detect Mouse Presence
Q32164: BASIC Example of CALL SetUEvent, ON UEVENT GOSUB Trapping
Q35968: CALL SetUEvent in Assembly Routine to Disable PRINT SCREEN
Q46182: ON TIMER Can Wrongly Trigger CALL SETUEVENT, ON UEVENT Trap
Q69159: Example of How to Call BASIC SetUEvent from C; ON UEVENT GOSUB

Sans oublier l'ERRORLEVEL de la QuickLibary standard non documenté par Microsoft lui-même ! La preuve :
Q41533: BASIC 7.00 Can Return Exit Code (Error Level) to Batch File

_________________

C:\ONGRTLNS.W95


Haut
Profil Citer
redstone02
Sujet du message : Re: Implémenter une gestion d'interruption EGA/VGA en QuickBASIC
Publié : 26 avr. 2020 20:06
Membre inscrit
Avatar de l’utilisateur
Hors-ligne
 
Messages : 248
Inscription : 21 déc. 2018 18:08
PC Rétro : coming soon
 
mais c'est simple changer erorlevel
shell "set erorlevel=1"

_________________

C:\>win
:tada:win 3.11:tada:


Haut
Profil Citer
gm86
Sujet du message : Re: Implémenter une gestion d'interruption EGA/VGA en QuickBASIC
Publié : 26 avr. 2020 21:12
Membre inscrit
Avatar de l’utilisateur
Hors-ligne
 
Messages : 1290
Inscription : 01 sept. 2008 19:07
 
C'est une astuce pour fichier batch qui a l'inconvénient de créer une variable ne revenant pas automatiquement à zéro.

_________________

C:\ONGRTLNS.W95


Haut
Profil
Afficher : Trier par : Ordre :
Répondre   Page 1 sur 1  [ 5 messages ]
Revenir à « Projets aboutis »
Aller :