LinkedIn YouTube Facebook
Szukaj

Wstecz
SoM / SBC

Zapisywanie danych z czujnika na karcie SD w systemie Arduino

Prezentujemy przykład zapisywania danych na karcie SD w systemie Arduino. Rejestrowane będą dane o temperaturze otoczenia, pobrane z popularnego sensora DS18B20. Cały projekt działa na płytce Cytron Maker Uno, w pełni kompatybilnej z Arduino UNO.

W roli modułu czytnika kard SD wykorzystano płytkę Fermion: SD Card Module z oferty DFRobot. Płytka zawiera slot na karty typu microSD i komunikuje się poprzez interfejs SPI. Zawiera wlutowane złącze szpilkowe, co pozwala na dołączenie modułu do płytki stykowej.

Rys. 1. Moduł czytnika kart SD Fermion: SD Card Module

W roli źródła danych zapisywanych na karcie SD zastosowano czujnik temperatury DS18B20. Pozwala na pomiar temperatury w zakresie -55~+125°C z dokładnością ±0,5°C (w zakresie -10~+85°C). Do komunikacji wykorzystywany jest interfejs 1-Wire. W celu łatwiejszego podłączenia do Arduino, skorzystamy z płytki KAmodDS18B20. Ta płytka jest wyposażona w złącza w standardzie Pmod oraz KAmod, ale można też skorzystać z tradycyjnych, pojedynczych przewodów.

Rys. 2. Moduł KAmodDS18B20 z czujnikiem temperatury DS18B20

 

Połączenie modułów do płytki Arduino

Sensor temperatury korzysta z interfejsu 1-Wire, natomiast czytnik kart z SPI. Arduino Uno ma wystarczającą liczbę wyprowadzeń, aby bez problemu podłączyć oba moduły. Oba korzystają z zasilania 5 V, co oznacza, że prawdopodobnie będzie trzeba je rozgałęzić, na przykład poprzez szynę zasilania na płytce stykowej.

Oto tabele prezentujące połączenia modułów z płytką Arduino:

Tab. 1. Połączenie płytki KAmodDS18B20 z Arduino

KAmodDS18B20 Maker Uno
VDD 5V
GND GND
1-W 2

 

Tab. 2. Połączenie czytnika kart SD z Arduino

Fermion: SD Card Module Maker Uno
MISO 12
SCK 13
SS 4
MOSI 11
GND GND
+5V 5V
 

Rys. 3. Połączenie wszystkich elementów układu

Kod przykładu

Tak jak wspomniano wcześniej, kod ma za zadanie odczytać dane z sensora temperatury, a następnie zapisać je w pliku na karcie SD. Dane będą zapisywane w pliku typu CSV z danymi oddzielonymi średnikiem, co pozwoli od razu otworzyć plik w programie Excel i LibreOffice.

Pierwszą kolumną danych będzie oznaczenie czasu pomiaru. W tym przypadku jest to aktualny wynik funkcji millis(), ale można łatwo zmodyfikować projekt, aby zapisywał w tym miejscu aktualną datę i godzinę. Drugą kolumną jest oczywiście aktualna temperatura.

Na początku importujemy niezbędne biblioteki. Do obsługi czytnika kart użyjemy dedykowanej biblioteki dla kart SD, a także pakietu do obsługi SPI. Podobna sytuacja jest w przypadku termometru, gdzie korzystamy z biblioteki DallasTemperature.h do obsługi czujnika oraz OneWire.h do obsługi interfejsu.

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

#include <OneWire.h>
#include <DallasTemperature.h>

W kolejnym kroku definiujemy wyprowadzenia używane przez poszczególne moduły. Przede wszystkim chodzi tu o port Chip Select interfejsu SPI oraz linię danych magistrali 1-Wire.

#define ONE_WIRE_BUS 2
const int chipSelect = 4;

Następnie definiujemy zmienne współpracujące z funkcją millis. W previousMillis będziemy przechowywać dane o poprzednio zapisanej wartości funkcji, którą potem będziemy porównywać z wartością aktualną. Stała interval natomiast przechowuje częstotliwość powtarzania pętli operacyjnej – będzie to 1000 ms, czyli 1 sekunda.

unsigned long previousMillis = 0;
const long interval = 1000;

Inicjalizujemy jeszcze obiekty klas OneWire i DallasTemperature, co umożliwi obsługę sensora temperatury.

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

Instrukcje w funkcji setup

W funkcji setup uruchamiam port szeregowy, inicjalizuję czujnik oraz kartę SD. Na porcie szeregowym wyświetlona zostanie informacja, czy karta jest poprawnie widoczna w systemie czy też nie (np. na skutek braku karty w slocie).

void setup() {
  Serial.begin(9600);
 
  sensors.begin();

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    while (1);
  }

  Serial.println("card initialized.");
}
 

Pętla główna

W pętli głównej programu inicjalizujemy zmienną currentMillis i zapisujemy do niej czas pracy programu w milisekundach. Następnie w warunku if sprawdzamy czy od ostatniego wykonania programu minęło zdefiniowane wcześniej 1000 ms. Dalsza część pętli nie wykona się o ile warunek ten nie zostanie spełniony.

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval){

Po spełnieniu warunku if, zapisujemy aktualną wartość czasową w zmiennej previousMillis do kolejnych porównań. Tworzymy teraz łańcuch znaków dataString, w którym będziemy zapisywać dane w postaci tekstowej. Do pustego łańcucha dodajemy timestamp ze zmiennej previousMillis, a następnie średnik i spację.

previousMillis = currentMillis;

String dataString = "";

dataString += previousMillis;
dataString +="\; ";

W kolejnym kroku uruchamiamy pomiar temperatury i wpisujemy go do zmiennej tempC. Zmienna ta bez problemu daje się dodać do łańcucha znaków za pomocą operatora +=.

sensors.requestTemperatures();
float tempC = sensors.getTempCByIndex(0);

dataString += tempC;

Następnie inicjujemy kotwicę do otwarcia pliku jednocześnie otwierając sam plik. Jeśli plik zostanie prawidłowo otwarty, to w następnym kroku program zapisze do niego utworzoną linijkę danych i zamknie plik. Następnie ta sama linijka zostanie wypisana na port szeregowy.

File dataFile = SD.open("datalog.csv", FILE_WRITE);

if (dataFile) {
  dataFile.println(dataString);
  dataFile.close();

  Serial.println(dataString);
}

else {
  Serial.println("error opening datalog.csv");
}

Taki sposób obsługi pliku oznacza, że plik otwierany jest tylko przez krótką chwilę na czas zapisania danych. Można więc odłączyć urządzenie od zasilania prawie bez obaw o uszkodzenie pliku. Po odłączeniu zasilania program zaczyna bez problemu z powrotem zapisywać dane w utworzonym pliku (aczkolwiek funkcja millis liczy od początku). Ciekawostką jest też fakt, że po włączeniu portu szeregowego, funkcja millis również rozpoczyna zliczanie od początku.

Pełen kod programu

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

#define ONE_WIRE_BUS 2
const int chipSelect = 4;

unsigned long previousMillis = 0;
const long interval = 1000;

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup() {
  Serial.begin(9600);
  sensors.begin();

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    while (1);
  }

  Serial.println("card initialized.");

}

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval){
    previousMillis = currentMillis;
    
    String dataString = "";
    dataString += previousMillis;
    dataString +="\; ";

    sensors.requestTemperatures();
    float tempC = sensors.getTempCByIndex(0);
    dataString += tempC;

    File dataFile = SD.open("datalog.csv", FILE_WRITE);

    if (dataFile) {
      dataFile.println(dataString);
      dataFile.close();

      Serial.println(dataString);
    }

    else {
      Serial.println("error opening datalog.csv");
    }
  }
}

Pliki projektowe są dostępne w sekcji Do pobrania.

Wynik pracy programu

Jak wspominałem, program zapisuje dane w pliku CSV w dwóch kolumnach oddzielonych średnikiem. Pozwala to łatwo zaimportować plik do Excela oraz wielu innych programów, ze względu na prostotę i powszechność tego formatu. Oczywiście po implementacji można dokonać analizy danych w bardzo szerokim zakresie.

Rys. 4. Dane z pliku zaimportowane w Excelu

Wszystkie komponenty użyte w projekcie są dostępne w ofercie sklepu Kamami.pl