# main.py
# Control de temperatura para huerta automática con RP2040 (Pi Pico)
from machine import Pin, PWM, I2C
import utime
# Importar las librerías externas que deben estar en el Pico
import ssd1306
import onewire
import ds18x20
## --- CONFIGURACIÓN DE PINES Y PARÁMETROS ---
# 🌡️ Sensor de Temperatura DS18B20 (Conexión GP28)
PIN_DATA_DS18B20 = 28
ds_pin = Pin(PIN_DATA_DS18B20)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))
roms = ds_sensor.scan()
if not roms:
print("❌ ERROR: No se encontró sensor DS18B20. Revisar cableado (pull-up 4.7k!).")
# 🖥️ Pantalla OLED I2C SSD1306 (Conexión GP4/SDA, GP5/SCL)
I2C_SDA = 4
I2C_SCL = 5
OLED_WIDTH = 128
OLED_HEIGHT = 64 # Usar 32 si tu pantalla es de 128x32
i2c = I2C(0, sda=Pin(I2C_SDA), scl=Pin(I2C_SCL), freq=400000)
oled = ssd1306.SSD1306_I2C(OLED_WIDTH, OLED_HEIGHT, i2c)
print("OLED inicializado.")
# 🚪 Servomotor (Compuerta) - GP14
PIN_SERVO = 14
servo_pwm = PWM(Pin(PIN_SERVO))
servo_pwm.freq(50) # 50 Hz = Periodo de 20ms
# Cálculo del Duty Cycle en Nanosegundos (ns) para control de ángulo
DUTY_MIN_NS = 500000 # ~0.5ms para 0 grados (Cerrada)
DUTY_MAX_NS = 2500000 # ~2.5ms para 180 grados
# Se define el valor específico para la apertura a 160°:
# Valor = 0.5ms + (160/180) * (2.0ms)
DUTY_160_NS = int(DUTY_MIN_NS + (160 / 180) * (DUTY_MAX_NS - DUTY_MIN_NS))
# 💨 Motor Paso a Paso (Ventilador) - Driver A4988 (Conexión GP16/DIR, GP17/STEP)
PIN_DIR_MOTOR = 16
PIN_STEP_MOTOR = 17
dir_pin = Pin(PIN_DIR_MOTOR, Pin.OUT)
step_pin = Pin(PIN_STEP_MOTOR, Pin.OUT)
steps_per_rev = 200 # Pasos por revolución (Full-step)
step_delay_us = 1000 # Retardo entre pasos (1 ms = velocidad baja)
# ⚙️ Parámetros de Control (Histéresis)
TEMP_MAX_ABRIR = 50.0 # Abrir compuerta y encender ventilador a 50.0°C
TEMP_MIN_CERRAR = 49.0 # Cerrar compuerta y apagar ventilador a 49.0°C
# 📝 Variables de Estado
ventilador_encendido = False
compuerta_abierta = False
## --- FUNCIONES DE CONTROL ---
def set_servo_angle(angle):
"""Ajusta el ángulo del servomotor usando duty_ns() para máxima precisión."""
# Uso del valor pre-calculado para 160° o 0° directamente
if angle >= 160:
duty_ns = DUTY_160_NS
else: # 0 grados
duty_ns = DUTY_MIN_NS
servo_pwm.duty_ns(duty_ns)
def motor_step(steps, direction):
"""Genera pasos en el motor. La dirección se controla con el pin DIR (0 o 1)."""
dir_pin.value(direction)
for _ in range(steps):
step_pin.value(1)
utime.sleep_us(step_delay_us)
step_pin.value(0)
utime.sleep_us(step_delay_us)
def ventilador_on():
global ventilador_encendido
if not ventilador_encendido:
# En la vida real, el ventilador giraría continuamente
# Aquí, le damos un pulso de inicio y cambiamos el estado
motor_step(steps_per_rev, 0)
ventilador_encendido = True
def ventilador_off():
global ventilador_encendido
if ventilador_encendido:
# Simplemente se detiene el envío de pulsos
ventilador_encendido = False
def read_temp():
"""Lee la temperatura del sensor DS18B20."""
if not roms:
return 0.0 # Devuelve 0.0 si el sensor no fue encontrado
try:
ds_sensor.convert_temp()
utime.sleep_ms(750) # Espera conversión del DS18B20
temp_c = ds_sensor.read_temp(roms[0])
return temp_c
except Exception as e:
print(f"Error en lectura de temperatura: {e}")
return 0.0
def control_puerta_ventilador(temp):
global compuerta_abierta
# Condición de Calentamiento: Si la temperatura ALCANZA o EXCEDE el límite alto
if temp >= TEMP_MAX_ABRIR:
if not compuerta_abierta:
set_servo_angle(160)
compuerta_abierta = True
ventilador_on()
# Condición de Enfriamiento: Si la temperatura CAE POR DEBAJO del límite bajo (Histéresis)
elif temp <= TEMP_MIN_CERRAR:
if compuerta_abierta:
set_servo_angle(0)
compuerta_abierta = False
ventilador_off()
# Si la temperatura está entre TEMP_MIN_CERRAR y TEMP_MAX_ABRIR, el estado se MANTIENE.
# (Esto evita que el sistema se encienda y apague rápidamente cerca del punto de consigna).
def update_oled(temp, estado_vent, estado_comp):
"""Muestra los datos en la pantalla OLED."""
oled.fill(0)
oled.text("--- GONDOLA HVAC ---", 0, 0)
# Formatear la temperatura con dos decimales
temp_text = f"Temp: {temp:.2f} C"
oled.text(temp_text, 0, 16)
# Estado del ventilador
vent_state = "ON" if estado_vent else "OFF"
oled.text(f"Ventilador: {vent_state}", 0, 32)
# Estado de la compuerta
comp_state = "ABIERTA (160)" if estado_comp else "CERRADA (0)"
oled.text(f"Compuerta: {comp_state}", 0, 48)
oled.show()
## --- LOOP PRINCIPAL ---
def main():
print("Iniciando sistema de control de temperatura...")
# Estado inicial
set_servo_angle(0)
ventilador_off()
while True:
# 1. Leer Temperatura
current_temp = read_temp()
# 2. Aplicar Lógica de Control
control_puerta_ventilador(current_temp)
# 3. Actualizar Pantalla (Muestra el estado actual)
update_oled(current_temp, ventilador_encendido, compuerta_abierta)
# 4. Simulación de rotación continua del ventilador
if ventilador_encendido:
# Genera un paso cada ciclo (manteniendo una rotación lenta/simulada)
motor_step(1, 0)
utime.sleep(2) # Espera 2 segundos antes de la siguiente iteración
if __name__ == '__main__':
main()