Salvando a Configuração do Programa na EEPROM do Arduino

Você já deve ter visto como salvar dados na eeprom do Arduino. E também deve ter notado que fica cada vez mais difícil organizar tudo quando a quantidade de dados cresce. Nesse post você vai aprender a facilitar (e muito) isso apenas utilizando struct (estrutura), um recurso da linguagem C.

As estruturas são uma forma de criar tipos de dados mais avançados. Com elas podemos armazenar conjunto de dados mais complexos, como os dados de cadastro de uma pessoa (nome, idade, cpf, etc), por exemplo .

Nesse post utilizaremos para algo muito comum em sistemas embarcados, que é o armazenamento das configurações dos dipositivos.

Um exemplo pode ser a configuração de um controlador de temperatura, que envolve: SETPOINT (temperatura a ser atingida), HISTERESE (variação da temperatura), OFFSET (valor a ser somado a leitura do sensor como calibração) e ISREF (modo de operação, ISREF=1 para refrigeração, e ISREF =0 para aquecimento).

A estrutura para armazenar esses dados ficariam como a seguinte:

struct sysConfig{
  float SETPOINT;
  float HISTERESE;
  float OFFSET;
  bool ISREF; 
};

Agora, acabamos de criar um novo tipo de dado. E podemos usá-lo em qualquer parte do nosso programa, basta declarar uma variável dessa forma:

struct sysConfig config1;

A variável config1 agora é do tipo sysConfig, e podemos utilizar as variáveis dentro dela assim:

config1.SETPOINT = 5.4;
config1.HISTERESE = 1.0;
config1.OFFSET = 0;
config1.ISREF = 1;

Podemos também declarar mais variáveis desse tipo, as quais serão totalmente intependentes, como config2.

struct sysConfig config1;
struct sysConfig config2;
config1.SETPOINT = 5.4; //aqui usamos SETPOINT de config1
config2.SETPOINT = 21.5; //aqui de config2

Função para Salvar Qualquer Coisa na EEPROM

Ok, e como salvamos a estrutura na EEPROM? Primeiro, a estrutura é mantida numa sequência de bytes na memória dinâmica do sistema. Sabendo o tamanho dessa sequência, podemos copiá-la completamente para a memória EEPROM. A função a seguir pode fazer isso. Aqui fazemos uso da função sizeof(...) para medir o tamanho do tipo sysConfig. Veja:

void eep_saveData(unsigned int address, void *data, unsigned int size){
  unsigned int i; 
  for(i = 0 ; i < size ; i++){
		EEPROM.write(address + i, *((byte *)data + i));
	}    
}

A função recebe o endereço, a partir do qual salvará os dados, os dados em sí e o tamanho dos dados. Veja que foi usado o tipo void* para data, isso porque a função não sabe que tipo de dado está recebendo. Dessa forma, tornamos a função bem genérica, agora ela pode salvar qualquer tipo de dado na memória EEPROM, não apenas a estrutura sysConfig.

O tipo void * é um ponteiro para uma variável de tipo desconhecido. Veja que no loop usamos (byte *)data para acessar a variável, aqui convertemos para um vetor de bytes. Veja:

Com data sendo um ponteiro para byte, usamos o * para acessar o valor contido no endereço indicado por este ponteiro.

O loop for percorre os bytes da variável data, cujo tamanho é especificado por size. Aí chamamos a função EEPROM.write para escrever na EEPROM. Como endereço, colocamos address + 1, pois o primeiro byte será salvo em address + 0, o segundo em address + 1 e assim por diante.

Função para Ler Qualquer Coisa da EEPROM

Agora, precisamos de uma função para ler o que a anterior escreveu na EEPROM. A ideia é similar, apenas tem o caminho inverso. Basicamente temos os mesmos parâmetros: address, data e size. A diferença está no loop, agora usamos EEPROM.read e atribuímos o retorno a variável data.

void eepw_loadData(unsigned int address, void *data, unsigned int size){
  unsigned int i;
  for(i = 0 ; i < size ; i++){
		*((byte * )data + i ) = (byte)EEPROM.read(address + i);
	}
}

Colocando Tudo Junto

Agora, vamos fazer um programa completo para testar essas funções. O sketch de teste ficará assim:

#include <EEPROM.h>

//Estrutura de dados.
struct sysConfig{
  float SETPOINT;
  float HISTERESE;
  float OFFSET;
  bool ISREF; 
};

//variável global para acessar as configurações de qualquer local.
struct sysConfig globalConfig;


/********************** Funções para salvar/ler EEPROM ************************/
void eep_saveData(unsigned int address, void *data, unsigned int size){
  unsigned int i; 
  for(i = 0 ; i < size ; i++){
    EEPROM.write(address + i, *((byte *)data + i));
  }    
}
void eep_loadData(unsigned int address, void *data, unsigned int size){
  unsigned int i;
  for(i = 0 ; i < size ; i++){
    *((byte * )data + i ) = (byte)EEPROM.read(address + i);
  }
}
/******************************************************************************/

/** Menu de Opcoes */
void printMenu(){
  Serial.println(F("***********************"));
  Serial.println(F("1. Carregar da EEPROM"));
  Serial.println(F("2. Salvar na EEPROM"));
  Serial.println(F("3. Altera SETPOINT"));
  Serial.println(F("4. Altera HISTERESE"));
  Serial.println(F("5. Altera OFFSET"));
  Serial.println(F("6. Altera ISREF"));
  Serial.println(F("7. Imprime Config"));
  Serial.println(F("-----------------------"));
  Serial.print(F("Opcao: "));
}

/** Aguarda até o usuário digitar. 
/* */ 
void aguardaReceber(){
  while(!Serial.available());
}

void printConfig(struct sysConfig* c){
  Serial.println(F("*** CONFIG PRINT ***"));
  
  
  Serial.print(F("SETPOINT: "));
  Serial.println(c->SETPOINT);

  Serial.print(F("HISTERESE: "));
  Serial.println(c->HISTERESE);

  Serial.print(F("OFFSET: "));
  Serial.println(c->OFFSET);

  Serial.print(F("ISREF: "));
  Serial.println(c->ISREF);
  
  Serial.println(F("********************"));  
}


void setup() {
  Serial.begin(9600);
  
  //Usuário terá 5 segundos para inserir dados
  Serial.setTimeout(100);
  delay(100);

  globalConfig.SETPOINT = 5.4;
  globalConfig.HISTERESE = 1.5;
  globalConfig.OFFSET = 0;
  globalConfig.ISREF = 1;

  /** Ao ligar, testamos se a EEPROM já está configurada.
   *  Se sim, carregamos os dados dela,
   *  senão, inicializamos com valores padrões/default.
   */
  if(EEPROM.read(0) == 0xA5){ //Se já escreveu na EEPROM

    Serial.println(F("Carregando da EEPROM."));
    eep_loadData(1, &globalConfig, sizeof(globalConfig));
    
  }else{ //Se nunca escreveu na EEPROM
    
    Serial.println(F("Inicializando EEPROM."));

    //Escreve valores padrão/default na EEPROM.
    eep_saveData(1, &globalConfig, sizeof(globalConfig));
    
    EEPROM.write(0, 0xA5); //Utiliza o endereço 0 para saber se a EEPROM
                           //foi incializada ou não.
  }

  //vamos medir o tamanho dessa estrutura.
  Serial.print(F("Tamanho: "));
  Serial.println(sizeof(globalConfig));
  
  Serial.println(F("Lembre de escolher a opcao Salvar... apos alterar algo."));

  printMenu();
}



void loop() {
  // Vamos processar cada opçao do Menu
  if(Serial.available()){
    switch(Serial.parseInt()){
      Serial.println("");
      Serial.println("");
      case 1: eep_loadData(1, &globalConfig, sizeof(globalConfig)); break;
      case 2: eep_saveData(1, &globalConfig, sizeof(globalConfig)); break;
      case 3: 
        Serial.print(F("Valor para SETPOINT: "));
      	aguardaReceber();
        globalConfig.SETPOINT = Serial.parseFloat();
        break;
      case 4: 
        Serial.print(F("Valor para HISTERESE: "));
      	aguardaReceber();
        globalConfig.HISTERESE = Serial.parseFloat();
        break;
      case 5: 
        Serial.print(F("Valor para OFFSET: "));
      	aguardaReceber();
        globalConfig.OFFSET = Serial.parseFloat();
        break;
      case 6: 
        Serial.print(F("Valor para ISREF: "));
      	aguardaReceber();
        globalConfig.ISREF = Serial.parseFloat();
        break;
      case 7: printConfig(&globalConfig); break;
    }
    printMenu();
  }
}

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *