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:
- 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.
- 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.
1 Answer 1
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
loop()
is good. Your lineint flag = 0;
should bevolatile int flag = 0;
. This lets the compiler know thatflag
may change unexpectedly, so it will know to reload it from memory more often. Otherwiseflag
might change and the loop code might miss it because the value is cached in a register.