![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Coordinamento di Francesco Sileno![]() |
di Francesco Sileno
All'inizio era il BEEP...Anzitutto domandatevi: come si fa a far emettere un BEEP allo speaker del PC? Forse qualcuno di voi sa che il carattere ASCII 0x07 (BELL, non a caso), combinato con una particolare funzione BIOS, può aiutarci.Ma cosa fa questa funzione BIOS? Tutto ha origine nel PIT, l'integrato che fa le veci di Mastro Hora nel nostro amato ammasso di circuiti.
Il PIT è famoso poichè detiene il controllo dell'IRQ0, collegato
all'INT8, il timer di sistema. Ma non fa solo quello: il PIT ha a disposizione
un
altro paio di contatori, uno dei quali progettato proprio per operare in
congiunzione con lo speaker.
Ma ci serve ancora qualcosa: per accendere e spegnere lo speaker, e per
collegarlo al timer 2 del PIT, dobbiamo agire altrove. Precisamente,
dobbiamo andare a scomodare il controller della tastiera, in quanto i 2 bit
che ci interessano sono stati mappati nella porta 0x60. Bitfields for KB controller port B (system control port) [output]: Bit(s) Description (Table P041) 7 pulse to 1 for IRQ1 reset (PC,XT) 6-4 reserved 3 I/O channel parity check disable 2 RAM parity check disable 1 speaker data enable <-- 0 timer 2 gate to speaker enable <-- SeeAlso: PORT 0061h-W,#P042Attivando i bit 0 e 1 accendiamo lo speaker, e facciamo in modo che il suo ingresso venga collegato all'uscita del timer 2 del PIT. CURIOSITA': Tenendo fede al principio per cui più le cose sono incasinate più ci si diverte, si è deciso di piazzare quei bit di controllo in avanzo in una porta qualsiasi avente bit disponibili. Il controller della tastiera ne aveva un mucchio, poichè gli hanno appioppato sia il controllo dello speaker, sia il controllo del gate A20. Questo perchè nei vecchi XT queste porte erano utilizzate da un altro integrato (il PPI), e perchè nei nuovi AT non sapevano più dove mettere il controllo per il gate A20, un importantissima opzione che permette anche ai P5 di lavorare in modo reale come un buon, vecchio, glorioso 8086. Bene dunque, lo speaker si può accendere e collegare al timer 2. Ma in che modo esce fuori il suono? Beh, vedete (nell'estratto menzionato in precedenza) che il PIT ha varie modalità di conteggio? Vedete quella denominata MODE 3 - SQUARE WAVE RATE GENERATOR? E' proprio quella che fa al caso nostro... cambiando la frequenza dell'onda, si ottengono diverse tonalità di un segnale ad onda quadra. Un piccolo sintetizzatore, che ha sicuramente molto da invidiare ad una DX7.
In che modo la frequenza dell'onda è legata al contatore del PIT? 1193181Hz -------------- = valore_contatore Onda_Quadra_HzPossiamo quindi spaziare da circa 18Hz (quando il contatore vale 0, che in realtà viene interpretato come 0x10000) a circa 1Mhz (quando il contatore vale 1), più che sufficenti per un orecchio umano, e anche per uno disumano. Siamo a posto! Per un primo esempio, nell'archivio incluso nell'articolo, esaminate NOTE.ASM.
Qualche precisazione:
Program_PIT MACRO Freq mov bx,Freq mov dx,012h mov ax,034DDh div bx [1] push ax mov al,0B6h out PIT_CONTROL,al [2] pop ax out PIT_CHAN_2,al xchg ah,al out PIT_CHAN_2,al [3] ENDM[1] si esegue la divisione 1193181/Freq, per sapere il valore del contatore. Eventualmente si può precalcolare questo valore, se, come nel nostro caso, si usano solo determinate frequenze. C'era un motivo per cui IO non lo avevo precalcolato, ma ora non ricordo... [2] si programma il timer 2, scrittura di una word, mode 3, 16 bit counter. 0B6h = 10110110b [3] si immette il valore del contatore. Prima il byte meno significativo, poi quello più significativo, visto che abbiamo a disposizione una sola porta da 8 bit per un valore a 16 bit. Musica, maestro!Ora possiamo sbizzarirci a comporre quello che vogliamo... prima però cerchiamo di semplificarci il lavoro.Anzitutto, una struttura che ci permetta di rappresentare una nota, intesa come frequenza, ottava e durata. Quindi una serie di durate 'standard', ovvero semicrome, crome, etc. NOTA_QUARTO EQU 200000 NOTA_SEDICESIMO EQU NOTA_QUARTO / 4 NOTA_OTTAVO EQU NOTA_QUARTO / 2 NOTA_META EQU NOTA_QUARTO * 2 NOTA_INTERO EQU NOTA_QUARTO * 4 Nota STRUC N_Valore dw ? N_Ottava db ? N_Durata dd ? Nota ENDS200000 corrisponde a 0.2 secondi, se cambiate questo automaticamente cambiare tutti le altre durate, alla successiva compilazione. Ora possiamo definire una sequenza di note in questo modo: For_Elisa Nota <NOTA_MI, 3, NOTA_OTTAVO> Nota <NOTA_REd,3, NOTA_OTTAVO> Nota <NOTA_MI, 3, NOTA_OTTAVO> Nota <NOTA_REd,3, NOTA_OTTAVO> Nota <NOTA_MI, 3, NOTA_OTTAVO> ... For_Elisa_notes dw ( $-For_Elisa ) / SIZE NotaL'ultima riga fa uso di un paio di direttive dell'assemblatore: $ indica l'offset nel segmento corrente alla posizione del carattere $, come dire Pippo dw offset Pippomentre SIZE da la dimensione in byte del dato specificato. $ - For_Elisa si traduce in offset_attuale-offset_inizio, e con la divisione sappiamo quante note ci sono nella sequenza. Per questo esempio, fate riferimento a: ELISA.ASM, SPEAKER.MAC, SPEAKER.PRC. Non mi sembra che ci sia nulla di complicato, se non che ovviamente non è per niente ottimizzato, e che sono ricorso a registri 32bit (EAX) per risparmiarmi un po' di intrallazzi. Si può arrivare anche ad un semplice effetto di polifonia: spezzettando ogni nota in intervalli di piccolissima durata, e suonando alternativamente i pezzettini di ogni voce. Con intervalli veramente microscpici, dovrebbe uscire fuori un effetto simile a quello che si poteva ascoltare sullo Spectrum, come anche sul gioco Elite per PC: VOCE 1 VOCE 2 ... Nota <NOTA_LA ,3 ,NOTA_VELOCE> Nota <NOTA_FA ,1 ,NOTA_VELOCE> Nota <NOTA_LA ,3 ,NOTA_VELOCE> Nota <NOTA_FA ,1 ,NOTA_VELOCE> Nota <NOTA_LA ,3 ,NOTA_VELOCE> Nota <NOTA_FA ,1 ,NOTA_VELOCE> Nota <NOTA_LA ,3 ,NOTA_VELOCE> Nota <NOTA_FA ,1 ,NOTA_VELOCE> Nota <NOTA_LA ,3 ,NOTA_VELOCE> Nota <NOTA_FA ,1 ,NOTA_VELOCE> ... ... poi venne il TADAAASfruttando quello che abbiamo appena appreso su altoparlanti e cronometri, possiamo idearci un metodo per fare riprodurre allo speaker dei suoni campionati, pur non avendo un DAC. In realtà il metodo l'ha inventato qualcun'altro tanto tempo fa, io mi limito a riproporvelo.Non faccio come certa gente che passa per sue vecchie idee di altri. Mentre un DAC a 8bit di una scheda sonora può darci 256 livelli di segnale, il nostro speaker può averne solo 2 (acceso/spento), e allora cosa ti ha pensato il geniaccio? Ha pensato di stabilire un rapporto di proporzionalità, per cui maggiore è il valore che ha il singolo campione, maggiore è il tempo che l'altoparlante rimane acceso.
Esaminiamo il mode 0 del PIT: dopo aver eseguito il contdown,
viene generato un IRQ. In realtà, solo il timer 0 ha collegato un IRQ, gli
altri non hanno nessun IRQ, ma sono collegati direttamente ad altri
dispositivi. E come
abbiamo visto, il timer 2 ha l'uscita collegata allo speaker.
[1]Tot_Time dipende dalla frequenza di riproduzione dei campioni.
Per 11025Hz, Tot_Time = 90us, secondo la formula F=1/T. (255-valore_campione) * pit_base_freq contatore = --------------------------------------- + 1 play_freq * 256O io in realtà non ho capito nulla di questo meccanismo, o c'è un piccolo dettaglio che mi sfugge. Se per caso qualcuno capisce cosa, può scrivermi.
Va bene, facciamo qualcosa di concreto. Supponiamo di avere file campionati
a 11025Hz, 8bit, mono, massimo 64k, in formato raw. Ovvero senza header,
semplicemente un campione appresso all'altro. cli Set_Speaker_On mov cx,file_size byte_loop: mov al,es:[si] converti campione secondo tabella out PIT_CHAN_2,al push cx mov cx,150h wait_loop: loop wait_loop pop cx inc si loop byte_loop Set_Speaker_Off sti
Ci sono un po' di curiosità in quest' ultimo esempio:
|
|||||||||||||||||||||||||||||||||
rimandatamente, Cthulhu |
HelpPC 2.10 Quick Reference Utility Copyright 1991 David Jurgens 8253/8254 PIT - Programmable Interval Timer Port 40h, 8253 Counter 0 Time of Day Clock (normally mode 3) Port 41h, 8253 Counter 1 RAM Refresh Counter (normally mode 2) Port 42h, 8253 Counter 2 Cassette and Speaker Functions Port 43h, 8253 Mode Control Register, data format: |7|6|5|4|3|2|1|0| Mode Control Register | | | | | | | +---- 0=16 binary counter, 1=4 decade BCD counter | | | | +-+-+----- counter mode bits | | +-+---------- read/write/latch format bits +-+------------- counter select bits (also 8254 read back command) Bits 76 Counter Select Bits 00 select counter 0 01 select counter 1 10 select counter 2 11 read back command (8254 only, illegal on 8253, see below) Bits 54 Read/Write/Latch Format Bits 00 latch present counter value 01 read/write of MSB only 10 read/write of LSB only 11 read/write LSB, followed by write of MSB Bits 321 Counter Mode Bits 000 mode 0, interrupt on terminal count; countdown, interrupt, then wait for a new mode or count; loading a new count in the middle of a count stops the countdown 001 mode 1, programmable one-shot; countdown with optional restart; reloading the counter will not affect the countdown until after the following trigger 010 mode 2, rate generator; generate one pulse after 'count' CLK cycles; output remains high until after the new countdown has begun; reloading the count mid-period does not take affect until after the period 011 mode 3, square wave rate generator; generate one pulse after 'count' CLK cycles; output remains high until 1/2 of the next countdown; it does this by decrementing by 2 until zero, at which time it lowers the output signal, reloads the counter and counts down again until interrupting at 0; reloading the count mid-period does not take affect until after the period 100 mode 4, software triggered strobe; countdown with output high until counter zero; at zero output goes low for one CLK period; countdown is triggered by loading counter; reloading counter takes effect on next CLK pulse 101 mode 5, hardware triggered strobe; countdown after triggering with output high until counter zero; at zero output goes low for one CLK period Read Back Command Format (8254 only) |7|6|5|4|3|2|1|0| Read Back Command (written to Mode Control Reg) | | | | | | | +--- must be zero | | | | | | +---- select counter 0 | | | | | +----- select counter 1 | | | | +------ select counter 2 | | | +------- 0 = latch status of selected counters | | +-------- 0 = latch count of selected counters +-+--------- 11 = read back command Read Back Command Status (8254 only, read from counter register) |7|6|5|4|3|2|1|0| Read Back Command Status | | | | | | | +--- 0=16 binary counter, 1=4 decade BCD counter | | | | +-+-+---- counter mode bits (see Mode Control Reg above) | | +-+--------- read/write/latch format (see Mode Control Reg) | +------------ 1=null count (no count set), 0=count available +------------- state of OUT pin (1=high, 0=low) - the 8253 is used on the PC & XT, while the 8254 is used on the AT+ - all counters are decrementing and fully independent - the PIT is tied to 3 clock lines all generating 1.19318 MHz. - the value of 1.19318MHz is derived from (4.77/4 MHz) and has it's roots based on NTSC frequencies - counters are 16 bit quantities which are decremented and then tested against zero. Valid range is (0-65535). To get a value of 65536 clocks you must specify 0 as the default count since 65536 is a 17 bit value. - reading by latching the count doesn't disturb the countdown but reading the port directly does; except when using the 8254 Read Back Command - counter 0 is the time of day interrupt and is generated approximately 18.2 times per sec. The value 18.2 is derived from the frequency 1.10318/65536 (the normal default count). - counter 1 is normally set to 18 (dec.) and signals the 8237 to do a RAM refresh approximately every 15ęs - counter 2 is normally used to generate tones from the speaker but can be used as a regular counter when used in conjunction with the 8255 - newly loaded counters don't take effect until after a an output pulse or input CLK cycle depending on the mode - the 8253 has a max input clock rate of 2.6MHz, the 8254 has max input clock rate of 10MHz Programming considerations: 1. load Mode Control Register 2. let bus settle (jmp $+2) 3. write counter value 4. if counter 0 is modified, an INT 8 handler must be written to call the original INT 8 handler every 18.2 seconds. When it does call the original INT 8 handler it must NOT send and EOI to the 8259 for the timer interrupt, since the original INT 8 handler will send the EOI also. Example code: countdown equ 8000h ; approx 36 interrupts per second cli mov al,00110110b ; bit 7,6 = (00) timer counter 0 ; bit 5,4 = (11) write LSB then MSB ; bit 3-1 = (011) generate square wave ; bit 0 = (0) binary counter out 43h,al ; prep PIT, counter 0, square wave&init count jmp $+2 mov cx,countdown ; default is 0x0000 (65536) (18.2 per sec) ; interrupts when counter decrements to 0 mov al,cl ; send LSB of timer count out 40h,al jmp $+2 mov al,ch ; send MSB of timer count out 40h,al jmp $+2 sti
Copyright © 1996 Beta. Tutti i diritti riservati.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |