Любой способ использовать DMA для передачи данных на SD - карту в ESP8266

esp8266 sd-card adc spiffs littlefs

Я использую ESP8266 со скоростью 160 МГц для записи результатов АЦП на SD-карту, мне нужно сделать это со скоростью 8 кГц. Это означает, что я должен сохранить его на SD-карте, иначе у меня закончится память до того, как пройдет 4 секунды. Я использовал timer1 для запуска выборки, но моя проблема заключается в том, что удивительно ESP8266 не может сделать это достаточно быстро, и максимальная частота дискретизации, которую я получаю, составляет около 7 кГц. Я также попытался записать данные в ESP8266 Flash с помощью littleFS в надежде на ускорение, но безуспешно. Вот мой код:

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

File dataFile;

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

void inline analogReader() // Выборка и запись обрабатываются последовательно :( 
{
  uint16_t dataByte = (uint16_t) analogRead(A0);
  dataFile.write((uint8_t*) &adcBuf, 2);
  
}

void ICACHE_RAM_ATTR onTimerISR() { // Таймер устанавливает флаг каждые 0,125 МС
  flag = 1;
}

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

  Serial.begin(115200);

  if (!SD.begin(D0)) {
    Serial.println("Карта вышла из строя или отсутствует");
    // don't do anything more:
    return;
  }
  Serial.println("карта инициализирована.");

  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;
  }

  // Установка таймера так, чтобы прерывание таймера срабатывало каждые 0,125 МС
  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
  {         // Флаг устанавливается снова до окончания одной итерации.
    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");
      }
    }

  }
}

Я даже использовал более быстрый метод system_adc_read_fast(buf, num, clk_div), который доступен в ESP-SDK, поэтому я отчаянно ищу один или два подхода ниже, если это возможно:

  1. Чтобы использовать неблокирующий подход: Если есть какая-либо возможность передать запись SD в DMA так, чтобы я только заполнял буфер ADC для DMA, или прерывание для ADC завершено так, чтобы я писал в SD, и когда прерывание сработает, поместите результат в буфер и вернитесь обратно к записи SD.
  2. Еще более быстрый подход как для выборки АЦП, так и для записи: Могу ли я увеличить скорость записи SD библиотеки SD? например, настроив SPI для максимальной скорости.

Я также попробовал библиотеку SdFat, но она не распознает мою SD-карту. Весь этот процесс кажется мне странным из-за того, что даже при использовании процессора с частотой 160 МГц и аппаратного обеспечения я не могу записать данные АЦП со скоростью 8000 сэмплов/С. У меня есть ограничение по ВРЕМЕНИ, поэтому, пожалуйста, помогите мне.

Любое предложение приветствуется. Заранее спасибо.

, 👍2

Обсуждение

Пожалуйста, помогите мне, ребята, нет никаких предложений? Как это возможно!, @A.R.S.D.

Мне не ясно, где происходит ваше узкое место в производительности. В вашем коде у вас есть прерывание, устанавливающее флаг, но ваша функция loop() занимает свое сладкое время, делая вещи, особенно такие вещи, как написание последовательного кода, который, вероятно, займет больше времени, чем интервал до следующего прерывания. Я подозреваю, что проблема заключается в накладных расходах на операции записи SD-карты. Возможно, вы могли бы паковать их так, чтобы вы записывали больше данных в SD реже., @jwh20

Спасибо. Я поставил код в loop() вместо самой процедуры прерывания, потому что это занимает больше времени, чем 125 мс или 1/8000 секунды. Поэтому перед завершением процедуры прерывания линия прерывания была восстановлена. Таким образом, процессор никогда не возвращался к loop (), и этот тайм-аут сторожевого пса произошел. несмотря на то, что я пытался кормить собаку в рутине прерывания :) . Хорошо, я согласен с вами. Может быть, дозирование может помочь улучшить производительность. Я пытаюсь. Я доложу о результате., @A.R.S.D.

Одна из проблем с вашей архитектурой loop() заключается в том, что если вы не завершите цикл до того, как произойдет следующее прерывание, вы пропустите чтение. Вы можете перепроектировать это так, чтобы прерывание считывало АЦП и сохраняло результат в буфере. Затем вы можете наблюдать за этим буфером, пока он не заполнится, захватить эти данные и начать запись на SD-карту. В этом случае у вас есть N прерываний для выполнения задачи. N-это количество показаний, которое может вместить ваш буфер., @jwh20

Поместить код в ' loop () - это хорошо. Ваша строка int flag = 0; должна быть volatile int flag = 0;`. Это позволяет компилятору знать, что "флаг" может неожиданно измениться, поэтому он будет знать, что нужно чаще перезагружать его из памяти. В противном случае "флаг" может измениться, и код цикла может пропустить его, потому что значение кэшируется в регистре., @romkey

Эй, ребята, вы пробовали читать АЦП и помещать результат в буфер в программе обслуживания прерываний таймера? Я не могу этого сделать, и ESP8266 продолжает сбрасываться с частотой таймера 8000 Гц, которую я настроил в коде, который я опубликовал, я действительно сталкивался с этой проблемой раньше, но я думал, что это потому, что я делаю как выборку, так и запись на SD, и это занимает много времени, поэтому watch dog продолжает сбрасывать MCU. но сейчас я просто попробовал сэмплировать АЦП и поместить результат в оперативную память. Я думаю, что это займет не так уж много времени., @A.R.S.D.


1 ответ


Лучший ответ:

3

Так что, извините за поздний ответ, я понял. Поскольку это заняло много времени для меня, и это может быть распространенной проблемой, я думаю, что стоит внести свой опыт для дальнейших подобных вопросов.

Как уже упоминалось, буферизация может сделать работу, вот некоторые ограничения, которые должны быть удовлетворены, чтобы иметь правильный механизм буферизации;

  • Запись на SD-карту НЕ ДОЛЖНА обрабатываться в таймере ISR, но хранить данные ADC в буфере, он же int [], который обычно хранится в SRAM (ваш таймер ISR должен быть настолько коротким и быстрым, насколько это необходимо)

  • Обработка чтения из буфера в SD вне таймера ISR in, например, в loop().

  • приоритет ISR должен быть больше, чем loop (), и это так по умолчанию.

  • Это хорошая практика, чтобы разделить ваш буфер на кусок 512 или 1024. например, 2 куска по 1024 байта составляют ваш буфер. вы проверяете, установлен ли флаг "заполнен" в цикле (), и когда это происходит, вызываете SDFile.write(..) для соответствующего заполненного фрагмента, а затем сбрасываете флаг.

Соблюдая эти ограничения, вы получаете следующие преимущества:

  • вы можете быть уверены, что ни один АЦП на время выборки не пропустил из-за вялого процесса записи SD.

  • буферизуя и заполняя данные, а затем сохраняя, у вас есть шанс избежать байтовой записи в SD, что действительно неэффективно. и вместо этого обрабатывать запись кусками по 512 или 1024 байта. что равно размеру сектора SDs. так что у вас есть более быстрый процесс записи SD тоже.

Таким образом, вы можете легко обрабатывать запись голоса до 16 ksps даже без использования DMA.

Я добавил код в мой github, в этом репозитории; https://github.com/A-R-S-D/WAVRecorder

,