2

I'm using ESP8266 at speed of 160 MHz to record ADC results into SD card, I need to do this at rate of 8 KHz. Means that i should save it on the SD Card or i'll run out of memory before 4 Sec passes. I used timer1 to trigger sampling, but my problem is that surprisingly ESP8266 can not do this fast enough and the maximum sampling rate i get is about 7 KHz. I also tried to write data in ESP8266 Flash using littleFS in hope of acceleration but there was no luck. Here is my code:

#include <SPI.h>
#include <SD.h>

File dataFile;

int flag = 0;
int count = 0, level = 0, se_count = 0;

void inline analogReader() // Sampling and Recording processed which is done sequentially :( 
{
  uint16_t dataByte = (uint16_t) analogRead(A0);
  dataFile.write((uint8_t*) &adcBuf, 2);
  
}

void ICACHE_RAM_ATTR onTimerISR() { // Timer sets the flag every 0.125 MS
  flag = 1;
}

void setup() {
  pinMode(A0, INPUT);
  pinMode(D0, OUTPUT);

  Serial.begin(115200);

  if (!SD.begin(D0)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");

  File root = SD.open("/");

  if (SD.exists("real_test.bin"))
  {
    SD.remove("real_test.bin");  
  }
  
  dataFile = SD.open("real_test.bin", FILE_WRITE);

  if (dataFile) {
    Serial.println("File created!");
  }
  else
  {
    Serial.println("Failed to create file!");
    return;
  }

  // Setting the timer so that timer interrupt fires every 0.125 MS
  timer1_attachInterrupt(onTimerISR);
  timer1_enable(TIM_DIV1/*from 80 Mhz*/, TIM_EDGE/*TIM_LEVEL*/, TIM_LOOP/*TIM_SINGLE*/);
  timer1_write(10000); /* ( 80 / 1 ) * 125 */
}

void loop() {
  if (flag) // To check whether its time to sampling or not, because of the low speed of the process
  {         // The flag is set again before one iteration ends.
    flag = 0;
  
    if (se_count < 10)
      analogReader();

    count++;
    if (count == 8000) // When 8000 sample & write process done notify using serial monitor. (it is 
                          always more that 1 Sec. for example 1.3 Sec.)
    {
      count = 0;
      level = !level;
      Serial.println(level);
  
      se_count++;
      if (se_count == 10) // When we done 8000 * 10 sample & write we close the file. it supposed to 
                          // be done in almost 10 sec. but it takes more time in practice. for 
                          // example 13 Sec. 
      {
        dataFile.close();
        Serial.println("Write ended");
      }
    }

  }
}

I even used a faster method system_adc_read_fast(buf, num, clk_div) that is available in ESP-SDK, So i'm desperately seeking for one or two of approaches bellow if possible:

  1. To use a non-blocking approach: If there is any possibility to hand SD writing to DMA so that i only fill the ADC buffer for DMA, or an interrupt for ADC complete so that i write to SD and when interrupt fired put the result in the buffer and return back to SD writing.
  2. Yet faster approach for both ADC sampling and writing: Can i increase the SD write speed of SD library? for example by configuring SPI for maximum speed rate.

I also tried SDFat library but it does not recognize my SD Card. The whole process is odd to me that even by using a 160 MHz CPU and hardware i can not record ADC data at rate of 8000 Sample/S. I have TIME limitation so please help me.

Any suggestion is appreciated. Thanks in advance.

6
  • Please help me guys, There is no suggestion? How possible!
    – A.R.S.D.
    Dec 8 '20 at 9:49
  • It's not clear to me where your performance bottleneck is happening. In your code you have an interrupt set a flag but your loop() function takes its sweet time doing things, especially things like writing serial that will likely take longer than the interval to the next interrupt. I suspect the problem is the overhead of an SD card write operation. Perhaps you might batch them up so that you write more data to the SD less often.
    – jwh20
    Dec 8 '20 at 12:39
  • Thanks. I put the code in loop() instead of interrupt routine itself because it takes longer time than 125 MS or 1/8000 Second to be done. So before finishing interrupt routine the interrupt line reasserted. So CPU never returned to loop() and that watch dog timeout happened. even though i tried to feed the dog in the interrupt routine :) . Ok i agree with you. May be batching can help improving the performance. I'm trying it. I'll report the result.
    – A.R.S.D.
    Dec 8 '20 at 13:32
  • One problem with your loop() architecture is that if you don't complete the loop before the next interrupt happens, you will miss a reading. You might redesign this so that the interrupt reads the ADC and stores the result in a buffer. Then you loop can watch that buffer until it get full, grab that data and start writing to the SD card. In that case you have N interrupts to complete the task. N is the number of readings your buffer can hold.
    – jwh20
    Dec 8 '20 at 13:41
  • Putting the code in loop() is good. Your line int flag = 0; should be volatile int flag = 0;. This lets the compiler know that flag may change unexpectedly, so it will know to reload it from memory more often. Otherwise flag might change and the loop code might miss it because the value is cached in a register.
    – romkey
    Dec 8 '20 at 14:16
2

So, sorry for late response, i figured it out. Because it took lots of time for me and it might be a prevalent issue, i think it worth to contribute my experience for further similar issues.

As mentioned buffering can do the job, here are some constraints that should be satisfied to have right buffering mechanism;

  • Writing to SD card SHOULD NOT be handled in the Timer ISR, but store ADC data into a buffer, aka int[] which is normally kept in SRAM (Your Timer ISR should be as short and fast as needed)

  • Handle reading from buffer to SD outside of Timer ISR in, for example in loop().

  • priority of ISR should be more than loop(), and it is so by default.

  • It's a good practice to divide your buffer into chunk of 512 or 1024. for example 2 chunks of 1024 Bytes making your buffer. you check for "filled" flag to be set in the loop() and when this happens call SDFile.write(..) for corresponding filled chunk and then reset the flag.

By conforming to these constraints you enjoy these benefits:

  • you can be sure that no ADC on time sampling missed because of sluggish SD write process.

  • by buffering and pilling data and then storing, you have chance to avoid byte wise write to SD, which is really inefficient. and instead handle write in chunks of 512 or 1024 bytes. which is equal to sector size of SDs. so you have faster SD write process too.

This way you can easily handle voice recording up to 16 ksps even without using DMA.

I have added the code to my github, in this repository; https://github.com/A-R-S-D/WAVRecorder

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.