Estación Multi-Sensor MKR1000: Rayos, Gas, UV, Temp, Humedad y Presión
Componentes y materiales
| Componente | Cantidad |
|---|---|
| Arduino MKR1000 | 1 |
| Sensor de rayos MOD-1016 (AS3935) | 1 |
| Sensor de gas MQ-9 (DFRobot) | 1 |
| Sensor humedad/temperatura HTU21D-F (Adafruit) | 1 |
| Sensor presión/altitud/temperatura BMP180 (Adafruit) | 1 |
| Sensor UV ML8511 (SparkFun) | 1 |
| RTC ChronoDot v2.1 | 1 |
| Shield meteorológico Photon (SparkFun) | 1 |
| NeoPixels (Adafruit) | 1 set |
| Protoboard | 1 |
| Cables jumper | 1 set |
Descripción del proyecto
Me encanta observar el clima y quería capturarlo y graficarlo. Las estaciones caseras muestran datos "ahora" y totales hora/día. Quería más, usando Arduino. Todos empiezan con temp/humedad, a veces presión barométrica, pero yo quería ¡más! Velocidad viento y lluvia necesitan entradas hardware. Al dominar I2C, encontré el detector de rayos AS3935. Pero... ¡no tenía suficientes interrupciones hardware en Arduino básico! SparkFun tiene shield para Photon, pero sigue limitado. Tenía que elegir y renunciar a sensores. :-(
Luego vi el MKR1000 y su MEJOR característica: ¡8 interrupciones hardware! ¡Ahora puedo tenerlo todo!
Elegir sus sensores
Los sensores vienen en tres tipos básicos:
- Analógicos: luz (IR, UV), dirección viento, gases, celdas de carga...
- I2C: temp, humedad, presión barométrica, acelerómetro, giroscopio...
- Interrupciones: pluviómetros, anemómetros, rayos, temporizadores... (Serial, PWM, relojes, servos usan interrupciones de tiempo)
El MKR1000 tiene E/S de sobra para todos.
¡OJO AL VOLTAJE DE E/S EN I2C E INTERRUPCIONES! MKR1000, Photon y muchos Arduino usan 3.3V, no 5V. Si su CPU y sensores usan voltajes distintos, necesitará level-shifters.
Todos mis sensores son comunes (Adafruit, SparkFun, Element-14), cuestan 5-10 USD. Sensores de gas: 10-20 USD. Detector de rayos (AS3935): elegí Embedded Adventures MOD-1016 ($26 USD, incluye antena). También compré SparkFun Photon Weather Shield y sus "weather meters" (anemómetro y veleta). Quizás añada sensor humedad suelo cuando decida montaje final. Algunos sensores no toleran cables largos.
Asignar sensores a pines
Una vez elegidos los sensores, se sabe qué entradas hacen falta. El siguiente paso: decidir qué sensor va en qué pin. Empiezo en papel y lo añado como comentario en bloque en el código.
Mis asignaciones para MKR1000:
- A0 (reservado para DAC futuro)
- A1 Sensor UV ML8511
- A2 Sensor luz visible TEMT6000
- A3 Sensor temp local TMP36
- A4 Sensor gas MQ-9
- A5 Sensor humedad suelo
- A6 Sensor velocidad viento?
- 0 (INT HW) Pulsador
- 1 (INT HW) Detector rayos AS3935
- 2 (INT HW) Anemómetro (interrupción por rotación)
- 3 (INT HW) Pluviómetro...
- 4, 5 Libres
- 6 (compartido con LED onboard) Buzzer piezo
- 7 Salida NeoPixel
- 11 I2C SDA
- 12 I2C SCL
Primeros pasos
Para principiantes: empiece con cada sensor por separado, cargue el ejemplo, instale librerías, cambie el pin al asignado, guarde copia en su carpeta del proyecto.
Cuando el test funcione y vea lecturas en Monitor Serie, añada el siguiente. Al terminar todos, estarán cableados y listo para integrar en el sketch grande.
Incluí RTC I2C (ChronoDot v2.1) para log en tarjeta SD. Aunque MKR1000 tiene RTC propio y batería, no logré que mantuviera la hora solo con batería, así que mantengo el ChronoDot.
En la foto de la protoboard vea los jumpers naranja para alimentación 3.3V a cada sensor; así los habilito/deshabilito fácilmente desenchufando.
Nota sobre librerías
Algunas librerías instalables NO funcionan perfecto con MKR1000 y/o Arduino IDE 1.6.7, y requieren ajustes. Ejemplo: macros ATOMIC_* en vieja librería AVR libc (eliminadas en IDE 1.6.5). Hay un hilo en foro Arduino.cc con problemas y soluciones. Hacer esos cambios es para nivel intermedio; puede intimidar a novatos. Autores de librerías antiguas pueden no actualizarlas.
Desafortunadamente, no sabrá qué librerías necesitan ajuste hasta comprar sensores y probarlos. Entonces, lea bien los mensajes de error naranja al subir el sketch: ¿el error está en su código o en la librería? Si es en librería, busque en web "arduino" + mensaje de error. Si no halla solución, contacte al autor.
Compré chips sueltos para ahorrar. Decidí que, hasta hacer mis propias PCBs (EagleCAD + OSHPark), es más fácil comprar módulos de Adafruit y SparkFun, que mantienen sus librerías al día.
Un apunte sobre WiFi y cifrado
Pensé en WiFi para subir datos a web. El MKR1000 tiene motor de cifrado para proteger datos en tránsito por Internet. Pero eso excede este primer proyecto. Está en mi lista para "versión 2". Aquí el diagrama de bloques del módulo SAMD21:
Consejo para WiFi onboard: Use la versión más reciente de la librería WiFi101. Si no, el sketch no reconocerá el módulo WiFi. Gracias a Charif Mahmoudi por señalarlo en su tutorial "Getting Started with MKR1000" en Hackster.
Consejos para sensores analógicos
La mayoría de módulos "salida analógica" entregan voltaje simple, leíble con analogRead. Pero luego hay que interpretarlo. Normalmente se usa map o algo de matemáticas. Elementos resistivos necesitan circuitería extra. Puede añadir potenciómetro para "afinar" voltaje.
Otros casos requieren ecuaciones para convertir voltaje en magnitud comprensible. El TMP36 es ejemplo bien documentado: diseñado para °C, mide voltaje y hace matemáticas para obtener °C (y °F si quiere). Leer datasheets explica el funcionamiento, pero es más fácil seguir pasos de otros al empezar.
Algunos sensores necesitan amplificador para que los pequeños cambios de voltaje sean legibles por el ADC (celdas de carga, humedad, acelerómetro). Aquí vale la pena gastar en módulos con amplificador y librería incluida.
Consejos para dispositivos I2C
I2C o "interfaz de dos hilos" necesita reloj (SCL) y datos (SDA). Maestro y esclavo(s) turnan en SDA. Por supuesto, necesitan alimentación y masa común con la CPU.
Cada dispositivo I2C tiene dirección hexadecimal en el bus. Evite duplicados. (Si necesita varios con misma dirección, use circuitería extra para habilitar/deshabilitar selectivamente). La documentación indica direcciones posibles.
NOTA: Si dos dispositivos comparten dirección y ambos tienen pin "Enable/Shutdown", puede deshabilitar uno mientras usa el otro. Pero NO corte alimentación al pin VDD, porque los sensores pueden captar parásitos de SDA/SCL y dar lecturas erróneas. El chequeo de errores I2C no detecta/corrige esto.
Las especificaciones I2C exigen resistencias pull-up "fuertes" en SCL y SDA, en el sensor más lejano. Algunos módulos ya las traen; añada jumpers si hace falta.
Además, un truco antiguo: añadir resistencia en serie en SDA y SCL mejora fiabilidad. Con MKR1000 (3.3V) uso 220 Ω; en CPU 5V pruebe 330 Ω. Vea esquema:
¿Qué hacer con los datos?
Por ahora los envío al Monitor Serie. Podría añadir LCD. Iba a usar tarjeta SD, pero con WiFi integrado quiero subirlos a la nube... imagine varias estaciones con lat/long conocidas, triangulando rayos comparando lecturas de potencia.
NOTA 2017-11-29: No he logrado que funcionen interrupciones hardware en MKR1000. Estoy preparando notas y experimentos para clase de 9º grado (13-14 años), basada en Adalogger M0 (guarda en SD, pines libres para WiFi). arduinoclass.pbworks.com
Mientras trabajaba en sensores, vi la lámpara SODERSVIK de IKEA. Imagine reemplazar sus LEDs blancos por 20 NeoPixels y añadir receptor IR. Viento soplando parecería nubes; color indicaría temperatura. El mando podría mostrar momentáneamente otros datos, como cambio de temp en 12h.
¿Qué quiere monitorizar y mostrar?
Código
Sketch principal de monitorización
/* RTC-Weather-Sensors_v6_MKR1000 by Zonker Harris Spring 2016
* ¡Placa pequeña con más de 2 interrupciones hardware!
* ¡Más WiFi, *y* crypto (¡MUY necesario para IoT!)
* Nota: pongo MUCHOS comentarios; al compilar se IGNORAN y no consumen memoria.
* Las librerías SÍ consumen memoria de programa... */
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>
#include <Adafruit_HTU21DF.h>
// NeoPixels como placeholder; librería necesita actualización de vector interrupción
// #include <Adafruit_NeoPixel.h>
// const int numLeds = 1;
// Adafruit_NeoPixel strip = Adafruit_NeoPixel(numLeds, 6, NEO_GRB + NEO_KHZ800);
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10180);
Adafruit_HTU21DF htu = Adafruit_HTU21DF();
// Pines analógicos
int UVOUT = A1; // ML8511 UV
int lightOut = A2; // TEMT6000 luz visible
int tmp36out = A3; // TMP36 temp local
int mq9out = A4; // MQ-9 gas
// A5 Humedad suelo, A6 Viento?
// 0 Pulsador, 1 Rayos, 2 Anemómetro, 3 Pluviómetro, 4, 5 libres
int sounderPin = 6; // Buzzer (compartido LED onboard)
int neopixelPin = 7; // NeoPixel
// Variables ChronoDot RTC (I2C 0x68)
int addrRTC = 0x68;
int seconds, seconds1, seconds10;
int minutes, minutes1, minutes10;
int hours, hours1, hours10;
int day, date, month, years;
// ... alarmas, status, aging, temp RTC
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println("i2c Sensor Deck");
pinMode(UVOUT, INPUT);
pinMode(lightOut, INPUT);
pinMode(tmp36out, INPUT);
pinMode(sounderPin, OUTPUT);
pinMode(neopixelPin, OUTPUT);
if (!bmp.begin()) {
Serial.println("BMP085/180 no detectado. Revise cableado/I2C.");
while (1);
}
displaySensorDetails();
if (!htu.begin()) {
Serial.println("HTU21-DF no encontrado!");
while (1);
}
// Inicializar ChronoDot (descomente, suba, comente de nuevo para fijar hora)
/* seconds=0; minutes=41; hours=20; day=7; date=3; month=1; years=16; initChrono(); */
}
void loop() {
beep(50, 2); // Inicio loop
// --- ChronoDot RTC ---
Wire.beginTransmission(0x68);
Wire.write((byte)0);
Wire.endTransmission();
Wire.requestFrom(0x68, 13);
while (Wire.available()) {
seconds = Wire.read(); minutes = Wire.read(); hours = Wire.read();
day = Wire.read(); date = Wire.read(); month = Wire.read(); years = Wire.read();
int a1secs = Wire.read(); int a1mins = Wire.read(); int a1hrs = Wire.read();
int a1daydate = Wire.read(); int a2mins = Wire.read(); int a2hrs = Wire.read();
int a2daydate = Wire.read(); int rtcstatus = Wire.read(); int aginginfo = Wire.read();
temprtc = Wire.read(); tempfrtc = Wire.read();
}
// Conversión BCD a decimal
seconds10 = (seconds & 0b11110000) >> 4; seconds1 = seconds & 0b00001111;
minutes10 = (minutes & 0b11110000) >> 4; minutes1 = minutes & 0b00001111;
hours10 = ((hours & 0b00100000) >> 5) * 2 + ((hours & 0b00010000) >> 4) * 1;
hours1 = hours & 0b00001111;
years += 2000;
temprtc = (temprtc & 0b01111111) + (((tempfrtc & 0b11000000) >> 6) * 0.25);
get_date();
Serial.print("ChronoDot - ");
Serial.print(hours10); Serial.print(hours1); Serial.print(":");
Serial.print(minutes10); Serial.print(minutes1); Serial.print(":");
Serial.print(seconds10); Serial.print(seconds1); Serial.print(" 20");
Serial.print(years); Serial.print(" "); Serial.print(month); Serial.print(" ");
Serial.print(date); Serial.print(" \t"); Serial.print(temprtc); Serial.println(" C");
delay(100);
// --- BMP180 ---
sensors_event_t event;
bmp.getEvent(&event);
float BMPtemperature;
bmp.getTemperature(&BMPtemperature);
float BMPtempF = BMPtemperature * 1.8 + 32;
Serial.print("Temp: "); Serial.print(BMPtemperature); Serial.print(" C (");
Serial.print(BMPtempF); Serial.print(" F) \t");
if (event.pressure) {
Serial.print("BMP180 - Pres: "); Serial.print(event.pressure); Serial.print(" hPa\t");
float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
Serial.print("Alt: "); Serial.print(bmp.pressureToAltitude(seaLevelPressure, event.pressure)); Serial.println(" m");
delay(100);
} else Serial.println("Sensor error");
// --- HTU21-DF ---
float HTUtemperature = htu.readTemperature();
float HTUtempF = HTUtemperature * 1.8 + 32;
Serial.print("HTU21-DF - Temp: "); Serial.print(HTUtemperature); Serial.print(" C (");
Serial.print(HTUtempF); Serial.print(" F)\tHum: "); Serial.print(htu.readHumidity()); Serial.println("%");
delay(100);
// --- TMP36 ---
int reading = averageAnalogRead(tmp36out);
float tmp36voltage = 0.0032258064516129 * reading; // 3.3V / 1023
float tmp36temperatureC = (tmp36voltage - 0.5) * 100;
Serial.print("TMP36 - temp: "); Serial.print(tmp36temperatureC); Serial.print(" C \t");
float tmp36temperatureF = tmp36temperatureC * 9.0 / 5.0 + 32.0;
Serial.print(tmp36temperatureF); Serial.print(" F, out: "); Serial.print(tmp36voltage); Serial.println("v");
delay(100);
// --- TEMT6000 Luz visible ---
int vLevel = averageAnalogRead(lightOut);
float newVOutVolts = 0.0032258064516129 * vLevel;
Serial.print("TEMT6000 out: "); Serial.println(vLevel);
delay(100);
// --- ML8511 UV ---
int uvLevel = averageAnalogRead(UVOUT);
float newOutVolts = 0.0032258064516129 * uvLevel;
float uvIntensity = mapfloat(newOutVolts, 0.99, 2.8, 0.0, 15.0);
Serial.print("ML8511 UV out: "); Serial.print(uvLevel);
Serial.print(" / UV Intensity (mW/cm^2): "); Serial.println(uvIntensity);
delay(100);
// --- MQ-9 Gas ---
int MQ9volts = analogRead(mq9out);
Serial.print("MQ-9 Gas: "); Serial.println(MQ9volts, DEC);
delay(100);
Serial.println("");
delay(3500); // Loop ~5 seg
}
// --- Funciones auxiliares ---
int averageAnalogRead(int pinToRead) {
byte numberOfReadings = 8;
unsigned int runningValue = 0;
for (int x = 0; x < numberOfReadings; x++) runningValue += analogRead(pinToRead);
runningValue /= numberOfReadings;
return runningValue;
}
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void beep(int duration, int count) {
for (int loop = 0; loop < count; loop++) {
digitalWrite(sounderPin, HIGH); delay(duration);
digitalWrite(sounderPin, LOW); delay(duration);
}
}
// ... funciones RTC (initChrono, set_date, get_date, set_time, get_time, decToBcd, bcdToDec)
// ... displaySensorDetails()
Lectura analógica promediada (reutilizable)
int averageAnalogRead(int pinToRead) {
byte numberOfReadings = 8;
unsigned int runningValue = 0;
for (int x = 0; x < numberOfReadings; x++) runningValue += analogRead(pinToRead);
runningValue /= numberOfReadings;
return runningValue;
}
Escáner bus I2C
// i2c_scanner - encuentra direcciones I2C
#include <Wire.h>
void setup() { Wire.begin(); Serial.begin(9600); Serial.println("\nI2C Scanner"); }
void loop() {
byte error, address; int nDevices = 0;
Serial.println("Scanning...");
for (address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at 0x"); if (address < 16) Serial.print("0");
Serial.print(address, HEX);
// Identificación de sensores conocidos
if (address == 3) Serial.print(" - AS3935 Lightning Sensor");
if (address == 64) Serial.print(" - HTU21D Humidity/Temp");
if (address == 104) Serial.print(" - DS1307 RTC (ChronoDot?)");
if (address == 119) Serial.print(" - BMP180 Pressure/Temp");
Serial.println(); nDevices++;
} else if (error == 4) {
Serial.print("Unknown error at 0x"); if (address < 16) Serial.print("0");
Serial.println(address, HEX);
}
}
if (nDevices == 0) Serial.println("No I2C devices found\n");
else Serial.println("done\n");
delay(5000);
}
Demo ML8511 (adaptado a 3.3V)
int UVOUT = A1;
void setup() { Serial.begin(9600); pinMode(UVOUT, INPUT); Serial.println("ML8511 example"); }
void loop() {
int uvLevel = averageAnalogRead(UVOUT);
float newOutVolts = 0.0032258064516129 * uvLevel;
float uvIntensity = mapfloat(newOutVolts, 0.99, 2.8, 0.0, 15.0);
Serial.print("ML8511 out: "); Serial.print(uvLevel);
Serial.print(" / UV Intensity: "); Serial.println(uvIntensity);
delay(100);
}
// averageAnalogRead y mapfloat como arriba
Esquemático
Proceso de manufactura
- Integración de datos de sensores en Raspberry Pi: Guía práctica
- Sensor de seguimiento de línea con Raspberry Pi – Detector simple KY‑033 para puerta/línea
- Construya una API robusta de temperatura y sensores en Raspberry Pi usando el módulo GY‑91
- Construye un sistema aeropónico automatizado con Raspberry Pi y sensor de humedad
- Domina GPIO de Raspberry Pi: interfaz sensor PIR movimiento en B+/Model 2
- Integrar radar pulsado Acconeer A111 con Raspberry Pi: guía práctica
- Cómo Conectar, Calibrar y Programar el Sensor PIR HC‑SR501 con Raspberry Pi
- Logger de Sensores Python/MicroPython: Almacena Datos de Sensores en Google Sheets Fácilmente
- Envío de Datos IMU Adafruit 10DOF desde Raspberry Pi 2 a Azure Event Hubs con Windows 10 IoT Core
- Apagado seguro y modo suspensión para Raspberry Pi – Circuito de gestión de energía DIY
- Cómo la fusión de sensores mejora la fiabilidad, seguridad y eficiencia en sistemas autónomos