from machine import Pin, I2C, WDT, PWM
import time
import ssd1306
import ujson
import os
from micropython import const
# --- CONFIGURAÇÕES DE HARDWARE ---
SETTINGS_FILE = "settings.json"
I2C_SDA, I2C_SCL = const(0), const(1)
BTN_PLUS_PIN, BTN_MINUS_PIN = const(2), const(3)
BTN_DIGIT_PIN, BTN_START_PIN = const(4), const(5)
SOL1_PIN, SOL2_PIN = const(6), const(7)
BUZZER_PIN = const(15)
# --- CONFIGURAÇÕES TÉCNICAS ---
MIN_TIME_MS, MAX_TIME_MS = const(50), const(10800000)
PULSE_MS = const(80) # Duração do disparo do MOSFET
POST_PULSE_FINISH_MS = const(120)
DEBOUNCE_MS = const(100) # Anti-pulo dos botões
CYCLE_COOLDOWN_MS = const(500) # Proteção entre disparos
STATE_IDLE, STATE_COUNTING, STATE_FINISHING = const(0), const(1), const(2)
DIGIT_STEPS = [10000000, 1000000, 100000, 10000, 1000, 100, 10, 1]
# --- SEGMENTOS PARA NÚMEROS GIGANTES (ESTILO INDUSTRIAL) ---
SEG_MAP = {
'0': (1,1,1,1,1,1,0), '1': (0,1,1,0,0,0,0), '2': (1,1,0,1,1,0,1),
'3': (1,1,1,1,0,0,1), '4': (0,1,1,0,0,1,1), '5': (1,0,1,1,0,1,1),
'6': (1,0,1,1,1,1,1), '7': (1,1,1,0,0,0,0), '8': (1,1,1,1,1,1,1),
'9': (1,1,1,1,0,1,1)
}
# --- INICIALIZAÇÃO ---
i2c = I2C(0, sda=Pin(I2C_SDA), scl=Pin(I2C_SCL), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
wdt = WDT(timeout=5000)
sol1, sol2 = Pin(SOL1_PIN, Pin.OUT, value=0), Pin(SOL2_PIN, Pin.OUT, value=0)
buzzer = PWM(Pin(BUZZER_PIN), duty_u16=0)
setpoint_ms = 10000
selected_digit = 4
cycle_state = STATE_IDLE
last_status = "PRONTO"
screen_dirty = blink_state = True
last_blink_ms = last_display_ms = last_finish_time = 0
run_start_us = pulse_end_ms = finish_time_ms = 0
pulse_active = second_shot_done = False
# --- FUNÇÕES ---
def beep(f, d):
try:
buzzer.freq(f); buzzer.duty_u16(32768)
time.sleep_ms(d); buzzer.duty_u16(0)
except: pass
def save_data():
try:
with open("settings.tmp", "w") as f: ujson.dump({"sp": setpoint_ms}, f)
os.rename("settings.tmp", SETTINGS_FILE)
except: pass
def draw_seg_digit(x, y, char):
if char not in SEG_MAP: return
segs = SEG_MAP[char]
w, h, t = 10, 20, 2
if segs[0]: oled.fill_rect(x+1, y, w-2, t, 1)
if segs[1]: oled.fill_rect(x+w-t, y+1, t, h//2-1, 1)
if segs[2]: oled.fill_rect(x+w-t, y+h//2+1, t, h//2-1, 1)
if segs[3]: oled.fill_rect(x+1, y+h-t, w-2, t, 1)
if segs[4]: oled.fill_rect(x, y+h//2+1, t, h//2-1, 1)
if segs[5]: oled.fill_rect(x, y+1, t, h//2-1, 1)
if segs[6]: oled.fill_rect(x+1, y+h//2-1, w-2, t, 1)
def draw_ui(ms_val, is_idle=False):
oled.fill(0)
s_val, m_val = ms_val // 1000, ms_val % 1000
txt = "{:05d}{:03d}".format(s_val, m_val)
x_off = 2
for i, char in enumerate(txt):
if i == 5:
oled.fill_rect(x_off - 3, 30, 2, 2, 1)
x_off += 2
show = not (is_idle and i == selected_digit and not blink_state)
if show: draw_seg_digit(x_off, 10, char)
if is_idle and i == selected_digit: oled.fill_rect(x_off, 32, 10, 1, 1)
x_off += 13
oled.hline(0, 48, 128, 1)
oled.text(last_status, 2, 54)
oled.show()
class SmartButton:
def __init__(self, p):
self.p = Pin(p, Pin.IN, Pin.PULL_UP)
self.ls, self.lt, self.h, self.rt = 1, 0, False, 0
def update(self):
n, c = time.ticks_ms(), self.p.value()
if c != self.ls:
if time.ticks_diff(n, self.lt) > 100:
self.ls, self.lt = c, n
if c == 0: self.h, self.rt = True, time.ticks_add(n, 600); return "press"
self.h = False
return None
if self.h and c == 0 and time.ticks_diff(n, self.rt) >= 0:
self.rt = time.ticks_add(n, 200); return "press"
return None
btn_plus_s, btn_minus_s = SmartButton(BTN_PLUS_PIN), SmartButton(BTN_MINUS_PIN)
btn_digit_s, btn_start_s = SmartButton(BTN_DIGIT_PIN), SmartButton(BTN_START_PIN)
def update_cycle():
global cycle_state, second_shot_done, pulse_active, last_status, finish_time_ms, last_finish_time, pulse_end_ms
now = time.ticks_ms()
# DESLIGAMENTO PRIORITÁRIO (Sem atraso de visor)
if pulse_active and time.ticks_diff(now, pulse_end_ms) >= 0:
sol1.off(); sol2.off(); pulse_active = False
if cycle_state == STATE_COUNTING:
curr_ms = time.ticks_diff(time.ticks_us(), run_start_us) / 1000
if not second_shot_done and curr_ms >= setpoint_ms:
sol1.on(); sol2.on(); pulse_active = True
pulse_end_ms = time.ticks_add(now, PULSE_MS)
second_shot_done = True
finish_time_ms = time.ticks_add(now, POST_PULSE_FINISH_MS)
cycle_state = STATE_FINISHING
elif cycle_state == STATE_FINISHING:
if time.ticks_diff(now, finish_time_ms) >= 0 and not pulse_active:
cycle_state, last_finish_time, last_status = STATE_IDLE, now, "SUCESSO"
save_data(); beep(1200, 200)
# --- INÍCIO ---
try:
with open(SETTINGS_FILE, "r") as f: setpoint_ms = ujson.load(f).get("sp", 10000)
except: pass
draw_ui(setpoint_ms, True)
while True:
wdt.feed()
update_cycle() # 1ª Prioridade: Controle do Tempo
t = time.ticks_ms()
if cycle_state == STATE_IDLE:
if time.ticks_diff(t, last_blink_ms) > 300:
blink_state, last_blink_ms, screen_dirty = not blink_state, t, True
if btn_plus_s.update() == "press":
setpoint_ms = min(MAX_TIME_MS, setpoint_ms + DIGIT_STEPS[selected_digit])
beep(2000, 20); screen_dirty = True
if btn_minus_s.update() == "press":
setpoint_ms = max(MIN_TIME_MS, setpoint_ms - DIGIT_STEPS[selected_digit])
beep(1800, 20); screen_dirty = True
if btn_digit_s.update() == "press":
selected_digit = (selected_digit + 1) % 8
beep(1500, 20); screen_dirty = True
if btn_start_s.update() == "press":
if time.ticks_diff(t, last_finish_time) > CYCLE_COOLDOWN_MS:
run_start_us, second_shot_done = time.ticks_us(), False
sol1.on(); sol2.on(); pulse_active = True
pulse_end_ms = time.ticks_add(t, PULSE_MS)
cycle_state, last_status = STATE_COUNTING, "RODANDO"; beep(2500, 40)
if screen_dirty:
draw_ui(setpoint_ms, True); screen_dirty = False
else:
# Modo Execução: Botão Stop
if btn_start_s.update() == "press":
sol1.off(); sol2.off(); pulse_active = False
cycle_state, last_status, screen_dirty = STATE_IDLE, "PAROU", True; beep(600, 150)
# ATUALIZAÇÃO DO VISOR COM "ZONA DE SILÊNCIO"
# Se o pulso estiver ativo, NÃO chamamos draw_ui para não atrasar o MOSFET
if not pulse_active and time.ticks_diff(t, last_display_ms) > 120:
curr = int(time.ticks_diff(time.ticks_us(), run_start_us)//1000)
draw_ui(curr, False)
last_display_ms = t
time.sleep_ms(1)