SDcard library: read byte from within interrupt doesn't seem to work

Hello all,

I'm using the default SD card library with the software spi function. I can read files from my SD card (I'm able to play a 10kHz 8but mono wav). Reading a byte and driving the speaker takes 45 us. 10 kHz sound requires a new byte fed to the speaker every 100 us, so I hooked up an interrupt to Timer 1 at this frequency. Working code:

volatile byte play_byte = 0;
volatile byte play_byte_used = 0;

void setup()
{
  Serial.begin(9600);
  
  // pins 7 (SCK), 6 (MISO), 5 (MOSI) are defined in SD card library  
  pinMode( 3, OUTPUT);        // for PWM sound generation
  pinMode( SS_CARD, OUTPUT);  // SS for sd card
  pinMode(MISO, OUTPUT);
  digitalWrite(4,HIGH);       // set SS high for software SPI

  // initialize SPI
  SPCR |= _BV(SPE);           // start hardware SPI in Slave mode
  SPI.attachInterrupt();      // attach slave mode interrupt handler for SPI

  // Set pin 3 for PWM generation, fast PWM mode, toggle on overflow and passing OCRB; OCRB (0-255) determines duty cycle
  TCCR2A = B00100011;
  TCCR2B = B00000001;
  OCR2B  = 0;            // duty cycle = 0 %

  //try to initialize SD card on startup
  initialize_SDcard();
  
  /******** Initialize Timer and 50 us interrupt ********/
  Timer1.initialize(100);             // initialize timer1, and set a 100 us period
  // In the PWM modes pin 1 and 9 are grouped, and pin 2 and 10
  Timer1.disablePwm(9);              // clear PWM on all pins
  Timer1.disablePwm(10);
  Timer1.attachInterrupt(callback);

  
  // start playing a WAV  
  openNewWav(4);
  Serial.println(millis());
  player_status = STATUS_PLAYING;
}


void loop()
{
  // nothing happens after setup

  if(play_byte_used && player_status==STATUS_PLAYING)
  {
    play_byte = myFile.read();
    play_byte_used = 0;
  }

}

void callback()
{
  if (player_status == STATUS_PLAYING)
  {
    OCR2B = play_byte;
    play_byte_used = 1;
    file_pos++;
    if (file_pos==100000 /*sample_size*/) player_status = STATUS_CLOSINGWAV;
    //delayMicroseconds(54);
  }
}

This sounds well. However, in order to expand my project, I want to move the "reading the byte from the sd-card" part to the interrupt function.

volatile byte play_byte = 0;
volatile byte play_byte_used = 0;

void setup()
{
  Serial.begin(9600);
  
  // pins 7 (SCK), 6 (MISO), 5 (MOSI) are defined in SD card library  
  pinMode( 3, OUTPUT);        // for PWM sound generation
  pinMode( SS_CARD, OUTPUT);  // SS for sd card
  pinMode(MISO, OUTPUT);
  digitalWrite(4,HIGH);       // set SS high for software SPI

  // initialize SPI
  SPCR |= _BV(SPE);           // start hardware SPI in Slave mode
  SPI.attachInterrupt();      // attach slave mode interrupt handler for SPI

  // Set pin 3 for PWM generation, fast PWM mode, toggle on overflow and passing OCRB; OCRB (0-255) determines duty cycle
  TCCR2A = B00100011;
  TCCR2B = B00000001;
  OCR2B  = 0;            // duty cycle = 0 %

  //try to initialize SD card on startup
  initialize_SDcard();
  
  /******** Initialize Timer and 50 us interrupt ********/
  Timer1.initialize(100);             // initialize timer1, and set a 100 us period
  // In the PWM modes pin 1 and 9 are grouped, and pin 2 and 10
  Timer1.disablePwm(9);              // clear PWM on all pins
  Timer1.disablePwm(10);
  Timer1.attachInterrupt(callback);

  
  // start playing a WAV  
  openNewWav(4);
  Serial.println(millis());
  player_status = STATUS_PLAYING;
}


void loop()
{
  // nothing happens after setup
}

void callback()
{
  if (player_status == STATUS_PLAYING)
  {
    play_byte = myFile.read();
    OCR2B = play_byte;
    play_byte_used = 1;
    file_pos++;
    if (file_pos==100000 /*sample_size*/) player_status = STATUS_CLOSINGWAV;
    //delayMicroseconds(54);
  }
}

My speaker now just garbles. Timewise this should fit.

I have no idea how to investigate further. I have been over all sd-card files, but found no apparent clue. Ideas are welcome.

Cheers, Jack

Timewise this should fit.

But, have you looked at whether or not interrupts are involved in SPI data movement? If so, then you can forget about SPI stuff happening in your ISR.

Paul, thank you for your reply.

I have tried the following in the past:

  • read data from flashmem from within TSR
  • send data to DAC or 595 from within TSR

Both successful, but both with hardware SPI. I will need the software SPI (bitbanging) method, because the hardware SPI pins are reserved to run the arduino in slave mode.

I dug into the SPI/interrupt question.

From the SD card library I find (Sd2Card.cpp):

#ifndef SOFTWARE_SPI
// functions for hardware SPI
/** Send a byte to the card */
static void spiSend(uint8_t b) {
  SPDR = b;
  while (!(SPSR & (1 << SPIF)));
}
/** Receive a byte from the card */
static  uint8_t spiRec(void) {
  spiSend(0XFF);
  return SPDR;
}
#else  // SOFTWARE_SPI
//------------------------------------------------------------------------------
/** nop to tune soft SPI timing */
#define nop asm volatile ("nop\n\t")
//------------------------------------------------------------------------------
/** Soft SPI receive */
uint8_t spiRec(void) {
  uint8_t data = 0;
  // no interrupts during byte receive - about 8 us
  cli();
  // output pin high - like sending 0XFF
  fastDigitalWrite(SPI_MOSI_PIN, HIGH);

  for (uint8_t i = 0; i < 8; i++) {
    fastDigitalWrite(SPI_SCK_PIN, HIGH);

    // adjust so SCK is nice
    nop;
    nop;

    data <<= 1;

    if (fastDigitalRead(SPI_MISO_PIN)) data |= 1;

    fastDigitalWrite(SPI_SCK_PIN, LOW);
  }
  // enable interrupts
  sei();
  return data;
}
//------------------------------------------------------------------------------
/** Soft SPI send */
void spiSend(uint8_t data) {
  // no interrupts during byte send - about 8 us
  cli();
  for (uint8_t i = 0; i < 8; i++) {
    fastDigitalWrite(SPI_SCK_PIN, LOW);

    fastDigitalWrite(SPI_MOSI_PIN, data & 0X80);

    data <<= 1;

    fastDigitalWrite(SPI_SCK_PIN, HIGH);
  }
  // hold SCK high for a few ns
  nop;
  nop;
  nop;
  nop;

  fastDigitalWrite(SPI_SCK_PIN, LOW);
  // enable interrupts
  sei();
}
#endif  // SOFTWARE_SPI

and (S2dPinmap.h):

static const uint8_t digitalPinCount = sizeof(digitalPinMap)/sizeof(pin_map_t);

uint8_t badPinNumber(void)
  __attribute__((error("Pin number is too large or not a constant")));

static inline __attribute__((always_inline))
  uint8_t getPinMode(uint8_t pin) {
  if (__builtin_constant_p(pin) && pin < digitalPinCount) {
    return (*digitalPinMap[pin].ddr >> digitalPinMap[pin].bit) & 1;
  } else {
    return badPinNumber();
  }
}
static inline __attribute__((always_inline))
  void setPinMode(uint8_t pin, uint8_t mode) {
  if (__builtin_constant_p(pin) && pin < digitalPinCount) {
    if (mode) {
      *digitalPinMap[pin].ddr |= 1 << digitalPinMap[pin].bit;
    } else {
      *digitalPinMap[pin].ddr &= ~(1 << digitalPinMap[pin].bit);
    }
  } else {
    badPinNumber();
  }
}
static inline __attribute__((always_inline))
  uint8_t fastDigitalRead(uint8_t pin) {
  if (__builtin_constant_p(pin) && pin < digitalPinCount) {
    return (*digitalPinMap[pin].pin >> digitalPinMap[pin].bit) & 1;
  } else {
    return badPinNumber();
  }
}
static inline __attribute__((always_inline))
  void fastDigitalWrite(uint8_t pin, uint8_t value) {
  if (__builtin_constant_p(pin) && pin < digitalPinCount) {
    if (value) {
      *digitalPinMap[pin].port |= 1 << digitalPinMap[pin].bit;
    } else {
      *digitalPinMap[pin].port &= ~(1 << digitalPinMap[pin].bit);
    }
  } else {
    badPinNumber();
  }
}

As far as I can see, only the interrupt are disabled when you are already in the ISR. Will this be a problem?

I have noticed that sometimes the serial window will return weird characters when I read the sd card from the ISR:

Initializing SD card...initialization done.

Opening /0004.wav
Found /0004.wav, size: 2883701
8 bit 10kHz mono wav, size 2883657 bS???R%¹iI+Ê%InR+%I¾R%¹iIR+%nI+Ê%nI+Ê%nI+Ê%InR+¹InR+¹I¾R+Ê%¹%ùI¾R+¹InR+¹InR%¹InR+¹I¾R+¹InR+Ê%¹InR+¹InR+ùI¾R+Ê%¹%ùInR+¹InR+¹InR%¹%ùInR+ùI¾R+%InR%

However, nowhere in the SD card library can I find a reference to Serial.print which should account for this...

I tried to hook up the ISR at the end of setup, after a delay, so I'm sure the file is opened well, but still no success.

Cheers,

Jack

I suspect reading SD data will not work in an interrupt routine since an entire 512 byte block will be read when requires. If you want to try here is the problem.

Software SPI was not designed to run in an ISR. The software SPI functions disable and enable interrupts with cli() and sei().

You should remove cli() and sei() if software SPI is always called with interrupts disabled.

A more general fix is to replace cli() with

  uint8_t oldSREG = SREG;
  cli();

And replace sei() with

  SREG = oldSREG;

This will have higher overhead but work with interrupts enabled or disabled.

That was spot on !

Can you explain why this works? I gather from this post

http://forum.arduino.cc/index.php?topic=109939.0

sort of what SREG should do. So at the start of my software SPI send function I save the value in SREG, and put it back in place when I'm done. Is information lost when I only use cli() en sei() ?

Cheers,

Jack