import machine
import time
from rp2 import PIO, StateMachine, asm_pio
# --- Configuración de Pines ---
ZERO_CROSS_PIN = 16 # Entrada del detector de cruce por cero
TRIAC_PIN = 17 # Salida hacia el gatillo del TRIAC
POT_PIN = 26 # Pin ADC0 para el potenciómetro
BUTTON_PIN = 15 # Pin para el botón físico (conectar a GND)
# --- Configuración de Red Eléctrica ---
AC_FREQUENCY = 60 # Cambiar a 50 si tu red es de 50Hz
HALF_CYCLE_US = int(1_000_000 / (2 * AC_FREQUENCY)) # 8333us para 60Hz
# --- Configuración de Filtros ---
FILTER_SIZE = 8 # Número de muestras para el promedio móvil del ADC
DEADBAND = 400 # Histéresis para evitar parpadeos por ruido
DEBOUNCE_MS = 250 # Tiempo de anti-rebote para el botón físico
# --- Programa Ensamblador PIO ---
@asm_pio(
set_init=PIO.OUT_LOW,
in_shiftdir=PIO.SHIFT_LEFT,
autopull=True,
pull_thresh=32
)
def ac_phase_control():
wait(1, pin, 0) # 1. Esperar cruce por cero (HIGH)
pull(noblock) # 2. Leer retraso desde el búfer FIFO
mov(x, osr)
label("delay_loop") # 3. Bucle de retraso
jmp(x_dec, "delay_loop")
set(pins, 1) # 4. Encender TRIAC
set(x, 49) # 5. Mantener pulso (~100 us)
label("pulse_loop")
jmp(x_dec, "pulse_loop")
set(pins, 0) # 6. Apagar pulso del TRIAC
wait(0, pin, 0) # 7. Esperar cruce por cero (LOW)
# --- Inicialización de Hardware ---
sm = StateMachine(
0,
ac_phase_control,
freq=1_000_000,
set_base=machine.Pin(TRIAC_PIN),
in_base=machine.Pin(ZERO_CROSS_PIN)
)
sm.active(1)
pot = machine.ADC(machine.Pin(POT_PIN))
# Botón configurado con resistencia Pull-Up interna (reacciona al conectar a GND)
btn_pin = machine.Pin(BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)
# --- Variables de Estado ---
system_enabled = True # Estado del sistema (True = ON, False = OFF)
last_button_time = 0 # Registro de tiempo para el anti-rebote
adc_history = * FILTER_SIZE
history_index = 0
last_stable_adc = 0
# --- Manejador del Botón (Interrupción IRQ) ---
def button_handler(pin):
"""Manejador por interrupción para alternar el estado del sistema con anti-rebote."""
global system_enabled, last_button_time
current_time = time.ticks_ms()
# Verificar si ha pasado suficiente tiempo desde la última pulsación real
if time.ticks_diff(current_time, last_button_time) > DEBOUNCE_MS:
system_enabled = not system_enabled
last_button_time = current_time
if not system_enabled:
# Apagado inmediato: enviamos el retraso máximo para apagar la carga
# El bloque de seguridad de set_power_from_adc forzará el apagado físico.
set_power_from_adc(0)
print("\n[SISTEMA APAGADO] - Carga desenergizada.")
else:
print("\n[SISTEMA ENCENDIDO] - Control por potenciómetro activo.")
# Asignar la interrupción al botón en el flanco de bajada (cuando se presiona)
btn_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler=button_handler)
# --- Funciones de Control ---
def read_filtered_pot():
global history_index
raw_val = pot.read_u16()
adc_history[history_index] = raw_val
history_index = (history_index + 1) % FILTER_SIZE
return sum(adc_history) // FILTER_SIZE
def set_power_from_adc(adc_value):
"""Convierte el ADC a microsegundos y actualiza el PIO."""
if not system_enabled:
# Si el sistema está apagado, fuerza el valor máximo de retraso
# Esto evita disparar el TRIAC por completo
sm.put(HALF_CYCLE_US // 2)
machine.Pin(TRIAC_PIN, machine.Pin.OUT).low()
return
percent = (adc_value / 65535.0) * 100
delay_us = int(HALF_CYCLE_US * (100 - percent) / 100)
# Márgenes de seguridad contra colisiones en los extremos
delay_us = max(50, min(HALF_CYCLE_US - 200, delay_us))
# El bucle PIO consume 2 ciclos por iteración
sm.put(delay_us // 2)
# --- Bucle Principal ---
try:
# Llenado inicial del filtro
for _ in range(FILTER_SIZE):
read_filtered_pot()
last_stable_adc = read_filtered_pot()
set_power_from_adc(last_stable_adc)
print("[SISTEMA INICIALIZADO] - Presiona el botón para encender/apagar.")
while True:
# Solo leemos y procesamos el potenciómetro si el sistema está encendido
if system_enabled:
current_adc = read_filtered_pot()
if abs(current_adc - last_stable_adc) > DEADBAND:
last_stable_adc = current_adc
set_power_from_adc(last_stable_adc)
calculated_percent = int((last_stable_adc / 65535.0) * 100)
print(f"Potenciómetro: {last_stable_adc} | Potencia: {calculated_percent}%")
# Pausa para no saturar el CPU
time.sleep(0.05)
except KeyboardInterrupt:
sm.active(0)
machine.Pin(TRIAC_PIN, machine.Pin.OUT).low()
print("\nSistema detenido de forma segura.")