{"id":1277,"date":"2021-11-14T16:39:00","date_gmt":"2021-11-14T19:39:00","guid":{"rendered":"https:\/\/www.leandroip.com\/?p=1277"},"modified":"2021-11-20T18:39:44","modified_gmt":"2021-11-20T21:39:44","slug":"salvar-a-configuracao-do-programa-eeprom-do-arduino","status":"publish","type":"post","link":"https:\/\/www.leandroip.com\/?p=1277","title":{"rendered":"Salvando a Configura\u00e7\u00e3o do Programa na EEPROM do Arduino"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Voc\u00ea j\u00e1 deve ter visto como salvar dados na eeprom do Arduino. E tamb\u00e9m deve ter notado que fica cada vez mais dif\u00edcil organizar tudo quando a quantidade de dados cresce. Nesse post voc\u00ea vai aprender a facilitar (e muito) isso apenas utilizando struct (estrutura), um recurso da linguagem C. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As estruturas s\u00e3o uma forma de criar tipos de dados mais avan\u00e7ados. Com elas podemos armazenar conjunto de dados mais complexos, como os dados de cadastro de uma pessoa (nome, idade, cpf, etc), por exemplo .<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nesse post utilizaremos para algo muito comum em sistemas embarcados, que \u00e9 o armazenamento das configura\u00e7\u00f5es dos dipositivos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Um exemplo pode ser a configura\u00e7\u00e3o de um controlador de temperatura, que envolve: SETPOINT (temperatura a ser atingida), HISTERESE (varia\u00e7\u00e3o da temperatura), OFFSET (valor a ser somado a leitura do sensor como calibra\u00e7\u00e3o) e ISREF (modo de opera\u00e7\u00e3o, ISREF=1 para refrigera\u00e7\u00e3o, e ISREF =0 para aquecimento).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A estrutura para armazenar esses dados ficariam como a seguinte:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>struct sysConfig{\n  float SETPOINT;\n  float HISTERESE;\n  float OFFSET;\n  bool ISREF; \n};<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Agora, acabamos de criar um novo tipo de dado. E podemos us\u00e1-lo em qualquer parte do nosso programa, basta declarar uma vari\u00e1vel dessa forma:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>struct sysConfig config1;<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">A vari\u00e1vel config1 agora \u00e9 do tipo sysConfig, e podemos utilizar as vari\u00e1veis dentro dela assim:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>config1.SETPOINT = 5.4;\nconfig1.HISTERESE = 1.0;\nconfig1.OFFSET = 0;\nconfig1.ISREF = 1;<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos tamb\u00e9m declarar mais vari\u00e1veis desse tipo, as quais ser\u00e3o  totalmente intependentes, como config2.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>struct sysConfig config1;\nstruct sysConfig config2;\nconfig1.SETPOINT = 5.4; \/\/aqui usamos SETPOINT de config1\nconfig2.SETPOINT = 21.5; \/\/aqui de config2<\/code><\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Fun\u00e7\u00e3o para Salvar Qualquer Coisa na EEPROM<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ok, e como salvamos a estrutura na EEPROM? Primeiro, a estrutura \u00e9 mantida numa sequ\u00eancia de bytes na mem\u00f3ria din\u00e2mica do sistema. Sabendo o tamanho dessa sequ\u00eancia, podemos copi\u00e1-la completamente para a mem\u00f3ria EEPROM. A fun\u00e7\u00e3o a seguir pode fazer isso. Aqui fazemos uso da fun\u00e7\u00e3o <code>sizeof(...)<\/code> para medir o tamanho do tipo sysConfig. Veja:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>void eep_saveData(unsigned int address, void *data, unsigned int size){\n  unsigned int i; \n  for(i = 0 ; i &lt; size ; i++){\n\t\tEEPROM.write(address + i, *((byte *)data + i));\n\t}    \n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">A fun\u00e7\u00e3o recebe o endere\u00e7o, a partir do qual salvar\u00e1 os dados, os dados em s\u00ed e o tamanho dos dados. Veja que foi usado o tipo <code>void* <\/code>para <code>data<\/code>, isso porque a fun\u00e7\u00e3o n\u00e3o sabe que tipo de dado est\u00e1 recebendo. Dessa forma, tornamos a fun\u00e7\u00e3o bem gen\u00e9rica, agora ela pode salvar qualquer tipo de dado na mem\u00f3ria EEPROM, n\u00e3o apenas a estrutura <code>sysConfig<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O tipo <code>void *<\/code> \u00e9 um ponteiro para uma vari\u00e1vel de tipo desconhecido. Veja que no loop usamos <code>(byte *)data<\/code> para acessar a vari\u00e1vel, aqui convertemos para um vetor de bytes. Veja:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.leandroip.com\/wp-content\/uploads\/2021\/11\/dataBytePointer.png\"><img loading=\"lazy\" decoding=\"async\" width=\"655\" height=\"168\" src=\"https:\/\/www.leandroip.com\/wp-content\/uploads\/2021\/11\/dataBytePointer.png\" alt=\"\" class=\"wp-image-1286\" srcset=\"https:\/\/www.leandroip.com\/wp-content\/uploads\/2021\/11\/dataBytePointer.png 655w, https:\/\/www.leandroip.com\/wp-content\/uploads\/2021\/11\/dataBytePointer-600x154.png 600w, https:\/\/www.leandroip.com\/wp-content\/uploads\/2021\/11\/dataBytePointer-300x77.png 300w, https:\/\/www.leandroip.com\/wp-content\/uploads\/2021\/11\/dataBytePointer-640x164.png 640w\" sizes=\"auto, (max-width: 655px) 100vw, 655px\" \/><\/a><\/figure><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Com <code>data <\/code>sendo um ponteiro para byte, usamos o <code>*<\/code> para acessar o valor contido no endere\u00e7o indicado por este ponteiro.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">O loop <code>for <\/code>percorre os bytes da vari\u00e1vel <code>data<\/code>, cujo tamanho \u00e9 especificado por <code>size<\/code>. A\u00ed chamamos a fun\u00e7\u00e3o <code>EEPROM.write<\/code> para escrever na EEPROM. Como endere\u00e7o, colocamos <code>address + 1<\/code>, pois o primeiro byte ser\u00e1 salvo em <code>address + 0<\/code>, o segundo em <code>address + 1<\/code> e assim por diante.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fun\u00e7\u00e3o para Ler Qualquer Coisa da EEPROM<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Agora, precisamos de uma fun\u00e7\u00e3o para ler o que a anterior escreveu na EEPROM. A ideia \u00e9 similar, apenas tem o caminho inverso. Basicamente temos os mesmos par\u00e2metros: <code>address<\/code>, <code>data <\/code>e <code>size<\/code>. A diferen\u00e7a est\u00e1 no loop, agora usamos <code>EEPROM.read<\/code> e atribu\u00edmos o retorno a vari\u00e1vel <code>data<\/code>.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>void eepw_loadData(unsigned int address, void *data, unsigned int size){\n  unsigned int i;\n  for(i = 0 ; i &lt; size ; i++){\n\t\t*((byte * )data + i ) = (byte)EEPROM.read(address + i);\n\t}\n}<\/code><\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Colocando Tudo Junto<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Agora, vamos fazer um programa completo para testar essas fun\u00e7\u00f5es. O sketch de teste ficar\u00e1 assim:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>#include &lt;EEPROM.h&gt;\n\n\/\/Estrutura de dados.\nstruct sysConfig{\n  float SETPOINT;\n  float HISTERESE;\n  float OFFSET;\n  bool ISREF; \n};\n\n\/\/vari\u00e1vel global para acessar as configura\u00e7\u00f5es de qualquer local.\nstruct sysConfig globalConfig;\n\n\n\/********************** Fun\u00e7\u00f5es para salvar\/ler EEPROM ************************\/\nvoid eep_saveData(unsigned int address, void *data, unsigned int size){\n  unsigned int i; \n  for(i = 0 ; i &lt; size ; i++){\n    EEPROM.write(address + i, *((byte *)data + i));\n  }    \n}\nvoid eep_loadData(unsigned int address, void *data, unsigned int size){\n  unsigned int i;\n  for(i = 0 ; i &lt; size ; i++){\n    *((byte * )data + i ) = (byte)EEPROM.read(address + i);\n  }\n}\n\/******************************************************************************\/\n\n\/** Menu de Opcoes *\/\nvoid printMenu(){\n  Serial.println(F(&quot;***********************&quot;));\n  Serial.println(F(&quot;1. Carregar da EEPROM&quot;));\n  Serial.println(F(&quot;2. Salvar na EEPROM&quot;));\n  Serial.println(F(&quot;3. Altera SETPOINT&quot;));\n  Serial.println(F(&quot;4. Altera HISTERESE&quot;));\n  Serial.println(F(&quot;5. Altera OFFSET&quot;));\n  Serial.println(F(&quot;6. Altera ISREF&quot;));\n  Serial.println(F(&quot;7. Imprime Config&quot;));\n  Serial.println(F(&quot;-----------------------&quot;));\n  Serial.print(F(&quot;Opcao: &quot;));\n}\n\n\/** Aguarda at\u00e9 o usu\u00e1rio digitar. \n\/* *\/ \nvoid aguardaReceber(){\n  while(!Serial.available());\n}\n\nvoid printConfig(struct sysConfig* c){\n  Serial.println(F(&quot;*** CONFIG PRINT ***&quot;));\n  \n  \n  Serial.print(F(&quot;SETPOINT: &quot;));\n  Serial.println(c-&gt;SETPOINT);\n\n  Serial.print(F(&quot;HISTERESE: &quot;));\n  Serial.println(c-&gt;HISTERESE);\n\n  Serial.print(F(&quot;OFFSET: &quot;));\n  Serial.println(c-&gt;OFFSET);\n\n  Serial.print(F(&quot;ISREF: &quot;));\n  Serial.println(c-&gt;ISREF);\n  \n  Serial.println(F(&quot;********************&quot;));  \n}\n\n\nvoid setup() {\n  Serial.begin(9600);\n  \n  \/\/Usu\u00e1rio ter\u00e1 5 segundos para inserir dados\n  Serial.setTimeout(100);\n  delay(100);\n\n  globalConfig.SETPOINT = 5.4;\n  globalConfig.HISTERESE = 1.5;\n  globalConfig.OFFSET = 0;\n  globalConfig.ISREF = 1;\n\n  \/** Ao ligar, testamos se a EEPROM j\u00e1 est\u00e1 configurada.\n   *  Se sim, carregamos os dados dela,\n   *  sen\u00e3o, inicializamos com valores padr\u00f5es\/default.\n   *\/\n  if(EEPROM.read(0) == 0xA5){ \/\/Se j\u00e1 escreveu na EEPROM\n\n    Serial.println(F(&quot;Carregando da EEPROM.&quot;));\n    eep_loadData(1, &globalConfig, sizeof(globalConfig));\n    \n  }else{ \/\/Se nunca escreveu na EEPROM\n    \n    Serial.println(F(&quot;Inicializando EEPROM.&quot;));\n\n    \/\/Escreve valores padr\u00e3o\/default na EEPROM.\n    eep_saveData(1, &globalConfig, sizeof(globalConfig));\n    \n    EEPROM.write(0, 0xA5); \/\/Utiliza o endere\u00e7o 0 para saber se a EEPROM\n                           \/\/foi incializada ou n\u00e3o.\n  }\n\n  \/\/vamos medir o tamanho dessa estrutura.\n  Serial.print(F(&quot;Tamanho: &quot;));\n  Serial.println(sizeof(globalConfig));\n  \n  Serial.println(F(&quot;Lembre de escolher a opcao Salvar... apos alterar algo.&quot;));\n\n  printMenu();\n}\n\n\n\nvoid loop() {\n  \/\/ Vamos processar cada op\u00e7ao do Menu\n  if(Serial.available()){\n    switch(Serial.parseInt()){\n      Serial.println(&quot;&quot;);\n      Serial.println(&quot;&quot;);\n      case 1: eep_loadData(1, &globalConfig, sizeof(globalConfig)); break;\n      case 2: eep_saveData(1, &globalConfig, sizeof(globalConfig)); break;\n      case 3: \n        Serial.print(F(&quot;Valor para SETPOINT: &quot;));\n      \taguardaReceber();\n        globalConfig.SETPOINT = Serial.parseFloat();\n        break;\n      case 4: \n        Serial.print(F(&quot;Valor para HISTERESE: &quot;));\n      \taguardaReceber();\n        globalConfig.HISTERESE = Serial.parseFloat();\n        break;\n      case 5: \n        Serial.print(F(&quot;Valor para OFFSET: &quot;));\n      \taguardaReceber();\n        globalConfig.OFFSET = Serial.parseFloat();\n        break;\n      case 6: \n        Serial.print(F(&quot;Valor para ISREF: &quot;));\n      \taguardaReceber();\n        globalConfig.ISREF = Serial.parseFloat();\n        break;\n      case 7: printConfig(&globalConfig); break;\n    }\n    printMenu();\n  }\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Voc\u00ea j\u00e1 deve ter visto como salvar dados na eeprom do Arduino. E tamb\u00e9m deve [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1297,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[35,36,37],"class_list":["post-1277","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-arduino","tag-eeprom","tag-struct","entry","grid"],"_links":{"self":[{"href":"https:\/\/www.leandroip.com\/index.php?rest_route=\/wp\/v2\/posts\/1277","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.leandroip.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.leandroip.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.leandroip.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.leandroip.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1277"}],"version-history":[{"count":16,"href":"https:\/\/www.leandroip.com\/index.php?rest_route=\/wp\/v2\/posts\/1277\/revisions"}],"predecessor-version":[{"id":1296,"href":"https:\/\/www.leandroip.com\/index.php?rest_route=\/wp\/v2\/posts\/1277\/revisions\/1296"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.leandroip.com\/index.php?rest_route=\/wp\/v2\/media\/1297"}],"wp:attachment":[{"href":"https:\/\/www.leandroip.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1277"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.leandroip.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1277"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.leandroip.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1277"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}