Проблема с записью на карточку SD

Banzay1983
Offline
Зарегистрирован: 30.08.2021

Нужно запустить устройство логирования.  Суть его следующая с трансформатора тока приходит сигнал (до 4В) на АЦП ADS1015.  АЦП измеряеь напряжение (выходной сигнал датчика) и передает на плату Ардуино УНО.  Переодичность запись 0,02 сек , но так как для УНо это практически не подьемная задача, то пока запись делается с переодичностью 2 секунды. Так как так часто дергать карточку sd вредно , то запись делается через массив (буфер) по сто элементов.  Файлы просили делать с количеством 30 000 строк в формате txt  или csv. Карточка micro sd,  модль SD карточки стандартный, коих много в магазинах. Код программы выглядит вот так

 

#include <SD.h>
#include <Wire.h>
#include <SPI.h>//
#include <DS3231.h> //внешние часы  
#include <Adafruit_ADS1X15.h> // Библиотека для работы с модулями АЦП ADS1115 и ADS1015
Adafruit_ADS1015 adc;    //  переменная АЦП
#define chipSelect 4 //пин подключения СД карточки
File dataFile;  //переменная работы с файлами
int counter = 0 ;    // замена i  
int step_counter;
uint32_t timer = 0;   // переменная таймера
int period;
 
 
 
void setup()
{
  
  SD.begin(chipSelect); //запуск сд карточки
    adc.begin();//запуск  ацп
   adc.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 3mV    (default), режим работы АЦП
   
}

void loop()
{
  
 
 int Time [100];//массив записи времени измерения
 int Buffer[100];//массив который хранит значения с ацп

 
  char filename[] = "L000.txt"; // Первоначальное название первого файла
  
      for ( int a = 0; a < 1000; a++) 
      {
        filename[1] = a / 100 + '0';
        filename[2] = (a / 10)%10 + '0';
        filename[3] = a %10 + '0';
      
         if (! SD.exists(filename))// Проверяем наличие файла с таким именем,
         //если такого файла нет на карточке запускаем процес логирования в файл с этим именем
          { 
           
            
              for  (int step_counter =0;step_counter<300;step_counter++ )//запись буфера в файл будет производится 300 раз
                {   
                  period = millis() - timer;// наш флаг переодичности опроса АЦП
                    if (period >=2000)
                     {
           
                          Buffer[counter]=(int)get_ADC();//явное преобразование float в int
                          Time[counter]=period;
                          
                            timer+= 2000;
                            counter++;
                      //period = millis();
                    }
               if (counter ==100)//если массив заполнен то шачинаем писать в файл
                { 
                  byte i=0;
                  
                   dataFile = SD.open(filename, FILE_WRITE);//создаем файл с именем файлнейм
                      if (dataFile)
                      { 
                        dataFile.println(dateString());//записываем время с внешних часов для сверки
                        while (i < counter)
                        {
                           dataFile.print(i+1);//порядковый номер в массиве (для проверки)
                          dataFile.print("|");
                          dataFile.print( Buffer[i]);
                          dataFile.print("|");
                          dataFile.println(Time[i]);
                         ++i;
                        }
                       // dataFile.flush();
                       
                      }
                       dataFile.close();
                       counter=0;
                        
                   }
                 
                } 
           
          }
      }
  }

String dateString ()//функция получения времени с внешних часов, пи открытии файла
{  //
  // Получение показаний с DS3231
  DS3231 clock;                                  // Связываем объект clock с библиотекой DS3231
  clock.begin();                                 // Инициализируем модуль RTC DS3231
  RTCDateTime DateTime;                          // Определяем переменную DateTime, как описанную структурой RTCDateTime (структура описана в библиотеке DS3231.h)
  DateTime = clock.getDateTime();                // Заполняем DateTime значениями, полученными при запросе текущего времени
  //
  String dataString = String(DateTime.day) + "." + String(DateTime.month) + "." + String(DateTime.year) + " " + String(DateTime.hour) + ":" + String(DateTime.minute) + ":" + String(DateTime.second) ; // Помещаем дату  в новую строку логов

  Serial.println(dataString);                    // Выводим строку логов на монитор серийного порта

  return (dataString);                           // Возвращаем полученные значения в место вызова функции
}

float get_ADC()//получение значений с ацп
{  
  int16_t adc0;
  float volts0;

  adc0 = adc.readADC_SingleEnded(0);
  volts0 = adc.computeVolts(adc0)*150.0;//умноаем значение с АЦП на поправочный коэффициент датчика
  return (volts0);
}

 

 

Banzay1983
Offline
Зарегистрирован: 30.08.2021

ПО частям программа работает.  Если запустить генерацию файлов в цикле. То ардуино  их создает,  пусть не 1000файлов но штук 300 создаст.  Если писать данные в один файл log. txt , то в них опять же пишется.  А вот если полностью скомпилировать код  и запустить, минут на пять. то происходит вот такая запись:(создалось 4 файла, в каждом файле запись производилась только один раз (один раз выгружался массив  по 100 элементов) и создавались они не по порядкуа как получится. Самая первая запись  это 667 фай, потом 951 , потом 461, потом 277. Почему она так работала,  проблема в карточке, в ардуино или что-то я пропустил в программе?

Banzay1983
Offline
Зарегистрирован: 30.08.2021

запись L667 началась  13.9.2021 14:58:49
L951  13.9.2021 15:5:29
l461  13.9.2021 15:2:9
L277   13.9.2021 15:8:49  
 

rkit
Offline
Зарегистрирован: 23.11.2016

Буфер уже есть в библиотеке. Не надо выдумывать дополнительную чушь.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Где Вы взяли этот код?

На ошибки в функции loop указать невозможно, т.к. она вся - одна сплошная ошибка. Она неправильно задумана и спроектирована.

Ну, вот как у Вас всё это работаtт? Ведите пальцем по коду и рfссeждайте.

  1. Заходим в loop
  2. Входим в цикл строки №35.
  3. В строках №№37-39 делаем имя файла "L000.txt"
  4. Заходим в цикл строки №46.
  5. Этот цикл отрабатывает 300 раз ни хрена не делая, т.к. за это время 2 сек, разумеется не пройдут.
  6. Возвращаемся к строкам №№37-39 и делаем имя "L001.txt"
  7. Опять Заходим в цикл строки №46 и опять он отрабатывает 300 раз ни хрена не делая, т.к. 2 сек всё ещё не прошли

Достаточно? Дальше сами будете разбираться?

b707
Offline
Зарегистрирован: 26.05.2017

Banzay1983 пишет:

А вот если полностью скомпилировать код  и запустить, минут на пять. то происходит вот такая запись:(создалось 4 файла, в каждом файле запись производилась только один раз (один раз выгружался массив  по 100 элементов) и создавались они не по порядкуа как получится. Самая первая запись  это 667 фай, потом 951 , потом 461, потом 277. Почему она так работала,  проблема в карточке, в ардуино или что-то я пропустил в программе?

как вы программу написали. так оно и работает. Посмотрите внимательнее в код - вы создаете файл, потом проверяете. не прошло ли 2 секунды? - если не прошло, этот файл бросаете, ничего в него не записав, создаете следующий... и так по кругу.

Когда, наконец, две секунды прошло - вы пишете один буфер данных в очередной файл и цикл начинается заново...

В коде логики вообще нет

Banzay1983
Offline
Зарегистрирован: 30.08.2021

ЕвгенийП пишет:

Где Вы взяли этот код?

На ошибки в функции loop указать невозможно, т.к. она вся - одна сплошная ошибка. Она неправильно задумана и спроектирована.

Ну, вот как у Вас всё это работаtт? Ведите пальцем по коду и рfссeждайте.

  1. Заходим в loop
  2. Входим в цикл строки №35.
  3. В строках №№37-39 делаем имя файла "L000.txt"
  4. Заходим в цикл строки №46.
  5. Этот цикл отрабатывает 300 раз ни хрена не делая, т.к. за это время 2 сек, разумеется не пройдут.
  6. Возвращаемся к строкам №№37-39 и делаем имя "L001.txt"
  7. Опять Заходим в цикл строки №46 и опять он отрабатывает 300 раз ни хрена не делая, т.к. 2 сек всё ещё не прошли

Достаточно? Дальше сами будете разбираться?

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

Banzay1983
Offline
Зарегистрирован: 30.08.2021

Мануалов работы с буфером не нашел (или работа с UART, а не с SD карточкой), возможно плохо искал, кроме мудренных алгоритмов которые требуют много ресурсов.  Может вы подскажите , где искать?  

rkit
Offline
Зарегистрирован: 23.11.2016

Banzay1983 пишет:

Мануалов работы с буфером не нашел (или работа с UART, а не с SD карточкой), возможно плохо искал, кроме мудренных алгоритмов которые требуют много ресурсов.  Может вы подскажите , где искать?  

В учебнике по С

b707
Offline
Зарегистрирован: 26.05.2017

Banzay1983 пишет:

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


да нет, одной скобкой не обойдетесь. Вам нужно кардинально менять логику программы, чтобы от жестко линейной последовательности одного цикла перейти к нескольким независимым циклам- в одном вы делаете 100 измерений, в другом - пишете на данные карту раз в 2 секунды,в третьем - меняете имя файла раз в 30 записей.

Banzay1983
Offline
Зарегистрирован: 30.08.2021

Как будет время почитаю. Пока, что я читал в 2019  Айры Пола  старенькую книжку, но он писал про с++  

Banzay1983
Offline
Зарегистрирован: 30.08.2021

b707 пишет:
Banzay1983 пишет:
Про пункт 4 вы меня натолкнули на мысль, сейчас попробую исправить.  Возможно я одну скобку не поставил из-за этого полторы недели не могу найти ошибку. 

 

да нет, одной скобкой не обойдетесь. Вам нужно кардинально менять логику программы, чтобы от жестко линейной последовательности одного цикла перейти к нескольким независимым циклам- в одном вы делаете 100 измерений, в другом - пишете на данные карту раз в 2 секунды,в третьем - меняете имя файла раз в 30 записей.

Пока нашел ошибку, я оказывается цикл записи в буфер не закрыл корректно. В результате программа заполняла раз массив , выгружала его в файл и летела  создавать другой файл, как вы и говорили. Полторы недели я эту ошибку не мог увидеть. Так что вам еще раз спасибо. 
На счет перехода на несколько циклов,  в 8 битных контроллерах параллельные процессы программно и аппаратно не поддерживаются.  Прерывание вторым таймером в атмеле ( на сколько я понял ) возможно только для того чтобы помигать светодиодом. Семафоры тут особо не применишь ( да и знакомился я с ними в 2017 -м году). Читал на одном форуме схожую проблему,  там дали ответ что полноценно это решится на 32 битных стм-ках с шиной DMA  ( ардуино DUE   плату или какую -то не ардуино).  

 
 

b707
Offline
Зарегистрирован: 26.05.2017

Для решения вашей задачи никаких параллельных процессов не требуется. Насчет прерываний и ДМА вообще полная чушь, даже комментировать не стану - вы просто ничего не поняли.
Чтобы понять , как на контроллере запустить несколько одновременных задач без реальной многозадачности - разберите широко известный пример " блинк без делей". Этот механизм подходит не только ддя мигания диодом, любая многозадачность на подобных контроллерах делается по этому принципу. Рискну даже сказать, что не поняв приципов работы миллис, ни одну серьезную программу не написать.

Banzay1983
Offline
Зарегистрирован: 30.08.2021

b707 пишет:
Для решения вашей задачи никаких параллельных процессов не требуется. Насчет прерываний и ДМА вообще полная чушь, даже комментировать не стану - вы просто ничего не поняли. Чтобы понять , как на контроллере запустить несколько одновременных задач без реальной многозадачности - разберите широко известный пример " блинк без делей". Этот механизм подходит не только ддя мигания диодом, любая многозадачность на подобных контроллерах делается по этому принципу. Рискну даже сказать, что не поняв приципов работы миллис, ни одну серьезную программу не написать.

Сегодня эксперементировал до 4 утра с записью в переодичностью в 20 мс,  
я подумаю, если будет возможность завтра над этой задачей, напишу свои соображения

Banzay1983
Offline
Зарегистрирован: 30.08.2021

вчера (вернее сегодня)с пятого раза  мой ответ отправился сюда...

Banzay1983
Offline
Зарегистрирован: 30.08.2021

Вчера изменил код (попробовал переделать задачу под первоначальные условия , когда опрашивать АЦП нужно как можно чаще, например 20мс, исправил грубую ошибку в цикле где опрашивается АЦП и производит запись в файл.  
-Файлы с txt  изменил на CSV
-И запись времени с внешних часов(DS3231) будет производится только один раз,  при открытии файла, дальше отсчет будет производится millis(). 

-   Так же файл  открывается в одном цикле функции loop()  один раз  и  закрывается перед завершением ее итерации.  

В  итоге файлы создаются  в правильном порядке,   промежутки между записями массивов приемлимы.  

Теперь у меня два вопроса:
- в итоге одна итерации  (или один шаг   , плохо знаю терминологию  функции loop длится в пределах 11 минут,  при том в начале функции loop  мы файл открываем на SD карте для записи и  в конце закрываем,  т.е можно сказать что перезапись карточки происходит раз  в 11 минут и она сможет работать так месяц по 8 часов в день?
- и выпала странная ошибка для меня, если судить  по показаниям внешних часов , то генерация между файла логов длится около 11 минут, когда в эксель сумирую  показаия с таймеров millis() (хазиси в массив Time[]  ,  то cумма millis  меньше на 21 секунду и так во всех файлах, которые я проверял, т .е ошибка программная, какая мне пока не удалось отследить

 

Вот код новой программы:
 

#include <SD.h>
#include <Wire.h>
#include <SPI.h>//
#include <DS3231.h> //внешние часы  
#include <Adafruit_ADS1X15.h> // Библиотека для работы с модулями АЦП ADS1115 и ADS1015
Adafruit_ADS1015 adc;    //  переменная АЦП
#define chipSelect 4 //пин подключения СД карточки
File dataFile;  //переменная работы с файлами
uint32_t timer = 0;   // переменная таймера
void setup()
{
  
  SD.begin(chipSelect); //запуск сд карточки
    adc.begin();//запуск  ацп
   adc.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 3mV    (default), режим работы АЦП
   
}

void loop()
{
  
 byte counter; //переменная для работы с размерами массивов
 int period;//переменная для определения времени опроса АЦП
 int Time [105];//массив записи времени измерения
 int Buffer[105];//массив который хранит значения с ацп
 
 
  char filename[] = "L000.csv"; // Первоначальное название первого файла
  
      for ( int a = 0; a < 1000; a++) 
      {
        filename[1] = a / 100 + '0';
        filename[2] = (a / 10)%10 + '0';
        filename[3] = a %10 + '0';
      
         if (! SD.exists(filename))// Проверяем наличие файла с таким именем,
         //если такого файла нет на карточке запускаем процес логирования в файл с этим именем
          { 
           
            dataFile = SD.open(filename, FILE_WRITE);//производим открытие файла для записи текущего времени
            if (dataFile)
            { 
             dataFile.println(dateString());
             dataFile.flush();
             
            } 
              for  (int step_counter =0;step_counter<300;step_counter++ )//запись буфера в файл будет производится 300 раз
                {   
                  counter =0;//переменная счетчик для контроля над массвами
                 while (counter<105)
                 {
                   period = millis() - timer;// наш флаг переодичности опроса АЦП
                       
                     if (period >=20)
                     {
                
               
                          Buffer[counter]=(int)get_ADC();//явное преобразование float в int
                          Time[counter]=period;
                           if( counter <=18) timer = millis();//более мягкий счетчик( не используется все время так как дает погрешность)
                           else timer+= 20; // более строгий счетчик
                           counter++;
                       
                    }
                 }
                  
                  counter=0;//обнулим эту переменную чтоб использовать ее для записи в файл
                  
                   
                      if (dataFile)
                      { 
                       
                        while ( counter < 105)
                        {
                           dataFile.print(counter+1);
                          dataFile.print(";");
                          dataFile.print( Buffer[counter]);
                          dataFile.print(";");
                          dataFile.println(Time[counter]);
                         ++counter;
                        }
                        dataFile.flush();
                       
                      }
                   
                } 
            dataFile.close();
          }
      }
 }

String dateString ()//функция получения времени с внешних часов, пи открытии файла
{  //
  // Получение показаний с DS3231
  DS3231 clock;                                  // Связываем объект clock с библиотекой DS3231
  clock.begin();                                 // Инициализируем модуль RTC DS3231
  RTCDateTime DateTime;                          // Определяем переменную DateTime, как описанную структурой RTCDateTime (структура описана в библиотеке DS3231.h)
  DateTime = clock.getDateTime();                // Заполняем DateTime значениями, полученными при запросе текущего времени
  //
  String dataString = String(DateTime.day) + "." + String(DateTime.month) + "." + String(DateTime.year) + " " + String(DateTime.hour) + ":" + String(DateTime.minute) + ":" + String(DateTime.second) ; // Помещаем дату  в новую строку логов

  Serial.println(dataString);                    // Выводим строку логов на монитор серийного порта

  return (dataString);                           // Возвращаем полученные значения в место вызова функции
}

float get_ADC()//получение значений с ацп
{  
  int16_t adc0;
  float volts0;

  adc0 = adc.readADC_SingleEnded(0);
  volts0 = adc.computeVolts(adc0)*150.0;
  return (volts0);
}

 

Banzay1983
Offline
Зарегистрирован: 30.08.2021

b707 пишет:
Для решения вашей задачи никаких параллельных процессов не требуется. Насчет прерываний и ДМА вообще полная чушь, даже комментировать не стану - вы просто ничего не поняли. Чтобы понять , как на контроллере запустить несколько одновременных задач без реальной многозадачности - разберите широко известный пример " блинк без делей". Этот механизм подходит не только ддя мигания диодом, любая многозадачность на подобных контроллерах делается по этому принципу. Рискну даже сказать, что не поняв принципов работы миллис, ни одну серьезную программу не написать.

Если вас не напряжет, я буду у вас как студент спрашивать наводящие вопросы.
Блинк  без delay делается миллисом,  можно сделать как бы два вложенных цикла один будет писать в массив данные , а второй будет писать из массива в файл отставая на несколько элементов от первого массива.  Ну это пока догадки

b707
Offline
Зарегистрирован: 26.05.2017

Banzay1983 пишет:

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

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