# ------------------------------------------------------------
# Sistema de riego automatizado - Fase 4
# Raspberry Pi Pico + LCD I2C + Sensores + Bombas
# Lenguaje: MicroPython
# Carlos Alexander Achicanoy Moncayo
# Sistemas Embebidos
# ------------------------------------------------------------
from machine import Pin, I2C, ADC
from time import sleep_ms, ticks_ms
# ------------------------------------------------------------
# CONFIGURACIÓN DE HARDWARE
# ------------------------------------------------------------
# I2C para LCD (PCF8574 + LCD 20x4, dirección 0x27 típica)
I2C_ID = 0
I2C_SDA = 0 # GP0
I2C_SCL = 1 # GP1
LCD_ADDR = 0x27
LCD_COLS = 20
LCD_ROWS = 4
# Sensores analógicos
adc_humedad = ADC(26) # GP26
adc_temp = ADC(27) # GP27
# Sensores de nivel ON/OFF (tanques)
N1_T1 = Pin(14, Pin.IN, Pin.PULL_DOWN)
N2_T1 = Pin(15, Pin.IN, Pin.PULL_DOWN)
N1_T2 = Pin(16, Pin.IN, Pin.PULL_DOWN)
N2_T2 = Pin(17, Pin.IN, Pin.PULL_DOWN)
# Salidas para bombas (pueden ser LEDs o relés en Wokwi)
bomba1_pin = Pin(10, Pin.OUT)
bomba2_pin = Pin(11, Pin.OUT)
# Botones de navegación
btn_anterior = Pin(2, Pin.IN, Pin.PULL_DOWN)
btn_siguiente = Pin(3, Pin.IN, Pin.PULL_DOWN)
btn_ok = Pin(4, Pin.IN, Pin.PULL_DOWN)
btn_regresar = Pin(5, Pin.IN, Pin.PULL_DOWN)
# ------------------------------------------------------------
# DRIVER SIMPLE PARA LCD I2C (HD44780 + PCF8574)
# ------------------------------------------------------------
# Comandos del LCD
LCD_CLR = 0x01
LCD_HOME = 0x02
LCD_ENTRY_MODE = 0x06
LCD_DISPLAY_ON = 0x0C
LCD_FUNCTION_SET = 0x28 # 4 bits, 2 líneas, 5x8
# Bits PCF8574 (pueden variar según módulo)
LCD_BACKLIGHT = 0x08
ENABLE = 0x04
class I2cLcd:
def __init__(self, i2c, addr, cols, rows):
self.i2c = i2c
self.addr = addr
self.cols = cols
self.rows = rows
self.backlight = LCD_BACKLIGHT
self._init_lcd()
def _write_byte(self, data):
self.i2c.writeto(self.addr, bytes([data | self.backlight]))
def _pulse_enable(self, data):
self._write_byte(data | ENABLE)
sleep_ms(1)
self._write_byte(data & ~ENABLE)
sleep_ms(1)
def _send_nibble(self, nibble, rs):
data = (nibble << 4) | rs
self._pulse_enable(data)
def _send_byte(self, value, rs):
high = (value >> 4) & 0x0F
low = value & 0x0F
self._send_nibble(high, rs)
self._send_nibble(low, rs)
def cmd(self, value):
self._send_byte(value, 0)
def write_char(self, char):
self._send_byte(ord(char), 1)
def clear(self):
self.cmd(LCD_CLR)
sleep_ms(2)
def home(self):
self.cmd(LCD_HOME)
sleep_ms(2)
def move_to(self, row, col):
# Direcciones base por fila (para 20x4)
row_offsets = [0x00, 0x40, 0x14, 0x54]
addr = 0x80 | (row_offsets[row] + col)
self.cmd(addr)
def putstr(self, text):
for ch in text:
self.write_char(ch)
def write_line(self, row, text):
# Rellenar con espacios manualmente (sin usar ljust)
if len(text) < self.cols:
txt = text + " " * (self.cols - len(text))
else:
txt = text[:self.cols]
self.move_to(row, 0)
self.putstr(txt)
def _init_lcd(self):
sleep_ms(20)
# Secuencia de inicio estándar 4 bits
self._send_nibble(0x03, 0)
sleep_ms(5)
self._send_nibble(0x03, 0)
sleep_ms(5)
self._send_nibble(0x03, 0)
sleep_ms(5)
self._send_nibble(0x02, 0) # 4 bits
self.cmd(LCD_FUNCTION_SET)
self.cmd(LCD_DISPLAY_ON)
self.cmd(LCD_CLR)
self.cmd(LCD_ENTRY_MODE)
sleep_ms(2)
# Inicialización de la LCD
i2c = I2C(I2C_ID, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=400000)
lcd = I2cLcd(i2c, LCD_ADDR, LCD_COLS, LCD_ROWS)
# ------------------------------------------------------------
# VARIABLES GLOBALES DE ESTADO
# ------------------------------------------------------------
# Reloj interno
reloj_hora = 15
reloj_min = 30
reloj_seg = 0
acum_ms_reloj = 0
# Modos: 0 = temporizado, 1 = control
modo_operacion = 0
# Pantalla actual (0 a 4)
pantalla_actual = 0
# Intervalo modo temporizado
hora_inicio = [15, 35, 0] # hh,mm,ss
hora_fin = [15, 40, 0] # hh,mm,ss
# Parámetros de riego
Ref_humedad_Low1 = 30.0
Ref_humedad_Low2 = 35.0
Ref_hum = 50.0
Ref_Temp_High = 30.0
Ref_Temp = 23.0
Area = 10.0
Prof = 0.20
Caudal_bombas = 5.0 # L/s
# Estado de riego
irrigacion_activa = False
modo_riego_actual = None # "TEMP" o "CONTROL"
volumen_calculado = 0.0
tiempo_riego_total = 0.0
tiempo_riego_restante = 0.0
# Alternancia de tanques / bombas
tanque_en_uso = 1 # 1 o 2
bomba_activa = 0 # 0=ninguna, 1=bomba1, 2=bomba2
# Estado botones (para detectar flancos)
prev_anterior = 0
prev_siguiente = 0
prev_ok = 0
prev_regresar = 0
# ------------------------------------------------------------
# FUNCIONES DE LECTURA DE SENSORES
# ------------------------------------------------------------
def leer_humedad():
# ADC 16 bits -> 0-100%
valor = adc_humedad.read_u16() # 0 - 65535
porcentaje = (valor / 65535.0) * 100.0
return porcentaje
def leer_temperatura():
# ADC 16 bits -> mapear 0-65535 a rango 15-40 °C aprox
valor = adc_temp.read_u16()
temp = 15.0 + (valor / 65535.0) * 25.0
return temp
# ------------------------------------------------------------
# FUNCIONES PARA BOMBAS Y TANQUES
# ------------------------------------------------------------
def apagar_bombas():
global bomba_activa
bomba1_pin.value(0)
bomba2_pin.value(0)
bomba_activa = 0
def seleccionar_tanque_con_nivel():
"""
Retorna 1 o 2 según tanque con nivel suficiente (N2_Tx en ON).
Si ningún tanque tiene N2 en ON, retorna 0.
Usa alternancia tanque_en_uso como prioridad.
"""
global tanque_en_uso
# Estados de nivel alto (N2)
t1_ok = (N2_T1.value() == 1)
t2_ok = (N2_T2.value() == 1)
if t1_ok and t2_ok:
# Alternar entre 1 y 2
if tanque_en_uso == 1:
tanque_en_uso = 2
else:
tanque_en_uso = 1
return tanque_en_uso
elif t1_ok:
tanque_en_uso = 1
return 1
elif t2_ok:
tanque_en_uso = 2
return 2
else:
return 0
def activar_bomba_por_tanque(tanque):
global bomba_activa
if tanque == 1:
bomba1_pin.value(1)
bomba2_pin.value(0)
bomba_activa = 1
elif tanque == 2:
bomba1_pin.value(0)
bomba2_pin.value(1)
bomba_activa = 2
else:
apagar_bombas()
def revisar_nivel_bomba():
"""
Verifica durante el riego si el tanque en uso se queda sin nivel (N1 OFF).
Si se queda sin agua, intenta cambiar al otro tanque.
"""
global tanque_en_uso, irrigacion_activa, tiempo_riego_restante
if not irrigacion_activa or bomba_activa == 0:
return
if tanque_en_uso == 1:
n1 = N1_T1.value()
else:
n1 = N1_T2.value()
# Si nivel < N1 (n1 == 0), intentar cambiar de tanque
if n1 == 0:
otro = 2 if tanque_en_uso == 1 else 1
if otro == 1:
ok = (N2_T1.value() == 1)
else:
ok = (N2_T2.value() == 1)
if ok:
tanque_en_uso = otro
activar_bomba_por_tanque(tanque_en_uso)
else:
# Ningún tanque disponible -> detener riego
apagar_bombas()
irrigacion_activa = False
tiempo_riego_restante = 0
# ------------------------------------------------------------
# CÁLCULO DE VOLUMEN Y TIEMPO DE RIEGO
# ------------------------------------------------------------
def calcular_volumen_agua(hum_suelo, temp_ambiente):
"""
Usa las ecuaciones del enunciado:
Vol_agua = Area * Prof * 1000 * (Ref_hum - hum_suelo)/100
Factor ajuste = 1 + (Temp - Ref_temp)/100
Vol_total = Vol_agua * Factor
"""
delta_h = Ref_hum - hum_suelo
if delta_h <= 0:
return 0.0
vol_base = Area * Prof * 1000.0 * (delta_h / 100.0)
factor_ajuste = 1.0 + ((temp_ambiente - Ref_Temp) / 100.0)
vol_total = vol_base * factor_ajuste
if vol_total < 0:
vol_total = 0
return vol_total
def calcular_tiempo_riego(volumen_litros):
if Caudal_bombas <= 0:
return 0.0
return volumen_litros / Caudal_bombas # segundos
# ------------------------------------------------------------
# RELOJ INTERNO
# ------------------------------------------------------------
def incrementar_reloj(segundos):
global reloj_hora, reloj_min, reloj_seg
reloj_seg += segundos
while reloj_seg >= 60:
reloj_seg -= 60
reloj_min += 1
while reloj_min >= 60:
reloj_min -= 60
reloj_hora += 1
while reloj_hora >= 24:
reloj_hora -= 24
def hora_a_segundos(h, m, s):
return h*3600 + m*60 + s
def reloj_en_segundos():
return hora_a_segundos(reloj_hora, reloj_min, reloj_seg)
def intervalo_activo():
"""
Retorna True si la hora actual está dentro del intervalo
[hora_inicio, hora_fin), asumiendo mismo día.
"""
actual = reloj_en_segundos()
ini = hora_a_segundos(*hora_inicio)
fin = hora_a_segundos(*hora_fin)
return ini <= actual < fin
# ------------------------------------------------------------
# MANEJO DE MODOS DE OPERACIÓN
# ------------------------------------------------------------
def iniciar_riego(modo, hum_suelo, temp_ambiente):
global irrigacion_activa, modo_riego_actual
global volumen_calculado, tiempo_riego_total, tiempo_riego_restante
global tanque_en_uso
# Seleccionar tanque disponible
tanque = seleccionar_tanque_con_nivel()
if tanque == 0:
# No hay tanques con nivel suficiente
apagar_bombas()
irrigacion_activa = False
modo_riego_actual = None
volumen_calculado = 0.0
tiempo_riego_total = 0.0
tiempo_riego_restante = 0.0
print("Aviso: Ningún tanque tiene nivel suficiente.")
return
# Calcular volumen y tiempo
volumen_calculado = calcular_volumen_agua(hum_suelo, temp_ambiente)
if volumen_calculado <= 0:
# No requiere agua
apagar_bombas()
irrigacion_activa = False
modo_riego_actual = None
tiempo_riego_total = 0.0
tiempo_riego_restante = 0.0
return
tiempo_riego_total = calcular_tiempo_riego(volumen_calculado)
tiempo_riego_restante = tiempo_riego_total
irrigacion_activa = True
modo_riego_actual = modo
tanque_en_uso = tanque
activar_bomba_por_tanque(tanque_en_uso)
print("Riego iniciado - modo:", modo, "Volumen:", volumen_calculado, "L Tiempo:", tiempo_riego_total, "s")
def actualizar_modo_temporizado(hum_suelo, temp_ambiente, dt):
global irrigacion_activa, modo_riego_actual, tiempo_riego_restante
if intervalo_activo():
if not irrigacion_activa:
iniciar_riego("TEMP", hum_suelo, temp_ambiente)
else:
# Ya está irrigando, descontar tiempo
if tiempo_riego_restante > 0:
tiempo_riego_restante -= dt
revisar_nivel_bomba()
else:
apagar_bombas()
irrigacion_activa = False
else:
# Fuera del intervalo -> apagar bombas
if irrigacion_activa and modo_riego_actual == "TEMP":
apagar_bombas()
irrigacion_activa = False
tiempo_riego_restante = 0
def actualizar_modo_control(hum_suelo, temp_ambiente, dt):
global irrigacion_activa, modo_riego_actual, tiempo_riego_restante
global Ref_humedad_Low1, Ref_humedad_Low2, Ref_Temp_High
# Condición 1
cond1 = (hum_suelo < Ref_humedad_Low1)
# Condición 2
cond2 = (temp_ambiente > Ref_Temp_High and hum_suelo < Ref_humedad_Low2)
if (cond1 or cond2):
if not irrigacion_activa:
iniciar_riego("CONTROL", hum_suelo, temp_ambiente)
else:
# ya está regando, seguir descontando
if tiempo_riego_restante > 0:
tiempo_riego_restante -= dt
revisar_nivel_bomba()
else:
apagar_bombas()
irrigacion_activa = False
else:
# No se cumplen condiciones, detener riego si estaba activo en control
if irrigacion_activa and modo_riego_actual == "CONTROL":
apagar_bombas()
irrigacion_activa = False
tiempo_riego_restante = 0
# ------------------------------------------------------------
# MENÚ Y PANTALLAS LCD
# ------------------------------------------------------------
def mostrar_pantalla(hum_suelo, temp_ambiente):
lcd.clear()
hora_str = "{:02d}:{:02d}:{:02d}".format(reloj_hora, reloj_min, reloj_seg)
if pantalla_actual == 0:
# Pantalla inicial: hora + modo actual
linea1 = "Hora " + hora_str
linea2 = "Modo Temporizado" if modo_operacion == 0 else "Modo Control"
linea3 = ""
linea4 = ""
elif pantalla_actual == 1:
# Selección de modo
linea1 = "Seleccion de modo"
linea2 = "Temp / Control"
linea3 = "Actual: " + ("Temp" if modo_operacion == 0 else "Control")
linea4 = "OK:cambiar Reg:salir"
elif pantalla_actual == 2:
# Modo temporizado: horario inicio/fin
linea1 = "{} Temp".format(hora_str)
linea2 = "Ini {:02d}:{:02d}:{:02d}".format(*hora_inicio)
linea3 = "Fin {:02d}:{:02d}:{:02d}".format(*hora_fin)
linea4 = "Riego: {}".format("ON" if irrigacion_activa and modo_riego_actual=="TEMP" else "OFF")
elif pantalla_actual == 3:
# Modo control: estado de riego
linea1 = "{} Cont".format(hora_str)
linea2 = "Riego: {}".format("ON" if irrigacion_activa and modo_riego_actual=="CONTROL" else "OFF")
linea3 = "Vol={:6.1f} lt".format(volumen_calculado)
linea4 = "T={:6.1f} seg".format(tiempo_riego_restante if tiempo_riego_restante > 0 else 0)
elif pantalla_actual == 4:
# Modo control: variables medidas
linea1 = "{} Cont".format(hora_str)
linea2 = "Hum suelo:{:5.1f}%".format(hum_suelo)
linea3 = "Temp amb:{:5.1f}C".format(temp_ambiente)
linea4 = ""
else:
linea1 = "Pantalla invalida"
linea2 = ""
linea3 = ""
linea4 = ""
lcd.write_line(0, linea1)
lcd.write_line(1, linea2)
lcd.write_line(2, linea3)
lcd.write_line(3, linea4)
# ------------------------------------------------------------
# MANEJO DE BOTONES (FLANCOS)
# ------------------------------------------------------------
def leer_botones_y_eventos():
global pantalla_actual, modo_operacion
global prev_anterior, prev_siguiente, prev_ok, prev_regresar
global hora_inicio
# Lecturas actuales
v_ant = btn_anterior.value()
v_sig = btn_siguiente.value()
v_ok = btn_ok.value()
v_reg = btn_regresar.value()
# Flancos de subida (0->1)
if v_sig == 1 and prev_siguiente == 0:
pantalla_actual += 1
if pantalla_actual > 4:
pantalla_actual = 0
if v_ant == 1 and prev_anterior == 0:
pantalla_actual -= 1
if pantalla_actual < 0:
pantalla_actual = 4
if v_ok == 1 and prev_ok == 0:
# Acciones según pantalla
if pantalla_actual == 1:
# Cambiar modo
modo_operacion = 1 - modo_operacion # alterna 0/1
elif pantalla_actual == 2:
# Editar hora de inicio (ejemplo simple: sumar 1 minuto)
hora_inicio[1] += 1
if hora_inicio[1] >= 60:
hora_inicio[1] = 0
hora_inicio[0] += 1
if hora_inicio[0] >= 24:
hora_inicio[0] = 0
# Se podría ampliar para editar hora_fin y parámetros, pero es opcional.
if v_reg == 1 and prev_regresar == 0:
# Volver a pantalla principal
pantalla_actual = 0
# Actualizar estados anteriores
prev_anterior = v_ant
prev_siguiente = v_sig
prev_ok = v_ok
prev_regresar = v_reg
# ------------------------------------------------------------
# BUCLE PRINCIPAL
# ------------------------------------------------------------
def main():
global acum_ms_reloj
lcd.clear()
lcd.write_line(0, "Sistema de Riego")
lcd.write_line(1, "Raspberry Pi Pico")
lcd.write_line(2, "Curso 208006 UNAD")
lcd.write_line(3, "Fase 4 Embebidos")
sleep_ms(2000)
last_ms = ticks_ms()
while True:
ahora = ticks_ms()
dt_ms = (ahora - last_ms)
if dt_ms < 0:
dt_ms = 0
last_ms = ahora
dt = dt_ms / 1000.0
# Actualizar reloj interno
acum_ms_reloj += dt_ms
while acum_ms_reloj >= 1000:
acum_ms_reloj -= 1000
incrementar_reloj(1)
# Leer sensores una vez por ciclo
hum = leer_humedad()
temp = leer_temperatura()
# Leer botones y manejar menú
leer_botones_y_eventos()
# Actualizar lógica de riego según modo
if modo_operacion == 0:
actualizar_modo_temporizado(hum, temp, dt)
else:
actualizar_modo_control(hum, temp, dt)
# Mostrar en LCD
mostrar_pantalla(hum, temp)
# Pequeña pausa
sleep_ms(200)
# ------------------------------------------------------------
# EJECUCIÓN
# ------------------------------------------------------------
if __name__ == "__main__":
main()