Skocz do zawartości

Zapis danych na karcie micro SD - pytania odnośnie optymalizacji procesu zapisu i systemu plików


Pomocna odpowiedź

Korzystając z wolnej godzinki (dłubany właśnie projekcik zadziałał szybciej niż myślałem) dospawałem czytnik kart SD do małego Arduino Nano (3.3V, 8MHz, Mega328) i napisałem króciutki program bazujący na standardowej bibliotece sd:

https://www.arduino.cc/en/Reference/SD

Testy były dwa i oba polegały na zapisaniu do pliku 1000 linii tekstu. W każdej linii były trzy liczby losowe 0-65535 w postaci znakowej, np:

234,85,28591

Pierwszy test wyglądał tak, że po każdorazowym utworzeniu stringu z wylosowanych liczb robiłem:

    start_time = millis();
   dataFile = SD.open("datalog1.txt", FILE_WRITE);
   dataFile.println(dataString);
   dataFile.close();
   stop_time = millis();

tak więc plik był otwierany do zapisu, wysyłałem tam przygotowaną linię tekstu (dataString) i od razu plik był zamykany. Byłem ciekaw czy taka wersja będzie dużo wolniejsza od tej, w której otwieram plik tylko raz na początku pracy programu, piszę linię prostą instrukcją powtórzoną 1000 razy (oczywiście za każdym razem dataString był tworzony z innych liczb losowych):

    start_time = millis();
   dataFile.println(dataString);
   stop_time = millis();

a plik zamykam raz, dopiero po wykonaniu 1000 zapisów.

Zaleta pierwszego sposobu jest taka, że uaktualniamy FAT na karcie po każdym zapisie więc plik "rośnie" i jest poprawny nawet jeśli program gdzieś utknie czy padnie zasilanie. W drugim przypadku dopóki nie wykonamy close(), żadnych nowych danych w pliku nie widać. Jeśli coś się dzieje złego, wszystkie wysłane dane są tracone.

No a teraz wyniki z fabrycznie nową kartą Toshiba SDHC 8GB. Czasy, jak widzicie były mierzone systemową funkcją millis() więc pomiary miały rozdzielczość 1ms:

Test1:

Minimalny czas trwania sekwencji open-println-close: 14ms

Maksymalny czas: 41ms

Całkowity czas zapisu 1000 linii: 19600-22300ms

Test1:

Minimalny czas trwania operacji println: 0ms

Maksymalny czas: 22ms

Całkowity czas zapisu 1000 linii: 1630-1660ms

W testach nie mierzyłem osobno czasów operacji open() i close() i to pewnie close() zajmuje mnóstwo czasu w teście 1, bo każdorazowo wymaga uaktualnienia FATu (zapisy!).

Biorąc pod uwagę zmierzony czas całkowity, to średni czas operacji zapisu jednej linii tekstu wyniósł więc ok. 21ms w pierwszym przypadku i 1.6ms w drugim.

Bezpieczeństwo zapisanych danych (test 1) kosztuje ponad 10 razy więcej czasu 🙁

Dodatkowo zmierzyłem, że otwieranie nieistniejącego pliku (tj. utworzenie nowego) do zapisu zabiera ponad 260ms(!), ale open() pliku który już był na karcie nie wygląda już tak tragicznie.

Pamiętając o tym i próbując zrobić system akwizycji danych napływających ciągle trzeba się liczyć z "dziurami" w pracy procesora rzędu ponad 40ms i na tyle danych z czujników trzeba mieć bufor w RAMie aby niczego podczas operacji "dyskowych" nie zgubić. Jeżeli jeszcze do tego chcemy dzielić dane na kolejne pliki i będziemy zmuszeni do ich zamykania i tworzenia nowych w trakcie pracy, "zwisy" zwiększają się 10-krotnie, do prawie 300ms.

Na obecną chwilę mogę powiedzieć, że implementacja, którą teraz realizuję, zakłada znacznie mniejszą częstotliwość do pliku 😃 Pomiar wykonuję teraz co 1 sek. , Arduino oblicza parametry statystyczne tych pomiarów, i tylko te są zapisywane do microSD, więc co sekundę trzeba zapisać tylko kilkadziesiąt bajtów 🙂

Teraz mam jednak inną "zagwozdkę". Kod się rozszerza. Wychodząc z aplikacji na PC, zdroworozsądkowe byłoby rozbicie poszczególnych funkcjonalności na pliki .cpp, a także zastosowanie typowego programowania obiektowego. Wiem, że tak ludzie robią programy na Arduino i AVR-y, nawet sam język Arduino to C++, nie mówiąc o "obiektowych" bibliotekach, itp. jednak czy takie podejście jest prawidłowe? Słyszałem że obiektówka jest trochę bardziej zasobożerna, ale w jakim stopniu? Na ile mogę sobie pozwolić przy tworzeniu plików z funkcjonalnościami i dodawaniu ich do "main.ino"? nie mówię tu już oczywiście o miejscu na pamięci programu 😉

Rozbijanie programu na pliki to jeszcze nie jest programowanie obiektowe. Oczywiście jest to jak najbardziej wskazane przy większych programach i w zasadzie nic cię to nie kosztuje, jeśli chodzi o zasoby Arduino.

Dynamiczne tworzenie i niszczenie obiektów w trakcie działania programu (i generalnie wszelkie dynamiczne alokowanie pamięci) może być natomiast problemem, bo nigdy nie wiesz kiedy ci się pamięć skończy albo zostanie tak sfragmentowana, że nic już się w niej nie zmieści. Przy tak małej ilości pamięci, jaką mają AVR-y, najlepiej jest ją sobie na sztywno podzielić, rezerwując miejsce na wszystko czego będziemy potrzebować z góry.

Oczywiście, nie pisałem o rozdzielaniu funkcjonalności na pliki nagłówkowe i źródłowe w kontekście programowania obiektowego, chodziło mi o dwie różne rzeczy 😉 Co do samego programowania: jakiś czas temu bawiłem się w programowanie obiektowe, i trochę PHP nawet zahaczyłem, ale teraz łapię się na tym, że podstawy mi uciekają 😋

  • 1 miesiąc później...

Projekt posuwa się do przodu. Rodzi się pytanie - czy ktoś może doradzić jak efektywnie zaimplementować filtry all-pass - do przesunięcia sygnału w fazie ? Interesujący jest artykuł:

https://openenergymonitor.org/emon/buildingblocks/explanation-of-the-phase-correction-algorithm

Problem jednak tkwi w tym, że muszę dobrać współczynnik poprawki fazy w zależności od okresu próbkowania. Ten czas mogę zmierzyć. Współczynnik poprawki oblicza inny program, jednak poprawka jest prawidłowa tylko dla określonego okresu próbkowania. Muszę więc tak zmodyfikować ten program kalibrujący, aby zastosował odpowiednie próbkowanie, np. za pomocą przerwań, czy dobrze myślę?

Pracując w czasie dyskretnym (to właśnie Twój przypadek) opóźnienie najłatwiej zrobisz na buforze kołowym. Deklarujesz jakąś tablicę o długości min o 1 większej niż potrzebujesz opóźnienia (liczonego w okresach próbkowania) i zapisujesz do niej sample - za każdym razem jedną, tę najnowszą i oczywiście pamiętasz gdzieś indeks zapisu. Drugi indeks - odczytu uruchamiasz N okresów próbkowania później i ten "goni" tamten posuwając się za każdym razem o jedną próbkę. Indeksy liczysz/inkrementujesz modulo długość tablicy, która dzięki temu staje się takim nieskończonym buforem, mówimy "kołowym" lub "cyklicznym". Zmieniając różnicę miedzy posuwającymi się indeksami zmieniasz opóźnienie. Ta idea jest trywialna i napiszesz to w C w ciągu 10 minut. Liczenie indeksów modulo M przyśpieszysz gdy rozmiar tablicy będzie w postaci M=2^k np. 16, 32, 64 itd.

Gorzej gdy potrzebujesz opóźnienia ułamkowego, np. 7.48 okresów próbkowania. Wtedy całkowite opóźnienie możesz zrobić opisaną wyżej metodą a ułamek załatwiasz interpolacją. Ta może być najprostsza - liniowa (to właśnie zrobili w podlinkowanym artykule), może być jakiegoś wyższego rzędu, ale "profesjonalnie" zaprzęga się do tego tzw. resampling filters. Nie ma sensu tu opisywać poszukaj tego hasła gdzieś w okolicach DSP. To powszechny problem każdego cyfrowego systemu audio w którym akurat trzeba zmieniać w locie częstotliwość próbkowania, np. z 8ksps na 44.1ksps. Temat jest dobrze rozpoznany, choć dla AVR może być wyzwaniem. Wszystko zależy od tego ile masz czasu na obliczenia.

Tego kawałka o wyznaczaniu poprawki fazy nie rozumiem, ale na pewno wiesz co robisz. Musisz dysponować liczbą będącą (być może niecałkowitą) wielokrotnością okresu próbkowania - bo to jest podstawowa jednostka czasu w takich systemach.

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...