import machine
import micropython
import time
from machine import Pin, I2C, Timer, ADC
from logo import dibujar_logo
from ssd1306 import SSD1306_I2C
from oled_utils import (dibujar_barra_estado,
limpiar_area_trabajo,
dibujar_titulo_modo)
from fourier import (modo_ej4, modo_sf1,
modo_ejprop1, modo_senoidal)
# ── Reservar buffer estático para diagnóstico de errores dentro de ISRs
micropython.alloc_emergency_exception_buf(100)
# ==============================================================================
# 1. INICIALIZACIÓN DE PERIFÉRICOS
# ==============================================================================
# ── Pantalla OLED SSD1306 (I2C0, GP0=SDA, GP1=SCL)
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400_000)
oled = SSD1306_I2C(128, 64, i2c)
oled.fill(0)
oled.text("UMSA", 50, 0)
oled.text("FACULTAD DE", 0, 9)
oled.text("INGENIERIA-ETN", 0, 18)
oled.text("Univ. Cristhian ", 0, 36)
oled.text("D. Cari Uluri", 0, 45)
oled.show()
time.sleep_ms(3000)
dibujar_logo(oled)
time.sleep_ms(3000)
# ── ADC interno VSYS (canal 29 — disponible en Pico W)
adc_vsys = ADC(29)
# ── Botones (PULL_UP: reposo = 1, pulsado = 0, IRQ en FALLING)
btn_next = Pin(18, Pin.IN, Pin.PULL_UP) # Avanza modo (→)
btn_prev = Pin(19, Pin.IN, Pin.PULL_UP) # Retrocede modo (←)
btn_reset = Pin(20, Pin.IN, Pin.PULL_UP) # Vuelve al modo 0
btn_info = Pin(21, Pin.IN, Pin.PULL_UP) # Muestra nombre del modo activo
# ==============================================================================
# 2-A. VARIABLES GLOBALES DE ESTADO
# ==============================================================================
# ── Reloj RTC por software
segundos = 0
minutos = 0
horas = 0
# ── Máquina de estados (4 modos)
MODOS = ["f(x)=e^-x T=0.5", "Cuadrada T=60",
"f(x)=pi-x ", "Senoidal "]
modo_actual = 0
modo_previo = -1 # Fuerza re-renderizado en el primer ciclo
# ── Banderas de sincronización (flags)
flag_estado = False # Levantada por el Timer HW cada 1 s
flag_modo = False # Levantada por cualquier IRQ de botón
flag_info = False # Levantada por btn_info
# ── Anti-rebote (debouncing)
REBOTE_MS = 200
t_ultimo_next = 0
t_ultimo_prev = 0
t_ultimo_reset = 0
t_ultimo_info = 0
# ==============================================================================
# 2-B. RUTINAS DE SERVICIO DE INTERRUPCIÓN (ISR)
# ==============================================================================
def isr_timer_estado(timer):
"""ISR del Timer de Hardware se ejecuta cada 1 000 ms.
Actualiza el reloj RTC por software y levanta flag_estado."""
global segundos, minutos, horas, flag_estado
segundos += 1
if segundos >= 60:
segundos = 0
minutos += 1
if minutos >= 60:
minutos = 0
horas += 1
if horas >= 24:
horas = 0
flag_estado = True # El main loop dibujará la barra de estado
def isr_btn_next(pin):
"""ISR de btn_next avanza cíclicamente al siguiente modo."""
global modo_actual, t_ultimo_next, flag_modo
t = time.ticks_ms()
if time.ticks_diff(t, t_ultimo_next) > REBOTE_MS:
modo_actual = (modo_actual + 1) % len(MODOS)
t_ultimo_next = t
flag_modo = True
def isr_btn_prev(pin):
"""ISR de btn_prev retrocede al modo anterior de forma cíclica."""
global modo_actual, t_ultimo_prev, flag_modo
t = time.ticks_ms()
if time.ticks_diff(t, t_ultimo_prev) > REBOTE_MS:
modo_actual = (modo_actual - 1) % len(MODOS)
t_ultimo_prev = t
flag_modo = True
def isr_btn_reset(pin):
"""ISR de btn_reset regresa siempre al modo 0 (Onda Cuadrada)."""
global modo_actual, t_ultimo_reset, flag_modo
t = time.ticks_ms()
if time.ticks_diff(t, t_ultimo_reset) > REBOTE_MS:
modo_actual = 0
t_ultimo_reset = t
flag_modo = True
def isr_btn_info(pin):
"""ISR de btn_info muestra el nombre del modo activo en pantalla."""
global t_ultimo_info, flag_info
t = time.ticks_ms()
if time.ticks_diff(t, t_ultimo_info) > REBOTE_MS:
t_ultimo_info = t
flag_info = True
# ==============================================================================
# 2-C. ASIGNACIÓN DE INTERRUPCIONES EXTERNAS (GPIO IRQ)
# ==============================================================================
# PULL_UP → reposo en HIGH → flanco FALLING al pulsar
btn_next.irq( trigger=Pin.IRQ_FALLING, handler=isr_btn_next)
btn_prev.irq( trigger=Pin.IRQ_FALLING, handler=isr_btn_prev)
btn_reset.irq(trigger=Pin.IRQ_FALLING, handler=isr_btn_reset)
btn_info.irq( trigger=Pin.IRQ_FALLING, handler=isr_btn_info)
# ==============================================================================
# 2-D. INICIALIZACIÓN DEL TIMER DE HARDWARE (1 canal disponible para usuario)
# ==============================================================================
timer_hw = Timer()
timer_hw.init(period=1000, mode=Timer.PERIODIC, callback=isr_timer_estado)
# ==============================================================================
# 3. LAZO PRINCIPAL (main loop)
# ==============================================================================
# Tabla de subprogramas indexada por modo_actual
subprogramas = [modo_ej4, modo_sf1, modo_ejprop1, modo_senoidal]
# Render inicial de la barra de estado
dibujar_barra_estado(oled, horas, minutos, segundos, adc_vsys)
try:
while True:
# ── A) Actualizar barra de estado (1 vez/s, solicitada por Timer HW)
if flag_estado:
dibujar_barra_estado(oled, horas, minutos, segundos, adc_vsys)
flag_estado = False
# ── B) Cambio de modo solicitado por IRQ de botón
if flag_modo or modo_actual != modo_previo:
limpiar_area_trabajo(oled)
dibujar_titulo_modo(oled, MODOS[modo_actual])
oled.show()
modo_previo = modo_actual
flag_modo = False
# ── C) Mostrar nombre del modo (btn_info)
if flag_info:
limpiar_area_trabajo(oled)
dibujar_titulo_modo(oled, MODOS[modo_actual])
oled.show()
time.sleep_ms(1500)
flag_info = False
# ── D) Ejecutar subprograma Fourier del modo activo
subprogramas[modo_actual](oled)
# Cede tiempo al scheduler (10 ms) sin bloquear las IRQs
time.sleep_ms(10)
except KeyboardInterrupt:
timer_hw.deinit()
oled.fill(0)
oled.text("Sistema", 30, 20)
oled.text("detenido.", 24, 32)
oled.show()
print("\nTimer liberado. Sistema detenido.")