#completo, con rampa, led encendido y guardar estado led en mem Flash
import machine
import time
import ujson
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)
STATUS_LED_PIN = 25 # LED integrado de la placa (puedes cambiarlo a un GPIO externo)
# --- 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 Parámetros de Control ---
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
SOFT_START_MS = 15 # Tiempo en milisegundos por cada incremento de 1% en la rampa
CONFIG_FILE = "ac_config.json" # Archivo para almacenar estado en memoria Flash
# --- 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))
btn_pin = machine.Pin(BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)
led_pin = machine.Pin(STATUS_LED_PIN, machine.Pin.OUT)
# --- Variables de Control de Estado ---
system_enabled = True # Estado del sistema (ON/OFF)
in_soft_start = False # Bandera indicadora de rampa activa
current_power_target = 0 # Potencia actual de salida real (0-100%)
last_button_time = 0 # Registro de tiempo para el anti-rebote
adc_history = * FILTER_SIZE
history_index = 0
last_stable_adc = 0
# --- Persistencia en Memoria Flash ---
def save_state_to_flash():
"""Guarda el estado actual de encendido en la memoria flash de forma segura."""
try:
data = {"system_enabled": system_enabled}
with open(CONFIG_FILE, "w") as f:
ujson.dump(data, f)
except Exception as e:
print(f"Error al escribir en Flash: {e}")
def load_state_from_flash():
"""Recupera el último estado guardado en la memoria flash."""
global system_enabled
try:
with open(CONFIG_FILE, "r") as f:
data = ujson.load(f)
system_enabled = data.get("system_enabled", True)
print(f"[FLASH] Estado cargado con éxito. Sistema: {'ENCENDIDO' if system_enabled else 'APAGADO'}")
except OSError:
# Si el archivo no existe en el primer arranque, inicializa por defecto
system_enabled = True
save_state_to_flash()
# --- Funciones de Control Físico ---
def set_raw_pio_power(percent):
"""Convierte directamente un porcentaje bruto (0-100) en el retraso del PIO."""
percent = max(0, min(100, percent))
delay_us = int(HALF_CYCLE_US * (100 - percent) / 100)
delay_us = max(50, min(HALF_CYCLE_US - 200, delay_us))
sm.put(delay_us // 2)
def execute_soft_start(target_percent):
"""Genera un incremento incremental (Rampa) hasta alcanzar la potencia objetivo."""
global current_power_target, in_soft_start
in_soft_start = True
print(f"Iniciando Rampa Suave: {current_power_target}% -> {target_percent}%")
# Subir paso a paso de 1% en 1%
while current_power_target < target_percent and system_enabled:
current_power_target += 1
set_raw_pio_power(current_power_target)
# Parpadeo rápido del LED indicando transición activa
led_pin.value(not led_pin.value())
time.sleep_ms(SOFT_START_MS)
in_soft_start = False
if system_enabled:
led_pin.high() # Dejar LED fijo una vez finalizada la rampa
# --- Manejador del Botón (Interrupción IRQ) ---
def button_handler(pin):
"""Manejador por interrupción para alternar el estado con guardado en Flash."""
global system_enabled, last_button_time, current_power_target
current_time = time.ticks_ms()
if time.ticks_diff(current_time, last_button_time) > DEBOUNCE_MS:
system_enabled = not system_enabled
last_button_time = current_time
# Guardar inmediatamente en Flash de manera persistente
save_state_to_flash()
if not system_enabled:
# Apagado inmediato por seguridad física
current_power_target = 0
set_raw_pio_power(0)
machine.Pin(TRIAC_PIN, machine.Pin.OUT).low()
print("\n[SISTEMA APAGADO] - Guardado en Flash.")
else:
print("\n[SISTEMA ENCENDIDO] - Iniciando rampa suave.")
btn_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler=button_handler)
# --- Funciones de Lectura Filtrada ---
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
# --- Configuración Inicial ---
load_state_from_flash()
# Llenado inicial del filtro ADC
for _ in range(FILTER_SIZE):
read_filtered_pot()
last_stable_adc = read_filtered_pot()
# Si arranca encendido, ejecuta la rampa desde 0 hasta el valor del potenciómetro
if system_enabled:
target_start = int((last_stable_adc / 65535.0) * 100)
execute_soft_start(target_start)
else:
set_raw_pio_power(0)
# --- Bucle Principal (Control del LED y Actualizaciones) ---
led_timer = 0
try:
while True:
if system_enabled:
current_adc = read_filtered_pot()
# Solo si no estamos en medio de la rampa inicial y hay cambios significativos
if not in_soft_start and abs(current_adc - last_stable_adc) > DEADBAND:
last_stable_adc = current_adc
current_power_target = int((last_stable_adc / 65535.0) * 100)
set_raw_pio_power(current_power_target)
print(f"Potencia Modificada: {current_power_target}%")
# Si terminó la rampa, asegurar que el LED permanezca fijo encendido
if not in_soft_start:
led_pin.high()
else:
# Efecto de respiración/parpadeo lento del LED cuando el sistema está en OFF
led_timer += 1
if led_timer % 10 == 0: # Cambia de estado cada ~500ms
led_pin.value(not led_pin.value())
time.sleep(0.05)
except KeyboardInterrupt:
sm.active(0)
machine.Pin(TRIAC_PIN, machine.Pin.OUT).low()
led_pin.low()
print("\nSistema apagado de forma segura.")