I actually try to make working a very simple wav player that is limited to 8 bits / 16 kHz / mono wave format files. Audio files are saved on a FAT16 formated MMC card. PIC 18F4520, MMC reader connected on PORTC, playing commands done via push buttons connected on some lines of PORTA, and DAC (simple R/2R ladder) connected to PORTB. PIC run at 20 MHz, but problem is exactly the same at 40 MHz (10 MHz with 4x PLL). MikroPascal V5.30.
Speed access for MMC card reading don't seem to be the problem, but I'm not totally sure.
Problem encountered : samples of the wav file are regulary read during 32 ms, and for some reason PORTB stay at a fixed value during about 8 ms. So, I got "dropout" every 40 ms, without any data loss. Every 40 ms, a value stay "locked" during 8 ms. Sound effect is similar to a chopper / AM modulator with 100% / 0% modulation on a 4/1 time ratio.
I tried to get sample from MMC card and send them to portB on two manners :
1 - byte by byte with a little delay added between each byte to play the file at correct sample rate;
2 - with Timer0 interrupt.
On both cases, problem is exactly the same.
To visually see the problem, i made a screenshot of what I get with a 1 kHz sinus :
http://www.sonelec-musique.com/images2/ ... h_000a.gif
And here the project schematic :
http://www.sonelec-musique.com/images2/ ... io_001.gif
Full code is following :
Code: Select all
{
program electronique_lecteur_audio_001_18f4520;
Wave audio files player
Only support Wav files Mono / 8 bits / 16 kHz
----------------------------------------------------------
Copyrights Remy Mallard - sonelec-musique.com - 2011
http://www.sonelec-musique.com/electronique_realisations_lecteur_audio_001.html
----------------------------------------------------------
Developped under MikroPascal V5.30
Tested on EasyPic4 dev board
Clock : ext 20 MHz crystal (PLL disabled)
}
program electronique_lecteur_audio_001_18f4520;
const
Line_Len = 43;
cFileExists = 1; // or = 0 (MMC_FAT_EXISTS) ???
cTMR0H = $FE; // $FE for 16 kHz, $FF for 22 kHz - Osc 20 MHz
cTMR0L = $D4; // $D4 for 16 kHz, $1F for 22 kHz - Osc 20 MHz
AudioFile0 = 'audio0.wav';
AudioFile1 = 'audio1.wav';
AudioFile2 = 'audio2.wav';
AudioFile3 = 'audio3.wav';
AudioFile4 = 'audio4.wav';
AudioFile5 = 'audio5.wav';
AudioFile6 = 'audio6.wav';
AudioFile10 = 'sinus1k.wav';
var
// for writing to output pin on PIC18 family, always use latch
Mmc_Chip_Select: sbit at LATC0_bit;
Mmc_Chip_Select_Direction: sbit at TRISC0_bit;
Out_LED_Green: sbit at LATD0_bit;
Out_LED_Red: sbit at LATD1_bit;
AudioFile: string[14]; // audio file to play
iSample: byte;
size: longint;
bTimerInt: boolean;
//buffer: array[512] of byte;
procedure Main_Init;
begin
AudioFile := AudioFile1; // not really needed
TRISA := $FF;
TRISB := $00;
PORTB := $7F; // max audio voltage / 2
TRISD := $00;
INTCON := 0;
INTCON2 := 0;
INTCON3 := 0;
T0CON := 0;
T0CON.T08BIT := 0; // Timer0 in 16 bits mode
// Timer0 Registers:16-Bit Mode; Prescaler=1:1; TMRH Preset=$FE; TMRL Preset=$CA; Freq=16 129,03226Hz; Period=62,00 µs
T0CON.TMR0ON := 1; // Timer0 On/Off Control bit: 1=Enables Timer0 / 0=Stops Timer0
T0CON.T08BIT := 0; // Timer0 8-bit/16-bit Control bit: 1=8-bit timer/counter / 0=16-bit timer/counter
T0CON.T0CS := 0; // TMR0 Clock Source Select bit: 0=Internal Clock (CLKO) / 1=Transition on T0CKI pin
T0CON.T0SE := 0; // TMR0 Source Edge Select bit: 0=low/high / 1=high/low
T0CON.PSA := 1; // Prescaler Assignment bit: 0=Prescaler is assigned; 1=NOT assigned/bypassed
T0CON.T0PS2 := 0; // bits 2-0 PS2:PS0: Prescaler Select bits
T0CON.T0PS1 := 0;
T0CON.T0PS0 := 0;
TMR0H := cTMR0H; // preset for Timer0 MSB register
TMR0L := cTMR0L; // preset for Timer0 LSB register
ADCON1 := ADCON1 or 0x0F; // Configure AN pins as digital
CMCON := CMCON or 7; // Turn off comparators
Out_LED_Green := 1;
Out_LED_Red := 1;
Delay_ms(500);
Out_LED_Green := 0;
Out_LED_Red := 0;
// SPI init and MMC init are done in main proc
end;
procedure Interrupt;
begin
// timer int occured ?
if INTCON.TMR0IF then
begin
bTimerInt := true;
TMR0H := cTMR0H;
TMR0L := cTMR0L;
INTCON.TMR0IF := false;
end;
end;
procedure Timer_SetInterrupt(bState: boolean);
begin
if bState then
begin
TMR0H := cTMR0H;
TMR0L := cTMR0L;
INTCON.TMR0IF := 0;
INTCON.GIE := 1;
INTCON.TMR0IE := 1;
T0CON.TMR0ON := 1;
end
else
begin
T0CON.TMR0ON := 0;
INTCON.TMR0IE := 0;
INTCON.GIE := 0;
end;
end;
procedure M_ReadAudioFile(idx: byte);
var
iStatus: byte;
bBadFile: boolean;
begin
case idx of
0 : AudioFile := AudioFile0; // not exists (wanted, only for test)
1 : AudioFile := AudioFile1;
2 : AudioFile := AudioFile2;
3 : AudioFile := AudioFile3;
4 : AudioFile := AudioFile4;
5 : AudioFile := AudioFile5;
6 : AudioFile := AudioFile6
else AudioFile := AudioFile10; // sinus 1 kHz file
end;
Out_LED_Green := 0;
Out_LED_Red := 0;
// verify audio file exists
iStatus := Mmc_Fat_Exists(AudioFile);
// verify audio file format (must be mono unsigned 8 bits / 16 kHz)
// file format verification not yet implemented
bBadFile := false;
// if file exists then read it and light on green LED
if (iStatus = cFileExists) and (not bBadFile) then
begin
Out_LED_Green := 1;
Mmc_Fat_Assign(AudioFile, 0);
Mmc_Fat_Reset(size); // go to file start (procedure returns size of file)
// for raw files, start reading at pos #0 (no header)
// for wav files, start reading at pos #44 (previous bytes are header)
Mmc_Fat_Seek(44);
if size > 44 then
size := size - 44;
// use of timer0 to seq samples
Timer_SetInterrupt(true);
while size > 1 do
begin
if bTimerInt then // wait next TMR0 int before take next sample
begin
Mmc_Fat_Read(iSample);
PORTB := iSample; // Write (send) audio sample data to PORTB
Dec(size);
bTimerInt := false;
end;
end;
Timer_SetInterrupt(false);
// no timer used, delay added to slow down sample rate
{
while size > 1 do
begin
Mmc_Fat_Read(iSample);
PORTB := iSample; // Write (send) audio sample data to PORTB
Dec(size);
Delay_us(40);
end;
}
PORTB := $7F; // max audio voltage / 2
Out_LED_Green := 0;
end
// if file not exists then light on red LED
else
begin
Out_LED_Red := 1;
Delay_ms(500);
Out_LED_Red := 0;
end;
end;
begin
Main_Init;
// Initialize SPI module at low speed
SPI1_Init_Advanced(_SPI_MASTER_OSC_DIV64, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH);
// Note: Mmc_Fat_Init tries to initialize a card more than once.
if Mmc_Fat_Init = 0 then
begin
// reinitialize SPI module at higher speed
SPI1_Init_Advanced(_SPI_MASTER_OSC_DIV4, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH);
// debug
M_ReadAudioFile(10);
// play file related to button pressed
while true do
begin
if button(PORTA, 0, 100, 0) then
M_ReadAudioFile(1);
if button(PORTA, 1, 100, 0) then
M_ReadAudioFile(2);
if button(PORTA, 2, 100, 0) then
M_ReadAudioFile(3);
if button(PORTA, 3, 100, 0) then
M_ReadAudioFile(4);
if button(PORTA, 4, 100, 0) then
M_ReadAudioFile(5);
if button(PORTA, 5, 100, 0) then
M_ReadAudioFile(6);
end;
end
else
// MMC FAT init not OK
begin
// blink red LED in infinite loop (need PIC reset / restart)
while true do
begin
Out_Led_Red := 1;
delay_ms(250);
Out_Led_Red := 0;
delay_ms(250);
end;
end;
end.
Any idea ?
Thanks for any help or suggestion,
Remy
Edit 22/11/2011 : I know that circular buffer is a good method to avoid this type of problem, but for a so low sample rate, I think this is not really necessary...
Edit 23/11/2011 : also tried this code in ReadFile proc, where samples are read "in advance" and sent to DAC juste after Timer0 int occurs. But same problem :
Code: Select all
Timer_SetInterrupt(true);
while size > 1 do
begin
Mmc_Fat_Read(iSample); // take next sample now from MMC card
while (not bTimerInt) do // wait next TMR0 int before send it to DAC
nop;
PORTB := iSample; // Write (send) audio sample data to PORTB
Dec(size);
bTimerInt := false;
end;
Timer_SetInterrupt(false);
Code: Select all
while iSize > 1 do
begin
if iSample < 255 then inc(iSample) else iSample := 0;
//Mmc_Fat_Read(iSample); // take next sample now from MMC card
while (not bTimerInt) do // wait next TMR0 int before send it to DAC
nop;
PORTB := iSample; // Write (send) audio sample data to PORTB
Dec(iSize);
bTimerInt := false;
end;