#include <Arduino.h>
#include <Wire.h>
// === CONFIGURACIÓN DE PINES ===
#define MOTOR_CW_PORT GPIOA
#define MOTOR_CW_PIN GPIO_PIN_2
#define MOTOR_CCW_PORT GPIOA
#define MOTOR_CCW_PIN GPIO_PIN_3
#define VALVE_PRE_PORT GPIOA
#define VALVE_PRE_PIN GPIO_PIN_4
#define VALVE_LAV_PORT GPIOA
#define VALVE_LAV_PIN GPIO_PIN_5
#define VALVE_SUAV_PORT GPIOA
#define VALVE_SUAV_PIN GPIO_PIN_6
#define PUMP_PORT GPIOA
#define PUMP_PIN GPIO_PIN_7
#define HEATER_PORT GPIOA
#define HEATER_PIN GPIO_PIN_1
#define BUZZER_PORT GPIOB
#define BUZZER_PIN GPIO_PIN_0
#define BTN_PRE_PORT GPIOB
#define BTN_PRE_PIN GPIO_PIN_4
#define BTN_LAV_PORT GPIOB
#define BTN_LAV_PIN GPIO_PIN_5
#define BTN_SPIN_PORT GPIOB
#define BTN_SPIN_PIN GPIO_PIN_8
#define BTN_TEMP_PORT GPIOB
#define BTN_TEMP_PIN GPIO_PIN_9
#define BTN_START_PORT GPIOA
#define BTN_START_PIN GPIO_PIN_8
#define IN_DOOR_PORT GPIOA
#define IN_DOOR_PIN GPIO_PIN_9
#define IN_LEVEL_PORT GPIOA
#define IN_LEVEL_PIN GPIO_PIN_10
#define ADC_SENSOR_TEMP_CH 0
#define ADC_ACELEROMETRO_CH 1
// === CONSTANTES ===
#define FILTRO_DEBOUNCE 250UL
#define T_REFRESCO_OLED 400UL
#define T_DESAGUE_MAX 45000UL
#define T_RAMPA_PASO 10000UL
#define T_CENTRIFUGADO 180000UL
#define T_REPOSO_MOTOR 4000UL
#define T_GIRO_ACTIVO 12000UL
#define HISTERESIS_TEMP 2.0f
#define LIMITE_DESBALANCE 300
#define SH1107_ADDRESS 0x3C
typedef enum {
ST_REPOSO, ST_PAUSA, ST_PRE_LLENADO, ST_PRE_CALENTAR, ST_PRE_LAVADO,
ST_PRE_DESAGUE, ST_LAV_LLENADO, ST_LAV_CALENTAR, ST_LAV_PRINCIPAL,
ST_LAV_DESAGUE, ST_ENJ_LLENADO, ST_ENJ_AGITACION, ST_ENJ_DESAGUE,
ST_RAMPA_CENTRIF, ST_CENTRIF_FLAT, ST_ALERTA_GOLPE
} EstadoLavadora;
EstadoLavadora estadoActual = ST_REPOSO;
EstadoLavadora estadoPrevio = ST_REPOSO;
int selPrelavadoMins = 0;
int selLavadoMins = 0;
int selEnjuaguesNum = 1;
int selCentrifugadoRpm = 0;
int selTemperatura = 0;
int contadorEnjuagues = 0;
int escalonRampa = 0;
int faseMotor = 0;
int valorBaseAcelerometro = 2048;
uint8_t buzzerPasoState = 0;
uint8_t botonInicioLiberado = 1;
uint32_t t_inicioEstado = 0;
uint32_t t_ultimoRefrescoOled = 0;
uint32_t t_ultimoBoton = 0;
uint32_t t_cronometroMotor = 0;
uint32_t t_lavadoAcumulado = 0;
uint32_t t_ultimoBuzzer = 0;
uint32_t t_rampaPasoInicio = 0;
ADC_HandleTypeDef hadc1;
// === DICCIONARIO COMPACTO REORTADO ===
const uint8_t fontCompacto[][5] PROGMEM = {
{0x00, 0x00, 0x00, 0x00, 0x00}, // (Espacio)
{0x00, 0x00, 0x5F, 0x00, 0x00}, // !
{0x23, 0x13, 0x08, 0x64, 0x62}, // %
{0x00, 0x36, 0x36, 0x00, 0x00}, // :
{0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0
{0x00, 0x42, 0x7F, 0x40, 0x00}, // 1
{0x42, 0x61, 0x51, 0x49, 0x46}, // 2
{0x21, 0x41, 0x45, 0x4B, 0x31}, // 3
{0x18, 0x14, 0x12, 0x7F, 0x10}, // 4
{0x27, 0x45, 0x45, 0x45, 0x39}, // 5
{0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6
{0x01, 0x71, 0x09, 0x05, 0x03}, // 7
{0x36, 0x49, 0x49, 0x49, 0x36}, // 8
{0x06, 0x49, 0x49, 0x29, 0x1E}, // 9
{0x7E, 0x11, 0x11, 0x11, 0x7E}, // A
{0x7F, 0x49, 0x49, 0x49, 0x36}, // B
{0x3E, 0x41, 0x41, 0x41, 0x22}, // C
{0x7F, 0x41, 0x41, 0x22, 0x1C}, // D
{0x7F, 0x49, 0x49, 0x49, 0x41}, // E
{0x7F, 0x09, 0x09, 0x01, 0x01}, // F
{0x3E, 0x41, 0x41, 0x51, 0x72}, // G
{0x7F, 0x08, 0x08, 0x08, 0x7F}, // H
{0x00, 0x41, 0x7F, 0x41, 0x00}, // I
{0x20, 0x40, 0x41, 0x3F, 0x01}, // J
{0x7F, 0x08, 0x14, 0x22, 0x41}, // K
{0x7F, 0x40, 0x40, 0x40, 0x40}, // L
{0x7F, 0x02, 0x04, 0x02, 0x7F}, // M
{0x7F, 0x04, 0x08, 0x10, 0x7F}, // N
{0x3E, 0x41, 0x41, 0x41, 0x3E}, // O
{0x7F, 0x09, 0x09, 0x09, 0x06}, // P
{0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q
{0x7F, 0x09, 0x19, 0x29, 0x46}, // R
{0x46, 0x49, 0x49, 0x49, 0x31}, // S
{0x01, 0x01, 0x7F, 0x01, 0x01}, // T
{0x3F, 0x40, 0x40, 0x40, 0x3F}, // U
{0x1F, 0x20, 0x40, 0x20, 0x1F}, // V
{0x7F, 0x20, 0x18, 0x20, 0x7F}, // W
{0x63, 0x14, 0x08, 0x14, 0x63}, // X
{0x03, 0x04, 0x78, 0x04, 0x03}, // Y
{0x61, 0x51, 0x49, 0x45, 0x43} // Z
};
void sh1107_command(uint8_t cmd) {
Wire.beginTransmission(SH1107_ADDRESS); Wire.write(0x00); Wire.write(cmd); Wire.endTransmission();
}
void sh1107_init(void) {
delay(50);
uint8_t seq[] = {0xAE, 0xDC, 0x00, 0x81, 0x2F, 0xA0, 0xC0, 0xA8, 0x3F, 0xD3, 0x60, 0xD5, 0x51, 0xD9, 0x22, 0xDA, 0x12, 0xDB, 0x35, 0xA4, 0xA6, 0xAF};
for(uint8_t i=0; i<22; i++) sh1107_command(seq[i]);
for (uint8_t p = 0; p < 8; p++) {
sh1107_command(0xB0 + p); sh1107_command(0x00); sh1107_command(0x10);
for (uint8_t i = 0; i < 128; i++) {
Wire.beginTransmission(SH1107_ADDRESS); Wire.write(0x40);
for (uint8_t j = 0; j < 16 && i < 128; j++, i++) Wire.write(0x00);
i--; Wire.endTransmission();
}
}
}
void sh1107_clear(void) {
for (uint8_t page = 0; page < 8; page++) {
sh1107_command(0xB0 + page); sh1107_command(0x00); sh1107_command(0x10);
for (uint8_t i = 0; i < 128; i++) {
Wire.beginTransmission(SH1107_ADDRESS); Wire.write(0x40);
for (uint8_t j = 0; j < 16 && i < 128; j++, i++) Wire.write(0x00);
i--; Wire.endTransmission();
}
}
}
void sh1107_set_cursor(uint8_t line, uint8_t col) {
sh1107_command(0xB0 + line);
sh1107_command(0x00 + ((col * 6) & 0x0F)); sh1107_command(0x10 + (((col * 6) >> 4) & 0x0F));
}
void sh1107_print(const char* str) {
while (*str) {
uint8_t c = *str++;
if (c >= 'a' && c <= 'z') c -= 32;
uint8_t idx = 0;
if (c == '!') idx = 1;
else if (c == '%') idx = 2;
else if (c == ':') idx = 3;
else if (c >= '0' && c <= '9') idx = 4 + (c - '0');
else if (c >= 'A' && c <= 'Z') idx = 14 + (c - 'A');
Wire.beginTransmission(SH1107_ADDRESS); Wire.write(0x40);
for (uint8_t i = 0; i < 5; i++) Wire.write(pgm_read_byte(&(fontCompacto[idx][i])));
Wire.write(0x00); Wire.endTransmission();
}
}
void sh1107_print_num(int num) {
char buf[6]; itoa(num, buf, 10); sh1107_print(buf);
}
void apagarActuadores(void);
void apagarMotores(void);
void controlarCicloMotor(uint32_t tiempoActual);
void controlarResistencia(float actual, int objetivo);
void emitirTonoAlarma(uint32_t tiempoActual);
void ejecutarTonoFin(void);
uint32_t calcularTiempoTotalRestante(void);
void gestionarMenuPulsadores(uint32_t tiempoActual);
uint8_t verificarDesbalance(void);
void actualizarPantallaMenu(void);
void actualizarPantallaProceso(const char* fase, const char* accion, uint32_t t_estadoRestante, uint32_t t_totalRestante);
uint16_t HAL_ADC_Read_Channel(uint32_t canal) {
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 5) == HAL_OK) return HAL_ADC_GetValue(&hadc1);
return 0;
}
void Lavadora_Process(void) {
uint32_t tiempoActual = HAL_GetTick();
uint8_t puertaAbierta = (HAL_GPIO_ReadPin(IN_DOOR_PORT, IN_DOOR_PIN) == GPIO_PIN_SET);
uint8_t tinaLlena = (HAL_GPIO_ReadPin(IN_LEVEL_PORT, IN_LEVEL_PIN) == GPIO_PIN_SET);
uint8_t desbalanceado = (abs((int)HAL_ADC_Read_Channel(ADC_ACELEROMETRO_CH) - valorBaseAcelerometro) > LIMITE_DESBALANCE);
float tempActual = ((float)HAL_ADC_Read_Channel(ADC_SENSOR_TEMP_CH) * 100.0f) / 4095.0f;
gestionarMenuPulsadores(tiempoActual);
if (puertaAbierta && estadoActual != ST_REPOSO && estadoActual != ST_PAUSA) {
if (estadoActual == ST_PRE_LAVADO || estadoActual == ST_LAV_PRINCIPAL) t_lavadoAcumulado += (tiempoActual - t_inicioEstado);
apagarActuadores(); estadoPrevio = estadoActual; estadoActual = ST_PAUSA;
}
if (desbalanceado && (estadoActual == ST_RAMPA_CENTRIF || estadoActual == ST_CENTRIF_FLAT)) {
apagarMotores(); estadoPrevio = estadoActual; estadoActual = ST_ALERTA_GOLPE;
t_inicioEstado = tiempoActual; t_ultimoBuzzer = tiempoActual;
}
uint32_t t_estadoRestante = 0;
uint32_t t_totalRestante = calcularTiempoTotalRestante();
switch (estadoActual) {
case ST_REPOSO:
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaMenu(); t_ultimoRefrescoOled = tiempoActual; }
break;
case ST_PAUSA:
apagarActuadores();
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) {
sh1107_init(); sh1107_set_cursor(0, 0); sh1107_print("PAUSA");
sh1107_set_cursor(2, 0); sh1107_print("STOP");
t_ultimoRefrescoOled = tiempoActual;
}
break;
case ST_ALERTA_GOLPE:
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_SET); emitirTonoAlarma(tiempoActual);
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) {
sh1107_init(); sh1107_set_cursor(1, 0); sh1107_print("ALERTA!");
t_ultimoRefrescoOled = tiempoActual;
}
if (tiempoActual - t_inicioEstado >= 12000UL) {
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(BUZZER_PORT, BUZZER_PIN, GPIO_PIN_RESET);
estadoActual = ST_RAMPA_CENTRIF; t_inicioEstado = tiempoActual; t_rampaPasoInicio = tiempoActual; escalonRampa = 0;
}
break;
case ST_PRE_LLENADO:
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("PRELAV", "LLENANDO", 0, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
HAL_GPIO_WritePin(VALVE_PRE_PORT, VALVE_PRE_PIN, GPIO_PIN_SET);
if (tinaLlena) {
HAL_GPIO_WritePin(VALVE_PRE_PORT, VALVE_PRE_PIN, GPIO_PIN_RESET);
estadoActual = (selTemperatura > 0) ? ST_PRE_CALENTAR : ST_PRE_LAVADO;
t_inicioEstado = tiempoActual; t_cronometroMotor = tiempoActual; faseMotor = 0;
}
break;
case ST_PRE_CALENTAR:
if (!tinaLlena) { HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, GPIO_PIN_RESET); estadoActual = ST_PRE_LLENADO; break; }
controlarResistencia(tempActual, selTemperatura);
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) {
sh1107_init(); sh1107_set_cursor(0, 0); sh1107_print("CALENTAR");
sh1107_set_cursor(2, 0); sh1107_print("T: "); sh1107_print_num((int)tempActual);
t_ultimoRefrescoOled = tiempoActual;
}
if (tempActual >= (float)selTemperatura) { HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, GPIO_PIN_RESET); estadoActual = ST_PRE_LAVADO; t_inicioEstado = tiempoActual; }
break;
case ST_PRE_LAVADO:
if (!tinaLlena) { apagarMotores(); t_lavadoAcumulado += (tiempoActual - t_inicioEstado); estadoActual = ST_PRE_LLENADO; break; }
controlarResistencia(tempActual, selTemperatura);
{
uint32_t t_max = (uint32_t)selPrelavadoMins * 60000UL;
uint32_t t_pas = (tiempoActual - t_inicioEstado) + t_lavadoAcumulado;
t_estadoRestante = (t_max > t_pas) ? (t_max - t_pas) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("PRELAV", "AGITA", t_estadoRestante, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
controlarCicloMotor(tiempoActual);
if (t_pas >= t_max) { apagarActuadores(); estadoActual = ST_PRE_DESAGUE; t_inicioEstado = tiempoActual; t_lavadoAcumulado = 0; }
}
break;
case ST_PRE_DESAGUE:
t_estadoRestante = (T_DESAGUE_MAX > (tiempoActual - t_inicioEstado)) ? (T_DESAGUE_MAX - (tiempoActual - t_inicioEstado)) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("PRE-DES", "EVACUA", t_estadoRestante, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_SET);
if (tiempoActual - t_inicioEstado >= T_DESAGUE_MAX) {
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_RESET);
if (selLavadoMins > 0) estadoActual = ST_LAV_LLENADO;
else if (selCentrifugadoRpm > 0) { estadoActual = ST_RAMPA_CENTRIF; t_rampaPasoInicio = tiempoActual; escalonRampa = 0; }
else { ejecutarTonoFin(); estadoActual = ST_REPOSO; }
}
break;
case ST_LAV_LLENADO:
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("LAVADO", "LLENA", 0, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
HAL_GPIO_WritePin(VALVE_LAV_PORT, VALVE_LAV_PIN, GPIO_PIN_SET);
if (tinaLlena) {
HAL_GPIO_WritePin(VALVE_LAV_PORT, VALVE_LAV_PIN, GPIO_PIN_RESET);
estadoActual = (selTemperatura > 0) ? ST_LAV_CALENTAR : ST_LAV_PRINCIPAL;
t_inicioEstado = tiempoActual; t_cronometroMotor = tiempoActual; faseMotor = 0;
}
break;
case ST_LAV_CALENTAR:
if (!tinaLlena) { HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, GPIO_PIN_RESET); estadoActual = ST_LAV_LLENADO; break; }
controlarResistencia(tempActual, selTemperatura);
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) {
sh1107_init(); sh1107_set_cursor(0, 0); sh1107_print("LAV CAL");
sh1107_set_cursor(2, 0); sh1107_print("T: "); sh1107_print_num((int)tempActual);
t_ultimoRefrescoOled = tiempoActual;
}
if (tempActual >= (float)selTemperatura) { HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, GPIO_PIN_RESET); estadoActual = ST_LAV_PRINCIPAL; t_inicioEstado = tiempoActual; }
break;
case ST_LAV_PRINCIPAL:
if (!tinaLlena) { apagarMotores(); t_lavadoAcumulado += (tiempoActual - t_inicioEstado); estadoActual = ST_LAV_LLENADO; break; }
controlarResistencia(tempActual, selTemperatura);
{
uint32_t t_max = (uint32_t)selLavadoMins * 60000UL;
uint32_t t_pas = (tiempoActual - t_inicioEstado) + t_lavadoAcumulado;
t_estadoRestante = (t_max > t_pas) ? (t_max - t_pas) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("LAVADO", "AGITA", t_estadoRestante, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
controlarCicloMotor(tiempoActual);
if (t_pas >= t_max) { apagarActuadores(); estadoActual = ST_LAV_DESAGUE; t_inicioEstado = tiempoActual; t_lavadoAcumulado = 0; }
}
break;
case ST_LAV_DESAGUE:
t_estadoRestante = (T_DESAGUE_MAX > (tiempoActual - t_inicioEstado)) ? (T_DESAGUE_MAX - (tiempoActual - t_inicioEstado)) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("LAV-DES", "EVACUA", t_estadoRestante, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_SET);
if (tiempoActual - t_inicioEstado >= T_DESAGUE_MAX) {
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_RESET);
if (selEnjuaguesNum > 0) { contadorEnjuagues = 0; estadoActual = ST_ENJ_LLENADO; }
else if (selCentrifugadoRpm > 0) { estadoActual = ST_RAMPA_CENTRIF; t_rampaPasoInicio = tiempoActual; escalonRampa = 0; }
else { ejecutarTonoFin(); estadoActual = ST_REPOSO; }
}
break;
case ST_ENJ_LLENADO:
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("ENJUAG", "LLENA", 0, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
HAL_GPIO_WritePin((contadorEnjuagues == (selEnjuaguesNum - 1)) ? VALVE_SUAV_PORT : VALVE_LAV_PORT, (contadorEnjuagues == (selEnjuaguesNum - 1)) ? VALVE_SUAV_PIN : VALVE_LAV_PIN, GPIO_PIN_SET);
if (tinaLlena) {
HAL_GPIO_WritePin(VALVE_SUAV_PORT, VALVE_SUAV_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(VALVE_LAV_PORT, VALVE_LAV_PIN, GPIO_PIN_RESET);
estadoActual = ST_ENJ_AGITACION; t_inicioEstado = tiempoActual; t_cronometroMotor = tiempoActual; faseMotor = 0;
}
break;
case ST_ENJ_AGITACION:
t_estadoRestante = (120000UL > (tiempoActual - t_inicioEstado)) ? (120000UL - (tiempoActual - t_inicioEstado)) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("ENJUAG", "AGITA", t_estadoRestante, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
controlarCicloMotor(tiempoActual);
if (tiempoActual - t_inicioEstado >= 120000UL) { apagarMotores(); estadoActual = ST_ENJ_DESAGUE; t_inicioEstado = tiempoActual; }
break;
case ST_ENJ_DESAGUE:
t_estadoRestante = (T_DESAGUE_MAX > (tiempoActual - t_inicioEstado)) ? (T_DESAGUE_MAX - (tiempoActual - t_inicioEstado)) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("ENJ-DES", "EVACUA", t_estadoRestante, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_SET);
if (tiempoActual - t_inicioEstado >= T_DESAGUE_MAX) {
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_RESET); contadorEnjuagues++;
if (contadorEnjuagues < selEnjuaguesNum) estadoActual = ST_ENJ_LLENADO;
else if (selCentrifugadoRpm > 0) { estadoActual = ST_RAMPA_CENTRIF; t_rampaPasoInicio = tiempoActual; escalonRampa = 0; }
else { ejecutarTonoFin(); estadoActual = ST_REPOSO; }
}
break;
case ST_RAMPA_CENTRIF:
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_SET);
t_estadoRestante = ((T_RAMPA_PASO * 4) > (tiempoActual - t_inicioEstado)) ? ((T_RAMPA_PASO * 4) - (tiempoActual - t_inicioEstado)) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) {
sh1107_init(); sh1107_set_cursor(0, 0); sh1107_print("SPIN RAMPA");
sh1107_set_cursor(2, 0); sh1107_print("P: "); sh1107_print_num(escalonRampa);
t_ultimoRefrescoOled = tiempoActual;
}
{
uint32_t delta = tiempoActual - t_rampaPasoInicio;
uint16_t mod = (escalonRampa == 0) ? 500 : (escalonRampa == 1) ? 400 : (escalonRampa == 2) ? 300 : 200;
uint16_t lim = (escalonRampa == 0) ? 150 : (escalonRampa == 1) ? 200 : (escalonRampa == 2) ? 220 : 170;
HAL_GPIO_WritePin(MOTOR_CW_PORT, MOTOR_CW_PIN, (delta % mod < lim) ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
if (tiempoActual - t_rampaPasoInicio >= T_RAMPA_PASO) {
escalonRampa++; t_rampaPasoInicio = tiempoActual;
if ((selCentrifugadoRpm == 400 && escalonRampa > 1) || (selCentrifugadoRpm == 800 && escalonRampa > 2) || (selCentrifugadoRpm == 1200 && escalonRampa > 3) || (escalonRampa > 3)) {
estadoActual = ST_CENTRIF_FLAT; t_inicioEstado = tiempoActual;
}
}
break;
case ST_CENTRIF_FLAT:
t_estadoRestante = (T_CENTRIFUGADO > (tiempoActual - t_inicioEstado)) ? (T_CENTRIFUGADO - (tiempoActual - t_inicioEstado)) : 0;
if (tiempoActual - t_ultimoRefrescoOled >= T_REFRESCO_OLED) { actualizarPantallaProceso("SPIN FLAT", "EXTRACT", t_estadoRestante, t_totalRestante); t_ultimoRefrescoOled = tiempoActual; }
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(MOTOR_CW_PORT, MOTOR_CW_PIN, GPIO_PIN_SET);
if (tiempoActual - t_inicioEstado >= T_CENTRIFUGADO) { apagarActuadores(); ejecutarTonoFin(); estadoActual = ST_REPOSO; }
break;
}
}
void controlarResistencia(float actual, int objetivo) {
if (objetivo <= 0) { HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, GPIO_PIN_RESET); return; }
HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, (actual >= (float)objetivo) ? GPIO_PIN_RESET : (actual < ((float)objetivo - HISTERESIS_TEMP)) ? GPIO_PIN_SET : HAL_GPIO_ReadPin(HEATER_PORT, HEATER_PIN));
}
void emitirTonoAlarma(uint32_t tiempoActual) {
if (tiempoActual - t_ultimoBuzzer >= 150) { buzzerPasoState = !buzzerPasoState; HAL_GPIO_WritePin(BUZZER_PORT, BUZZER_PIN, buzzerPasoState ? GPIO_PIN_SET : GPIO_PIN_RESET); t_ultimoBuzzer = tiempoActual; }
}
void ejecutarTonoFin(void) {
for (int i = 0; i < 3; i++) { HAL_GPIO_WritePin(BUZZER_PORT, BUZZER_PIN, GPIO_PIN_SET); HAL_Delay(400); HAL_GPIO_WritePin(BUZZER_PORT, BUZZER_PIN, GPIO_PIN_RESET); HAL_Delay(200); }
}
void apagarMotores(void) { HAL_GPIO_WritePin(MOTOR_CW_PORT, MOTOR_CW_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_CCW_PORT, MOTOR_CCW_PIN, GPIO_PIN_RESET); }
void apagarActuadores(void) {
HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(VALVE_PRE_PORT, VALVE_PRE_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(VALVE_LAV_PORT, VALVE_LAV_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(VALVE_SUAV_PORT, VALVE_SUAV_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_RESET); apagarMotores();
}
void controlarCicloMotor(uint32_t tiempoActual) {
uint32_t d = tiempoActual - t_cronometroMotor;
if (faseMotor == 0 && d >= T_REPOSO_MOTOR) { faseMotor = 1; t_cronometroMotor = tiempoActual; }
else if (faseMotor == 1) { HAL_GPIO_WritePin(MOTOR_CCW_PORT, MOTOR_CCW_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_CW_PORT, MOTOR_CW_PIN, GPIO_PIN_SET); if (d >= T_GIRO_ACTIVO) { faseMotor = 2; t_cronometroMotor = tiempoActual; } }
else if (faseMotor == 2 && d >= T_REPOSO_MOTOR) { faseMotor = 3; t_cronometroMotor = tiempoActual; }
else if (faseMotor == 3) { HAL_GPIO_WritePin(MOTOR_CW_PORT, MOTOR_CW_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_CCW_PORT, MOTOR_CCW_PIN, GPIO_PIN_SET); if (d >= T_GIRO_ACTIVO) { faseMotor = 0; t_cronometroMotor = tiempoActual; } }
}
unsigned long calcularTiempoTotalRestante(void) {
if (estadoActual == ST_REPOSO || estadoActual == ST_PAUSA) return 0;
unsigned long t = 0; uint32_t ta = HAL_GetTick();
if (estadoActual <= ST_PRE_DESAGUE) {
unsigned long tot = ((unsigned long)selPrelavadoMins * 60000UL) + T_DESAGUE_MAX;
unsigned long con = (estadoActual == ST_PRE_LAVADO) ? ((ta - t_inicioEstado) + t_lavadoAcumulado) : (((unsigned long)selPrelavadoMins * 60000UL) + (ta - t_inicioEstado));
if (tot > con) t += (tot - con);
}
if (estadoActual <= ST_LAV_DESAGUE) {
unsigned long tot = ((unsigned long)selLavadoMins * 60000UL) + T_DESAGUE_MAX;
unsigned long con = (estadoActual == ST_LAV_PRINCIPAL) ? ((ta - t_inicioEstado) + t_lavadoAcumulado) : (((unsigned long)selLavadoMins * 60000UL) + (ta - t_inicioEstado));
if (estadoActual < ST_LAV_LLENADO) t += tot; else if (tot > con) t += (tot - con);
}
if (estadoActual <= ST_ENJ_DESAGUE) {
unsigned long tot = 120000UL + T_DESAGUE_MAX; int rem = selEnjuaguesNum - contadorEnjuagues;
if (estadoActual >= ST_ENJ_LLENADO) {
unsigned long con = (estadoActual == ST_ENJ_AGITACION) ? (ta - t_inicioEstado) : (120000UL + (ta - t_inicioEstado));
if (tot > con) t += (tot - con);
if (rem > 1) t += (unsigned long)(rem - 1) * tot;
} else t += (unsigned long)selEnjuaguesNum * tot;
}
if (selCentrifugadoRpm > 0 && estadoActual <= ST_CENTRIF_FLAT) {
unsigned long tot = (T_RAMPA_PASO * 4) + T_CENTRIFUGADO;
unsigned long con = (estadoActual == ST_RAMPA_CENTRIF) ? ((unsigned long)escalonRampa * T_RAMPA_PASO + (ta - t_rampaPasoInicio)) : ((T_RAMPA_PASO * 4) + (ta - t_inicioEstado));
if (estadoActual < ST_RAMPA_CENTRIF) t += tot; else if (tot > con) t += (tot - con);
}
return t;
}
void gestionarMenuPulsadores(uint32_t tiempoActual) {
if (tiempoActual - t_ultimoBoton < FILTRO_DEBOUNCE) return;
if (estadoActual == ST_REPOSO) {
if (HAL_GPIO_ReadPin(BTN_PRE_PORT, BTN_PRE_PIN) == GPIO_PIN_RESET) { selPrelavadoMins = (selPrelavadoMins == 0) ? 5 : (selPrelavadoMins == 5) ? 10 : (selPrelavadoMins == 10) ? 15 : 0; t_ultimoBoton = tiempoActual; }
if (HAL_GPIO_ReadPin(BTN_LAV_PORT, BTN_LAV_PIN) == GPIO_PIN_RESET) { selLavadoMins = (selLavadoMins == 0) ? 15 : (selLavadoMins == 15) ? 30 : (selLavadoMins == 30) ? 45 : (selLavadoMins == 45) ? 60 : (selLavadoMins == 60) ? 90 : (selLavadoMins == 90) ? 120 : 0; t_ultimoBoton = tiempoActual; }
if (HAL_GPIO_ReadPin(BTN_SPIN_PORT, BTN_SPIN_PIN) == GPIO_PIN_RESET) { selCentrifugadoRpm = (selCentrifugadoRpm == 0) ? 400 : (selCentrifugadoRpm == 400) ? 800 : (selCentrifugadoRpm == 800) ? 1200 : (selCentrifugadoRpm == 1200) ? 1600 : 0; t_ultimoBoton = tiempoActual; }
if (HAL_GPIO_ReadPin(BTN_TEMP_PORT, BTN_TEMP_PIN) == GPIO_PIN_RESET) { selTemperatura = (selTemperatura == 0) ? 30 : (selTemperatura == 30) ? 40 : (selTemperatura == 40) ? 60 : 0; t_ultimoBoton = tiempoActual; }
}
if (HAL_GPIO_ReadPin(BTN_START_PORT, BTN_START_PIN) == GPIO_PIN_RESET) {
if (botonInicioLiberado) {
t_ultimoBoton = tiempoActual; botonInicioLiberado = 0;
if (estadoActual == ST_REPOSO) {
if (selPrelavadoMins > 0) estadoActual = ST_PRE_LLENADO; else if (selLavadoMins > 0) estadoActual = ST_LAV_LLENADO; else if (selCentrifugadoRpm > 0) { estadoActual = ST_RAMPA_CENTRIF; t_rampaPasoInicio = tiempoActual; escalonRampa = 0; }
t_inicioEstado = tiempoActual;
} else if (estadoActual == ST_PAUSA) { estadoActual = estadoPrevio; t_inicioEstado = tiempoActual; t_cronometroMotor = tiempoActual; }
else { if (estadoActual == ST_PRE_LAVADO || estadoActual == ST_LAV_PRINCIPAL) t_lavadoAcumulado += (tiempoActual - t_inicioEstado); apagarActuadores(); estadoPrevio = estadoActual; estadoActual = ST_PAUSA; }
}
} else botonInicioLiberado = 1;
}
void actualizarPantallaMenu(void) {
sh1107_clear(); sh1107_set_cursor(0, 0); sh1107_print("LAV IND");
sh1107_set_cursor(2, 0); sh1107_print("P: "); sh1107_print_num(selPrelavadoMins);
sh1107_set_cursor(3, 0); sh1107_print("L: "); sh1107_print_num(selLavadoMins);
sh1107_set_cursor(4, 0); sh1107_print("E: "); sh1107_print_num(selEnjuaguesNum);
sh1107_set_cursor(5, 0); sh1107_print("S: "); sh1107_print_num(selCentrifugadoRpm);
sh1107_set_cursor(6, 0); sh1107_print("T: "); if (selTemperatura == 0) sh1107_print("F"); else sh1107_print_num(selTemperatura);
}
void actualizarPantallaProceso(const char* fase, const char* accion, uint32_t t_estadoRestante, uint32_t t_totalRestante) {
sh1107_clear(); sh1107_set_cursor(0, 0); sh1107_print(fase); sh1107_set_cursor(2, 0); sh1107_print(accion);
uint32_t estMin = (t_estadoRestante / 1000UL) / 60UL; uint32_t estSeg = (t_estadoRestante / 1000UL) % 60UL;
sh1107_set_cursor(4, 0); sh1107_print("R: "); sh1107_print_num(estMin); sh1107_print(":"); sh1107_print_num(estSeg);
uint32_t totMin = (t_totalRestante / 1000UL) / 60UL; uint32_t totSeg = (t_totalRestante / 1000UL) % 60UL;
sh1107_set_cursor(6, 0); sh1107_print("T: "); sh1107_print_num(totMin); sh1107_print(":"); sh1107_print_num(totSeg);
}
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_1, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
static void MX_ADC1_Init(void) {
hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
HAL_ADC_Init(&hadc1);
ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
void setup() {
HAL_Init(); MX_GPIO_Init(); MX_ADC1_Init();
Wire.begin(); sh1107_init(); delay(50);
valorBaseAcelerometro = HAL_ADC_Read_Channel(ADC_ACELEROMETRO_CH);
}
void loop() {
Lavadora_Process();
}