Este documento contiene una colección de 10 programas educativos diseñados para la Raspberry Pi Pico W, enfocados en el uso de sensores digitales y analógicos junto con una pantalla OLED SSD1306.
Cada programa incluye:
- Código funcional y completo.
- Integración con sensores comunes.
- Visualización de datos en pantalla OLED.
- Buenas prácticas de programación y estructura clara.
- Microcontrolador: Raspberry Pi Pico W
- Pantalla: OLED SSD1306 (I2C)
- Sensores: Varían por programa (ej. temperatura, luz, movimiento, distancia, etc.)
- Entorno sugerido: Wokwi o hardware real.
"""
🏆 ESTACIÓN METEOROLÓGICA INTELIGENTE v1.0
📊 Monitoreo avanzado con alertas y análisis de tendencias
🔧 Hardware: Raspberry Pi Pico W + OLED SSD1306 + DHT22
👨💻 Código con driver SSD1306 integrado para Wokwi
"""
# ==================== IMPORTACIÓN DE LIBRERÍAS ====================
from machine import Pin, I2C
import dht
import time
import gc
# ==================== DRIVER SSD1306 INTEGRADO ====================
class SSD1306:
"""Driver básico para OLED SSD1306 optimizado para Wokwi"""
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
# Inicialización del display
self._init_display()
def _init_display(self):
"""Secuencia de inicialización SSD1306"""
init_cmds = [
0xAE, # Display OFF
0x20, 0x00, # Horizontal addressing mode
0xB0, # Page start address
0xC8, # COM scan direction
0x00, 0x10, # Column start address
0x40, # Display start line
0x81, 0x7F, # Contrast
0xA1, # Segment remap
0xA6, # Normal display
0xA8, 0x3F, # Multiplex ratio
0xA4, # Display from RAM
0xD3, 0x00, # Display offset
0xD5, 0x80, # Clock divide ratio
0xD9, 0xF1, # Pre-charge period
0xDA, 0x12, # COM pin configuration
0xDB, 0x40, # VCOM detect
0x8D, 0x14, # Charge pump
0xAF # Display ON
]
for cmd in init_cmds:
self._write_cmd(cmd)
def _write_cmd(self, cmd):
"""Enviar comando al display"""
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def _write_data(self, data):
"""Enviar datos al display"""
self.i2c.writeto(self.addr, bytearray([0x40]) + data)
def fill(self, color):
"""Llenar pantalla con color (0=negro, 1=blanco)"""
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
"""Establecer pixel individual"""
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
"""Escribir texto usando font básico 8x8"""
# Font básico 8x8 para caracteres ASCII básicos
font = {
'A': [0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00],
'B': [0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00],
'C': [0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00],
'D': [0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00],
'E': [0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00],
'F': [0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00],
'G': [0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00],
'H': [0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00],
'I': [0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00],
'L': [0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00],
'M': [0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00],
'N': [0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00],
'O': [0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
'P': [0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00],
'R': [0x7C, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0x00],
'S': [0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00],
'T': [0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00],
'U': [0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
'V': [0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00],
'W': [0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00],
'X': [0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00],
'Y': [0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00],
'Z': [0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00],
'0': [0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00],
'1': [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00],
'2': [0x3C, 0x66, 0x06, 0x1C, 0x30, 0x60, 0x7E, 0x00],
'3': [0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00],
'4': [0x0C, 0x1C, 0x3C, 0x6C, 0x7E, 0x0C, 0x0C, 0x00],
'5': [0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00],
'6': [0x1C, 0x30, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00],
'7': [0x7E, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00],
'8': [0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00],
'9': [0x3C, 0x66, 0x66, 0x3E, 0x06, 0x0C, 0x38, 0x00],
' ': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
'.': [0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00],
':': [0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00],
'-': [0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00],
'=': [0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00],
'%': [0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00],
'|': [0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18],
}
for i, char in enumerate(string.upper()):
if char in font:
char_x = x + i * 8
if char_x < self.width - 8:
for row in range(8):
byte = font[char][row]
for col in range(8):
if byte & (0x80 >> col):
self.pixel(char_x + col, y + row, 1)
def show(self):
"""Actualizar display con buffer"""
# Establecer posición inicial
self._write_cmd(0x21) # Column address
self._write_cmd(0x00) # Column start
self._write_cmd(0x7F) # Column end
self._write_cmd(0x22) # Page address
self._write_cmd(0x00) # Page start
self._write_cmd(0x07) # Page end
# Enviar buffer
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i+16]
self._write_data(chunk)
# ==================== SISTEMA DE DATOS INTELIGENTE ====================
class EstacionMeteorologica:
def __init__(self):
self.historial_temp = []
self.historial_hum = []
self.max_registros = 10
self.lecturas_totales = 0
self.errores_sensor = 0
# Umbrales de alerta
self.temp_min_alerta = 15.0
self.temp_max_alerta = 35.0
self.hum_min_alerta = 30.0
self.hum_max_alerta = 80.0
print("ESTACION INICIADA")
def agregar_lectura(self, temp, hum):
if len(self.historial_temp) >= self.max_registros:
self.historial_temp.pop(0)
self.historial_hum.pop(0)
self.historial_temp.append(temp)
self.historial_hum.append(hum)
self.lecturas_totales += 1
def calcular_promedios(self):
if not self.historial_temp:
return 0, 0, "SIN DATOS"
temp_prom = sum(self.historial_temp) / len(self.historial_temp)
hum_prom = sum(self.historial_hum) / len(self.historial_hum)
if len(self.historial_temp) >= 3:
tendencia_temp = self.historial_temp[-1] - self.historial_temp[-3]
if tendencia_temp > 1:
tendencia = "SUBIENDO"
elif tendencia_temp < -1:
tendencia = "BAJANDO"
else:
tendencia = "ESTABLE"
else:
tendencia = "ANALIZANDO"
return temp_prom, hum_prom, tendencia
def verificar_alertas(self, temp, hum):
alertas = []
if temp < self.temp_min_alerta:
alertas.append("FRIO EXTREMO")
elif temp > self.temp_max_alerta:
alertas.append("CALOR EXTREMO")
if hum < self.hum_min_alerta:
alertas.append("MUY SECO")
elif hum > self.hum_max_alerta:
alertas.append("MUY HUMEDO")
if temp > 30 and hum > 70:
alertas.append("BOCHORNO")
return alertas
def mostrar_interfaz(self, oled, temp, hum, alertas):
oled.fill(0)
# Pantalla rotativa cada 4 segundos
pantalla = (time.ticks_ms() // 4000) % 3
if pantalla == 0:
self._pantalla_principal(oled, temp, hum)
elif pantalla == 1:
self._pantalla_estadisticas(oled)
else:
self._pantalla_alertas(oled, alertas)
oled.show()
def _pantalla_principal(self, oled, temp, hum):
oled.text("METEO STATION", 0, 0)
oled.text("=============", 0, 8)
oled.text(f"TEMP: {temp:.1f}C", 0, 20)
oled.text(f"HUM: {hum:.1f}%", 0, 32)
oled.text(f"LECTURAS: {self.lecturas_totales}", 0, 48)
def _pantalla_estadisticas(self, oled):
temp_prom, hum_prom, tendencia = self.calcular_promedios()
oled.text("ANALISIS", 0, 0)
oled.text("========", 0, 8)
oled.text(f"T.PROM: {temp_prom:.1f}C", 0, 20)
oled.text(f"H.PROM: {hum_prom:.1f}%", 0, 32)
oled.text(f"TEND: {tendencia}", 0, 44)
oled.text(f"ERRORES: {self.errores_sensor}", 0, 56)
def _pantalla_alertas(self, oled, alertas):
oled.text("ALERTAS", 0, 0)
oled.text("=======", 0, 8)
if alertas:
for i, alerta in enumerate(alertas[:3]):
oled.text(alerta[:15], 0, 20 + i*12)
else:
oled.text("TODO NORMAL", 0, 28)
oled.text("AMBIENTE OK", 0, 40)
# ==================== CONFIGURACIÓN DE HARDWARE ====================
# I2C para OLED
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
oled = SSD1306(128, 64, i2c)
# Sensor DHT22
sensor_ambiente = dht.DHT22(Pin(2))
# ==================== PROGRAMA PRINCIPAL ====================
def main():
estacion = EstacionMeteorologica()
# Mensaje inicial
oled.fill(0)
oled.text("INICIANDO...", 0, 20)
oled.text("CALIBRANDO", 0, 32)
oled.show()
time.sleep(2)
print("MONITOREO INICIADO")
while True:
try:
# Lectura del sensor
sensor_ambiente.measure()
time.sleep(0.1)
temperatura = sensor_ambiente.temperature()
humedad = sensor_ambiente.humidity()
# Validación de datos
if -40 <= temperatura <= 80 and 0 <= humedad <= 100:
estacion.agregar_lectura(temperatura, humedad)
alertas = estacion.verificar_alertas(temperatura, humedad)
# Mostrar en pantalla
estacion.mostrar_interfaz(oled, temperatura, humedad, alertas)
# Log en consola
print(f"T:{temperatura}C | H:{humedad}% | A:{len(alertas)}")
if alertas:
print(f"ALERTAS: {', '.join(alertas)}")
else:
print(f"LECTURA INVALIDA: T={temperatura}, H={humedad}")
estacion.errores_sensor += 1
except OSError as e:
print(f"ERROR SENSOR: {e}")
estacion.errores_sensor += 1
oled.fill(0)
oled.text("ERROR SENSOR", 0, 20)
oled.text("REINTENTANDO", 0, 32)
oled.show()
except Exception as e:
print(f"ERROR CRITICO: {e}")
finally:
gc.collect()
time.sleep(2)
# ==================== EJECUCIÓN ====================
if __name__ == "__main__":
print("ESTACION METEOROLOGICA v1.0")
print("=" * 30)
main()
"""
🎯 SISTEMA DE RADAR ULTRASÓNICO AVANZADO v2.0
📡 Detección de obstáculos con mapa visual dinámico
🔧 Hardware: Pico W + OLED SSD1306 + HC-SR04
👨💻 Sistema de navegación y análisis predictivo
"""
# ==================== IMPORTACIÓN DE LIBRERÍAS ====================
from machine import Pin, I2C, time_pulse_us
import time
import gc
import math
# ==================== DRIVER SSD1306 INTEGRADO ====================
class SSD1306:
"""Driver optimizado para OLED SSD1306"""
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
init_cmds = [
0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF
]
for cmd in init_cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def line(self, x0, y0, x1, y1, color):
"""Dibujar línea usando algoritmo de Bresenham"""
dx = abs(x1 - x0)
dy = abs(y1 - y0)
sx = 1 if x0 < x1 else -1
sy = 1 if y0 < y1 else -1
err = dx - dy
while True:
self.pixel(x0, y0, color)
if x0 == x1 and y0 == y1:
break
e2 = 2 * err
if e2 > -dy:
err -= dy
x0 += sx
if e2 < dx:
err += dx
y0 += sy
def circle(self, cx, cy, radius, color):
"""Dibujar círculo"""
x = radius
y = 0
err = 0
while x >= y:
self.pixel(cx + x, cy + y, color)
self.pixel(cx + y, cy + x, color)
self.pixel(cx - y, cy + x, color)
self.pixel(cx - x, cy + y, color)
self.pixel(cx - x, cy - y, color)
self.pixel(cx - y, cy - x, color)
self.pixel(cx + y, cy - x, color)
self.pixel(cx + x, cy - y, color)
if err <= 0:
y += 1
err += 2*y + 1
if err > 0:
x -= 1
err -= 2*x + 1
def rect(self, x, y, w, h, color, fill=False):
"""Dibujar rectángulo"""
if fill:
for i in range(h):
self.line(x, y + i, x + w - 1, y + i, color)
else:
self.line(x, y, x + w - 1, y, color)
self.line(x, y + h - 1, x + w - 1, y + h - 1, color)
self.line(x, y, x, y + h - 1, color)
self.line(x + w - 1, y, x + w - 1, y + h - 1, color)
def text(self, string, x, y):
"""Escribir texto con font básico"""
font = {
'A': [0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00],
'B': [0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00],
'C': [0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00],
'D': [0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00],
'E': [0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00],
'F': [0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00],
'G': [0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00],
'H': [0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00],
'I': [0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00],
'L': [0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00],
'M': [0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00],
'N': [0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00],
'O': [0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
'P': [0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00],
'R': [0x7C, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0x00],
'S': [0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00],
'T': [0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00],
'U': [0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
'V': [0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00],
'X': [0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00],
'0': [0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00],
'1': [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00],
'2': [0x3C, 0x66, 0x06, 0x1C, 0x30, 0x60, 0x7E, 0x00],
'3': [0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00],
'4': [0x0C, 0x1C, 0x3C, 0x6C, 0x7E, 0x0C, 0x0C, 0x00],
'5': [0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00],
'6': [0x1C, 0x30, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00],
'7': [0x7E, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00],
'8': [0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00],
'9': [0x3C, 0x66, 0x66, 0x3E, 0x06, 0x0C, 0x38, 0x00],
' ': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
'.': [0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00],
':': [0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00],
'-': [0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00],
'=': [0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00],
'%': [0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00],
'|': [0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18],
'/': [0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00],
}
for i, char in enumerate(string.upper()):
if char in font:
char_x = x + i * 8
if char_x < self.width - 8:
for row in range(8):
byte = font[char][row]
for col in range(8):
if byte & (0x80 >> col):
self.pixel(char_x + col, y + row, 1)
def show(self):
"""Actualizar display"""
self.i2c.writeto(self.addr, bytearray([0x21, 0x00, 0x7F]))
self.i2c.writeto(self.addr, bytearray([0x22, 0x00, 0x07]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i+16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
# ==================== SISTEMA DE RADAR AVANZADO ====================
class SistemaRadar:
def __init__(self, pin_trigger, pin_echo):
self.trigger = Pin(pin_trigger, Pin.OUT)
self.echo = Pin(pin_echo, Pin.IN)
# Historial y estadísticas
self.distancias_historial = []
self.max_historial = 20
self.lecturas_totales = 0
self.errores_lectura = 0
# Configuración de alertas
self.distancia_critica = 20.0 # 20cm - PELIGRO
self.distancia_alerta = 50.0 # 50cm - PRECAUCIÓN
self.distancia_maxima = 400.0 # 4m - Rango máximo
# Calibración automática
self.calibrando = True
self.lecturas_calibracion = 0
self.suma_calibracion = 0
# Estados del sistema
self.estado_alerta = "NORMAL"
self.detecciones_consecutivas = 0
print("RADAR INICIALIZADO")
print("RANGO: 2cm - 400cm")
def medir_distancia(self):
"""Medición ultrasónica de alta precisión"""
try:
# Limpiar trigger
self.trigger.value(0)
time.sleep_us(2)
# Pulso de trigger (10µs)
self.trigger.value(1)
time.sleep_us(10)
self.trigger.value(0)
# Medir echo con timeout
duracion = time_pulse_us(self.echo, 1, 30000) # 30ms timeout
if duracion < 0:
return None # Timeout
# Calcular distancia: (duración * velocidad del sonido) / 2
# Velocidad del sonido: ~343 m/s = 0.0343 cm/µs
distancia = (duracion * 0.0343) / 2
# Filtrar lecturas fuera de rango
if 2 <= distancia <= self.distancia_maxima:
return round(distancia, 1)
else:
return None
except Exception as e:
print(f"ERROR MEDICION: {e}")
return None
def procesar_lectura(self, distancia):
"""Procesamiento inteligente de datos"""
if distancia is None:
self.errores_lectura += 1
return False
# Agregar al historial
if len(self.distancias_historial) >= self.max_historial:
self.distancias_historial.pop(0)
self.distancias_historial.append(distancia)
self.lecturas_totales += 1
# Calibración automática (primeras 10 lecturas)
if self.calibrando and self.lecturas_calibracion < 10:
self.suma_calibracion += distancia
self.lecturas_calibracion += 1
if self.lecturas_calibracion >= 10:
distancia_base = self.suma_calibracion / 10
self.calibrando = False
print(f"CALIBRACION COMPLETA - BASE: {distancia_base:.1f}cm")
# Determinar estado de alerta
self._actualizar_estado_alerta(distancia)
return True
def _actualizar_estado_alerta(self, distancia):
"""Sistema de alertas por proximidad"""
if distancia <= self.distancia_critica:
if self.estado_alerta != "CRITICO":
self.detecciones_consecutivas = 1
else:
self.detecciones_consecutivas += 1
self.estado_alerta = "CRITICO"
elif distancia <= self.distancia_alerta:
if self.estado_alerta != "ALERTA":
self.detecciones_consecutivas = 1
else:
self.detecciones_consecutivas += 1
self.estado_alerta = "ALERTA"
else:
self.estado_alerta = "NORMAL"
self.detecciones_consecutivas = 0
def obtener_estadisticas(self):
"""Análisis estadístico del radar"""
if not self.distancias_historial:
return {
'promedio': 0,
'minima': 0,
'maxima': 0,
'variabilidad': 'SIN DATOS'
}
promedio = sum(self.distancias_historial) / len(self.distancias_historial)
minima = min(self.distancias_historial)
maxima = max(self.distancias_historial)
# Análisis de variabilidad
if len(self.distancias_historial) >= 5:
ultimas_5 = self.distancias_historial[-5:]
rango = max(ultimas_5) - min(ultimas_5)
if rango < 5:
variabilidad = "ESTABLE"
elif rango < 20:
variabilidad = "VARIABLE"
else:
variabilidade = "INESTABLE"
else:
variabilidad = "CALCULANDO"
return {
'promedio': round(promedio, 1),
'minima': minima,
'maxima': maxima,
'variabilidad': variabilidad
}
# ==================== SISTEMA DE VISUALIZACIÓN ====================
class VisualizadorRadar:
def __init__(self, oled):
self.oled = oled
self.modo_pantalla = 0 # 0=Radar, 1=Gráfico, 2=Datos, 3=Estadísticas
self.centro_x = 64
self.centro_y = 32
self.radio_max = 30
def mostrar_interfaz(self, radar, distancia_actual):
"""Interfaz multi-modo con rotación automática"""
self.oled.fill(0)
# Cambiar modo cada 4 segundos
self.modo_pantalla = (time.ticks_ms() // 4000) % 4
if self.modo_pantalla == 0:
self._pantalla_radar(distancia_actual, radar.estado_alerta)
elif self.modo_pantalla == 1:
self._pantalla_grafico(radar.distancias_historial)
elif self.modo_pantalla == 2:
self._pantalla_datos(distancia_actual, radar)
else:
self._pantalla_estadisticas(radar)
self.oled.show()
def _pantalla_radar(self, distancia, estado):
"""Vista de radar circular con sweep"""
# Título
self.oled.text("RADAR SWEEP", 0, 0)
# Círculos concéntricos (rangos)
for r in [10, 20, 30]:
self.oled.circle(self.centro_x, self.centro_y, r, 1)
# Líneas de referencia (cruz)
self.oled.line(self.centro_x - 35, self.centro_y, self.centro_x + 35, self.centro_y, 1)
self.oled.line(self.centro_x, self.centro_y - 35, self.centro_x, self.centro_y + 35, 1)
# Punto de detección
if distancia and distancia <= 400:
# Escalar distancia al radio (0-400cm -> 0-30px)
radio_punto = min(int(distancia / 400 * 30), 30)
# Mostrar como punto en ángulo 0° (derecha)
punto_x = self.centro_x + radio_punto
punto_y = self.centro_y
# Punto de detección
self.oled.circle(punto_x, punto_y, 2, 1)
# Línea desde centro hasta detección
self.oled.line(self.centro_x, self.centro_y, punto_x, punto_y, 1)
# Estado de alerta
if estado == "CRITICO":
self.oled.text("PELIGRO!", 0, 56)
elif estado == "ALERTA":
self.oled.text("PRECAUCION", 0, 56)
else:
self.oled.text("DESPEJADO", 0, 56)
# Distancia actual
if distancia:
self.oled.text(f"{distancia}CM", 90, 8)
def _pantalla_grafico(self, historial):
"""Gráfico de barras de historial"""
self.oled.text("HISTORIAL", 0, 0)
if len(historial) >= 2:
# Tomar últimas 16 lecturas para el gráfico
datos = historial[-16:] if len(historial) >= 16 else historial
# Escalar datos (0-400cm -> 0-40px altura)
max_altura = 40
for i, dist in enumerate(datos):
x = i * 8
altura = min(int(dist / 400 * max_altura), max_altura)
# Dibujar barra desde abajo
self.oled.line(x, 63, x, 63 - altura, 1)
# Punto en la cima
self.oled.pixel(x, 63 - altura, 1)
else:
self.oled.text("RECOPILANDO", 0, 30)
self.oled.text("DATOS...", 0, 40)
def _pantalla_datos(self, distancia, radar):
"""Datos numéricos detallados"""
self.oled.text("DATOS RADAR", 0, 0)
self.oled.text("===========", 0, 8)
if distancia:
self.oled.text(f"DIST: {distancia} CM", 0, 20)
else:
self.oled.text("DIST: ERROR", 0, 20)
self.oled.text(f"ESTADO: {radar.estado_alerta}", 0, 32)
self.oled.text(f"LECTURAS: {radar.lecturas_totales}", 0, 44)
self.oled.text(f"ERRORES: {radar.errores_lectura}", 0, 56)
def _pantalla_estadisticas(self, radar):
"""Análisis estadístico completo"""
stats = radar.obtener_estadisticas()
self.oled.text("ESTADISTICAS", 0, 0)
self.oled.text("============", 0, 8)
self.oled.text(f"PROM: {stats['promedio']}CM", 0, 20)
self.oled.text(f"MIN: {stats['minima']}CM", 0, 32)
self.oled.text(f"MAX: {stats['maxima']}CM", 0, 44)
self.oled.text(f"VAR: {stats['variabilidad']}", 0, 56)
# ==================== CONFIGURACIÓN DE HARDWARE ====================
# I2C para OLED (misma configuración de Práctica 1)
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
oled = SSD1306(128, 64, i2c)
# Sistema de radar
radar = SistemaRadar(pin_trigger=3, pin_echo=4) # GP3 y GP4
visualizador = VisualizadorRadar(oled)
# ==================== PROGRAMA PRINCIPAL ====================
def main():
"""Sistema principal de radar con monitoreo continuo"""
# Pantalla de inicio
oled.fill(0)
oled.text("SISTEMA RADAR", 0, 10)
oled.text("INICIALIZANDO", 0, 25)
oled.text("CALIBRANDO...", 0, 40)
oled.show()
time.sleep(3)
print("SISTEMA RADAR ACTIVO")
print("RANGO: 2-400cm")
print("MODOS: RADAR | GRAFICO | DATOS | STATS")
while True:
try:
# Medir distancia
distancia = radar.medir_distancia()
# Procesar lectura
lectura_valida = radar.procesar_lectura(distancia)
# Actualizar visualización
visualizador.mostrar_interfaz(radar, distancia)
# Log en consola
if lectura_valida and distancia:
estado_emoji = {
"NORMAL": "✅",
"ALERTA": "⚠️",
"CRITICO": "🔴"
}
print(f"{estado_emoji.get(radar.estado_alerta, '❓')} "
f"Dist: {distancia}cm | Estado: {radar.estado_alerta} | "
f"Consecutivas: {radar.detecciones_consecutivas}")
# Alerta sonora simulada en consola
if radar.estado_alerta == "CRITICO" and radar.detecciones_consecutivas >= 3:
print("🚨 ALERTA CRÍTICA: OBSTÁCULO MUY CERCA! 🚨")
else:
print(f"❌ Error de lectura | Total errores: {radar.errores_lectura}")
except Exception as e:
print(f"💥 ERROR CRÍTICO: {e}")
# Pantalla de error
oled.fill(0)
oled.text("ERROR SISTEMA", 0, 20)
oled.text("REINICIANDO", 0, 35)
oled.show()
finally:
# Gestión de memoria y temporización
gc.collect()
time.sleep(0.5) # 2 lecturas por segundo para radar
# ==================== EJECUCIÓN ====================
if __name__ == "__main__":
print("🎯 SISTEMA DE RADAR ULTRASÓNICO v2.0")
print("=" * 40)
main()
from machine import Pin, I2C, ADC
import time
import gc
# ==================== DRIVER SSD1306 INTEGRADO ====================
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, char in enumerate(string):
char_code = ord(char)
for row in range(8):
bits = FONT6x8.get(char_code, FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + col + i * 6, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i + 16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
# Fuente básica 6x8 ASCII
FONT6x8 = {
i: [0]*8 for i in range(128)
}
# Solo números y letras mínimas para texto básico
FONT6x8.update({
32: [0x00]*8, # espacio
48: [0x3E,0x51,0x49,0x45,0x3E,0x00,0x00,0x00], # 0
49: [0x00,0x42,0x7F,0x40,0x00,0x00,0x00,0x00], # 1
50: [0x62,0x51,0x49,0x49,0x46,0x00,0x00,0x00], # 2
51: [0x22,0x49,0x49,0x49,0x36,0x00,0x00,0x00], # 3
52: [0x18,0x14,0x12,0x7F,0x10,0x00,0x00,0x00], # 4
53: [0x2F,0x49,0x49,0x49,0x31,0x00,0x00,0x00], # 5
54: [0x3E,0x49,0x49,0x49,0x30,0x00,0x00,0x00], # 6
55: [0x01,0x71,0x09,0x05,0x03,0x00,0x00,0x00], # 7
56: [0x36,0x49,0x49,0x49,0x36,0x00,0x00,0x00], # 8
57: [0x06,0x49,0x49,0x49,0x3E,0x00,0x00,0x00], # 9
65: [0x7E,0x09,0x09,0x09,0x7E,0x00,0x00,0x00], # A
76: [0x7F,0x40,0x40,0x40,0x40,0x00,0x00,0x00], # L
79: [0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00], # O
80: [0x7F,0x09,0x09,0x09,0x06,0x00,0x00,0x00], # P
66: [0x7F,0x49,0x49,0x49,0x36,0x00,0x00,0x00], # B
69: [0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00], # E
71: [0x3E,0x41,0x49,0x49,0x3A,0x00,0x00,0x00], # G
78: [0x7F,0x04,0x08,0x10,0x7F,0x00,0x00,0x00], # N
83: [0x46,0x49,0x49,0x49,0x31,0x00,0x00,0x00], # S
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
58: [0x00,0x36,0x36,0x00,0x00,0x00,0x00,0x00], # :
})
# ==================== INICIALIZACIÓN ====================
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
sensor_gas = ADC(Pin(26)) # GP26 = ADC0 = pin 31
# ==================== FUNCIÓN DE ESTADO ====================
def nivel_gas(valor):
if valor > 60000:
return "PELIGRO", "💀"
elif valor > 40000:
return "ALTO", "⚠️"
elif valor > 20000:
return "MODERADO", "🔆"
else:
return "BAJO", "✅"
# ==================== BUCLE PRINCIPAL ====================
while True:
lectura = sensor_gas.read_u16()
estado, icono = nivel_gas(lectura)
print(f"{icono} Nivel de gas: {lectura} | Estado: {estado}")
oled.fill(0)
oled.text("SENSOR MQ-2", 0, 0)
oled.text(f"NIVEL: {lectura}", 0, 20)
oled.text(f"ESTADO: {estado}", 0, 40)
oled.show()
gc.collect()
time.sleep(0.5)
from machine import Pin, I2C
import time
import gc
# ==================== DRIVER OLED SSD1306 ====================
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, char in enumerate(string):
char_code = ord(char)
for row in range(8):
bits = FONT6x8.get(char_code, FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + col + i * 6, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i + 16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
FONT6x8 = {
i: [0]*8 for i in range(128)
}
FONT6x8.update({
32: [0x00]*8,
77: [0x7F,0x09,0x09,0x09,0x7F,0x00,0x00,0x00], # M
79: [0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00], # O
86: [0x7F,0x04,0x08,0x10,0x7F,0x00,0x00,0x00], # V
73: [0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00], # I
78: [0x7F,0x02,0x04,0x08,0x7F,0x00,0x00,0x00], # N
69: [0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00], # E
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
68: [0x7F,0x41,0x41,0x41,0x3E,0x00,0x00,0x00], # D
69: [0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00], # E
67: [0x3E,0x41,0x41,0x41,0x22,0x00,0x00,0x00], # C
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
58: [0x00,0x36,0x36,0x00,0x00,0x00,0x00,0x00], # :
})
# ==================== INICIALIZACIÓN ====================
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
pir = Pin(15, Pin.IN) # OUT del PIR a GP15
# ==================== LOOP PRINCIPAL ====================
while True:
estado = pir.value()
if estado:
mensaje = "MOVIMIENTO"
icono = "🚶"
else:
mensaje = "SIN MOVIMIENTO"
icono = "💤"
print(f"{icono} Estado PIR: {mensaje}")
oled.fill(0)
oled.text("SENSOR PIR", 0, 0)
oled.text(f"ESTADO:", 0, 20)
oled.text(mensaje[:13], 0, 35) # recorte por largo
oled.show()
gc.collect()
time.sleep(0.5)
from machine import Pin, I2C, ADC
import time
import gc
# ==================== DRIVER OLED SSD1306 ====================
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, char in enumerate(string):
char_code = ord(char)
for row in range(8):
bits = FONT6x8.get(char_code, FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + col + i * 6, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i + 16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
FONT6x8 = {
i: [0]*8 for i in range(128)
}
FONT6x8.update({
32: [0x00]*8,
74: [0x00,0x41,0x7F,0x01,0x00,0x00,0x00,0x00], # J
79: [0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00], # O
89: [0x07,0x08,0x70,0x08,0x07,0x00,0x00,0x00], # Y
83: [0x46,0x49,0x49,0x49,0x31,0x00,0x00,0x00], # S
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
73: [0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00], # I
67: [0x3E,0x41,0x41,0x41,0x22,0x00,0x00,0x00], # C
75: [0x7F,0x08,0x14,0x22,0x41,0x00,0x00,0x00], # K
})
# ==================== INICIALIZACIÓN ====================
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
x_axis = ADC(Pin(26)) # VRx → GP26 (ADC0)
y_axis = ADC(Pin(27)) # VRy → GP27 (ADC1)
btn = Pin(15, Pin.IN, Pin.PULL_UP) # SW → GP15
# ==================== LOOP PRINCIPAL ====================
while True:
x_val = x_axis.read_u16()
y_val = y_axis.read_u16()
presionado = not btn.value() # activo bajo
estado = "PRESIONADO" if presionado else "SUELTO"
icono = "🕹️" if presionado else "🎮"
print(f"{icono} X:{x_val} | Y:{y_val} | Btn: {estado}")
oled.fill(0)
oled.text("JOYSTICK", 0, 0)
oled.text(f"X: {x_val}", 0, 20)
oled.text(f"Y: {y_val}", 0, 35)
oled.text(f"BTN: {estado}", 0, 50)
oled.show()
gc.collect()
time.sleep(0.2)
from machine import Pin, I2C, ADC
import time
import gc
# ==================== DRIVER OLED SSD1306 ====================
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, char in enumerate(string):
char_code = ord(char)
for row in range(8):
bits = FONT6x8.get(char_code, FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + col + i * 6, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i + 16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
FONT6x8 = {
i: [0]*8 for i in range(128)
}
FONT6x8.update({
32: [0x00]*8,
80: [0x7F,0x09,0x09,0x09,0x06,0x00,0x00,0x00], # P
79: [0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00], # O
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
69: [0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00], # E
78: [0x7F,0x02,0x04,0x08,0x7F,0x00,0x00,0x00], # N
67: [0x3E,0x41,0x41,0x41,0x22,0x00,0x00,0x00], # C
73: [0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00], # I
77: [0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00,0x00], # M
})
# ==================== INICIALIZACIÓN ====================
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
pot = ADC(Pin(26)) # Señal del potenciómetro al pin GP26 (ADC0)
# ==================== LOOP PRINCIPAL ====================
while True:
lectura = pot.read_u16()
porcentaje = int((lectura / 65535) * 100)
print(f"🎚️ Potenciómetro: {lectura} | {porcentaje}%")
oled.fill(0)
oled.text("POTENCIOMETRO", 0, 0)
oled.text(f"VALOR: {lectura}", 0, 20)
oled.text(f"{porcentaje} %", 0, 40)
oled.show()
gc.collect()
time.sleep(0.3)
from machine import Pin, I2C, ADC
import time
import gc
# ==================== DRIVER OLED SSD1306 ====================
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, char in enumerate(string):
char_code = ord(char)
for row in range(8):
bits = FONT6x8.get(char_code, FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + col + i * 6, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i + 16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
FONT6x8 = {
i: [0]*8 for i in range(128)
}
FONT6x8.update({
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
69: [0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00], # E
77: [0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00,0x00], # M
80: [0x7F,0x09,0x09,0x09,0x06,0x00,0x00,0x00], # P
67: [0x3E,0x41,0x41,0x41,0x22,0x00,0x00,0x00], # C
})
# ==================== INICIALIZACIÓN ====================
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
temp_sensor = ADC(Pin(26)) # OUT del LM35 al GP26
# ==================== LOOP PRINCIPAL ====================
while True:
lectura = temp_sensor.read_u16()
voltaje = (lectura / 65535) * 3.3 # escala ADC
temperatura = voltaje * 100 # 10 mV/°C para LM35
print(f"🌡️ Temp: {temperatura:.1f}°C ({voltaje:.2f}V)")
oled.fill(0)
oled.text("SENSOR TEMP", 0, 0)
oled.text(f"{temperatura:.1f} C", 0, 20)
oled.text(f"{voltaje:.2f} V", 0, 40)
oled.show()
gc.collect()
time.sleep(0.5)
from machine import Pin, I2C, ADC
import time
import gc
# ========== DRIVER OLED SSD1306 ==========
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, char in enumerate(string):
char_code = ord(char)
for row in range(8):
bits = FONT6x8.get(char_code, FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + col + i * 6, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i + 16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
FONT6x8 = {
i: [0]*8 for i in range(128)
}
FONT6x8.update({
76: [0x7F,0x40,0x40,0x40,0x40,0x00,0x00,0x00], # L
73: [0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00], # I
71: [0x3E,0x41,0x49,0x49,0x3A,0x00,0x00,0x00], # G
72: [0x7F,0x08,0x08,0x08,0x7F,0x00,0x00,0x00], # H
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
79: [0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00], # O
75: [0x7F,0x08,0x14,0x22,0x41,0x00,0x00,0x00], # K
})
# ========== INICIALIZACIÓN ==========
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
ldr_analog = ADC(Pin(26)) # AO → GP26
ldr_digital = Pin(15, Pin.IN) # DO → GP15
# ========== LOOP ==========
while True:
analog_val = ldr_analog.read_u16()
digital_val = ldr_digital.value()
nivel = int((analog_val / 65535) * 100)
if digital_val == 0:
estado = "MUCHA LUZ"
icono = "☀️"
else:
estado = "POCA LUZ"
icono = "🌙"
print(f"{icono} LUZ: {nivel}% | Estado: {estado}")
oled.fill(0)
oled.text("SENSOR LDR", 0, 0)
oled.text(f"NIVEL: {nivel}%", 0, 20)
oled.text(f"ESTADO:", 0, 40)
oled.text(estado, 0, 50)
oled.show()
gc.collect()
time.sleep(0.3)
from machine import Pin, I2C
import time
import gc
# ========== DRIVER OLED SSD1306 ==========
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10, 0x40,
0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F, 0xA4, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB,
0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, char in enumerate(string):
char_code = ord(char)
for row in range(8):
bits = FONT6x8.get(char_code, FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + col + i * 6, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
chunk = self.buffer[i:i + 16]
self.i2c.writeto(self.addr, bytearray([0x40]) + chunk)
FONT6x8 = {
i: [0]*8 for i in range(128)
}
FONT6x8.update({
77: [0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00,0x00], # M
79: [0x3E,0x41,0x41,0x41,0x3E,0x00,0x00,0x00], # O
86: [0x7F,0x04,0x08,0x10,0x7F,0x00,0x00,0x00], # V
73: [0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00], # I
78: [0x7F,0x02,0x04,0x08,0x7F,0x00,0x00,0x00], # N
69: [0x7F,0x49,0x49,0x49,0x41,0x00,0x00,0x00], # E
84: [0x01,0x01,0x7F,0x01,0x01,0x00,0x00,0x00], # T
})
# ========== INICIALIZACIÓN ==========
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
pir = Pin(15, Pin.IN)
# ========== LOOP ==========
while True:
estado = pir.value()
if estado:
texto = "MOVIMIENTO"
icono = "🚶"
else:
texto = "SIN MOVIMIENTO"
icono = "💤"
print(f"{icono} {texto}")
oled.fill(0)
oled.text("SENSOR PIR", 0, 0)
oled.text(f"{texto}", 0, 30)
oled.show()
gc.collect()
time.sleep(0.5)
from machine import Pin, I2C
import time
import struct
import gc
# ===== SSD1306 OLED DRIVER =====
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.pages = height // 8
self.buffer = bytearray(width * self.pages)
self._init_display()
def _init_display(self):
cmds = [0xAE, 0xA6, 0x20, 0x00, 0x40, 0xA1, 0xA8, 0x3F, 0xC8,
0xD3, 0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12,
0xDB, 0x40, 0x8D, 0x14, 0xAF]
for cmd in cmds:
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def fill(self, color):
fill_byte = 0xFF if color else 0x00
for i in range(len(self.buffer)):
self.buffer[i] = fill_byte
def pixel(self, x, y, color):
if 0 <= x < self.width and 0 <= y < self.height:
page = y // 8
bit = y % 8
index = x + page * self.width
if color:
self.buffer[index] |= (1 << bit)
else:
self.buffer[index] &= ~(1 << bit)
def text(self, string, x, y):
for i, c in enumerate(string):
for row in range(8):
bits = FONT6x8.get(ord(c), FONT6x8[32])[row]
for col in range(6):
if bits & (1 << (5 - col)):
self.pixel(x + i * 6 + col, y + row, 1)
def show(self):
self.i2c.writeto(self.addr, bytearray([0x21, 0, 127]))
self.i2c.writeto(self.addr, bytearray([0x22, 0, 7]))
for i in range(0, len(self.buffer), 16):
self.i2c.writeto(self.addr, bytearray([0x40]) + self.buffer[i:i + 16])
FONT6x8 = {i: [0]*8 for i in range(128)}
FONT6x8.update({
88: [0x63,0x36,0x1C,0x1C,0x36,0x63,0x00,0x00],
89: [0x07,0x08,0x70,0x08,0x07,0x00,0x00,0x00],
90: [0x61,0x51,0x49,0x45,0x43,0x00,0x00,0x00],
58: [0x00,0x36,0x36,0x00,0x36,0x36,0x00,0x00],
32: [0x00]*8
})
# ===== MPU6050 DRIVER SIMPLE =====
class MPU6050:
def __init__(self, i2c, addr=0x68):
self.addr = addr
self.i2c = i2c
self.i2c.writeto_mem(self.addr, 0x6B, b'\x00') # Wake up
def leer_aceleracion(self):
data = self.i2c.readfrom_mem(self.addr, 0x3B, 6)
ax, ay, az = struct.unpack('>hhh', data)
return ax / 16384, ay / 16384, az / 16384
# ===== INICIALIZACIÓN =====
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = SSD1306(128, 64, i2c)
mpu = MPU6050(i2c)
# ===== LOOP =====
while True:
ax, ay, az = mpu.leer_aceleracion()
print(f"📈 AX: {ax:.2f} | AY: {ay:.2f} | AZ: {az:.2f}")
oled.fill(0)
oled.text("MPU6050 (ACC)", 0, 0)
oled.text(f"X: {ax:.2f}", 0, 20)
oled.text(f"Y: {ay:.2f}", 0, 35)
oled.text(f"Z: {az:.2f}", 0, 50)
oled.show()
gc.collect()
time.sleep(0.4)
A continuación se muestran capturas de pantalla tomadas directamente desde el simulador Wokwi o pruebas reales, como evidencia de funcionamiento de cada uno de los 10 programas implementados con Raspberry Pi Pico W, sensores y pantalla OLED: