import machine
import utime
import dht
from pico_i2c_lcd import I2cLcd
# ==========================================
# 1. CONFIGURAÇÃO DOS PINOS (GPIO)
# ==========================================
# Sensores DHT22
dht1 = dht.DHT22(machine.Pin(2))
dht2 = dht.DHT22(machine.Pin(3))
# Configuração do LCD I2C (Barramento I2C0, SDA=GP4, SCL=GP5)
i2c = machine.I2C(0, sda=machine.Pin(4), scl=machine.Pin(5), freq=100000)
# Endereço I2C comum: 0x27. Altere para 0x3F se necessário.
lcd = I2cLcd(i2c, 0x27, 2, 16)
# Atuadores PWM (Frequência padrão de 1000Hz)
# O PWM do Pico possui resolução nativa de 16 bits (valores de 0 a 65535)
pwm_vent = machine.PWM(machine.Pin(6))
pwm_vent.freq(1000)
pwm_rele = machine.PWM(machine.Pin(7))
pwm_rele.freq(1000)
# Botões com pull-up interno ativo
btn_inc = machine.Pin(8, machine.Pin.IN, machine.Pin.PULL_UP)
btn_dec = machine.Pin(9, machine.Pin.IN, machine.Pin.PULL_UP)
# ==========================================
# 2. PARÂMETROS DO PID E VARIÁVEIS DE CONTROLE
# ==========================================
# PID Temperatura
Kp_T, Ki_T, Kd_T = 25.0, 0.1, 1.0
erroAnterior_T = 0.0
integral_T = 0.0
# PID Umidade
Kp_H, Ki_H, Kd_H = 22.0, 0.05, 0.5
erroAnterior_H = 0.0
integral_H = 0.0
# Setpoints iniciais
tempDesejada = 35.0
umiDesejada = 55.0
# Controle de Tempo (Debounce e Loop)
ultimoTempoBotao = 0
delayDebounce = 200 # milissegundos
tempoAnterior = utime.ticks_ms()
# ==========================================
# 3. FUNÇÕES AUXILIARES
# ==========================================
def atualizarDisplaySetpoint():
"""Atualiza apenas o Setpoint na tela quando alterado."""
lcd.move_to(9, 0)
lcd.putstr(f"SP:{tempDesejada:.1f}")
# Caractere de grau (grau no LCD Hitachi padrão costuma ser o index 223 ou customizado)
lcd.putchar(chr(223))
lcd.putstr(" ")
def limitar(valor, min_v, max_v):
"""Limita um valor entre o mínimo e máximo."""
return max(min_v, min(valor, max_v))
def mapear_e_aplicar_pwm(pino_pwm, valor_pid):
"""
Mapeia a saída antiga do Arduino (0-255) para
a resolução de 16 bits do MicroPython (0-65535).
"""
# Garante limite estrito de segurança
valor_pid = limitar(valor_pid, 0, 255)
# Converte de 0-255 para 0-65535
duty_16bit = int((valor_pid / 255.0) * 65535)
pino_pwm.duty_u16(duty_16bit)
# ==========================================
# 4. INICIALIZAÇÃO DO SISTEMA
# ==========================================
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("Sumry 10.2kW PID")
lcd.move_to(0, 1)
lcd.putstr("Iniciando...")
utime.sleep(2)
lcd.clear()
# Loop Principal
while True:
tempoAtual = utime.ticks_ms()
# --- LEITURA DOS BOTÕES (DEBOUNCE) ---
if utime.ticks_diff(tempoAtual, ultimoTempoBotao) > delayDebounce:
if btn_inc.value() == 0: # Botão pressionado vai para LOW (0)
tempDesejada += 0.5
ultimoTempoBotao = tempoAtual
atualizarDisplaySetpoint()
elif btn_dec.value() == 0:
tempDesejada -= 0.5
ultimoTempoBotao = tempoAtual
atualizarDisplaySetpoint()
# Cálculo do dt (tempo decorrido em segundos)
dt = utime.ticks_diff(tempoAtual, tempoAnterior) / 1000.0
# Executa o cálculo do PID estritamente a cada 1 segundo (ou mais)
if dt >= 1.0:
try:
# Leituras do Sensor 1
dht1.measure()
h1 = dht1.humidity()
t1 = dht1.temperature()
# Leituras do Sensor 2
dht2.measure()
h2 = dht2.humidity()
t2 = dht2.temperature()
except OSError as e:
# Substitui o "isnan" do C++ capturando a falha de comunicação física do sensor
print("Falha na leitura dos sensores DHT!")
lcd.move_to(0, 0)
lcd.putstr("Erro Sensores ")
tempoAnterior = tempoAtual
continue
# --- CÁLCULO DAS MÉDIAS ---
tempMedia = (t1 + t2) / 2.0
umiMedia = (h1 + h2) / 2.0
# ==========================================
# PID DE TEMPERATURA (Controle Exaustor)
# ==========================================
erro_T = tempMedia - tempDesejada
P_T = Kp_T * erro_T
integral_T += erro_T * dt
integral_T = limitar(integral_T, -100.0, 100.0)
I_T = Ki_T * integral_T
derivativa_T = (erro_T - erroAnterior_T) / dt
D_T = Kd_T * derivativa_T
saidaPID_T = P_T + I_T + D_T
saidaPID_T = limitar(saidaPID_T, 0, 255)
mapear_e_aplicar_pwm(pwm_vent, saidaPID_T)
erroAnterior_T = erro_T
# ==========================================
# PID DE UMIDADE (Controle Desumidificador)
# ==========================================
erro_H = umiMedia - umiDesejada
P_H = Kp_H * erro_H
integral_H += erro_H * dt
integral_H = limitar(integral_H, -100.0, 100.0)
I_H = Ki_H * integral_H
derivativa_H = (erro_H - erroAnterior_H) / dt
D_H = Kd_H * derivativa_H
saidaPID_H = P_H + I_H + D_H
saidaPID_H = limitar(saidaPID_H, 0, 255)
mapear_e_aplicar_pwm(pwm_rele, saidaPID_H)
erroAnterior_H = erro_H
# Atualiza a marcação de tempo do ciclo
tempoAnterior = tempoAtual
# ==========================================
# EXIBIÇÃO NO DISPLAY LCD I2C
# ==========================================
# Linha 1
lcd.move_to(0, 0)
lcd.putstr(f"T:{tempMedia:.1f}C")
lcd.putchar(chr(223))
lcd.putstr(f"SP:{tempDesejada:.1f}")
lcd.putchar(chr(223))
# Linha 2
pctPWM_H = int((saidaPID_H / 255.0) * 100)
lcd.move_to(0, 1)
lcd.putstr(f"U:{int(umiMedia)}%D:{pctPWM_H}%C:{int(saidaPID_T)} ")
# ==========================================
# MONITOR SERIAL (Terminal REPL)
# ==========================================
print(f"T1: {t1:.1f}°C | T2: {t2:.1f}°C | T_Média: {tempMedia:.1f}°C | PWM_Vent: {int(saidaPID_T)}")
print(f"H1: {h1:.0f}% | H2: {h2:.0f}% | H_Média: {umiMedia:.0f}% | PWM_Umi: {int(saidaPID_H)}")
print("-" * 60)
# Uma pequena pausa para não travar o processador do Pico desnecessariamente
utime.sleep_ms(10)