door Thijs Schoonbrood

Hardware Project: Bouw zelf een Covox!

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ In dit bestand vindt u een uitgebreidere verhandeling   ³
³ over het programmeren met de Covox zelfbouw sound       ³
³ interface, zoals daarover is geschreven in Computer!    ³
³ Totaal nummer 3. Mocht u nog vragen hebben, zendt dan   ³
³ een briefje aan de redactie. Wij zorgen dan voor        ³
³ doorzending van uw brief aan de auteur.                 ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

JAAaaaa... Programmeren !!

Voor er geprogrammeerd zal worden moet ik U vertellen dat het volgende geen makkelijke stof is. Programmeren is echt niet moeilijk, maar voor dit stuk van het artikel is wel gedegen kennis van assemblerprogrammering vereist. Info over assembler is te bekomen bij Methode Vanderaart.

Ik zal eerst uitleggen hoe geluidsgolven eruit 'zien'. Zoals U waarschijnlijk reeds weet is geluid niets anders dan trillende lucht. U kunt de klankkleur beinvloeden door de
trillingen anders te laten verlopen. Zo kennen we b.v. de sinus-, de zaagtand-, de puls- en de driehoeksgolf. De frequentie is het omgekeerde van de tijd die nodig is om 1 volledige golf te 'doorlopen'. Als je dus een frequentie van 1000Hz hebt, is de tijd die nodig is om 1 volledige sinusgolf af te spelen 0.001 seconde.

Behalve frequentie heeft elke toon ook nog een volume. Het volume van een bepaald geluid is in feite niets anders dan het verschil tussen de maximale en de minimale waardes. Als U de interface steeds laat switchen tussen 0FFh en 000h, krijgt U de luidste toon die de interface kan genereren. (Het verschil is immers 0FFh, de maximale waarde voor 8 bitten.) Pendelen tussen 07F en 000h levert een toon met halve geluidssterkte op.

Hoe stelt een programma een bepaalde spanning in? Simpel, door gewoon de gewenste waarde met een OUT naar de poort te sturen. Regel: Hoe hoger de verzonden waarde, hoe hoger de spanning. Wat is de juiste poort ? Dat hangt er vanaf. Het onderstaande schema zou duidelijkheid moeten scheppen:

Printerpoort:			|	I/O Adres
----------------------------------------------
Printerpoort op MDA kaart	|	003BCh
Printerpoort #1			|	00378h
Printerpoort #2			|	00278h

In feite zou U nu al wat geluid moeten kunnen produceren, alleen zit U dan met het probleem van de timing. Hoe krijg ik de computer zover dat hij bijvoorbeeld 1000 keer per seconde de waarde op de printerpoort verandert? Omdat er veel verschillende computers in omloop zijn met elk een aparte kloksnelheid kunnen we dus geen wachtlussen inbouwen.

Dat wordt anders opgelost, en wel met de timerchip.
Eigenlijk is het niet helemaal volgens het boekje, omdat dat niet op alle computers werkt. Op 95% van de conputers werkt het echter wel, en het is de enige manier om goed met de interface om te gaan. Of het bij U werkt? Weet ik niet, hoogstwaarschijnlijk wel, maar U zult het even moeten proberen...

De timerchip valt zo te programmeren dat er 1000
interrupts per seconde genereert worden. Als U bijvoorbeeld de interrupthandler de waarde op de poort laat afwisselen tussen 000h en 0FFh, dan krijgt U een pulsgolf met een frequentie van 500Hz en maximaal volume.

Omdat goede documentatie over de timerchip schaars is, zet ik het nog even op een rijtje voor de wat minder bedeelden wat documentatie betreft. 

De timerchip bezit 3 counters die aftellen naar nul. Bij iedere nul-doorgang veroorzaakt de chip een bepaalde actie. Counter 0 zorgt voor een IRQ 0. (De overige counters zijn hier niet van toepassing. Counter 1 dient voor de refresh en counter 2 is voor de interne speaker) De processor antwoordt op een IRQ 0 met een INT 8h. Het is zaak die INT 8h af te vangen en onze eigen routine te installeren. Let op! De oude INT 8h wordt niet na afloop van onze routine aangeroepen. Dat heeft als gevolg dat de klok stil blijft staan. Jammer voor de klok, want het gaat te diep om ook nog uit te leggen hoe die klok toch kan doorlopen. (Het gaat hier overigens niet om een middeltje tegen ouder worden, hoor!) Verder kan het ook zijn dat hardware intensieve TSR's niet meer naar behoren werken. De oude INT 8h met de juiste frequentie (18.2Hz) aanroepen kan soelaas bieden. Bedenk echter wel dat U niet weer hoelang de orginele INT 8h duurt. Als deze te lang is kan het zijn dat er 'tikken' te horen zijn. Mensen die toch een oplossing hebben gevonden nodig ik uit om eens een diskje met de source daarop op te sturen. Op het einde van de nieuwe INT 8h wordt de waarde 020h naar poort 020h gestuurd, zodat de interruptcontroller weet dat de interrupt afgehandeld is.

Normaal wordt INT 8h 18.2 keer per seconde aangeroepen door de timerchip. Dat is natuurlijk veel te langzaam voor geluid. Hoe de frequentie te veranderen? De timerchip verlaagt 1193180 keer per seconde de counters. Counter 0 wordt door de BIOS na de POST (=Power On Self Test) geinitialiseerd op 0FFFFh. 1193180/0FFFFh=18.2. U ziet dat 18.2Hz de laagste frequentie is die U kunt instellen, aangezien 0FFFFh de hoogste waarde is die in een 16-bits counter past. Nieuwe waardes voor de timer worden dus alsvolgt berekend: Waarde=1193180/Frequentie. Bij een
frequentie van 1000 Ints/Sec levert dat dus 1193 op. Op een 4.77Mhz PC mag U maximaal 4 keer de frequentie aan kloktikken gebruiken per interrupthandler. Nu is dat dus 4772. Nooit alles uit de kan halen, want dan staat het proces op de voorgrond bijna stil! In principe kan U op een 8Mhz PC meer kloktikken gebruiken, maar dan werkt uw programma niet meer op een normale, slome PC. Probeer dat dus zoveel mogelijk te vermijden. Heeft uw programma een 12Mhz of snellere processor nodig? Dan kunt U er donder op zeggen dat het programma op een 286 of beter draait. Maak dan ook gebruik van de extra instructies die U tot uw beschikking hebt, dat scheelt ook weer een fikse slok op de borrel. (Of de
interrupthandler, zo U wilt.)

Als U de waarde voor de timerchip berekend heeft moet die natuurlijk aan de timerchip worden doorgegeven. Moeilijk ? Welnee. U dient eerst de chip te vertellen dat er een nieuwe counter-waarde gestuurd gaat worden. Via IO-poort 043h voorziet U de chip van commando's. Een commando-byte ziet er alsvolgt uit:

7	6	5	4	3	2	1	0
SC1	SC0	RL1	RL0	M2	M1	M0	BCD

SC1 & SC0 bepalen welke counter U van een commando wilt voorzien. Beide bitjes worden op 0 gezet, omdat we alleen counter 0 willen wijzigen. RL1 & RL0 zet U op 1, zodat eerst de lage en daarna de hoge byte wordt gestuurd. (De andere mogelijkheden zijn nu niet van belang.) M2, M1 & M0 kiezen de mode uit waarin de timer werkt. Mode 3 moet U hebben. M2 wordt dus 0, M1 & M0 worden 1. BCD bepaalt of de teller in BCD-mode werkt. Hier moet dat niet. Ook BCD op 0 dus. Dat levert dan het volgende bit-patroon op: 00110110b=036h.

Nadat de commando-byte via poort 043h naar de timerchip is gestuurd zal deze ook van de nieuwe tellerwaarde kennis moeten nemen. Voor counter 0 moet dat op IO-poort 040h. Eerst het lage deel van het 16-bits getal, daarna het hoge.

Het wordt tijd dat we na deze droge theorie eens wat praktisch gaan doen. Stel dat we een 1000Hz pulsgolf te
voorschijn willen toveren. Hoe te doen? Eerst de frequentie op 2000Hz instellen. Ja, U leest het goed, 2000Hz en geen 1000. De interrupt moet immers 2x per geluidsperiode worden aangeroepen. Een keer om het signaal 'hoog' (b.v. 0FFh voor maximaal volume) te maken, en daarna nog eens om het signaal weer op 0 te brengen. In het programma stelt SetFreq de frequentie van de interrupts in. (Zie voorbeeld #1)

Daarna moeten we een eigen interrupthandler gaan
installeren. InstallInt doet dat in het voorbeeld. Assembleer het programma, link het en maak er een .COM-file van. Start die maar eens op. Nadat de nieuwe interrupt operationeel is zal er geluid uit de speakers van uw versterker klinken, mits U alles goed heeft aangesloten en het ding aanstaat. Dat geluid zal klinken totdat U op een toets drukt. U kunt het programma ook zo wijzigen dat de toon een bepaalde tijdsduur krijgt, maar dat laat ik aan de creatieve geesten onder de lezers over. Uiteraard zijn ook hier reacties welkom.

Natuurlijk is een pulsgolf niet echt wat we willen, want dat kan zelfs het interne speakertje van een PC. Stel dat we een sinusgolf willen hebben, dan wordt het al wat lastiger. Omdat een sinus moeilijk real-time te berekenen is maken we gewoon een tabel met daarin een periode van een sinusgolf. Door nu een programma te schrijven dat zo'n tabel kan 'afspelen' wordt het in principe mogelijk om elk soort waveform te gebruiken, U hoeft immers alleen de tabel te wijzigen.

Dat is gedaan in voorbeeld #2. Het programma voorziet in een puls-, sinus-, zaagtand en driehoeksgolven. Alle tabellen bestaan uit 64 bytes. Als U een toon van 200Hz wilt genereren, moet U de interruptfrequentie dus op 64*200=12800 instellen. Let bij trage computers op dat U de frequentie niet te hoog maakt. Als de processor een interrupt aan het afhandelen is en de timerchip staat alweer klaar staat met de volgende kunnen er problemen ontstaan. De interrupthandler heeft nu zo'n 300
kloktikken nodig op 'n 8088. (Op AT's beduidend minder nodig.) De maximale frequentie van de interrupts is dus 4770000/300=15900. Altijd wat tijd voor de voorgrond overlaten, dus niet hoger gaan dan 15000 ints/sec. Dat geeft dan een maximale geluidsfrequentie van 15000/64=234. Dat is natuurlijk veel te laag voor een leuk melodietje. De frequentie is op verschillende manieren te
verhogen. U kunt proberen de interrupthandler nog wat sneller te maken, en dat kan zekers! 

Probeer maar eens een 8-bits tellertje als pointer in de waveform. Ook zou U ervoor kunnen zorgen dat de registers die de handler gebruikt niet op de voorgrond gebruikt worden, zodat de handler ze niet steeds hoeft te pushen en poppen. Als U dat doet kunt U als pointer voor het waveform ook een register gebruiken. Om hogere tonen te krijgen kunt U natuurlijk ook gewoon van een 10Mhz AT uitgaan, maar mensen met een PCtje zullen dat niet waarderen, en het getuigt m.i. niet van vindingrijkheid en programmeerkunst! Beter is om een kleiner waveform te pakken. 16 bytes voor een zaagtand is eigenlijk ook genoeg bij hoge
frequenties. Als de frequentie lager wordt dan 234 schakelt U gewoon weer terug naar 64 bytes. Verder zijn de waveforms
'Zaagtand' en 'Driehoek' makkelijk realtime te berekenen, want ook weer de nodige snelheidswinst oplevert.

Toontjes afspelen is natuurlijk wel leuk, maar
gedigitaliseerde muziek afspelen is nog veel leuker. De hier besproken interface is niet geschikt om te digitaliseren, maar hij kan wel samples afspelen. Nu heb ik wel een interface die geschikt is om te digitaliseren, en laat ik nu de moeite hebben genoemen om een aantal drums te samplen voor U...

De samples zijn gedigitaliseerd met een frequentie van 10Khz. Dat is eigenlijk een vrij lage samplerate, maar alle samples moesten passen in 64K, vandaar. Ik heb alle 25 drums onder een mooi drumprogramma, dat onder de Organizer van John Vanderaart draait, gehangen. Onderaan dit artikel kunt U zien waar dat programma te downloaden is. 

Het gaat te ver om hier de hele listing af te drukken, maar ik kan wel uitleggen hoe dat programma ongeveer werkt. Eigenlijk is het bijna het zelfde programma als voorbeeld #2, maar dan met veel meer - en grotere - waveforms. Die waveforms worden met een vaste frequentie van 10000 bytes/sec naar de interface gestuurd. De samples worden aangewezen door een paar pointers in een tabel, zodat samples van verschillende lengten mogelijk zijn. Welke sample ten gehore wordt gebracht hangt af van de toets die U indrukte. Eerst wordt die toets omgezet naar een hoofdletter. Daarna wordt de tabel met pointers geindexeerd aan de hand van de ASCII waarde van de toets. De interrupthandler begint met afspelen, totdat het einde van de sample bereikt is. Leuk toch?

Dit programma is inclusief source en Organizer te
downloaden is bij: "Roel's Praathoek", 077-547521. Alle
baudrates, tot 14400. Als U voor de eerste keer inlogt, kunt U al downloaden. Vanuit het hoofdmenu gaat U naar het softwaremenu met de 'S'. Daarna drukt op op '*' om van filearea te wisselen. Area 10 moet U hebben. Met de 'F' van Files krijgt U de inhoudsopgave op uw scherm. Zoek de files uit die U wilt hebben, en druk daarna op de 'D' van Download. Pak het correcte protocol, en downloaden maar!

Als er nog mensen zijn die zelf wat hebben programmeerd voor de interface, dan hoor ik daar graag van. NetMail naar: 77:8310/509 voor TDN en 17:786/509 voor ()lympic. Natuurlijk kunt U ook een berichtje achter laten op het eerder genoemde BBS.

Succes, Thijs

; ****
; **** Geprogrammeerd door Thijs Schoonbrood
; ****

Example1		SEGMENT PARA
			ORG 0100h
			ASSUME CS:Example1,DS:Example1

StartUp:		jmp SetUp

TimerFreq		EQU 1193180
TimerCount0		EQU 040h
TimerComReg		EQU 043h
InterFreq		EQU TimerFreq / 2000
Port			EQU 0378h

Txt			DB 10,13,'Een 1000Hz pulsgolf.'
			DB 10,13,'$'
Current			DB 0
Old08			DW 0, 0

New08:			push ax
			push dx
			cmp CS:Current,0
			jz New080
			mov CS:Current,0
			mov dx,Port
			xor al,al
			out dx,al
			jmp SHORT New081
New080:			mov CS:Current,1
			mov dx,Port
			mov al,0FFh
			out dx,al
New081:			mov al,020h		; EOI to PIC
			out 020h,al
			pop dx
			pop ax
			iret

SetFreq:		cli
			mov dx,TimerComReg
			mov al,00110110b	; Command Byte
			out dx,al
			mov dx,TimerCount0
			mov al,cl		; First LowByte
			out dx,al
			mov al,ch		; Then HighByte
			out dx,al
			sti
			ret

InstallInt:		mov ax,03508h		; Get current handler
			int 21h
			mov Old08,bx
			mov Old08+2,es
			mov ax,02508h		; Set our handler
			mov dx,OFFSET New08
			int 21h
			ret

RestoreInt:		mov ax,02508h
			mov dx,Old08		; Restore former
			mov ds,Old08+2		; handler
			int 21h
			ret
 
SetUp:			mov ah,9		; Display String
			mov dx,OFFSET Txt
			int 21h
			call InstallInt
			mov cx,InterFreq	; Set frequency
			call SetFreq
			xor ah,ah		; Wait for key
			int 16h
			mov cx,0FFFFh		; Restore original
			call SetFreq		; frequency
			call RestoreInt
			int 20h			; Return to DOS

Example1		ENDS
			END StartUp


; ****
; **** Geprogrammeerd door Thijs Schoonbrood
; ****

======================================================


Example2		SEGMENT PARA
			ORG 0100h
			ASSUME CS:Example2,DS:Example2

StartUp:		jmp SetUp

TimerCount0		EQU 040h
TimerComReg		EQU 043h
InterFreq		EQU 1193180 / 12800
Port			EQU 0378h
Txt1			DB 10,13,'Voorbeeld #1. Een 200Hz driehoeksgolf.'
			DB '    <TOETS>$'
Txt2			DB 10,13,'Voorbeeld #2. Een 200Hz zaagtandgolf.'
			DB '    <TOETS>$'
Txt3			DB 10,13,'Voorbeeld #3. Een 200Hz sinusgolf.'
			DB '    <TOETS>$'
Txt4			DB 10,13,'Voorbeeld #4. Ruis.'
			DB '    <TOETS>',10,13,'$'
WaveForm		DW 0
TabPtr			DW 0
Old08			DW 0, 0
Driehoek		DB 000, 008, 016, 024, 032, 040, 048, 056
			DB 064, 072, 080, 088, 096, 104, 112, 120
			DB 128, 136, 144, 152, 160, 168, 176, 184
			DB 192, 200, 208, 216, 224, 232, 240, 248
			DB 255, 248, 240, 232, 224, 216, 208, 200
			DB 192, 184, 176, 168, 160, 152, 144, 136
			DB 128, 120, 112, 104, 096, 088, 080, 072
			DB 064, 056, 048, 040, 032, 024, 016, 008
Zaagtand		DB 000, 002, 004, 006, 008, 010, 012, 014
			DB 016, 018, 020, 022, 024, 026, 028, 030
			DB 032, 034, 036, 038, 040, 042, 044, 046
			DB 048, 050, 052, 054, 056, 058, 060, 062
			DB 064, 066, 068, 070, 072, 074, 076, 078
			DB 080, 082, 084, 086, 088, 090, 092, 094
			DB 096, 098, 100, 102, 104, 106, 108, 110
			DB 112, 114, 116, 118, 120, 122, 124, 126
Sinus			DB 127, 139, 152, 164, 176, 187, 198, 208
			DB 217, 225, 233, 239, 244, 249, 252, 253
			DB 254, 253, 252, 249, 244, 239, 233, 225
			DB 217, 208, 198, 187, 176, 164, 152, 139
			DB 127, 115, 102, 090, 078, 067, 056, 046
			DB 037, 029, 021, 015, 010, 005, 002, 001
			DB 000, 001, 002, 005, 010, 015, 021, 029
			DB 037, 046, 056, 067, 078, 090, 102, 115
Noise			DB 247, 051, 132, 033, 223, 006, 121, 243
			DB 081, 196, 176, 225, 076, 159, 211, 171
			DB 119, 246, 056, 245, 141, 248, 033, 115
			DB 254, 049, 175, 170, 095, 019, 208, 108
			DB 118, 205, 145, 117, 024, 242, 162, 230
			DB 035, 201, 250, 115, 045, 062, 229, 135
			DB 213, 219, 232, 187, 196, 058, 068, 074
			DB 192, 207, 014, 129, 102, 012, 027, 064

New08:                  push ax                 ; 15
			push bx                 ; 15
			push dx                 ; 15
			mov dx,Port             ;  4
			inc CS:TabPtr           ; 31
			cmp CS:TabPtr,64        ; 21
			jb New080               ;  4 / 16
			mov CS:TabPtr,0         ; 22
New080:                 mov bx,CS:TabPtr        ; 20
			add bx,CS:WaveForm      ; 21
			mov al,CS:[bx]          ; 10
			out dx,al               ; 12
			mov al,020h             ;  4
			out 020h,al             ; 14
			pop dx                  ; 12
			pop bx                  ; 12
			pop ax                  ; 12
			iret                    ; 44

SetFreq:		cli
			mov dx,TimerComReg
			mov al,00110110b	; Command Byte
			out dx,al
			mov dx,TimerCount0
			mov al,cl		; First LowByte
			out dx,al
			mov al,ch		; Then HighByte
			out dx,al
			sti
			ret

InstallInt:		mov ax,03508h		; Get current handler
			int 21h
			mov Old08,bx
			mov Old08+2,es
			mov ax,02508h		; Set my handler
			mov dx,OFFSET New08
			int 21h
			ret

RestoreInt:		mov ax,02508h
			mov dx,Old08		; Restore former
			mov ds,Old08+2		; handler
			int 21h
			ret
 
SetUp:			call InstallInt
			mov cx,InterFreq	; Set frequency
			call SetFreq
			mov WaveForm,OFFSET Driehoek
			mov ah,9		; Display String
			mov dx,OFFSET Txt1
			int 21h
			xor ah,ah		; Wait for key
			int 16h
			mov WaveForm,OFFSET Zaagtand
			mov ah,9
			mov dx,OFFSET Txt2
			int 21h
			xor ah,ah
			int 16h
			mov WaveForm,OFFSET Sinus
			mov ah,9
			mov dx,OFFSET Txt3
			int 21h
			xor ah,ah
			int 16h
			mov WaveForm,OFFSET Noise
			mov ah,9
			mov dx,OFFSET Txt4
			int 21h
			xor ah,ah
			int 16h
			mov cx,0FFFFh		; Restore original
			call SetFreq		; frequency
			call RestoreInt
			int 20h			; Return to DOS

Example2		ENDS
			END StartUp


ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
º  In het artikel in Computer! Totaal wordt      º
º  melding gemaakt van een drum-programma,       º
º  dat gratis beschikbaar wordt gesteld aan      º
º  de lezers. Helaas hebben we vastgesteld       º
º  dat het programma niet voor 100% vlekkeloos   º
º  werkt. Het programma zal pas op bulletin-     º
º  boards worden geplaatst als een goed wer-     º
º  kende uitvoering beschikbaar is.              º
º                                                º
ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ¼