Registrador de dados do módulo do Cartão SD no Arduino

Tempo de leitura: 8 minutes

Este tutorial explorará a gama de recursos disponíveis para a biblioteca Arduino SD usando um exemplo do mundo real de registro de dados. A biblioteca SD permite aos usuários ler / gravar, listar arquivos, criar / remover arquivos e criar / excluir diretórios. Além disso, desenvolveremos um algoritmo que cria um novo arquivo toda vez que a placa Arduino é reiniciada, o que impedirá a substituição de registros de dados existentes. O arquivo de dados resultante estará em formato separado por vírgulas e conterá vários pontos de dados, incluindo um carimbo de data / hora em milissegundos desde o início do programa. Portanto, é importante registrar a hora de início do programa. Para tarefas de monitoramento de tempo muito precisas, um relógio de tempo real é recomendado, entretanto, para os experimentos realizados aqui, o tempo relativo é suficiente.

 

Lista de peças e ligação

Os componentes principais usados para este tutorial são a placa Arduino e o módulo de cartão SD, portanto, esses são os únicos componentes necessários. No entanto, um exemplo do mundo real será realizado com o sensor de pressão BME280, que emite pressão, temperatura e umidade. Ele também contém um algoritmo que converte pressão em altitude – portanto, também o registraremos. Uma bateria de polímero de lítio (LiPo) será usada para que o datalogger possa ser portátil, o que requer uma placa de ensaio e um regulador de 3,7 V a 5,0V também. Consequentemente, toda a lista de peças é fornecida abaixo para o datalogger portátil:

  1. SD Card Module – R$ 58,07 (3 pcs) [Amazon]

  2. Arduino Uno – R$ 35,94 [Amazon]

  3. 16GB SD Card and USB Adapter – R$ 28,28 [Amazon]

  4. BME280 Pressure Sensor – R$74,95 [Amazon]

  5. 650 mAh LiPo Battery – R$ 100,94 (2 pcs) [Amazon]

  6. 3.7V para 5V Conversor

  7. ProtoBoard – R$ 25,90 (1 pcs) [Amazon]

Começaremos conectando todo o conjunto de peças à placa Arduino. Isso nos permitirá deixar de lidar apenas com o cartão SD para salvar e armazenar dados no cartão SD do sensor de pressão BME280. O digrama de fiação é mostrado abaixo:


Ligação do Arduino à bateria, módulo de cartão SD e BME280 para registro de dados

A tabela de ligação correspondente também é mostrada abaixo.

Arduino PinoBME280 PinoArduino PinoSD Card Module
5VVIN5VVCC
GNDGNDGNDGND
A5SCKD11MOSI
A4SDID12MISO
D13SCK
D4CS

Na próxima seção, leremos e escreveremos no cartão SD.

 

Noções básicas da biblioteca Arduino SD

Este tutorial se concentra na criação de arquivos e salvamento de dados neles em um formato simples e pronto para usar. Assumindo que o módulo SD está conectado corretamente ao módulo Arduino aderente ao diagrama acima, podemos começar lendo e gravando arquivos no cartão SD. O cartão micro SD deve ser formatado usando o sistema de arquivos FAT16 de acordo com as sugestões no site do Arduino (aqui). Abaixo está uma rotina simples que grava e lê dados no cartão SD, garantindo que a ligação e a formatação foram feitas corretamente:

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

File testfile;
String fileName = "test.csv";
volatile int int_iter = 0;

void setup() {
  Serial.begin(9600); // inicia a porta serial
  // aguarde o módulo SD iniciar
  if (!SD.begin(4)) {
    Serial.println("Nenhum módulo SD detectado");
    while (1);
  }
  // veja se o arquivo de teste existe, exclua-o se existir
  // então imprime cabeçalhos e começa um novo  
  if (SD.exists(fileName)){
    Serial.println(fileName+" existe, excluindo e começando de novo");
    SD.remove(fileName);
  }
  testfile = SD.open(fileName, FILE_WRITE);
  if (testfile) {
    // salvar cabeçalhos em arquivo
    testfile.println("Timestamp,Data");
    testfile.close();
  } else {
    // se o arquivo não abriu, imprime um erro:
    Serial.println("erro ao abrir arquivo");
  }
}

void loop() {
  // salve o novo inteiro a cada loop e aguarde 1s
  testfile = SD.open(fileName, FILE_WRITE);
  if (testfile) {
    // salve um número diferente a cada loop
    testfile.println(String(millis())+","+String(int_iter));
    testfile.close();
    Serial.println("Salvando "+String(int_iter));
  } else {
    Serial.println("erro ao abrir arquivo");
  }
  int_iter+=1;
  delay(1000);
}

O código acima procura um arquivo chamado “test.csv” e o apaga se já existe e o cria se não existir. Em seguida, o programa salva cabeçalhos chamados “Timestamp” e “Data”. Ele abre e fecha o arquivo “test.csv” de cada loop e salva os milissegundos desde que o programa foi iniciado e um número inteiro iterativo. Se, posteriormente, executarmos o script integrado do Arduino SD chamado “DumpFile” – que despeja o conteúdo de um cartão SD, podemos ver como o script de teste acima imprime seus dados no cartão SD:

Impressão do Arduino SD para script de teste

 

Também podemos executar o script integrado do Arduino denominado “listfiles” para garantir ainda mais que nosso script esteja funcionando corretamente. No início da impressão dos listfiles, o arquivo “test.csv” deve ser impresso com seu tamanho aproximado. Todas essas verificações garantem que nosso programa está funcionando corretamente, junto com nosso módulo de cartão SD e cartão micro SD. Além disso, se o usuário deseja verificar com segurança se os dados estão sendo salvos, ele sempre pode ejetar o cartão micro SD e lê-lo em um computador através da porta USB. O Excel ou um programa semelhante deve ler facilmente os dados .csv.

Idealmente, não queremos excluir arquivos e preferimos iterar sobre os arquivos existentes para criar uma série de arquivos que façam sentido cronologicamente. Neste tutorial, irei trabalhar apenas com o diretório raiz e criar arquivos com base em uma convenção de nomenclatura específica. Usando esta convenção, seremos capazes de criar até 1000 arquivos exclusivos executando o mesmo programa sem sobrescrever. Eu criei um script simplificado baseado na rotina SD “listfiles” que irá listar todos os arquivos no diretório raiz do cartão SD. Essa rotina está impressa abaixo.

//Printing files in the root directory of an SD card
#include <SPI.h>
#include <SD.h>
File root;
void setup() {
  Serial.begin(9600);
  if (!SD.begin(4)) {
    Serial.println("Inicialização falhou!");
    while (1);
  }
  root = SD.open("/");
  while(true){
    File entry = root.openNextFile();
    if (!entry){
      break;
    }
    Serial.println(entry.name());
  }
}
void loop() {
  // nothing happens after setup finishes.
}

Usando esta rotina, podemos verificar os arquivos no diretório e garantir que nossas rotinas estão funcionando corretamente. Queremos garantir a iteração de arquivos específicos chamados “DATAxxx”, em que xxx é um número de 0 a 999. Por exemplo, podemos nomear um arquivo “DATA000” na primeira execução de um programa. Na próxima vez que o programa for iniciado, a rotina criará automaticamente um arquivo chamado “DATA001” e o próximo será “DATA002” e assim por diante. Também é importante observar que nenhum outro arquivo deve ter três números na posição 4-7 na pasta raiz. Enquanto isso for verdade, não deve haver problemas com o procedimento de nomenclatura nas rotinas a seguir. Em caso de dúvida, execute o programa listar arquivos acima e verifique se os arquivos estão sendo criados na ordem incremental adequada. Isso será importante posteriormente neste tutorial.

 

Interface com o sensor de pressão BME280

O BME280 é conectado no modo I2C de forma que pressão, temperatura e umidade possam ser lidas usando a biblioteca “Wire” no Arduino. A biblioteca BME280 pode ser baixada do site da Adafruit. Estaremos registrando a temperatura, umidade, pressão e altitude calculada. Para começar, incluí uma demonstração simples do BME280 que imprime as variáveis mencionadas acima como valores separados por vírgula na porta serial. O código é mostrado abaixo.

// Impressão simples das variáveis do sensor BME280 em formato csv
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme; // I2C

void setup() {
    Serial.begin(9600);
    if (!bme.begin()) {
        Serial.println("Não foi possível encontrar um sensor BME280 válido, verifique a ligação!");
        while (1);
    }
}
void loop() {
  String vars = "";
  vars += String(bme.readTemperature());
  vars += ",";
  vars += String(bme.readHumidity());
  vars += ",";
  vars += String(bme.readPressure());
  vars += ",";
  vars += String(bme.readAltitude(SEALEVELPRESSURE_HPA));
  Serial.println(vars);
}

O BME280 usa a fórmula barométrica para aproximar a altitude em função da variação da pressão na atmosfera. Para medições altamente precisas, a variável “SEALEVELPRESSURE_HPA” deve ser atualizada usando um valor calibrado de uma estação meteorológica próxima, no entanto, para os propósitos simples deste tutorial, isso não é necessário. Ainda podemos aproximar variações rápidas de altitude usando a pressão padrão ao nível do mar (1013,25 hPa).

Salvando dados reais no cartão SD

Agora que temos uma ideia do que esperar do módulo SD e do sensor de pressão BME280, podemos salvar os dados no cartão SD e verificar os resultados. O código de proteção SD BME280 completo é mostrado abaixo, que é essencialmente uma implementação do código BME280 acima e do método de proteção SD, junto com a convecção de nomenclatura discutida acima que impede a substituição. O código completo é fornecido abaixo e na página GitHub do projeto.

#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

File main_folder; // inicializa a pasta para salvar
File dataFile; // inicializar o arquivo sd
const int chipSelect = 4; // Pino CS no módulo de cartão SD
int prev_file_indx = 0; // usado para nomear arquivos
String fileName = "000";

// medições feitas em r_0 (nível do mar)
const float station_elev = 11.0; // elevação da estação (KNYC usado aqui)
const float p_0 = 1017.3*100.0;

Adafruit_BME280 bme; // inicia sensor BME280

void setup() {
    // Serial.begin(9600); // use a porta serial se a impressão for desejada
    // verifique se o BME280 está funcionando
    bool status;
    status = bme.begin();  
    if (!status) {
        while (1);
    }
    // verifique se o cartão SD está funcionando
    if (!SD.begin(chipSelect)) {
      return;
    }
    main_folder = SD.open("/");
    fileName = sd_saver(main_folder);
}

void loop() {  
  // seção salvar SD
  String data_array = "";
  data_array += String(millis()); // economiza milissegundos desde o início do programa
  data_array += ",";
  data_array += String(bme.readTemperature()); // salvando temperatura
  data_array += ",";
  data_array += String(bme.readHumidity()); // salvando humidade
  data_array += ",";
  data_array += String(bme.readPressure()); // salvar pressão em Pa
  data_array += ",";
  data_array += String(bme.readAltitude(p_0/100.0)+station_elev); // salvar altitude da rotina Adafruit

  // Gravando e salvando o cartão SD
  dataFile = SD.open("DATA"+fileName+".csv",FILE_WRITE);
  // se o arquivo for válido, escreva nele:
  if(dataFile){
    dataFile.println(data_array);
    dataFile.close();
  }
  delay(100);
}

String sd_saver(File dir){
  while(true){
    // itera todos os arquivos para garantir que não haja sobregravações
    File entry = dir.openNextFile();
    if (!entry){
      break;
    }
    // rotina de nomenclatura
    String entry_name = entry.name();
    if ((entry_name.substring(4,7)).toInt()>=prev_file_indx){
      prev_file_indx = (entry_name.substring(4,7)).toInt()+1;
      if (prev_file_indx>=100){
        fileName = String(prev_file_indx);
      } else if (prev_file_indx>=10){
        fileName = "0"+String(prev_file_indx);
      } else{
        fileName = "00"+String(prev_file_indx);
      }
    }
    entry.close();
  }
  return fileName;
}
Configuração de registro de dados do Arduino com bateria LiPo e módulo SD

Ao executar o código acima, os dados BME280 de temperatura, umidade, pressão e altitude devem ser enviados da seguinte maneira para um arquivo .csv:

Um gráfico de amostra da aproximação da altitude é mostrado no gráfico abaixo. O sensor foi levantado 2,15 me depois retornado ao local original, tudo isso pode ser visto na figura (com algumas ressalvas).

Podemos ver vários artefatos do sensor BME280. Em 10 Hz (atraso de 100 ms), o ruído é de cerca de 25 cm no cálculo da altitude. Além disso, conforme o tempo passa, as medições de pressão começam a flutuar. Essa deriva é resultado das mudanças naturais da pressão atmosférica. Portanto, o BME280 tem melhor desempenho durante mudanças rápidas de pressão, como elevadores em movimento, subir escadas ou drones voando verticalmente. Uma maneira de limpar as medições é fazer uma convolução das medições ao longo do tempo – isso estabilizará o sinal resultante e permitirá uma melhor aproximação das mudanças de altitude.

 

Conclusão

Neste tutorial, um sistema simples de registro de dados usando o Arduino foi explorado usando um novo método de salvamento de arquivos e dados do mundo real. Usando os métodos descritos acima, o sensor de pressão BME280 emitiu temperatura, umidade, pressão e altitude, todas as quais foram salvas em um arquivo exclusivo em um cartão SD. O método do cartão SD é útil para situações em que os usuários desejam salvar dados por um longo período ou usar uma solução portátil para fazer medições. Depois que os dados são salvos no cartão SD, o usuário pode pegar os dados e processá-los em qualquer plataforma, analisando os dados usando a formatação simples separada por vírgulas. Isso permite uma variedade de aplicações em clima, drones, posicionamento interno e outros campos onde a aquisição de dados portátil ou de longo prazo é importante.