from machine import Pin, I2C, PWM
import utime as time
import ssd1306
import ds1307
# ================================
# PINES
# ================================
PIN_BTN_SET = 26 # SET con pull-up interno (botón a GND, activo LOW)
PIN_BTN_RESET = 27 # RESET con pull-up interno (botón a GND, activo LOW)
PIN_LED = 13
PIN_BUZZER = 12 # ⚠️ Si da problemas al arrancar, mover a 14/25/27
PIN_SERVO = 32
row_pins = [19, 18, 5, 17]
col_pins = [16, 4, 2, 15]
keys = [
['1','2','3','A'],
['4','5','6','B'],
['7','8','9','C'],
['*','0','#','D']
]
# ================================
# HARDWARE
# ================================
btn_set = Pin(PIN_BTN_SET, Pin.IN, Pin.PULL_UP)
btn_reset = Pin(PIN_BTN_RESET, Pin.IN, Pin.PULL_UP)
led = Pin(PIN_LED, Pin.OUT)
# Buzzer en PWM (tono continuo)
BUZZER_FREQ = 2000 # Hz
BUZZER_DUTY = 512 # ~50% (0-1023)
buzzer_pwm = PWM(Pin(PIN_BUZZER), freq=BUZZER_FREQ)
buzzer_pwm.duty(0) # apagado inicial
# Servo (50 Hz). En hardware real, calibra duty o usa duty_ns si está disponible.
servo = PWM(Pin(PIN_SERVO), freq=50)
servo.duty(30) # posición inicial
# Keypad
rows = [Pin(pin, Pin.OUT) for pin in row_pins]
cols = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in col_pins]
# I2C (OLED + RTC)
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
rtc = ds1307.DS1307(i2c)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
# ================================
# ESTADOS
# ================================
CLOCK_MODE = 0
SET_MODE = 1
READY_MODE = 2
ALARM_MODE = 3
currentState = CLOCK_MODE
alarmSet = False
alarmHour = -1
alarmMinute = -1
servoActivated = False
inputBuffer = ""
lastDisplayUpdate_ms = time.ticks_ms()
displayInterval_ms = 500 # 0.5 s
ignoreResetUntil_ms = 0 # ventana anti-glitch al entrar a ALARM (ms)
# ================================
# UTILIDADES
# ================================
def now():
y, m, d, w, hh, mm, ss, _ = rtc.datetime()
return hh, mm, ss
def button_press_and_hold(pin, hold_ms=150):
"""
Devuelve True si el botón estuvo en LOW continuo >= hold_ms.
Wiring esperado: NO -> GND con pull-up (activo LOW).
"""
if pin.value() == 0:
t0 = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), t0) < hold_ms:
if pin.value() != 0:
return False
return True
return False
# ================================
# KEYPAD
# ================================
def read_keypad():
for r in range(4):
rows[r].value(1)
for r in range(4):
rows[r].value(0)
for c in range(4):
if cols[c].value() == 0:
while cols[c].value() == 0:
time.sleep_ms(10)
rows[r].value(1)
return keys[r][c]
rows[r].value(1)
return None
# ================================
# DISPLAY
# ================================
def show_clock():
oled.fill(0)
hh, mm, ss = now()
oled.text("Hora:", 0, 0)
oled.text(f"{hh:02d}:{mm:02d}:{ss:02d}", 0, 16)
oled.text("Alarma:", 0, 40)
if alarmSet:
oled.text(f"{alarmHour:02d}:{alarmMinute:02d}", 64, 40)
else:
oled.text("--:--", 64, 40)
oled.show()
def show_set():
oled.fill(0)
oled.text("Ingresa HHMM:", 0, 0)
s = inputBuffer + "_" * (4 - len(inputBuffer))
oled.text(f"{s[0]}{s[1]}:{s[2]}{s[3]}", 0, 25)
oled.text("A=OK B=Borrar", 0, 55)
oled.show()
def show_alarm():
oled.fill(0)
oled.text(" ALARMA !!!", 0, 0)
oled.text("Hora:", 0, 30)
oled.text(f"{alarmHour:02d}:{alarmMinute:02d}", 40, 30)
oled.text("Presiona RESET", 0, 50)
oled.show()
# ================================
# ESTADOS
# ================================
def enter_set_mode():
global currentState, inputBuffer
currentState = SET_MODE
inputBuffer = ""
show_set()
def enter_alarm():
global currentState, alarmSet, servoActivated, ignoreResetUntil_ms
currentState = ALARM_MODE
alarmSet = False
# Ventana anti-glitch: 1 s sin aceptar RESET (sube a 3000 ms si ves ruido)
ignoreResetUntil_ms = time.ticks_add(time.ticks_ms(), 1000)
# Activar servo solo una vez
if not servoActivated:
servoActivated = True
servo.duty(90)
time.sleep_ms(600)
servo.duty(30)
show_alarm()
def reset_clock():
global currentState, alarmSet, alarmHour, alarmMinute, servoActivated, inputBuffer
alarmSet = False
alarmHour = -1
alarmMinute = -1
servoActivated = False
inputBuffer = ""
led.value(0)
buzzer_pwm.duty(0) # apagar el tono
servo.duty(30)
currentState = CLOCK_MODE
show_clock()
# ================================
# MAIN LOOP
# ================================
print("Sistema iniciado...")
while True:
# BOTÓN SET (solo CLOCK o READY)
if currentState in (CLOCK_MODE, READY_MODE) and button_press_and_hold(btn_set, hold_ms=150):
enter_set_mode()
time.sleep_ms(300)
# BOTÓN RESET SOLO EN ALARMA (con ventana anti-glitch)
if currentState == ALARM_MODE:
# Mantener outputs encendidos en ALARM
led.value(1)
buzzer_pwm.duty(BUZZER_DUTY) # tono continuo
# Acepta RESET sólo después de la ventana anti-glitch
if time.ticks_diff(time.ticks_ms(), ignoreResetUntil_ms) >= 0:
if button_press_and_hold(btn_reset, hold_ms=600):
# print(">> RESET detectado, volviendo a CLOCK") # descomenta para depurar
reset_clock()
time.sleep_ms(300)
else:
# En cualquier otro estado, outputs apagados
led.value(0)
buzzer_pwm.duty(0)
# KEYPAD SOLO EN MODO SET
if currentState == SET_MODE:
key = read_keypad()
if key:
if key.isdigit() and len(inputBuffer) < 4:
inputBuffer += key
show_set()
elif key == "B" and inputBuffer:
inputBuffer = inputBuffer[:-1]
show_set()
elif key == "A" and len(inputBuffer) == 4:
hh = int(inputBuffer[:2])
mm = int(inputBuffer[2:4])
if 0 <= hh < 24 and 0 <= mm < 60:
alarmHour = hh
alarmMinute = mm
alarmSet = True
currentState = READY_MODE
show_clock()
# DISPLAY UPDATE (cada 0.5 s)
if time.ticks_diff(time.ticks_ms(), lastDisplayUpdate_ms) > displayInterval_ms:
lastDisplayUpdate_ms = time.ticks_ms()
if currentState in (CLOCK_MODE, READY_MODE):
show_clock()
elif currentState == SET_MODE:
show_set()
elif currentState == ALARM_MODE:
show_alarm()
# CHECK ALARM continuo
if alarmSet and currentState in (CLOCK_MODE, READY_MODE):
hh, mm, ss = now()
if hh == alarmHour and mm == alarmMinute:
enter_alarm()
time.sleep_ms(50)