35- Arduino, Construyendo Grandes Proyectos


Escribir un programa.

En esta lecci贸n, hablaremos sobre c贸mo los makers arduino se diferencian de los programadores, por qu茅 no se entienden y no se gustan entre s铆, y tambi茅n c贸mo un 芦boceto禄 se diferencia de un programa.

Empecemos de lejos: Lenguajes de programaci贸n. El microcontrolador en s铆 est谩 programado en lenguaje ensamblador (assembler, asm), y cada MC tiene su propio conjunto limitado de comandos. Cada comando se ejecuta en un ciclo del procesador, es decir, al programar en ASM tenemos el m谩ximo control sobre la velocidad de ejecuci贸n del c贸digo, su tama帽o y en general los procesos que ocurren dentro del Mc: por eso se le llama -lenguaje de bajo nivel. Es muy dif铆cil programar en lenguaje ensamblador, porque el conjunto de instrucciones es muy peque帽o, estos comandos son elementales (no en t茅rminos de uso, sino en la esencia misma) y muchas cosas est谩ndar para otros lenguajes tienen que reinventarse literalmente y describirse manualmente: navegar manualmente por el espacio de direcciones, manipular la memoria y trabajar en conjunto con la documentaci贸n para un MC espec铆fico y conocimiento de su arquitectura. Hay mucho c贸digo y parece incomprensible por decirlo suavemente:

// c贸digo de ejemplo en asme
loop:
st X, %[set_hi]
sbrs %[LEDbuffer], 7
st X, %[set_lo]
lsl  %[LEDbuffer]
dec   %[counter]
rjmp .+0
rjmp .+0
rjmp .+0
brcc to_end
st  X,%[set_lo]
to_end:
brne  loop
: [counter] "=&d" (ctr)
: [LEDbuffer] "r" (*data_ptr++), "x" (ws2812_port_1)

En algunos casos, es pr谩cticamente imposible leer y comprender el c贸digo ASM desconocido sin los comentarios de los desarrolladores y el conocimiento de la documentaci贸n del microcontrolador. Por eso programar microcontroladores sol铆a ser una ocupaci贸n muy dif铆cil, y como 鈥渉obby鈥 era inaccesible para una persona sin una especialidad correspondiente.

Ahora, el compilador genera el c贸digo ensamblador a partir de lenguajes de nivel superior, que son f谩ciles y agradables de escribir: el mismo Arduino se puede programar en C, C ++, Basic, as铆 como un mont贸n de shells de programaci贸n visual. Usamos funciones y herramientas de lenguaje listas para usar y comprensibles, escribimos un par de decenas o cientos de l铆neas en ellas y el compilador las convierte en decenas de miles de l铆neas de c贸digo en ensamblador que funcionar谩n en el Mc.

En el IDE de Arduino, programamos en C++. 驴o C? Buena pregunta, porque de hecho escribimos en ambos: el lenguaje C ++ es el lenguaje C, en el que se agregaron clases, objetos, herencia y todo lo relacionado con ellos, C++ originalmente se llam贸 incluso 鈥 C con clases 鈥. C++ le permite utilizar todas las delicias de la programaci贸n orientada a objetos: programaci贸n orientada a objetos, gracias a la cual se puede organizar un programa enorme de manera muy agradable, legible, puede crear varios m贸dulos y bibliotecas independientes entre s铆, construir la estructura del proyecto de la manera m谩s eficiente, y tambi茅n proporciona su conveniente revisi贸n y edici贸n. Y ahora pasamos a la parte donde dir茅 鈥 recuerda todos los ejemplos de las lecciones anteriores y las fuentes de mis proyectos. No puedes hacer eso

En general, se pueden distinguir dos enfoques para el desarrollo de programas: procedimental y orientado a objetos (hay un tercero – estilo Arduino, cuando todas las funciones y variables se mezclan en un archivo). En t茅rminos generales, el enfoque procedimental es un mont贸n de funciones y variables, a veces esparcidas en archivos separados, y OOP encapsula todo el c贸digo en clases que interact煤an entre s铆 y con el programa principal. En la comunidad de Arduino, casi siempre se encuentra el primer tipo, porque es mucho m谩s f谩cil para un principiante trabajar de esta manera, los programas en general son peque帽os y sencillos: el programa principal en s铆 est谩 escrito de manera procedimental, pero usa 芦bibliotecas禄, que son b谩sicamente clases. Todos los ejemplos oficiales y no oficiales se construyen de esta manera, y el propio IDE de Arduino llama a sus documentos bocetos (del ingl茅s. sketch – sketch), porque Arduino est谩 concebido como una plataforma de entrenamiento y prototipado r谩pido, y no para el desarrollo de proyectos grandes y serios. En el IDE de Arduino, en lugar de un administrador de documentos normal, tenemos pesta帽as que no funcionan de la manera m谩s obvia y est谩n claramente dise帽adas para un enfoque de procedimiento. Echemos un vistazo m谩s de cerca a las caracter铆sticas de los enfoques.


OOP y enfoque procedimental.

Umbral de entrada

Comenzamos a pensar y escribir de manera procedimental desde las primeras lecciones, porque es simple. Trabajar con OOP requiere un conocimiento mucho m谩s profundo de C++, y el trabajo eficiente requiere el m谩ximo.

Tama帽o del c贸digo

Envolver todo en una fila en clases conduce a la generaci贸n de una gran cantidad de c贸digo como texto: solo tienes que escribir mucho m谩s, respectivamente, leerlo tambi茅n. Hacer esto en un IDE nativo, por cierto, es muy desagradable en comparaci贸n con los entornos de desarrollo m谩s antiguos, que tienen sustituci贸n autom谩tica de palabras y miembros de la clase, as铆 como un 芦谩rbol禄 del proyecto y sus archivos.

Peso y velocidad de ejecuci贸n de c贸digo

Los compiladores se actualizan y mejoran constantemente, incluso el IDE de Arduino integrado en avr-gcc: las nuevas versiones optimizan el c贸digo cada vez mejor, haci茅ndolo m谩s ligero y r谩pido. Sin embargo, una gran cantidad de clases anidadas y largas cadenas de datos se ejecutar谩n inevitablemente un poco m谩s lento y ocupar谩n m谩s espacio que un enfoque de procedimiento m谩s compacto.

Alcance y nombre

En un enfoque de procedimiento, debe monitorear constantemente el uso de nombres de variables y funciones y debe evitar repeticiones, si es posible, esconderlas dentro de documentos separados y tenerlas siempre en cuenta. Un peque帽o error, como usar una variable global en lugar de una local, puede generar errores que son dif铆ciles de rastrear. Al envolver partes del c贸digo en clases, obtenemos, hablando en t茅rminos generales, programas separados cuyos nombres de funciones y variables est谩n separados del resto del c贸digo y no les importa que otras clases o en el programa principal tengan lo mismo.

Proyectos mayores

Es mucho m谩s agradable escribir un programa grande con un mont贸n de subrutinas con OOP, y no solo escribir, sino tambi茅n mejorar en el futuro.

Peque帽os proyectos y prototipos

Escribir un peque帽o programa en un estilo de procedimiento es mucho m谩s f谩cil y m谩s r谩pido que en C++ cl谩sico, por lo que Arduino IDE es s贸lo un bloc de notas que guarda un dibujo en su extensi贸n .ino, en lugar de los archivos de biblioteca .h y .cpp: a nosotros no nos es necesario pensar en la estructura del archivo del proyecto, solo escribimos el c贸digo y listo. Para la creaci贸n r谩pida de prototipos y la depuraci贸n de peque帽os algoritmos, esto funciona muy bien, pero con un proyecto grande pueden comenzar problemas e inconvenientes.

Bibliotecas y compatibilidad

Si abre cualquier biblioteca para Arduino, entonces, con un 99,9% de probabilidad, ver谩 una clase all铆. El poder de OOP es que tomamos una biblioteca (o simplemente alg煤n tipo de clase), la agregamos a nuestro c贸digo y no obtenemos ning煤n conflicto de nombres ni intersecciones de c贸digo en general (en el 99% de los casos): la clase funciona por s铆 sola, es un programa separado. Si toma, por ejemplo, alg煤n fragmento de c贸digo 芦desnudo禄 y lo inserta en su mismo c贸digo desnudo, habr谩 una probabilidad bastante alta de intersecciones y conflictos, y lo peor es cuando el compilador no ve errores pero el programa no funciona adecuadamente.


驴Qu茅 hacer y c贸mo escribir a continuaci贸n?

Si es un principiante y reci茅n se est谩 sumergiendo en el idioma, es mejor escribir como est谩 escrito. Se considera una buena pr谩ctica tener un n煤mero m铆nimo, o mejor a煤n, la ausencia de variables globales para todo el programa: muy a menudo la global puede hacerse local est谩tica y ocultarse del resto del c贸digo sin perder funcionalidad. Las partes separadas e independientes del programa (botones de sondeo, procesamiento de valores, env铆o y an谩lisis de datos, etc.) pueden agruparse en una clase, colocarse en un archivo separado y, por lo tanto, el c贸digo del programa principal se puede reducir, aumentando su legibilidad y estructuraci贸n. Pero no olvides que un mismo problema se puede resolver de infinitas formas, y no todas son 贸ptimas.

Si el programa tiene los mismos 芦bloques禄 que requieren el mismo conjunto de variables, ser谩 mucho m谩s conveniente envolverlos en una clase. Adem谩s, con el tiempo, se acumular谩 un conjunto de estas minibibliotecas y ser谩 muy conveniente utilizarlas en trabajos futuros. En mis lecciones hay una lecci贸n sobre clases y sobre escritura de bibliotecas, pero solo se analiza una peque帽a y m谩s b谩sica parte de las capacidades de OOP. Para escribir herramientas poderosas y vers谩tiles, estudia cualquier lecci贸n de C++, despu茅s de estudiar mis lecciones estar谩s listo para ellas y todo estar谩 claro all铆.

Adem谩s, las clases en C++ tienen una caracter铆stica tan poderosa como la herencia: una clase puede heredar las capacidades de otra clase. Por ejemplo, casi todas las bibliotecas de visualizaci贸n, as铆 como Serial y Soft Serial, tienen un m茅todo 芦todo terreno禄. print(), que muestra variables de cualquier tipo, puede mostrar un n煤mero en diferentes representaciones, formatear la salida de n煤meros flotantes, etc. Un punto interesante aqu铆 es que todas estas capacidades se implementan en la clase Print est谩ndar, que se encuentra entre el resto de los archivos en el n煤cleo IDE de Arduino, y todas las dem谩s bibliotecas simplemente heredan todas las capacidades de salida. De hecho, solo se debe implementar la biblioteca de visualizaci贸n / serial.print(), y absolutamente todo el resto de la versatilidad de salida es proporcionada por la 芦cooperaci贸n禄 con la clase de impresi贸n. En este ciclo de lecciones, no analizaremos la herencia y otras herramientas de programaci贸n orientada a objetos, porque es poco probable que sea 煤til para usted y ya se entiende perfectamente en cualquier libro o en cualquier tutorial de C++ en Internet.

Envolverse en una clase no es una panacea: si un programa no implica la creaci贸n y el uso de m煤ltiples instancias de s铆 mismo, entonces simplemente se puede colocar en un archivo separado, como un conjunto de funciones y variables. En este caso, las variables globales deben hacerse est谩ticas para que no sean 芦visibles禄 desde otros archivos de programa.

驴Por qu茅 y c贸mo trabajar con 茅l? Al crear grandes proyectos (y en general), se debe adherir al concepto de 芦datos por separado, c贸digo por separado禄, es decir, no debe haber variables globales que est茅n en el alcance de todo el programa, al menos su n煤mero debe ser minimizado. Las variables globales se pueden ocultar dentro del archivo, que es proporcionado por la palabra clave st谩tic (las variables deben ser declaradas en un .c o .cpp archivo!), y usted puede compartir sus valores con el resto del c贸digo del programa y establecer un nuevo valor utilizando funciones separadas. Como ejemplo de un proyecto muy grande realizado con una estructura de archivos comprensible y sin usar OOP y clases, firmware GRBL. Adem谩s, la mayor铆a de las variables globales se pueden ocultar dentro de funciones donde se necesiten (es decir, si se necesitan s贸lo dentro de una funci贸n espec铆fica), nuevamente usando est谩tic. Hablamos de esto al principio, en la lecci贸n sobre tipos de datos.

Ejemplo 1

Echemos un vistazo a un ejemplo de c贸mo convertir un c贸digo 芦vinigrette禄 terrible con un mont贸n de variables globales y un desorden en el ciclo principal en un programa comprensible con rutinas independientes separadas.

En este ejemplo, tenemos dos botones conectados (en los pines D2 y D3) y un LED (usamos el pin D13). Escribamos un programa que har谩 parpadear un LED y sondear谩 de forma asincr贸nica los botones con amortiguaci贸n programada del rebote de contacto. Usando los botones, puede cambiar la frecuencia de parpadeo del LED. No tiene sentido comentar el c贸digo en detalle, porque hemos analizado todas las construcciones utilizadas m谩s de una vez en el ciclo de lecciones.

// pines
const byte btn1 = 2;
const byte btn2 = 3;
const byte led = 13;
//cambiar paso
const int step = 50;
//temporizadores de rebote de bot贸n
uint32_t btn1Tmr;
uint32_t btn2Tmr;
// banderas para sondeo de botones  
bool btn1Flag;
bool btn2Flag;
//  variable para el led
uint32_t ledTmr;
int ledPeriod = 1000; // per铆odo inicial 1 segundo
bool ledState = false;
void setup () {
  //configurar pines
  pinMode(btn1, INPUT_PULLUP);
  pinMode(btn2, INPUT_PULLUP);
  pinMode(led, OUTPUT);
}
void loop() {
  // temporizacion led
  if (millis() - ledTmr >= ledPeriod) {
    ledTmr = millis();
    ledState = !ledState;
    digitalWrite(led, ledState);
  }
  // encuesta para el primer bot贸n con 100ms antirrebote
  bool btn1State = digitalRead(btn1);
  if (!btn1State && !btn1Flag && millis() - btn1Tmr >= 100) {
    btn1Flag = true;    
    btn1Tmr = millis();
    ledPeriod += step;    // aumentar el per铆odo
  }
  if (btn1State && btn1Flag) {
    btn1Flag = false;
    btn1Tmr = millis();
  }
  // encuesta para el segundo bot贸n con 100ms antirrebote
  bool btn2State = digitalRead(btn2);
  if (!btn2State && !btn2Flag && millis() - btn2Tmr >= 100) {
    btn2Flag = true;    
    btn2Tmr = millis();
    ledPeriod -= step;    //  disminuir el per铆odo
  }
  if (btn2State && btn2Flag) {
    btn2Flag = false;
    btn2Tmr = millis();
  }
}

Agregar botones adicionales o funcionalidad LED adicional al programa generar谩 una gran confusi贸n y un aumento en la cantidad de c贸digo, ser谩 mucho m谩s dif铆cil de entender. Envuelva el procesamiento del bot贸n en una clase, porque ya tenemos dos botones id茅nticos, y en el futuro ser谩 posible agregar m谩s cosas al programa. Movamos inmediatamente la clase a un archivo separado y dise帽茅moslo como una biblioteca:

button.h

// clase de bot贸n
#pragma once
#include <Arduino.h>
#define _BTN_DEB_TIME 100  // iempo de espera anti-rebote
class Button {
  public:
    Button (byte pin) : _pin(pin) {
      pinMode(_pin, INPUT_PULLUP);
    }
    bool click() {
      bool btnState = digitalRead(_pin);
      if (!btnState && !_flag && millis() - _tmr >= _BTN_DEB_TIME) {
        _flag = true;
        _tmr = millis();
        return true;
      }
      if (btnState && _flag) {
        _flag = false;
        _tmr = millis();
      }
      return false;
    }
  private:
    const byte _pin;
    uint32_t _tmr;
    bool _flag;
};

El controlador de botones ahora funciona as铆: devuelve True si se presion贸 el bot贸n correspondiente. En el programa principal pondremos el m茅todo clic () en la condici贸n y cambiaremos el per铆odo del LED de acuerdo con 茅l.

Planeo que en este proyecto solo tendr茅 un LED parpadeante, y no lo envolver茅 en una clase: simplemente pondr茅 las funciones en un archivo (para un ejemplo de implementaci贸n del proyecto de esta manera).

led.h

// LED parpadeante
#pragma once
#include <Arduino.h>
void LEDinit(byte pin, int period);
void LEDblink();
void LEDadjust(int val);

led.cpp

#include "led.h"
//las variables est谩ticas ser谩n "visibles" solo en este archivo
static int _period;
static byte _pin;
static uint32_t _tmr;
static bool _flag;
void LEDinit(byte pin, int period) {
  _pin = pin;
  _period = period;
  pinMode(_pin, OUTPUT);
}
void LEDblink() {
  if (millis() - _tmr >= _period) {
    _tmr = millis();
    _flag = !_flag;
    digitalWrite(_pin, _flag);
  }
}
void LEDadjust(int val) {
  _period += val;
}

Tenga en cuenta que dentro de los archivos us茅 variables con los mismos nombres, pero hice que estas variables fueran est谩ticas u ocultas en una clase. Esto es muy bueno, porque nunca se cruzan entre s铆 y puede usar lo mismo para denotar variables con un significado similar. Esto nos da dos m贸dulos separados, dos subrutinas separadas con las que se puede interactuar desde el n煤cleo del programa. Cambi茅 el per铆odo de parpadeo del LED a trav茅s de la funci贸n LEDadjust (), que lleva la correcci贸n al valor actual. El valor 芦actual禄 inicial se establece durante la inicializaci贸n en LEDinit ().

Bueno, pongamos nuestras bibliotecas al lado del archivo principal del programa, incl煤yelos en el c贸digo y veamos c贸mo se ve nuestro proyecto ahora:

// cambiar paso
const int step = 50;
// biblioteca de LED
#include "led.h"
// biblioteca de button
#include "button.h"
Button btn1(2);
Button btn2(3);
void setup() {
  // specifica el pin y el per铆odo de inicio
  LEDinit(13, 1000);
}
void loop() {
  LEDblink();   // parpadea
  if (btn1.click()) LEDadjust(step);
  if (btn2.click()) LEDadjust(-step);
}

Bueno, 隆esto es otra cosa! Ahora las moscas est谩n separadas de las chuletas y podemos refinar cuidadosamente ambos m贸dulos independientemente uno del otro. Por cierto, 驴qu茅 pasa con el tama帽o del c贸digo? El primer ejemplo toma 1306 bytes de Flash y 26 bytes de RAM, y el nuevo … 1216 bytes de Flash y 29 bytes de RAM. La cantidad de c贸digo (el n煤mero de l铆neas) ha aumentado, 隆pero su peso ha disminuido en 100 bytes! El hecho es que tenemos dos instancias del bot贸n, que se sondean esencialmente de la misma manera. Hicimos la encuesta como un m茅todo de clase y el compilador no la duplic贸 para diferentes botones.

Desarrollemos un poco el programa y agreguemos otro bot贸n con el que podr谩s encender y apagar el LED, para lo cual agregaremos esta caracter铆stica a su biblioteca. Y agregue un m茅todo a la clase de bot贸n que devolver谩 True mientras mantiene presionado el bot贸n para cambiar la frecuencia manteniendo presionado el bot贸n en consecuencia.

Agregue una condici贸n complicada al controlador de botones que volver谩 True por temporizador, si el bot贸n se mantiene presionado, es decir, a煤n no se ha soltado despu茅s de presionar. As铆, ser谩 posible cambiar el valor una vez con un 鈥渃lic鈥, o mantenerlo presionado y cambiar谩 paso a paso, como en cualquier reloj chino.

button.h

//  clase button
#pragma once
#include <Arduino.h>
#define _BTN_DEB_TIME 100  // tiempo de espera anti-rebote
#define _BTN_HOLD_TIME 400  // 褌tiempo de espera pulso
class Button {
  public:
    Button (byte pin) : _pin(pin) {
      pinMode(_pin, INPUT_PULLUP);
    }
    bool click() {
      bool btnState = digitalRead(_pin);
      if (!btnState && !_flag && millis() - _tmr >= _BTN_DEB_TIME) {
        _flag = true;
        _tmr = millis();
        return true;
      }
      if (!btnState && _flag && millis() - _tmr >= _BTN_HOLD_TIME) {
        _tmr = millis();
        return true;
      }
      if (btnState && _flag) {
        _flag = false;
        _tmr = millis();
      }
      return false;
    }
  private:
    const byte _pin;
    uint32_t _tmr;
    bool _flag;
};

Puede implementar el LED de encendido / apagado como el 芦estado禄 de todo el m贸dulo del programa usando una bandera (el m茅todo principal blink () se ejecutar谩 en 茅l), agregarlo a las variables. Hay varias formas de tirar de la bandera:

  • Haga una funci贸n toggle () que simplemente invertir谩 la bandera
  • Haga que las funciones habiliten () y deshabiliten (), la bandera respectivamente
  • Leer el estado actual

Etc. Deteng谩monos en la configuraci贸n manual y la lectura del estado como una opci贸n universal.

led.h

//LED parpadeante
#pragma once
#include <Arduino.h>
void LEDinit(byte pin, int period);
void LEDblink();
void LEDadjust(int val);
void LEDsetState(bool state);
bool LEDgetState();

led.cpp

#include "led.h"
//as variables est谩ticas ser谩n "visibles" solo en este archivo
static int _period;
static byte _pin;
static uint32_t _tmr;
static bool _flag;
static bool _state = true;
void LEDinit(byte pin, int period) {
  _pin = pin;
  _period = period;
  pinMode(_pin, OUTPUT);
}
void LEDblink() {
  if (_state && millis() - _tmr >= _period) {
    _tmr = millis();
    _flag = !_flag;
    digitalWrite(_pin, _flag);
  }
}
void LEDadjust(int val) {
  _period += val;
}
void LEDsetState(bool state) {
  _state = state;
  if (_state) digitalWrite(_pin, 0);
}
bool LEDgetState() {
  return _state;
}

Agregue un bot贸n m谩s al programa principal en el pin D4 y cambie el estado del LED:

// cambiar paso
const int step = 50;
//biblioteca de LED
#include "led.h"
//  biblioteca de botones
#include "button.h"
Button btn1(2);
Button btn2(3);
Button btn3(4);
void setup() {
  // especifica el pin y el per铆odo de inicio
  LEDinit(13, 1000);
}
void loop() {
  LEDblink();   //  parpadea
  if (btn1.click()) LEDadjust(step);
  if (btn2.click()) LEDadjust(-step);
  if (btn3.click()) LEDsetState(!LEDgetState());
}

Ahora los botones en los pines 2 y 3 hacen clic para aumentar y disminuir la frecuencia de parpadeo del LED, cuando se mantiene presionado, la frecuencia cambia autom谩ticamente con el mismo paso y se configura en el per铆odo button.h, y al hacer clic en el bot贸n del pin 4, puede activar o desactivar el proceso de parpadeo.

As铆, ya hemos obtenido algunos desarrollos, que pueden insertarse completamente en otro proyecto, directamente en un archivo, y utilizarse. 脡sta es la belleza de la programaci贸n orientada a objetos y, en general, el concepto de separar datos del c贸digo y rechazar variables globales para todo el programa.

Ejemplo 2

A continuaci贸n, recordemos el ejemplo con la estaci贸n meteorol贸gica de la lecci贸n sobre c贸mo escribir un boceto en el curso b谩sico y vamos a tratar de 芦peinarlo禄 un poco: envolver todo en clases, dispersarlo en archivos separados y separar los datos entre s铆. Aunque todav铆a crearemos una clase para el temporizador milis, porque all铆 se usa en tres lugares, y con un mayor refinamiento, es posible que se necesiten m谩s temporizadores. El proyecto original tiene 10.078 bytes de Flash y 511 RAM.

Estaci贸n meteorol贸gica

// ajustes
#define ONE_WIRE_BUS 2  // 锌懈薪 ds18b20
// biblotecas
#include <RTClib.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// OBJETOS Y VARIABLES
// la direcci贸n puede ser 0x27 o 0x3f
LiquidCrystal_I2C lcd(0x3f, 16, 2); //// Configurar la pantalla
RTC_DS3231 rtc;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
uint32_t myTimer1, myTimer2, myTimer3;
boolean LEDflag = false;
float tempSum = 0, temp;
byte tempCounter;
void setup() {
  Serial.begin(9600); // para depurar
  pinMode(13, 1);
  // mostrar
  lcd.init();
  lcd.backlight();  //Enciende la luz de fondo de la pantalla
  // termometro
  sensors.begin();
  sensors.setWaitForConversion(false);  //obtener datos de forma asincr贸nica
  // reloj
  rtc.begin();
  // establecer el tiempo para compilar el tiempo
  if (rtc.lostPower()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
}
void loop() {
  //2 veces por segundo
  if (millis() - myTimer1 >= 500) {
    myTimer1 = millis(); //  reiniciar el temporizador
    toggleLED();
  }
  // 5 veces por segundo
  if (millis() - myTimer2 >= 200) {
    myTimer2 = millis(); //  reiniciar el temporizador
    getTemp();
  }
  // cada segundo
  if (millis() - myTimer3 >= 1000) {
    myTimer3 = millis(); //  reiniciar el temporizador
    redrawDisplay();
  }
}
void toggleLED() {
  digitalWrite(13, LEDflag); //  encendido apagado
  LEDflag = !LEDflag; //invertir flag
}
void getTemp() {
  // suma la temperatura en una variable com煤n
  tempSum += sensors.getTempCByIndex(0);
  sensors.requestTemperatures();
  //contador de medidas
  tempCounter++;
  if (tempCounter >= 5) { // si es 5
    tempCounter = 0;  // cero
    temp = tempSum / 5; // promedio
    tempSum = 0;  // cero
  }
}
void redrawDisplay() {
  // hora
  DateTime now = rtc.now(); //consigue tiempo
  lcd.setCursor(0, 0);      // cursor a 0,0
  lcd.print(now.hour());    // reloj
  lcd.print(':');
  // el primer cero es para decorar
  if (now.minute() < 10) lcd.print(0);
  lcd.print(now.minute());
  lcd.print(':');
  // el primer cero es para decorar
  if (now.second() < 10) lcd.print(0);
  lcd.print(now.second());
  // TEMP
  lcd.setCursor(11, 0);    // cursor en 11.0
  lcd.print("Temp:");
  lcd.setCursor(11, 1);    // cursor en 11,1
  lcd.print(temp);
  // fecha
  lcd.setCursor(0, 1);      //cursor en 0,1
  //el primer cero es para decorar
  if (now.day() < 10) lcd.print(0);
  lcd.print(now.day());
  lcd.print('.');
  //el primer cero es para decorar
  if (now.month() < 10) lcd.print(0);
  lcd.print(now.month());
  lcd.print('.');
  lcd.print(now.year());
}

Entonces, envolv铆 todo en clases e hice est谩ticos los objetos de las bibliotecas externas conectadas, para que no fueran 芦visibles禄 desde el programa principal y no pudieran mezclarse con nada. No toqu茅 la pantalla, toda la salida permaneci贸 como estaba, la pantalla en s铆 芦se enlaza禄 en el boceto principal.

Boceto principal

// BIBLIOTECAS
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // Configurar la pantalla
#include "led.h"
Led led(13);  // LED en el pin 13
#include "timer.h"
Timer ledTimer(500);      // Temporizador LED para 500ms
Timer tempTimer(800);     //  temporizador del sensor 800 ms
Timer displayTimer(1000); // muestra la salida 1 segundo
#include "realTime.h"
RealTime rtc;
#include "temperature.h"
Temperature dallas;
void setup() {
  Serial.begin(9600); //para depurar
  dallas.begin();
  rtc.begin();
  lcd.init();
  lcd.backlight();  // Enciende la luz de fondo de la pantalla
}
void loop() {
  if (ledTimer.ready()) led.toggle();
  if (tempTimer.ready()) dallas.filter();
  if (displayTimer.ready()) redrawDisplay();
}
void redrawDisplay() {
  // hora
  rtc.update(); // pilla tiempo
  lcd.setCursor(0, 0);      //cursor en 0,0
  lcd.print(rtc.hour());    // reloj
  lcd.print(':');
  
  //  el primer cero es para decorar
  if (rtc.minute() < 10) lcd.print(0);
  lcd.print(rtc.minute());
  lcd.print(':');
  
  // el primer cero es para decorar
  if (rtc.second() < 10) lcd.print(0);
  lcd.print(rtc.second());
  
  // TEMP
  lcd.setCursor(11, 0);    // cursor en 11.0
  lcd.print("Temp:");
  lcd.setCursor(11, 1);    // cursor en  11,1
  lcd.print(dallas.get());
  
  // fecha
  lcd.setCursor(0, 1);      // cursor en  0,1
  
  // el primer cero es para decorar
  if (rtc.day() < 10) lcd.print(0);
  lcd.print(rtc.day());
  lcd.print('.');
  
  //el primer cero es para decorar
  if (rtc.month() < 10) lcd.print(0);
  lcd.print(rtc.month());
  lcd.print('.');
  lcd.print(rtc.year());
}

led.h

#pragma once
#include <Arduino.h>
// Clase de LED
class Led {
  public:
    // crear un pin
    Led (byte pin) {
      _pin = pin;
      pinMode(_pin, OUTPUT);
    }
    // cambiar de estado
    void toggle() {
      _state = !_state;
      digitalWrite(_pin, _state);
    }
  private:
    byte _pin;
    bool _state;
};

timer.h

#pragma once
#include <Arduino.h>
//  clase de temporizador por milisegundos
class Timer {
  public:
    //  crear con un per铆odo espec铆fico
    Timer (int period) {
      _period = period;
    }
    //devuelve verdadero cuando se activa el per铆odo
    bool ready() {
      if (millis() - _tmr >= _period) {
        _tmr = millis();
        return true;
      }
      return false;
    }
  private:
    uint32_t _tmr;
    int _period;
};

realTime.h

#pragma once
#include <Arduino.h>
#include <RTClib.h>
#include <Wire.h>
class RealTime {
  public:
    void begin();
    void update();
    byte hour();
    byte minute();
    byte second();
    byte day();
    byte month();
    int year();
  private:
    byte _h, _m, _s;
    byte _day, _month;
    int _year;
};

realTime.cpp

#include "realTime.h"
static RTC_DS3231 rtc;
void RealTime::begin() {
  rtc.begin();
  // establecer el tiempo para compilar la hora
  if (rtc.lostPower()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
}
void RealTime::update() {
  DateTime now = rtc.now();
  _h = now.hour();
  _m = now.minute();
  _s = now.second();
  _day = now.day();
  _month = now.month();
  _year = now.year();
}
byte RealTime::hour() {
  return _h;
}
byte RealTime::minute() {
  return _m;
}
byte RealTime::second() {
  return _s;
}
byte RealTime::day() {
  return _day;
}
byte RealTime::month() {
  return _month;
}
int RealTime::year() {
  return _year;
}

temperature.h

#pragma once
// clase de sondeo y filtrado del sensor
#define ONE_WIRE_BUS 2  // pin ds18b20
#include <Arduino.h>
#include <OneWire.h>
#include <DallasTemperature.h>
class Temperature {
  public:
    void begin();
    void filter();
    float get();
  private:
    float tempSum = 0, temp = 0;
    byte tempCounter = 0;
};

temperature.cpp

#include "temperature.h"
static OneWire oneWire(ONE_WIRE_BUS);
static DallasTemperature sensors(&oneWire);
void Temperature::begin() {
  // term贸metro
  sensors.begin();
  sensors.setWaitForConversion(false);   // obtener datos de forma asincr贸nica
}
void Temperature::filter() {
  // suma la temperatura en una variable com煤n
  tempSum += sensors.getTempCByIndex(0);
  sensors.requestTemperatures();
  //  contador de medidas
  tempCounter++;
  if (tempCounter >= 5) {   // si m谩s de 5  
    tempCounter = 0;        // si 0
    temp = tempSum / 5;     // promedio
    tempSum = 0;            // si 0
  }
}
float Temperature::get() {
  return temp;
}

S铆, el c贸digo se ha hecho mucho m谩s grande, lo escribimos m谩s largo, ahora toma 10322 y 539 bytes de Flash y RAM, respectivamente (240 y 28 bytes m谩s), pero nuestro boceto se  ha convertido en un proyecto completo: puedes trabajar y completar cada 芦m贸dulo禄 por separado y no tener miedo de interferir con el c贸digo principal, puede reemplazar muy convenientemente el sensor o el reloj en tiempo real con cualquier otro, y as铆 sucesivamente. Ser谩 agradable y comprensible trabajar con dicho c贸digo incluso despu茅s de varios a帽os, cuando todo est茅 olvidado, y ser谩 m谩s f谩cil para otra persona entenderlo. Esta es la esencia de este enfoque para escribir programas grandes.


Deja un comentario