Skip to content

Instantly share code, notes, and snippets.

@IoTeacher
Forked from enrmx/25aUltimoLDI10prg.md
Created May 24, 2025 19:59
Show Gist options
  • Save IoTeacher/b3b34de1bce7b9941f19fa69fd064d09 to your computer and use it in GitHub Desktop.
Save IoTeacher/b3b34de1bce7b9941f19fa69fd064d09 to your computer and use it in GitHub Desktop.
25aUltimoLDI10prg

📘 Proyecto Educativo: 10 Programas con Raspberry Pi Pico W + OLED + Sensores

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.

⚙️ Requisitos Generales

  • 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.

🧪 Programa 1

"""
🏆 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()


🧪 Programa 2

"""
🎯 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()


🧪 Programa 3

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)


🧪 Programa 4

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)


🧪 Programa 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)


🧪 Programa 6

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)


🧪 Programa 7

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)


🧪 Programa 8

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)


🧪 Programa 9

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)


🧪 Programa 10

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)



📷 Evidencia Visual de Ejecución

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:

Programa 1

Image

Programa 2

Image

Programa 3

Image

Programa 4

Image

Programa 5

Image

Programa 6

Image

Programa 7

Image

Programa 8

Image

Programa 9

Image

Programa 10

Image


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment