01 · Punto de partida
Qué vamos a construir
Una pequeña estación de medición que cada medio segundo lee la temperatura, la presión y la humedad del ambiente y la distancia a un objeto, y las envía por el puerto serie en una línea como T=25,08 C P=1006 hPa H=45 % D=1001 mm. Reúne casi todo lo del curso: I2C (tema 33), UART (tema 32), un temporizador con captura de entrada (tema 24), aritmética entera y calibración (tema 36).
Los dos sensores enseñan lecciones opuestas:
- El BME280 (de Bosch) es un sensor digital: el microcontrolador no mide una tensión, lee registros. Pero lo que guarda en ellos son cuentas crudas de 20 bits, y cada chip trae de fábrica 18 coeficientes de calibración con los que se las convierte en unidades. El trabajo del programa es leerlos y aplicar la fórmula.
- El HC-SR04 es un sensor de tiempo de vuelo: emite una ráfaga de ultrasonido de 40 kHz y avisa con un pulso en el pin ECHO cuyo ancho es el tiempo que tardó el eco en volver. La distancia sale de
d = t · v / 2, con v la velocidad del sonido, que depende de la temperatura.
02 · Dos técnicas
El diseño: un bus y un temporizador
| Sensor | Interfaz | Qué entrega | Técnica del microcontrolador |
|---|---|---|---|
| BME280 | I2C (PC4, PC5), dirección 0x76 o 0x77 | Cuentas crudas de temperatura, presión y humedad | Lectura de registros y compensación con enteros de 32 bits |
| HC-SR04 | TRIG (PD2) y ECHO (PB0 = ICP1) | Un pulso cuyo ancho es el tiempo de vuelo | Captura de entrada del Timer1: el hardware guarda el instante de cada flanco |
La captura de entrada (tema 24) es la herramienta natural para medir el ancho de un pulso. Cuando llega un flanco al pin ICP1, el hardware copia el contador del Timer1 en ICR1 sin intervención del programa: la latencia de la interrupción no altera la medición. Se restan dos capturas (flanco de subida y flanco de bajada) y queda el ancho en cuentas. Con el divisor 64, la cuenta es de 4 µs: eso son 0,69 mm de recorrido del eco, mucho menos que la resolución real del módulo (unos 3 mm).
El flujo de cada ciclo es:
- Pedir una medición al BME280 en modo forzado y esperar unos 10 ms.
- Leer 8 bytes y compensar. Sale la temperatura.
- Calcular la velocidad del sonido con esa temperatura.
- Disparar el HC-SR04 con un pulso de 10 µs y esperar el eco (hasta 25 ms).
- Convertir el ancho del eco en distancia con la velocidad calculada.
- Imprimir la línea.
03 · Paso 1
El cableado
| Módulo | Pin del módulo | Arduino Uno |
|---|---|---|
| BME280 | VCC (o VIN) | 5 V (si el módulo tiene regulador; si no, 3,3 V) |
| BME280 | GND | GND |
| BME280 | SDA | A4 (PC4) |
| BME280 | SCL | A5 (PC5) |
| BME280 | SDO | GND → dirección 0x76; VCC → 0x77 |
| BME280 | CSB | VCC (modo I2C; en GND usaría SPI) |
| HC-SR04 | VCC | 5 V |
| HC-SR04 | GND | GND |
| HC-SR04 | TRIG | 2 (PD2) |
| HC-SR04 | ECHO | 8 (PB0 = ICP1) |
Cuatro precauciones:
- La tensión del BME280. El chip es de 3,3 V. Muchos módulos traen regulador y conversión de nivel y admiten 5 V; otros son solo de 3,3 V y se dañan con 5 V. Leé la serigrafía o la descripción del vendedor antes de alimentarlo.
- Pull-ups del I2C. Los módulos suelen traer los suyos (10 kΩ); con dos o tres módulos en paralelo, la resistencia equivalente baja (tema 33).
- El pin ECHO debe ir a PB0. Es el único pin con captura de entrada del Timer1; en el Uno es el digital 8.
- El HC-SR04 consume unos 15 mA durante la medición: alimentarlo desde el pin de 5 V de la placa está bien, pero no desde un pin digital. Si el microcontrolador es de 3,3 V, el ECHO (5 V) necesita un divisor o un conversor de nivel.
04 · Paso 2
Hablar con el BME280
| Dirección | Nombre | Contenido |
|---|---|---|
| 0xD0 | id | Identificador: 0x60 para el BME280 (0x58 si es un BMP280) |
| 0xE0 | reset | Escribir 0xB6 reinicia el chip |
| 0xF2 | ctrl_hum | Sobremuestreo de la humedad (bits 2:0); se aplica al escribir ctrl_meas |
| 0xF3 | status | Bit 3: medición en curso; bit 0: copiando datos de calibración |
| 0xF4 | ctrl_meas | Sobremuestreo de temperatura (7:5) y presión (4:2); modo (1:0): 00 reposo, 01 forzado, 11 normal |
| 0xF5 | config | Tiempo de espera en modo normal y filtro |
| 0xF7 a 0xFE | datos | Presión (20 bits), temperatura (20 bits) y humedad (16 bits), en 8 bytes seguidos |
| 0x88 a 0xA1, 0xE1 a 0xE7 | calibración | Coeficientes de fábrica de temperatura, presión y humedad |
Se trabaja en modo forzado: el programa ordena una medición, el chip la hace y vuelve a reposo (consume casi nada). La secuencia es: escribir ctrl_hum, escribir ctrl_meas con el modo forzado, esperar a que termine y leer los 8 bytes de datos en una sola transacción (lectura de ráfaga: el chip incrementa la dirección solo). Con sobremuestreo ×1 en las tres magnitudes, la conversión dura como máximo 9,3 ms.
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <util/twi.h>
/* i2c_init, i2c_start, i2c_enviar, i2c_recibir, i2c_stop: tema 33
uart_init, uart_texto, uart_u16, uart_fijo: temas 30 y 32 */
#define BME_DIR 0x76
static int8_t bme_leer(uint8_t reg, uint8_t *dst, uint8_t n) {
int8_t error = 0;
if (i2c_start() != TW_START) { error = -1; goto fin; }
if (i2c_enviar((BME_DIR << 1) | TW_WRITE, TW_MT_SLA_ACK)) { error = -2; goto fin; }
if (i2c_enviar(reg, TW_MT_DATA_ACK)) { error = -3; goto fin; }
if (i2c_start() != TW_REP_START) { error = -4; goto fin; }
if (i2c_enviar((BME_DIR << 1) | TW_READ, TW_MR_SLA_ACK)) { error = -5; goto fin; }
while (n--) *dst++ = i2c_recibir(n != 0); // ACK salvo en el último byte
fin:
i2c_stop();
return error;
}
static int8_t bme_escribir(uint8_t reg, uint8_t valor) {
int8_t error = 0;
if (i2c_start() != TW_START) { error = -1; goto fin; }
if (i2c_enviar((BME_DIR << 1) | TW_WRITE, TW_MT_SLA_ACK)) { error = -2; goto fin; }
if (i2c_enviar(reg, TW_MT_DATA_ACK)) { error = -3; goto fin; }
if (i2c_enviar(valor, TW_MT_DATA_ACK)) { error = -4; goto fin; }
fin:
i2c_stop();
return error;
}
static int8_t bme_medir(int32_t *adc_T, int32_t *adc_P, int32_t *adc_H) {
uint8_t d[8];
if (bme_escribir(0xF2, 0x01)) return -1; // humedad ×1
if (bme_escribir(0xF4, (1 << 5) | (1 << 2) | 0x01)) return -1; // temperatura ×1, presión ×1, modo forzado
_delay_ms(10); // conversión: 9,3 ms como máximo
if (bme_leer(0xF7, d, 8)) return -2;
*adc_P = ((int32_t)d[0] << 12) | ((int32_t)d[1] << 4) | (d[2] >> 4);
*adc_T = ((int32_t)d[3] << 12) | ((int32_t)d[4] << 4) | (d[5] >> 4);
*adc_H = ((int32_t)d[6] << 8) | d[7];
return 0;
}
Antes de cualquier otra cosa, un programa prudente lee el registro 0xD0 y comprueba que vale 0x60. Si no, el sensor no está conectado, la dirección es la otra (0x77) o el chip no es un BME280. Es el equivalente a la lectura de identificación que recomendamos en SPI (tema 34).
05 · Paso 3
Compensar las lecturas
Cada BME280 sale de fábrica con 18 coeficientes medidos en ese chip: 3 de temperatura, 9 de presión y 6 de humedad. Se leen una sola vez al arrancar. Con ellos, las fórmulas que publica Bosch convierten las cuentas crudas en centésimas de grado, pascales y %HR × 1024, usando solo enteros de 32 bits. La temperatura se calcula primero porque genera una variable intermedia, t_fine, que necesitan la presión y la humedad.
static struct {
uint16_t T1; int16_t T2, T3;
uint16_t P1; int16_t P2, P3, P4, P5, P6, P7, P8, P9;
uint8_t H1; int16_t H2; uint8_t H3; int16_t H4, H5; int8_t H6;
} cal;
#define LE16(b, i) ((uint16_t)((b)[i] | ((uint16_t)(b)[(i) + 1] << 8)))
static void bme_calibracion(void) {
uint8_t b[24], h[7];
bme_leer(0x88, b, 24); // 0x88 a 0x9F: temperatura y presión
cal.T1 = LE16(b, 0); cal.T2 = (int16_t)LE16(b, 2); cal.T3 = (int16_t)LE16(b, 4);
cal.P1 = LE16(b, 6); cal.P2 = (int16_t)LE16(b, 8); cal.P3 = (int16_t)LE16(b, 10);
cal.P4 = (int16_t)LE16(b, 12); cal.P5 = (int16_t)LE16(b, 14); cal.P6 = (int16_t)LE16(b, 16);
cal.P7 = (int16_t)LE16(b, 18); cal.P8 = (int16_t)LE16(b, 20); cal.P9 = (int16_t)LE16(b, 22);
bme_leer(0xA1, &cal.H1, 1);
bme_leer(0xE1, h, 7); // humedad
cal.H2 = (int16_t)LE16(h, 0); cal.H3 = h[2];
cal.H4 = (int16_t)(((int16_t)(int8_t)h[3] << 4) | (h[4] & 0x0F));
cal.H5 = (int16_t)(((int16_t)(int8_t)h[5] << 4) | (h[4] >> 4));
cal.H6 = (int8_t)h[6];
}
static int32_t bme_temperatura(int32_t adc_T, int32_t *t_fine) { // centésimas de °C
int32_t var1 = (((adc_T >> 3) - ((int32_t)cal.T1 << 1)) * (int32_t)cal.T2) >> 11;
int32_t var2 = (((((adc_T >> 4) - (int32_t)cal.T1) * ((adc_T >> 4) - (int32_t)cal.T1)) >> 12)
* (int32_t)cal.T3) >> 14;
*t_fine = var1 + var2;
return (*t_fine * 5 + 128) >> 8;
}
static uint32_t bme_presion(int32_t adc_P, int32_t t_fine) { // pascales
int32_t var1 = (t_fine >> 1) - 64000;
int32_t var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * (int32_t)cal.P6;
var2 += (var1 * (int32_t)cal.P5) << 1;
var2 = (var2 >> 2) + ((int32_t)cal.P4 << 16);
var1 = (((cal.P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + (((int32_t)cal.P2 * var1) >> 1)) >> 18;
var1 = ((32768 + var1) * (int32_t)cal.P1) >> 15;
if (var1 == 0) return 0; // evita dividir por cero
uint32_t p = (((uint32_t)(1048576 - adc_P) - (uint32_t)(var2 >> 12))) * 3125;
p = (p < 0x80000000UL) ? (p << 1) / (uint32_t)var1 : (p / (uint32_t)var1) * 2;
var1 = ((int32_t)cal.P9 * (int32_t)(((p >> 3) * (p >> 3)) >> 13)) >> 12;
var2 = ((int32_t)(p >> 2) * (int32_t)cal.P8) >> 13;
return (uint32_t)((int32_t)p + ((var1 + var2 + cal.P7) >> 4));
}
static uint32_t bme_humedad(int32_t adc_H, int32_t t_fine) { // %HR × 1024
int32_t v = t_fine - 76800;
v = (((((adc_H << 14) - ((int32_t)cal.H4 << 20) - ((int32_t)cal.H5 * v)) + 16384) >> 15) *
(((((((v * (int32_t)cal.H6) >> 10) * (((v * (int32_t)cal.H3) >> 11) + 32768)) >> 10) + 2097152)
* (int32_t)cal.H2 + 8192) >> 14));
v -= (((((v >> 15) * (v >> 15)) >> 7) * (int32_t)cal.H1) >> 4);
if (v < 0) v = 0;
if (v > 419430400) v = 419430400;
return (uint32_t)(v >> 12);
}
Estas fórmulas no se «entienden»: se copian del datasheet, y la prueba de que están bien copiadas es reproducir su ejemplo numérico. Con los coeficientes del ejemplo de Bosch, las cuentas 519 888 (temperatura) y 415 148 (presión) deben dar 25,08 °C y algo más de 100 650 Pa. La primera herramienta del laboratorio lo hace. El código C reproduce las mismas operaciones de 32 bits.
Con la presión se puede calcular la altitud: h = 44 330 · (1 − (p / p0)0,1903) metros, con p0 la presión al nivel del mar (1013,25 hPa en la atmósfera estándar). El clima mueve p0 hasta unos ±30 hPa (más de 200 m), por eso un barómetro no da altura absoluta sin referencia, aunque sí diferencias de altura (un piso son 0,4 hPa).
06 · Paso 4
El HC-SR04 con captura
El módulo se dispara con un pulso alto de al menos 10 µs en TRIG. Poco después emite 8 pulsos de 40 kHz y sube ECHO; ECHO baja cuando recibe el eco. Si no hay obstáculo, el módulo baja ECHO solo después de unos 38 ms. Se recomienda esperar al menos 60 ms entre mediciones, para que el eco anterior se extinga.
static volatile uint16_t t_subida;
static volatile uint16_t eco_cuentas; // ancho del eco en cuentas de 4 µs
static volatile uint8_t eco_listo;
static void sonar_init(void) {
DDRD |= (1 << PD2); // TRIG: salida
DDRB &= ~(1 << PB0); // ICP1: entrada
TCCR1A = 0;
TCCR1B = (1 << ICNC1) | (1 << ICES1) | (1 << CS11) | (1 << CS10); // filtro de ruido, flanco de subida, divisor 64
TIMSK1 = (1 << ICIE1); // interrupción de captura
}
ISR(TIMER1_CAPT_vect) {
if (TCCR1B & (1 << ICES1)) { // flanco de subida: empieza el eco
t_subida = ICR1;
TCCR1B &= ~(1 << ICES1); // ahora se espera el de bajada
} else { // flanco de bajada: termina el eco
eco_cuentas = (uint16_t)(ICR1 - t_subida); // resta modular de 16 bits
TCCR1B |= (1 << ICES1);
eco_listo = 1;
}
}
static void sonar_disparar(void) {
eco_listo = 0;
TCCR1B |= (1 << ICES1); // siempre se empieza esperando la subida
PORTD |= (1 << PD2);
_delay_us(10);
PORTD &= ~(1 << PD2);
}
// Distancia en milímetros; temp_centi en centésimas de °C
static uint16_t distancia_mm(uint16_t cuentas, int32_t temp_centi) {
uint32_t v10 = (uint32_t)(3313L + temp_centi * 606L / 10000L); // velocidad en décimas de m/s
uint32_t t_us = (uint32_t)cuentas * 4UL;
return (uint16_t)(t_us * v10 / 20000UL); // d = t · v / 2
}
Detalles que importan. El divisor 64 da una cuenta de 4 µs y un desborde del contador a los 262 ms: sobra para el eco máximo de 38 ms. Con el divisor 8 (0,5 µs por cuenta), el contador de 16 bits da la vuelta a los 32,8 ms y un eco «sin obstáculo» de 38 ms se mediría como 5 ms, es decir, un objeto falso a 90 cm. La resta modular ICR1 − t_subida sobre enteros sin signo de 16 bits da el ancho correcto aunque el contador pase por cero entre los dos flancos (tema 24). El filtro de ruido (ICNC1) exige cuatro muestras iguales antes de aceptar un flanco: elimina falsos disparos a costa de 4 ciclos de retardo, irrelevante acá. Y la velocidad del sonido se calcula con una aproximación lineal, 331,3 + 0,606 · T m/s, que es válida en el rango ambiente y que en décimas de m/s se escribe 3313 + 0,0606 · Tcenti.
07 · Paso 5
El programa completo
int main(void) {
uart_init();
i2c_init();
sonar_init();
sei();
uint8_t id = 0;
bme_leer(0xD0, &id, 1);
if (id != 0x60) { uart_texto("BME280 no encontrado\r\n"); for (;;) { } }
bme_escribir(0xE0, 0xB6); _delay_ms(5); // reinicio por software
bme_calibracion();
for (;;) {
int32_t aT, aP, aH, t_fine;
if (bme_medir(&aT, &aP, &aH) != 0) { uart_texto("error I2C\r\n"); _delay_ms(500); continue; }
int32_t centi = bme_temperatura(aT, &t_fine);
uint32_t pa = bme_presion(aP, t_fine);
uint32_t hum = bme_humedad(aH, t_fine);
sonar_disparar();
for (uint8_t i = 0; i < 40 && !eco_listo; i++) _delay_ms(1); // hasta 40 ms
uart_texto("T="); uart_fijo((int16_t)centi, 2); uart_texto(" C ");
uart_texto("P="); uart_u16((uint16_t)(pa / 100)); uart_texto(" hPa ");
uart_texto("H="); uart_u16((uint16_t)(hum / 1024)); uart_texto(" % ");
uart_texto("D=");
if (eco_listo) uart_u16(distancia_mm(eco_cuentas, centi)); else uart_texto("--");
uart_texto(" mm\r\n");
_delay_ms(500);
}
}
El orden tiene un motivo: la lectura del BME280 va primero porque su temperatura se usa para corregir la distancia. El _delay_ms y la impresión por sondeo bloquean la CPU durante decenas de milisegundos, algo aceptable en un registrador lento. En un programa que además deba atender otras cosas, la medición del eco, la conversión del BME280 y la impresión se reparten con una máquina de estados (tema 42): la captura de entrada ya trabaja sola, y el BME280 mide en segundo plano.
08 · Paso 6
Verificar
- El identificador. El registro 0xD0 debe leerse como 0x60. Si da 0x58, es un BMP280 (sin humedad); si no hay respuesta, revisá el cableado y la dirección (0x76 o 0x77).
- La temperatura. Compará con otro termómetro o con el LM35 del taller del tema 30. El BME280 tiene una exactitud de ±1 °C, y se calienta un poco con su propio consumo y con la cercanía de la placa: separalo del microcontrolador unos centímetros.
- La presión. Debe estar entre 950 y 1050 hPa a nivel del mar. Compará con la presión informada por un servicio meteorológico cercano (hay que corregir por la altitud: unos 12 hPa cada 100 m). Subir el sensor a un piso más alto debe bajar la lectura unos 0,4 hPa.
- La humedad. Exhalar sobre el sensor debe subirla más de 20 puntos en unos segundos y volver a su valor en medio minuto.
- La distancia. Con una regla y una pared plana, medí a 20, 50, 100 y 200 cm. El error debe ser de unos pocos milímetros. Probá también un objeto inclinado (el eco se desvía y no vuelve) y uno blando como una tela (absorbe el sonido): son los fallos típicos del ultrasonido.
- El efecto de la temperatura. Calentá o enfriá el aire (con un secador, o saliendo al exterior en invierno): sin la corrección de temperatura, la distancia cambiaría un 0,17 % por cada grado.
- Los fallos. Tapá el HC-SR04 con la mano pegada (distancia mínima, menor de 2 cm: lectura inválida) y apuntalo al cielo (sin eco:
D=--).
09 · Analogía con programación
Compensación, eco y tiempos
Este programa implementa la compensación entera y la de coma flotante del fabricante y las compara con el ejemplo de la hoja de datos, calcula la velocidad del sonido y el error de suponer 343 m/s, simula el desborde del contador de 16 bits con dos divisores y arma el presupuesto de tiempo de un ciclo de la estación.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
import math
# --- coeficientes de compensacion (valores del ejemplo del datasheet del BMP280) ---
CAL = dict(T1=27504, T2=26435, T3=-1000, P1=36477, P2=-10685, P3=3024, P4=2855, P5=140,
P6=-7, P7=15500, P8=-14600, P9=6000)
# coeficientes de humedad: valores de ejemplo, no pertenecen a un chip real
CAL_H = dict(H1=75, H2=362, H3=0, H4=316, H5=50, H6=30)
def i32(x):
x &= 0xFFFFFFFF
return x - (1 << 32) if x & 0x80000000 else x
def u32(x):
return x & 0xFFFFFFFF
def temperatura_entera(adc_T, c):
var1 = (((adc_T >> 3) - (c["T1"] << 1)) * c["T2"]) >> 11
var2 = ((((((adc_T >> 4) - c["T1"]) * ((adc_T >> 4) - c["T1"])) >> 12) * c["T3"]) >> 14)
t_fine = var1 + var2
return (t_fine * 5 + 128) >> 8, t_fine # centesimas de grado
def presion_entera(adc_P, t_fine, c):
var1 = (t_fine >> 1) - 64000
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * c["P6"]
var2 = var2 + ((var1 * c["P5"]) << 1)
var2 = (var2 >> 2) + (c["P4"] << 16)
var1 = (((c["P3"] * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + ((c["P2"] * var1) >> 1)) >> 18
var1 = ((32768 + var1) * c["P1"]) >> 15
if var1 == 0:
return 0
p = u32((1048576 - adc_P) - (var2 >> 12)) * 3125
p = u32(p)
if p < 0x80000000:
p = u32(p << 1) // var1
else:
p = (p // var1) * 2
var1 = (c["P9"] * (((p >> 3) * (p >> 3)) >> 13)) >> 12
var2 = ((p >> 2) * c["P8"]) >> 13
return p + ((var1 + var2 + c["P7"]) >> 4) # pascales
def temperatura_float(adc_T, c):
v1 = (adc_T / 16384.0 - c["T1"] / 1024.0) * c["T2"]
v2 = (adc_T / 131072.0 - c["T1"] / 8192.0) ** 2 * c["T3"]
t_fine = v1 + v2
return t_fine / 5120.0, t_fine
def presion_float(adc_P, t_fine, c):
v1 = t_fine / 2.0 - 64000.0
v2 = v1 * v1 * c["P6"] / 32768.0
v2 = v2 + v1 * c["P5"] * 2.0
v2 = v2 / 4.0 + c["P4"] * 65536.0
v1 = (c["P3"] * v1 * v1 / 524288.0 + c["P2"] * v1) / 524288.0
v1 = (1.0 + v1 / 32768.0) * c["P1"]
p = 1048576.0 - adc_P
p = (p - v2 / 4096.0) * 6250.0 / v1
v1 = c["P9"] * p * p / 2147483648.0
v2 = p * c["P8"] / 32768.0
return p + (v1 + v2 + c["P7"]) / 16.0
def humedad_entera(adc_H, t_fine, h):
v = t_fine - 76800
v = (((((adc_H << 14) - (h["H4"] << 20) - (h["H5"] * v)) + 16384) >> 15) *
(((((((v * h["H6"]) >> 10) * (((v * h["H3"]) >> 11) + 32768)) >> 10) + 2097152) * h["H2"] + 8192) >> 14))
v = v - (((((v >> 15) * (v >> 15)) >> 7) * h["H1"]) >> 4)
v = max(0, min(419430400, v))
return (v >> 12) / 1024.0 # %HR
def humedad_float(adc_H, t_fine, h):
v = t_fine - 76800.0
v = (adc_H - (h["H4"] * 64.0 + h["H5"] / 16384.0 * v)) * (h["H2"] / 65536.0 * (1.0 + h["H6"] / 67108864.0 * v * (1.0 + h["H3"] / 67108864.0 * v)))
v = v * (1.0 - h["H1"] * v / 524288.0)
return max(0.0, min(100.0, v))
adc_T, adc_P, adc_H = 519888, 415148, 28500
t_c, t_fine = temperatura_entera(adc_T, CAL)
t_f, tf_f = temperatura_float(adc_T, CAL)
print(f"temperatura: entera {t_c / 100:.2f} C, coma flotante {t_f:.3f} C (datasheet: 25,08 C)")
p_i = presion_entera(adc_P, t_fine, CAL)
p_f = presion_float(adc_P, tf_f, CAL)
print(f"presion: entera {p_i} Pa, coma flotante {p_f:.2f} Pa (datasheet: 100653 Pa)")
h_i, h_f = humedad_entera(adc_H, t_fine, CAL_H), humedad_float(adc_H, tf_f, CAL_H)
print(f"humedad: entera {h_i:.2f} %, coma flotante {h_f:.2f} %")
altura = 44330 * (1 - (p_i / 101325) ** 0.1903)
print(f"altitud respecto de 1013,25 hPa: {altura:.1f} m")
peor_t = max(abs(temperatura_entera(a, CAL)[0] / 100 - temperatura_float(a, CAL)[0]) for a in range(480000, 560000, 997))
print(f"diferencia maxima de temperatura entera frente a flotante: {peor_t:.3f} C")
# --- HC-SR04: del tiempo del eco a la distancia ---
def velocidad_sonido(temp_c):
return 331.3 + 0.606 * temp_c # m/s
print()
print("temperatura velocidad us por cm (ida y vuelta)")
for temp in (-10, 0, 20, 35):
v = velocidad_sonido(temp)
print(f"{temp:8d} C {v:7.1f} m/s {2e4 / v:6.2f}")
print()
print("error de usar 343 m/s fijos a 1 metro:")
for temp in (0, 20, 35):
t_us = 2 * 1.0 / velocidad_sonido(temp) * 1e6
medido = t_us * 1e-6 * 343 / 2
print(f" a {temp:2d} C el eco dura {t_us:6.0f} us y se calcula {medido * 100:6.1f} cm (error {(medido - 1) * 100:+.1f} cm)")
# --- captura con un temporizador de 16 bits: el eco "sin obstaculo" dura unos 38 ms ---
def medir(ancho_us, prescaler, f_cpu=16e6):
tick = prescaler / f_cpu
ticks = int(ancho_us * 1e-6 / tick)
inicio = 12345
fin = (inicio + ticks) & 0xFFFF # el contador de 16 bits se desborda
medido = (fin - inicio) & 0xFFFF # resta modular
return medido * tick * 1e6
print()
print("eco de 38 ms (sin obstaculo) y de 5,83 ms (1 metro), medidos con el temporizador de 16 bits:")
for prescaler in (8, 64):
sin_obs, un_metro = medir(38000, prescaler), medir(5830, prescaler)
print(f" divisor {prescaler:2d}: 38 ms -> {sin_obs / 1000:5.2f} ms (distancia {sin_obs * 1e-6 * 343 / 2 * 100:6.1f} cm); 5,83 ms -> {un_metro / 1000:4.2f} ms")
# --- presupuesto de tiempo de un ciclo de la estacion ---
def ciclo(osrs=1, baud=9600, chars=48, i2c=100e3, dist_max=4.0, temp=25):
conversion = 1.25 + 2.3 * osrs + (2.3 * osrs + 0.575) + (2.3 * osrs + 0.575) # ms
lectura_i2c = (9 * (1 + 1 + 1 + 8) + 3) / i2c * 1000 # ms: escribir registro y leer 8 bytes
eco = 2 * dist_max / velocidad_sonido(temp) * 1000
imprimir = chars * 10 / baud * 1000
return conversion, lectura_i2c, eco, imprimir
c, l, e, p = ciclo()
print()
print(f"ciclo: conversion BME280 {c:.1f} ms, lectura I2C {l:.2f} ms, eco maximo {e:.1f} ms, imprimir 48 caracteres a 9600 {p:.1f} ms")
print(f"secuencial: {c + l + e + p:.1f} ms; con la conversion y el eco solapados: {max(c, e) + l + p:.1f} ms")
Resultado: con los coeficientes del ejemplo del datasheet, la temperatura entera da 25,08 °C (25,082 con coma flotante) y la presión entera 100 656 Pa contra 100 653,27 de la versión de coma flotante: 3 Pa de diferencia, el costo de los 32 bits. La humedad con coeficientes de ejemplo coincide en 0,01 %HR. A 1 metro, suponer 343 m/s fijos da +3,5 cm a 0 °C y −2,7 cm a 35 °C. Con el divisor 8 un eco de 38 ms se mide como 5,23 ms (89,7 cm falsos); con el divisor 64 se mide bien. Un ciclo completo tarda 83 ms en secuencia y 74 ms si se solapan la conversión y el eco; imprimir 48 caracteres a 9600 baudios (50 ms) es lo que más pesa.
Los coeficientes de humedad de este programa son de ejemplo, no de un chip real; solo sirven para comprobar que la versión entera concuerda con la de coma flotante.
10 · Exploración interactiva
Eco, compensación y tiempos
La primera herramienta convierte el ancho del eco en distancia (y al revés) con la corrección por temperatura. La segunda compensa las cuentas crudas del BME280 con la fórmula entera de 32 bits. La tercera estima el tiempo de un ciclo de la estación.
Banco de la estación
1. HC-SR04: tiempo de vuelo y distancia
2. BME280: compensación entera
3. Tiempo de un ciclo
Recorrido guiado
- Con 5830 µs y 20 °C: 100,1 cm. La velocidad es 343,4 m/s, es decir 58,24 µs por centímetro de distancia. Con el divisor 64, el eco ocupa 1458 cuentas de 4 µs (0,69 mm por cuenta).
- Pasá a 0 °C: ignorar la temperatura da +3,4 cm (3,5 %). Con el divisor 8, la nota avisa que el contador da la vuelta antes de los 38 ms.
- En la segunda herramienta, 519 888, 415 148 y 28 500 dan 25,08 °C, 100 656 Pa y 45,57 %HR. Los registros son
65 5A C0 7E ED 00 6F 54: presión, temperatura y humedad empaquetadas. - Subí
adc_Ta 540 000: 31,38 °C y la presión sube a 101 625 Pa con las mismas cuentas de presión (la compensación depende de la temperatura). - En la tercera: conversión de 9,3 ms, lectura I2C de 1,0 ms, eco máximo de 23,3 ms e impresión de 50 ms a 9600 baudios. El ciclo dura 83,6 ms en secuencia y 74,3 ms solapando. Pasá a 115 200 baudios: imprimir cae a 4,2 ms y el ciclo a unos 38 ms.
- Subí el sobremuestreo a ×16: la conversión pasa a 112,8 ms y ya domina el ciclo.
Desafío: la altura de un edificio con un barómetro
Con la presión de la planta baja (1006,56 hPa) y la del último piso (1002,3 hPa) se puede estimar la altura del edificio. La diferencia es 4,26 hPa, y como cerca del nivel del mar cada hPa son unos 8,4 m, el edificio mide unos 36 m. No hace falta conocer p0, porque el clima afecta ambas lecturas casi por igual. El ruido de la presión con sobremuestreo ×1 es de algunos pascales (decenas de centímetros de altura), y con más sobremuestreo y filtro baja a centímetros; promediar varias lecturas también ayuda.
11 · Siguiente nivel
Del prototipo al producto
- Filtrar la distancia. El HC-SR04 produce lecturas atípicas (ecos perdidos, rebotes múltiples). Una mediana de 5 lecturas (tema 36) elimina los picos sin retardar el escalón.
- No esperar parado. Reemplazar los
_delay_mspor una base de tiempo y una máquina de estados (tema 42): disparar, esperar el eco, leer el BME280, imprimir. - Reducir el consumo. El BME280 en modo forzado consume microamperios entre mediciones; el microcontrolador puede dormir entre ciclos (tema 40). El HC-SR04 gasta 15 mA y se puede apagar con un transistor.
- Calibrar. Comparar la distancia con una regla y la temperatura con un patrón, y corregir con un offset (tema 36). Para la altitud, ajustar p0 con la presión local del servicio meteorológico.
- Ahorrar tiempo de bus. Leer la calibración una sola vez (ya se hace) y todos los datos de una vez en una lectura de ráfaga.
- Detectar fallas. Un código crudo de 0x80000 en la presión (valor «omitido» tras un error) o un eco de 0 o de 38 ms exacto son lecturas que no corresponden a una medida.
12 · Comprensión
Confusiones frecuentes
«El BME280 me da la temperatura directamente»
Da cuentas de 20 bits. Hay que leer los coeficientes de fábrica de ese chip y aplicar la fórmula de compensación; sin ella el valor no significa nada.
«La distancia es el tiempo del eco por la velocidad del sonido»
Falta dividir por 2: el eco recorre el camino de ida y de vuelta. Es el error más común al implementar el HC-SR04.
«Con un contador de 16 bits siempre me alcanza»
Depende del divisor. Con divisor 8 el contador da la vuelta a los 32,8 ms y un eco sin obstáculo de 38 ms se mide mal. Hay que comparar el máximo del sensor con el alcance del contador.
«La velocidad del sonido es 343 m/s»
Lo es a 20 °C. Cambia unos 0,6 m/s por grado: a 0 °C son 331 m/s y a 35 °C, 352 m/s. A un metro de distancia son más de 3 cm de diferencia.
«La altitud sale directamente de la presión»
Solo con una referencia: la presión al nivel del mar cambia con el clima hasta ±30 hPa. Las diferencias de altura entre dos puntos cercanos sí son confiables.
13 · Práctica guiada
Predecí antes de comprobar
1. De eco a distancia
El eco dura 11,6 ms a 20 °C. ¿A qué distancia está el objeto y cuántas cuentas del Timer1 (divisor 64) son?
Ver solución
d = 11 600 µs × 343,4 m/s / 2 = 199,2 cm. Cuentas = 11 600 / 4 = 2900.
2. El error de la temperatura
Un objeto está a 2,00 m y el aire está a 35 °C, pero el programa supone 343 m/s. ¿Qué lee?
Ver solución
La velocidad real es 331,3 + 0,606 × 35 = 352,5 m/s. El eco dura 2 × 2 / 352,5 = 11,35 ms. Con 343 m/s se calcula 11,35 ms × 343 / 2 = 194,6 cm: 5,4 cm de menos.
3. Empaquetar y desempaquetar
Los bytes de temperatura son 0x7E, 0xED, 0x00. ¿Qué valor de adc_T forman?
Ver solución
adc_T = (0x7E << 12) | (0xED << 4) | (0x00 >> 4) = 516 096 + 3792 + 0 = 519 888.
4. El desborde del contador
Con el divisor 8 a 16 MHz, ¿hasta qué eco puede medir el Timer1 sin dar la vuelta y a qué distancia equivale?
Ver solución
65 536 × 0,5 µs = 32,8 ms, que equivalen a 5,6 m a 20 °C. Un eco de 38 ms excede ese límite y se mediría como 38 − 32,8 = 5,2 ms.
5. Una altitud
La presión medida es 954,6 hPa y la del nivel del mar, 1013,25 hPa. ¿A qué altitud estás?
Ver solución
h = 44 330 × (1 − (954,6 / 1013,25)0,1903) = 44 330 × (1 − 0,9887) = unos 500 m.
14 · Síntesis
Ideas para recordar
- Un sensor digital entrega cuentas crudas más coeficientes de fábrica: el programa los lee una vez y aplica la fórmula de compensación del fabricante, con enteros de 32 bits.
- El BME280 se maneja en modo forzado: se ordena una medición, se espera su duración (9,3 ms con ×1) y se leen los 8 bytes de datos en una ráfaga; el registro 0xD0 identifica el chip.
- La temperatura se calcula primero porque produce
t_fine, que necesitan la presión y la humedad. - El HC-SR04 convierte la distancia en el ancho de un pulso: d = t · v / 2. El temporizador con captura de entrada mide ese ancho sin que la latencia de la interrupción lo afecte.
- La velocidad del sonido (331,3 + 0,606 · T m/s) depende de la temperatura; ignorarla cuesta más de 3 cm por metro entre 0 y 35 °C.
- Hay que comparar el alcance del contador con el eco máximo (38 ms): el divisor 64 sirve, el 8 no.
- El tiempo de un ciclo lo dominan las esperas y la impresión por sondeo; solaparlas con interrupciones y máquinas de estados lo acelera.
¿Listo para el tema 38?
Antes de avanzar, explicá cómo se mide el ancho de un pulso con la captura de entrada, por qué se calcula primero la temperatura del BME280 y qué pasa si el eco excede el alcance del contador. Si podés justificar las tres respuestas, tenés dominados los sensores digitales y de tiempo.
En el próximo tema pasamos del lado de los actuadores: control de actuadores: potencia desde un pin. Un pin entrega 20 mA; un motor necesita amperios. Veremos transistores, MOSFET, relés, el diodo de rueda libre y los drivers.