Hi, everybody.
Before I start writing, I will let you know that my English is not good enough.
Maybe some vocabulraries or grammar can be wrong.
By the way, I'm making wav player with SD card not using any sound related libraries.
Using PCM data in wav to make PWM, and convert the PWM to music using LPF & amp. & speaker.
Until now, It can make sound but has some problems.
Tone of the sound is same to original wav file, but only tempo is lagging.
Timer interrrupt happens by 44.1kHz and PCM data is uploaded to OCRnx in every interrupt.
Then OCRnx make PWM(Timer1&3, 62.5kHz, 8-bit fast PWM.)
- The wav files also have 44.1kHz sampling rate.
** I checked the PWM and interrupt frquency with an osciloscope, it was fine(62.5/44.1kHz for each.)
Before I explain more about the problem, here is the code.
#include <SD.h>
#include <SPI.h>
#include <LiquidCrystal.h>
/* pin number */
#define CS 53
#define SW4 21
#define SW2 20
#define SW1 19
#define PH_B 18
#define PH_A 17
#define lPWM_H 11
#define lPWM_L 12
#define rPWM_H 5
#define rPWM_L 2
#define F_SAMPLE 44.1e3
#define VOL_MAX 1 // unsettled
#define VOL_MIN -5 // unsettled
LiquidCrystal lcd(9,8,7,6,4,3);
File root, wav;
char wavName[20][12];
byte buf[2][3072];
volatile uint32_t bufCnt = 0;
volatile uint32_t bytCnt = 0;
volatile int8_t encCnt = 0;
volatile bool bufFlag = false;
volatile bool bufSelc = false;
uint32_t datSize;
uint16_t timeEnd;
uint16_t timeElp;
uint8_t wavNum;
uint8_t volCtr = 2;
uint16_t bufSum = 0;
void setup() {
SD.begin(CS);
lcdInit();
wavInfo();
wavInit(0);
pinInit();
interruptInit();
}
void loop() {
// timeUpdate();
if(bufFlag) bufUpdate();
}
void lcdInit() {
lcd.begin(16,2);
lcd.setCursor(5,1);
lcd.print(": / :");
}
void wavInfo() {
root = SD.open("/");
wav = root.openNextFile();
wav.close();
for(uint8_t i = 0; ; i++) {
wav = root.openNextFile();
if(!wav) {
wav.close();
wavNum = i;
return;
}
strcpy(wavName[i],wav.name());
wav.close();
}
}
void wavInit(int8_t n) {
bufCnt = 0;
bytCnt = 0;
datSize = 0;
wav = SD.open(wavName[n]);
wav.seek(40);
for(uint8_t i = 0; i < 4; i++)
datSize += (uint32_t)wav.read() << (8 * i);
timeEnd = datSize / (4 * F_SAMPLE);
wav.read(buf[0], 3072);
wav.read(buf[1], 3072);
lcd.home();
lcd.print(" ");
lcd.home();
lcd.print(wavName[n]);
lcd.setCursor(11,1);
lcd.print((timeEnd / 60) / 10, DEC);
lcd.setCursor(12,1);
lcd.print((timeEnd / 60) % 10, DEC);
lcd.setCursor(14,1);
lcd.print((timeEnd % 60) / 10, DEC);
lcd.setCursor(15,1);
lcd.print((timeEnd % 60) % 10, DEC);
}
void pinInit() {
pinMode(SW4,INPUT);
pinMode(SW2,INPUT);
pinMode(SW1,INPUT);
pinMode(PH_B,INPUT);
pinMode(PH_A,INPUT);
pinMode(lPWM_H,OUTPUT);
pinMode(lPWM_L,OUTPUT);
pinMode(rPWM_H,OUTPUT);
pinMode(rPWM_L,OUTPUT);
}
void interruptInit() {
// external interrupt
EICRA = 0b10111111; // PH_A: falling edge, SW: rising edge
EIFR = 0b00001111; // Clear INT3, 2, 1, 0 Flag
EIMSK = 0b00001111; // Enable INT3, 2, 1, 0
/* Timer1,3 */
TCCR1A = 0b10100001;
TCCR1B = 0b00001001;
TCCR3A = 0b10100001;
TCCR3B = 0b00001001;
// Clear OCnx on compare match
// 8-bit fast PWM mode (62.5kHz), prescaler = 1
/* Timer4 */
TCCR4A = 0b00000000;
TCCR4B = 0b00001001;
// CTC mode (TOP = OCR4A) , prescaler = 1
OCR4A = F_CPU / (1 + F_SAMPLE);
TCNT4 = 0b00000000; // counter initializtion
TIFR4 = 0b00000010; // Enable output compare A Match Flag
TIMSK4 = 0b00000000; // Disable compare match A interrupt
sei();
}
void timeUpdate() {
timeElp = (bytCnt + 3072 * bufCnt) / (4 * F_SAMPLE);
if(timeElp == timeEnd) {
TIMSK4 &= ~_BV(OCIE4A);
wav.close();
wavInit(encCnt);
}
lcd.setCursor(3,1);
lcd.print((timeElp / 60) / 10, DEC);
lcd.setCursor(4,1);
lcd.print((timeElp / 60) % 10, DEC);
lcd.setCursor(6,1);
lcd.print((timeElp % 60) / 10, DEC);
lcd.setCursor(7,1);
lcd.print((timeElp % 60) % 10, DEC);
}
void bufUpdate() {
bufFlag = false;
wav.read(buf[bufSelc],3072);
}
ISR(TIMER4_COMPA_vect) {
bufSum = buf[bufSelc][bytCnt] + (buf[bufSelc][bytCnt+1] << 8) + 0x8000;
bufSum >>= volCtr;
OCR1B = bufSum & 0x00FF;
OCR1A = (bufSum & 0xFF00) >> 8;
bytCnt += 2;
bufSum = buf[bufSelc][bytCnt] + (buf[bufSelc][bytCnt+1] << 8) + 0x8000;
bufSum >>= volCtr;
OCR3B = bufSum & 0x00FF;
OCR3A = (bufSum & 0xFF00) >> 8;
bytCnt += 2;
if( bytCnt == 3072 ) {
bufFlag = true;
bytCnt = 0;
bufCnt++;
bufSelc = !bufSelc;
}
}
ISR(INT3_vect) {
TIMSK4 &= ~_BV(OCIE4A);
switch(digitalRead(PH_B)) {
case LOW: encCnt++; // CW
break;
case HIGH: encCnt--; // CCW
}
if(encCnt > wavNum - 1) encCnt = 0;
else if(encCnt < 0) encCnt = wavNum - 1;
wav.close();
wavInit(encCnt);
}
ISR(INT2_vect) {
TIMSK4 ^= _BV(OCIE4A);
// Remove noise during pause
OCR1B = 0;
OCR1A = 0;
OCR3B = 0;
OCR3A = 0;
}
ISR(INT1_vect) {
if(volCtr < VOL_MAX) volCtr++;
}
ISR(INT0_vect) {
if(volCtr > VOL_MIN) volCtr--;
}
After buf[0] is finished, it starts read wav file from SD card in loop().
And interrupt start to read buf[1] to update OCRnx.
As I thought, interrupt keeps happening 44.1kHz during reading 3072 byte from SD.
But now I think that wav.read(buf[bufSelc],3072) stops timer4 interrupt until it is finished.
Every wav.read(,) inserts subtle delay between every 3072 bytes.
So the tone of the sound is OK, but only tempo is lagging.
If my guess is right, how can I fix this code?
Or not, what is the real reason and how can I fix it?
Please give me enlightenment...
P.S.
I found that timer4's one interrupt cycle spends 16~20us,
wav.read(buf[bufSelc],3072) spends 5.55ms, which is 7us per one sample(4-byte.)
One wav sample should spend no more than 22.6ms(44.1kHz.)
If read function and timer interrupt are compatible,
wav.read(buf[bufSelc],3072) should spend time lower than (5.55ms -> )2ms.
Then what is a solution to read wav file much faster then now?
read() in SD.h is not fast enough to play 44.1kHz 16-bit stereo wav file..