Medidor de CO2 casero con Arduino DIY

Como seguramente ya sabrás a estas alturas, posiblemente gracias a las insistentes aportaciones del profesor José Luis Jiménez, a día de hoy parece que la principal vía de transmisión del virus es mediante aerosoles, y una forma sencilla y económica de estimar la probabilidad de contagio es midiendo la cantidad de CO2, y para ello puedes recurrir a un medidor comercial, o a uno casero, que es para lo que estamos aquí 🙂

Es algo que todavía no doy por concluido, pero el montaje más decente que he hecho por el momento es este:

Un medidor portátil de CO2 es poco mediático, pero seguramente sea más efectivo para evitar contagios que las desinfecciones masivas y desproporcionadas que se ven en televisión.

Índice

Ya que el artículo ha quedado un poquito largo de más, voy a intentar facilitar la navegación por el mismo con un índice para ir directamente a las distintas secciones.

0.- Introducción
1.- Tipos de montajes
___a) Indicación por LEDs
___b) Indicación en LCD 16×2
___c) Indicación en display gráfico OLED
2.- Lista de la compra
3.- Hora de programar
___a) Código para LEDs
___b) Código para LCD 16×2
___c) Código para display gráfico OLED


Introducción

Hoy os traigo un montaje muy útil en los tiempos de pandemia por coronavirus que corren, ya que cualquier ayuda es buena para evitar sufrir a la maldita covid-19: un medidor casero portátil de CO2.

Covid-19 | Por qué desinfectar superficies es «perder tiempo y dinero»

Lo que la ciencia ya sabe de la covid: ventilar es más eficaz que controlar aforos y limpiar superficies

Desinfección de calles por COVID19
Que políticamente esto vende mucho aunque valga de poco, está más que claro.

Ya hacía tiempo que tenía ganas de meterme en el mundo Arduino, y finalmente ha sido este proyecto de construir un medidor de CO2 el que me ha animado a hacerlo, interactuando con sensores y con displays.

Además como en la variedad está el gusto, os presento tres posibles montajes.

Sobra decir que esto es para quien le guste cacharrear, hacerse las cosas uno mismo, o como dicen los anglosajones, el DIY (do it yourself). Si prefieres tener algo con diseño chulo y que (en principio) funciona bien, puedes irte a medidores comerciales de calidad del aire que incluyan nivel de CO2 como este que os enlazo, que en principio tiene las 3 Bs tan buscadas 🙂

Si quieres algo de mayor calidad (te va a dar lecturas más precisas/reales), deberías buscar uno que tenga sensor NDIR, como por ejemplo este:

Si prefieres seguir con el DIY, sigue leyendo. Te indicaré qué componentes necesitas, cómo montarlos, y te daré el código fuente lleno de explicaciones. En el caso de que te surja alguna duda puedes publicar un comentario sobre este post, y te responderé lo antes que me sea posible. Verás que pongo lista de compra de Amazon y de AliExpress. Ya sabéis cómo va esto: si lo quieres «ya» (24-48h en la mayoría de los casos) pide en Amazon, si no te importa esperar 3-5 semanas AliExpress es más barato y raro es que paren estas cosas pequeñas y baratas en la aduana.


Tipos de montajes

Antes de meternos en terreno pantanoso (programación C/C++ para Arduino) conviene que elijas qué tipo de montaje vas a realizar. Echa un vistazo a las tres opciones que te propongo, y dependiendo de lo que ya tengas por casa y de lo que necesites comprar piensa qué prefieres. Si no tienes nada de nada, la diferencia de precio puede ser mínima, de hecho puede que incluso te salga más barata montar la versión «sofisticada» con LCD o incluso display gráfico, que la versión con LEDs porque se compran kits de los componentes, no unidades sueltas.

El sensor elegido es de tipo NDIR (Non Dispersive Infrared Detector), ya que aunque más caro, se supone que es el tipo que da las lecturas más fiables.

El conexionado entre componentes para cada uno de los montajes, como podrás ver más adelante, está especificado al principio de cada uno de los códigos fuente. Si necesitas alguna aclaración al respecto deja un comentario y te responderé lo antes que me sea posible.

1.- Indica el nivel de CO2 mediante 3 LEDs de colores

Esta fue mi primer montaje, sencillamente porque con los primeros componentes que recibí (placa Arduino y sensor) sumados a lo que tenía por casa, era lo único que podía montar.

2.- Indica el nivel de CO2 en un LCD de 16×2 caracteres

De esta opción no os puedo poner foto propia porque todavía no ha pasado por mis manos ninguna pantalla de este tipo, pero os dejo enlaces de otras personas para que veáis cómo quedaría.

Ejemplo de medidor CO2 con LCD de 16x2 caracteres
Fuente: javiergarciaescobedo.es

3.- Indica el nivel de CO2 en pantalla gráfica, incluyendo smileys más o menos felices

Al final este es el montaje que da más «juego». Te permite añadir añadir gráficos, mostrando unos u otros según el nivel de CO2. Además como verás en las fotos a continuación he intentado conseguir una presentación en cierto modo elegante, y sobre todo portátil.

Carcasa y circuitería de powerbank que pretendía utilizar. En un despiste un hilo tocó donde no debía y freí un regulador de tensión.

Carcasa y circuitería de powerbank que pretendía utilizar. En un despiste un hilo tocó donde no debía y freí un regulador de tensión.

Uno de los primeros montajes con display gráfico.

Uno de los primeros montajes con display gráfico.

Posteriormente agregué gráfico y cuenta atrás para el calentamiento del sensor.

Posteriormente agregué gráfico y cuenta atrás para el calentamiento del sensor.

Si el nivel de CO2 es inferior a 700 ppm, muestra smiley sonriente.

Si el nivel de CO2 es inferior a 700 ppm, muestra smiley sonriente.

Si el nivel de CO2 está entre 700 y 1000 ppm, muestra smiley no muy contento... Conviene comenzar a ventilar.

Si el nivel de CO2 está entre 700 y 1000 ppm, muestra smiley no muy contento… Conviene comenzar a ventilar.

Si el nivel de CO2 es igual o superior a 1000 ppm, muestra smiley triste. Urge ventilar, y a ser posible abandonar la habitación.

Si el nivel de CO2 es igual o superior a 1000 ppm, muestra smiley triste. Urge ventilar, y a ser posible abandonar la habitación.

A día de hoy contiene: clon de Arduino mini, sensor MH-Z19C, display OLED, batería de móvil viejo, conversor step-up DC-DC, cable USB por si la batería se acaba (estoy esperando circuito de carga para integrarlo)

A día de hoy contiene: clon de Arduino mini, sensor MH-Z19C, display OLED, batería de móvil viejo, conversor step-up DC-DC, cable USB por si la batería se acaba (estoy esperando circuito de carga para integrarlo)

Si bien es cierto que los niveles marcados son orientativos, y dependerán entre otras cosas en buena medida de la concentración exterior de CO2 en tu zona, de si las personas que estén en el mismo recinto que tú están usando o no mascarilla… creo que son unos valores bastante acertados y sensatos.

Este es un proyecto que todavía no doy por concluido, pues tengo en camino cajas nuevas y más espaciosas en las que intentaré dejar el montaje mejor hecho, con sistema de carga, baterías de mayor capacidad, y pequeños interruptores para encender y apagar de forma sencilla (ahora mismo hay uno minúsculo, de difícil acceso).


Lista de la compra

He intentado seleccionar los artículos que te permitan tener todo lo necesario sin que el gasto se dispare, y con envío Prime para Amazon. En cualquier caso los precios y el tipo de envío pueden ir variando con el tiempo. En el caso de que quieras realizar más de un montaje (para amigos o familiares, por ejemplo) puede que te interese mirar otros artículos similares donde el precio sea más económico al comprar varias unidades.

Para el montaje con LEDs (1), necesitarás comprar LEDs y resistencias. Para los otros no serán necesarios.
Para el montaje con LCD (2), el módulo LCD de 16×2.
Para el montaje con display gráfico (3), el módulo OLED.

  • Sensor MH-Z19 (Preferiblemente compra el MH-Z19B, ya que el C sólo acepta voltaje de entrada 4.9 a 5.1v, y si varía un poco las lecturas son muy imprecisas)
    AliExpress: Módulo MH-Z19B (20.55€, 7abr2021)
    Amazon:
  • Display OLED I2C 128×64 px. Sólo si vas a hacer el montaje de display gráfico OLED.
    AliExpress: Módulo OLED blanco 0.96″ (2.84€ 7abr2021)
    Amazon:

Hora de programar

Voy a intentar explicar todo de la forma más sencilla posible para que cualquiera pueda hacerlo, aunque no tenga experiencia previa en programación.

En el mundo Arduino están los llamados sketches, que es el nombre que se le da a los programas escritos en su lenguaje de programación (algo entre C y C++). Estos sketches se abren con el programa Arduino IDE y este se encargará de compilar el código (traducirlo a lenguaje máquina que entienda la plaquita Arduino) y de cargarlo mediante USB.

En C/C++ existe lo que se denomina librería, que no es otra cosa que una parte de código que, como se usa a menudo, se deja a mano para poder incorporarlo a tu código sin tener que volver a escribirlo entero (ni copiarlo-pegarlo). En el caso de nuestro medidor verás que llamaremos a varias librerías, por ejemplo las creadas para interactuar con el sensor de CO2, y con la pantalla elegida.

¿Cómo cargar el código a tu plaquita Arduino? Muy fácil, copia el código de más abajo que corresponda al montaje que has hecho, y abre Arduino IDE. Pégalo ahí (borra lo que aparece inicialmente), y revisa lo siguiente:

En Herramientas, Placa, asegúrate que tienes seleccionada la que te corresponda. Si cargas el programa en una errónea dejará de funcionar. Aunque seguramente puedas recuperarla, tendrás que perder tiempo en intentarlo hasta conseguirlo.

Asegúrate de seleccionar la placa Arduino correcta antes de subir el programa
Asegúrate de seleccionar la placa Arduino correcta antes de subir el programa

En Herramientas, Puerto, asegúrate de que te haya detectado la placa Arduino que tengas conectada por USB. Aparecerá en el listado.

Por último vamos a añadir las librerías que seguramente todavía no tengas instaladas. Entra en Programa, Incluir Librería, Administrar bibliotecas. Busca mhz19 e instala la de Jonathan Dempsey. Para el LCD 16×2 busca liquidcrystal_i2c e instala la de Frank de Brabander. Para el display gráfico OLED, busca adafruit_gfx e instala Adafruit GFX Library de Adafruit, y busca también ssd1306 e instala Adafruit SSD1306 de Adafruit. Con esto creo que estarían todas, ya que el resto van incorporadas de serie con Arduino IDE.

Una vez copiado el código y revisados los puntos comentados (placa, puerto y librerías), puedes seleccionar en el menú Programa, Subir. Automáticamente verificará, compilará, y subirá el programa a la placa.

Recomendación: no intentes leer el código directamente en esta web, cópialo y pégalo en Arduino IDE, o en un editor tipo Notepad++. Así verás que el código se colorea y es mucho más sencillo interpretarlo.

Empecemos…

1.- Código fuente para indicar el nivel de co2 mediante 3 leds de colores

/*
Código fuente para sacar los datos por el puerto serie del MH-Z19.
Representamos nivel de CO2 con 3 LEDs (verde, amarillo, rojo).
- Verde <700 ppm CO2
- Amarillo 700-999 ppm CO2
- Rojo 1000+ ppm CO2

Estoy usando una placa Arduino Pro Micro (no original) basada en Atmel Mega32U4
Un sensor NDIR de CO2 Winsen MH-Z19C (no es exactamente esta versión, pero coincide prácticamente todo: https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19c-pins-type-co2-manual-ver1_0.pdf)

Conexionado:
- VCC en Arduino a línea +
- GND en Arduino a línea -

- Línea + a VCC de MH-Z19C (rojo)
- Línea - a GND de MH-Z19C (negro)
- 14 en Arduino a Tx de MH-Z19C (verde)
- 15 en Arduino a Rx de MH-Z19C (azul)

- 16 en Arduino a R220 Ohm y en serie + del LED verde
- 10 en Arduino a R220 Ohm y en serie + del LED amarillo/rojo
- 9 en Arduino a R220 Ohm y en serie + del LED rojo/rojo2
- Negativo de los 3 LEDs a GND en Arduino

Uso la librería MH-Z19 de WifWaf, que permite interactuar vía puerto serie con el sensor. https://github.com/WifWaf/MH-Z19
En la librería MHZ19.h se pueden ver qué funciones están implementadas.
*/

#include <MHZ19.h>
#include <SoftwareSerial.h>

// Pin RX Arduino conectado al pin TX del MHZ19 (cable verde). En Arduino Micro sólo se pueden usar como RX los pines 8, 9, 10, 11, 14, 15, o 16 (https://www.arduino.cc/en/Reference/SoftwareSerial)
#define RX_PIN 14
// Pin TX Arduino conectado al pin RX del MHZ19 (cable azul)
#define TX_PIN 15

// Pines para los LEDs de colores
#define PIN_VERDE 16
#define PIN_AMARILLO 10
#define PIN_ROJO 9

// ppm CO2 de cambio para LEDs
int amarillo = 700;
int rojo = 1000;

// Objeto para sensor MHZ19
MHZ19 myMHZ19;
// Serial requerido por el MHZ19
SoftwareSerial mySerial(RX_PIN, TX_PIN);

// Contador para temporizar las mediciones
unsigned long timer = 0;

// Variables para controlar el precalentamiento del sensor MH-Z19C
unsigned int precalentamiento = 60000; // En ms. Deberían ser 60 segs de precalentamiento según datasheet.
int alternancialed = 500; // En ms. Alternancia de leds durante precalentamiento.
int tled1=precalentamiento/3; // Cuando encenderemos el 2º led durante el precalentamiento
int tled2=tled1*2; // Cuando encenderemos el 3º
bool caliente = false;




void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  myMHZ19.begin(mySerial);
  // Turn auto calibration ON (para OFF escribir autoCalibration(false))
  myMHZ19.autoCalibration();
  pinMode(PIN_VERDE, OUTPUT);
  pinMode(PIN_AMARILLO, OUTPUT);
  pinMode(PIN_ROJO, OUTPUT);
}

void ledverde() {
	digitalWrite(PIN_VERDE, HIGH); 
}

void ledamarillo() {
  	digitalWrite(PIN_AMARILLO, HIGH);
}
  
void ledrojo() {
  	digitalWrite(PIN_ROJO, HIGH);
}

void ledsoff() {
	digitalWrite(PIN_VERDE, LOW); 
  	digitalWrite(PIN_AMARILLO, LOW);
  	digitalWrite(PIN_ROJO, LOW);
}


void loop() {
  //Precalentamiento
  if (caliente == false) {
    ledverde();
  
    if(precalentamiento<tled2) {
      ledamarillo();
    }
    if(precalentamiento<tled1) {
      ledrojo();
    }
    delay(alternancialed);
    ledsoff();
    delay(alternancialed);
    precalentamiento=precalentamiento-alternancialed-alternancialed;

    if(precalentamiento<=0) { 
      caliente = true; 
    }

	// Mostramos el estado del calentamiento por salida serie
	Serial.print("Calentando: ");
	int calentamientorestante = precalentamiento/1000; //precal esta en ms, dividimos /1000 para ver segs
	Serial.print(calentamientorestante);
	Serial.println("s restantes");
  }


  if (caliente == true) {
        
      // Obtener la medición de CO2 actual como ppm
      int nivelCO2 = myMHZ19.getCO2();

      // Mostrar el nivel de CO2 en el monitor serie
      Serial.print("CO2 (ppm): ");                      
      Serial.print(nivelCO2);  

      // Obtener la temperatura actual en grados Celsius. No es demasiado útil, ya que el sensor se calienta y la temperatura siempre es superior a la real.
      // Es algo para uso interno del sensor sacado por ingeniería inversa, no un valor a tener en cuenta en la práctica.
      int8_t temperatura = myMHZ19.getTemperature();

      // Mostrar la temperatura en el monitor serie
      Serial.print(" Temp. (C): ");
      Serial.println(temperatura);  
      
      if (nivelCO2 > rojo) {
        ledrojo(); // Encendemos pin rojo al superar el nivel especificado en la variable rojo
      } else {
        digitalWrite(PIN_ROJO, LOW); // Apagamos pin rojo
      }

      if (nivelCO2 > amarillo) {
        ledamarillo();
      } else {
        digitalWrite(PIN_AMARILLO, LOW);
      }

      ledverde(); // El pin verde siempre tendrá que estar encendido una vez acabado el calibrado

	  delay(2000); // Esperamos 2 segundos entre lectura y lectura
    }
}

2.- Código fuente para indicar el nivel de co2 en LCD de 16×2 caracteres

Detalle importante: este código no he llegado a probarlo por mí mismo, ya que no tengo ningún LCD de este tipo. En cualquier caso lo he revisado y creo que debería funcionar sin problema.

/*
Código fuente para sacar los datos por el puerto serie del MH-Z19.
También se puede leer la temperatura, así como enviarle comandos.
En la librería MHZ19.h se pueden ver qué funciones están implementadas.

Conexionado:
- VCC en Arduino a línea +
- GND en Arduino a línea -

- Línea + a VCC de MH-Z19C (rojo)
- Línea - a GND de MH-Z19C (negro)
- 6 en Arduino a Rx de MH-Z19C (azul)
- 7 en Arduino a Tx de MH-Z19C (verde)

- GND en LCD a línea -
- VCC en LCD a línea +
- SDA en LCD a 4 en Arduino
- SCL en LCD a 5 en Arduino
*/

#include <LiquidCrystal _I2C.h>
#include <MHZ19.h>
#include <SoftwareSerial.h>

// Pin RX Arduino conectado al pin TX del MHZ19
#define RX_PIN 7
// Pin TX Arduino conectado al pin RX del MHZ19
#define TX_PIN 6

// Objeto para sensor MHZ19
MHZ19 myMHZ19;
// Serial requerido por el MHZ19
SoftwareSerial mySerial(RX_PIN, TX_PIN);

// Pantalla LCD de 16x2
LiquidCrystal_I2C lcd(0x27,16,2);  

// Contador para temporizar las mediciones
unsigned long timer = 0;

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  mySerial.begin(9600);
  myMHZ19.begin(mySerial);
  // Turn auto calibration OFF (ON: autoCalibration())
  myMHZ19.autoCalibration(false);
}

void loop() {
  // Tomar mediciones cada 2 segundos
  if (millis() - timer >= 2000) {
    
    // Obtener la medición de CO2 actual como ppm
    int nivelCO2 = myMHZ19.getCO2();

    // Mostrar el nivel de CO2 en el monitor serie
    Serial.print("CO2 (ppm): ");                      
    Serial.println(nivelCO2);  

    // Mostrar el nivel de CO2 en la pantalla LCD
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("CO2 (ppm): ");                          
    lcd.setCursor(11, 0);
    lcd.print(nivelCO2);

    // Obtener la temperatura actual en grados Celsius
    int8_t temperatura = myMHZ19.getTemperature();

    // Mostrar la temperatura en el monitor serie
    Serial.print("Temperatura (C): ");                  
    Serial.println(temperatura);  

    // Mostrar la temperatura en la pantalla LCD
    lcd.setCursor(0, 1);
    lcd.print("Temp. (C): ");                          
    lcd.setCursor(11, 1);
    lcd.print(temperatura);

    // Almacenar una referencia al momento actual para controlar tiempo transcurrido
    timer = millis();
  }
}

3.- Código fuente para indicar el nivel de co2 en display gráfico OLED

Este código es en el que más he trabajado, ya que considero es el que permite hacer el montaje más estético a la vez que funcional.

/*
Con este código hacemos lectura de CO2 y temperatura por serial, y mostramos resultado en display gráfico OLED.

La temperatura la voy a omitir porque no es precisa (de hecho no es una función ofrecida oficialmente por el fabricante).

Conexionado:
- Línea + a VCC de MH-Z19C (rojo)
- Línea - a GND de MH-Z19C (negro)
- 14 en Arduino a Tx de MH-Z19C (verde)
- 15 en Arduino a Rx de MH-Z19C (azul)

- VCC en Arduino, a VCC del LCD
- GND en Arduino, a GND del LCD
- Pin 2 en Arduino, a SDA en LCD
- Pin 3 en Arduino, a SCL en LCD

FAQ:
¿Qué voltaje de entrada acepta la placa Arduino WAVGAT Pro Micro ATmega32U4? De 5 a 12V DC a través del pin RAW.
¿y el MH-Z19C? 5.0V +-0.1V DC. Fuera de esos voltajes los resultados de las lecturas son inestables.
¿y el display OLED? 3.3 - 5V DC.

Si Arduino acepta distintos voltajes de entrada, ¿es capaz de sacar 5VDC estables para alimentar el resto de componentes? Sí, a través del pin VCC saca 5V DC estables, aunque no es capaz de dar mucha corriente (100mA si mal no recuerdo).

Lo ideal sería poder alimentar Arduino con una celda Li-Ion 18650 (3.7-4.2v) y que con eso se pudiesen alimentar todos los componentes.
Para ello habrá que usar un regulador step-up para subir el voltaje a 5VDC y alimentar todo desde ahí, o dos celdas en serie para llegar a 7.4-8.4VDC y alimentar Arduino por pin RAW y el resto de componentes por VCC.

Otra opción es reutilizar un viejo powerbank, cuyo circuito servirá para cargar la batería, y también para suministrar 5VDC constantes a la placa Arduino y el resto de componentes.

= Sobre los niveles de CO2 =
Exteriores 400ppm CO2, 700ppm empezar a ventilar, 1000ppm ventilación urgente:
https://www.heraldo.es/noticias/aragon/2020/11/29/a-la-caza-de-aerosoles-en-zaragoza-el-nivel-de-co2-del-tranvia-un-bar-o-una-clase-de-primaria-1407601.html
https://www.20minutos.es/noticia/4478013/0/medidores-co2-covid-19-para-que-sirven-donde-comprar/

Se podría establecer un umbral de 800-1000 ppm de concentración de CO2 que no debería superarse como garantía de una buena ventilación. (página 25):
https://www.mscbs.gob.es/profesionales/saludPublica/ccayes/alertasActual/nCov/documentos/COVID19_Aerosoles.pdf

Datasheet 1.2 del MH-Z19C-DZ:
https://www.berrybase.at/media/pdf/74/dc/c9/produkt_downloads-MH-Z19C-PC_Datenblattpdf.pdf



Librerías utilizadas
--------------------
MH-Z19 de WifWaf, que permite interactuar vía puerto serie con el sensor.
Adafruit GFX para trabajar con pantallas gráficas.
Adafruit SSD1306 para trabajar con pantallas gráficas que usen dicho controlador. 

Todas ellas instalables desde la propia aplicación Arduino.


ChangeLog
---------
1.0: Muestra gráfico de precalentamiento y versión, luego carita más o menos sonriente según el nivel (<700, 700-1000, >1000 ppm CO2).

Mejoras en mente
----------------
Añadir pulsador para forzar calibrado (tendrá que estar al menos 20 mins al aire libre en una zona con baja concentración de CO2 [400ppm]).
Se puede hacer por código, o directamente uniendo los cables marrón y negro del sensor durante al menos 7 segundos.

Hacer lectura del voltaje de la batería para calcular carga restante y mostrar en pantalla.

*/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <MHZ19.h>
#include <SoftwareSerial.h>


#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#if (SSD1306_LCDHEIGHT != 64)
#error("Altura incorrecta, por favor, modifica Adafruit_SSD1306.h");
#endif

// Pin RX Arduino conectado al pin TX del MHZ19 (cable verde). En Arduino Micro sólo se pueden usar como RX los pines 8, 9, 10, 11, 14, 15, o 16 (https://www.arduino.cc/en/Reference/SoftwareSerial)
#define RX_PIN 14
// Pin TX Arduino conectado al pin RX del MHZ19 (cable azul)
#define TX_PIN 15

// Objeto para sensor MHZ19
MHZ19 myMHZ19;
// Serial requerido por el MHZ19
SoftwareSerial mySerial(RX_PIN, TX_PIN);

unsigned long preheat = 60000; //En ms. 1 min según indica el vendedor, y según he comprobado por mí mismo.
char version[] = "1.0"; //Versión de este software, que se mostrará en la pantalla de calentamiento.
int ppm = 0;
int loopdelay = 5000;   //Retraso en milisegundos entre repetición del bucle de lectura de CO2.
                        //Dado que el tiempo de respuesta del sensor (T90) es de <120segs, el poner un valor muy bajo no tiene sentido.
int loopdelay2 = 0;     //Cuando este delay llegue a 0, haremos las lecturas de CO2.                  

void setup() { // Es una función void porque no devuelve ningún resultado, setup porque sólo se ejecuta al arrancar Arduino
  Serial.begin(9600);
  mySerial.begin(9600);
  myMHZ19.begin(mySerial);
  
  // Desactivamos el auto-calibrado del sensor (Para activar: autoCalibration())
  // Según datasheet del fabricante, se auto-calibra cada 24h de funcionamiento, suponiendo que
  // haya estado en un ambiente con 400ppm de CO2 durante un periodo "largo" (al menos 20 minutos, parece ser).
  // Si se va a encender en periodos de tiempo cortos, como por ejemplo usándolo como medidor portátil, 
  // conviene desactivar el autocalibrado y hacerlo de forma manual cada cierto tiempo.
  // Para calibrar de forma manual, poner el sensor en ambiente limpio (al aire libre) varios minutos,
  // posteriormente unir en el sensor el cable HD (marrón) con GND (negro) durante 7 segundos
  myMHZ19.autoCalibration(false);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // inicializamos con la dirección I2C 0x3C (para 128x64)
  display.setTextColor(WHITE);
}

// Imágenes convertidas a hex desde https://diyusthad.com/image2cpp

// 'bien', 32x32px
const unsigned char bien [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x70, 0x0e, 0x00, 0x01, 0x80, 0x01, 0x80, 
  0x03, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, 0x60, 0x0c, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x18, 
  0x10, 0x00, 0x00, 0x08, 0x20, 0x70, 0x0e, 0x04, 0x20, 0x98, 0x19, 0x04, 0x61, 0x08, 0x10, 0x86, 
  0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 
  0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x40, 0x02, 0x02, 0x40, 0x40, 0x02, 0x02, 
  0x40, 0x20, 0x04, 0x02, 0x20, 0x10, 0x08, 0x04, 0x20, 0x0f, 0xf0, 0x04, 0x10, 0x00, 0x00, 0x08, 
  0x10, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x40, 
  0x01, 0x80, 0x01, 0x80, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00
};

// 'regular', 32x32px
const unsigned char regular [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x70, 0x06, 0x00, 0x01, 0x80, 0x01, 0x80, 
  0x03, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x10, 0x18, 0x00, 0x00, 0x18, 
  0x10, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x04, 0x20, 0x70, 0x0e, 0x04, 0x60, 0xf0, 0x0f, 0x02, 
  0x40, 0xf0, 0x0f, 0x02, 0x40, 0x70, 0x0e, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 
  0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 
  0x40, 0x7f, 0xfe, 0x02, 0x20, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x08, 
  0x18, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x40, 
  0x01, 0x80, 0x01, 0x80, 0x00, 0x60, 0x06, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'mal', 32x32px
const unsigned char mal [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0xe0, 0x06, 0x00, 0x01, 0x80, 0x01, 0x80, 
  0x02, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x18, 
  0x30, 0x00, 0x00, 0x08, 0x21, 0x08, 0x31, 0x84, 0x61, 0x88, 0x11, 0x04, 0x40, 0xf0, 0x1e, 0x04, 
  0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0xc0, 0x00, 0x00, 0x02, 
  0xc0, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x07, 0xe0, 0x02, 0x40, 0x18, 0x38, 0x02, 
  0x40, 0x20, 0x0c, 0x04, 0x60, 0x40, 0x04, 0x04, 0x20, 0x40, 0x02, 0x0c, 0x30, 0x00, 0x00, 0x08, 
  0x10, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00, 0x60, 0x02, 0x00, 0x00, 0xc0, 
  0x01, 0x80, 0x03, 0x00, 0x00, 0x70, 0x0e, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00
};

// 'termometro', 64x55px
const unsigned char termometro [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x00, 0x00, 
  0x10, 0x00, 0x20, 0x00, 0x38, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x1e, 0x00, 0x00, 0x00, 
  0x10, 0x78, 0x20, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x10, 0xcc, 0x20, 0x00, 0x0f, 0xc0, 0x00, 0x00, 
  0x11, 0x86, 0x20, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x11, 0x07, 0xa0, 0x00, 0x07, 0xf8, 0x00, 0x00, 
  0x11, 0x06, 0x20, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x11, 0x06, 0x20, 0x00, 0x07, 0xfc, 0x00, 0x00, 
  0x11, 0x07, 0xa0, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x11, 0x06, 0x20, 0x00, 0x07, 0xbe, 0x00, 0x00, 
  0x11, 0x06, 0x20, 0x00, 0x07, 0x9f, 0x00, 0x00, 0x11, 0x07, 0xa0, 0x00, 0x07, 0x9f, 0x00, 0x00, 
  0x11, 0x06, 0x20, 0x00, 0x07, 0x8f, 0x00, 0x00, 0x11, 0x06, 0x20, 0x00, 0x0f, 0x8f, 0x00, 0x00, 
  0x11, 0x07, 0xa0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x11, 0x06, 0x20, 0x00, 0x0f, 0x0f, 0x00, 0x00, 
  0x11, 0x36, 0x20, 0x00, 0x1e, 0x0f, 0x00, 0x00, 0x11, 0x37, 0xa0, 0x00, 0x1e, 0x0f, 0x00, 0x00, 
  0x11, 0x36, 0x20, 0x00, 0x3e, 0x0e, 0x00, 0x70, 0x11, 0x36, 0x20, 0x00, 0x3c, 0x0e, 0x01, 0xe0, 
  0x11, 0x37, 0xa0, 0x00, 0x7c, 0x1e, 0x03, 0xe0, 0x11, 0x36, 0x20, 0x00, 0x78, 0x1c, 0x0f, 0xe0, 
  0x11, 0x36, 0x20, 0x00, 0xf0, 0x3c, 0x1f, 0xe0, 0x11, 0x37, 0xa0, 0x00, 0xf0, 0x78, 0x3f, 0xc0, 
  0x11, 0x36, 0x20, 0x01, 0xf0, 0x78, 0xff, 0xc0, 0x11, 0x36, 0x20, 0x01, 0xe0, 0xf1, 0xfb, 0xe0, 
  0x11, 0x37, 0xa0, 0x03, 0xe1, 0xf1, 0xf3, 0xe0, 0x11, 0x36, 0x20, 0x03, 0xc1, 0xe3, 0xe7, 0xe0, 
  0x11, 0x36, 0x20, 0x03, 0xc3, 0xc7, 0xe7, 0xe0, 0x11, 0x37, 0xa0, 0x07, 0xc7, 0x8f, 0xc7, 0xf0, 
  0x11, 0x36, 0x20, 0x07, 0x8f, 0x8f, 0x87, 0xf0, 0x11, 0x36, 0x20, 0x07, 0x8f, 0x1f, 0x87, 0xf0, 
  0x11, 0x37, 0xa0, 0x0f, 0x9f, 0x1f, 0x87, 0xf8, 0x11, 0x36, 0x20, 0x0f, 0x9e, 0x3f, 0x83, 0xf8, 
  0x11, 0x36, 0x20, 0x0f, 0xfe, 0x3f, 0x83, 0xf8, 0x13, 0x33, 0x20, 0x0f, 0xfc, 0x3f, 0xc3, 0xf8, 
  0x16, 0x79, 0xa0, 0x0f, 0xfc, 0x3f, 0xc7, 0xfc, 0x14, 0xfc, 0xa0, 0x0f, 0xfc, 0x3f, 0xe7, 0xfc, 
  0x15, 0xfc, 0xa0, 0x07, 0xfc, 0x1f, 0xe7, 0xfc, 0x15, 0xfe, 0xa0, 0x07, 0xfc, 0x1f, 0xef, 0xf8, 
  0x14, 0xfc, 0xa0, 0x07, 0xfe, 0x0f, 0xff, 0xf8, 0x14, 0xfc, 0xa0, 0x03, 0xfe, 0x03, 0xff, 0xf8, 
  0x16, 0x79, 0xa0, 0x01, 0xff, 0x00, 0xff, 0xf0, 0x13, 0x03, 0x20, 0x00, 0xff, 0xc1, 0xff, 0xf0, 
  0x11, 0xfe, 0x20, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x10, 0x78, 0x20, 0x00, 0x3f, 0xff, 0xff, 0xc0, 
  0x10, 0x00, 0x20, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x18, 0x00, 0x60, 0x00, 0x03, 0xff, 0xfc, 0x00, 
  0x0f, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};



void displayPreheating(int secLeft) { //Función para llamar desde el programa principal (loop)
  display.setTextSize(1);
  display.println("        PRECALENTANDO");
  display.println("           SENSOR CO2");
  display.setTextSize(1);
  display.println();
  display.setTextSize(2);
  display.print("      ");
  display.print(secLeft);
  display.println("s");
  display.setTextSize(1);
  display.println("");
  display.print("                v");
  display.println(version);
  display.drawBitmap(0, 0, termometro, 64, 55, WHITE);
  display.display();
}

void displayPPM(long ppm) {
  display.setTextSize(2);
  display.println("  CO2 PPM");
  display.setTextSize(1);
  display.println();
  if (ppm < 1000) {
    display.setTextSize(2);
    display.print("  ");  // Si solo son 3 dígitos, metemos espacios delante
  }
  display.setTextSize(3);
  display.println(ppm);

  // Draw the bitmap:
  // drawBitmap(x position, y position, bitmap data, bitmap width, bitmap height, color)
  if (ppm < 700) { display.drawBitmap(96, 20, bien, 32, 32, WHITE); }
  if (ppm >= 700 && ppm < 1000) { display.drawBitmap(96, 20, regular, 32, 32, WHITE); }
  if (ppm > 1000) { display.drawBitmap(96, 20, mal, 32, 32, WHITE); }
  
  display.setTextSize(1);
  display.println();
  display.println("   alexfernandez.es");
  
  display.display();
}

void loop() { // Programa principal, que se ejecuta en bucle mientras la placa Arduino esté encendida
    // Obtener la medición de CO2 actual como ppm
    int ppm = myMHZ19.getCO2();
  
    // Mostrar el nivel de CO2 en el monitor serie
    Serial.print("CO2 (ppm): ");                      
    Serial.println(ppm);  

  //Mostramos la medición también en el display
  display.clearDisplay();
  display.setCursor(0,0);
  if (preheat > 0) {
    displayPreheating((preheat/1000));
    preheat=preheat-1000;
  }
  else {
    if (loopdelay2 == 0) {
      displayPPM(ppm);
      loopdelay2 = loopdelay;

    // Obtener la temperatura actual en grados Celsius
    // No la muestro porque no es precisa, al no ser función oficialmente ofrecida por el fabricante
    /*
    int8_t temperatura = myMHZ19.getTemperature();

    // Mostrar la temperatura en el monitor serie
    Serial.print("Temperatura (C): ");                  
    Serial.println(temperatura);  

    // Mostrar la temperatura en la pantalla LCD
    display.setTextSize(2);
    display.print("Temperatura (C): ");
    display.print(temperatura);
  */
      
    } else {
      loopdelay2=loopdelay2-1000;
    }
  }
  
  
  delay(1000); 	
}

Con esto creo que he dejado bastante bien explicado todo lo necesario. Lo único que puede que no esté claro es cómo hacer montajes en protoboard o en placas de baquelita perforada. Si alguien necesita explicación y fotos que lo deje en un comentario y haré lo que pueda para añadir ese contenido.

Espero que os sea de utilidad.
Todos juntos conseguiremos deshacernos de este mal bicho.

Stay safe 😉


¿Te ha parecido interesante? ¡Compártelo! --> Facebooktwitterlinkedinmail

 

4 thoughts on “Medidor de CO2 casero con Arduino DIY

  1. Durante 5 minutos estaba convencido de hacerlo para tenerlo en el aula, luego ya vinieron los cuadros grises de código y todo se nubló en mi mente. Jajajaja
    ¡Pedazo artículo! ¡Eres un puto crack! ¡Qué poco explotado estás para lo que vales!

    • Tú que me miras con buenos ojos :*
      Si te animas a hacerlo pregunta las dudas que tengas y te echaré una mano sin problema 🙂

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.