From 7ecedf7accef7913c17bef68de7631150f78ffae Mon Sep 17 00:00:00 2001 From: Marco Gallegos Date: Sun, 18 Jan 2026 14:24:12 -0600 Subject: [PATCH] Add drill_vanity.ino and update README --- README.md | 246 ++++++++++++++++++++++++++------------------- drill_vanity.ino | 253 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 399 insertions(+), 100 deletions(-) create mode 100644 drill_vanity.ino diff --git a/README.md b/README.md index 810f8e0..d21b7f9 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,189 @@ -# Proyecto de Control de Motor DC: Consola Digital v1.0 - -**Autor:** Marco Gallegos -**Fecha:** Enero 2024 - ---- +# Proyecto de Control de Motor DC ## 1. Descripción General -Este proyecto consiste en una consola de control inteligente para un motor de corriente continua (DC). Utiliza un microcontrolador **ESP8266 (HW-364A)** para gestionar la potencia a través de un driver **DRV8871**, ofreciendo una interfaz de usuario profesional con pantalla **OLED**, encoder rotatorio y botones de dirección. +Este proyecto implementa una consola digital de control para un motor de corriente continua (DC), orientada a operación segura, control fino de velocidad y protección mecánica/eléctrica. A diferencia de un control directo con potenciómetro, el sistema introduce: -El sistema prioriza la **seguridad mecánica**, implementando rampas de aceleración y una **máquina de estados** que evita arranques accidentales o cambios de giro bruscos que puedan dañar engranajes o el motor. +- Máquina de estados para evitar arranques accidentales. +- Aceleración y desaceleración progresiva (rampa). +- Inversión de giro con secuencia controlada. +- Interfaz hombre–máquina con encoder rotatorio, botones y pantalla OLED. + +El sistema se desarrolla inicialmente sobre **HW-364A (ESP8266)**, con una ruta clara de migración a **ESP32 DevKit V1**. --- -## 2. Especificaciones de Hardware +## 2. Arquitectura del Sistema -### 2.1 Módulo de Control (Placa Azul) - -La interfaz de usuario se centraliza en un módulo que incluye un **OLED de 0.96"**, un **encoder con botón** y **dos botones laterales**. - -**Orden físico de los pines:** +### 2.1 Flujo Funcional ``` -CON | SDA | SCL | PSH | RTA | TRB | BAK | GND | VCC +Usuario (Encoder / Botones) + ↓ + ESP8266 + ↓ + Driver DRV8871 + ↓ + Motor DC + ↓ + OLED (Feedback) ``` --- -### 2.2 Driver de Potencia (DRV8871) +## 3. Hardware -* **Voltaje de Motor:** Hasta 45V (fuente externa) -* **Corriente:** Pico de 3.6A -* **Control:** PWM bidireccional (Puente H) +### 3.1 Controlador Principal + +- Placa: HW-364A +- MCU: ESP8266 +- Lógica: 3.3 V +- Limitación crítica: GPIO reducidos + +### 3.2 Sistema de Energía + +- Tierra común (GND) para todo el sistema +- Motor alimentado por fuente externa independiente +- Electrónica alimentada a 3.3 V + +### 3.3 Asignación de Pines + +| Función | Señal | Pin | +|------|------|-----| +| OLED SDA | Datos I2C | D2 | +| OLED SCL | Reloj I2C | D1 | +| Encoder A | Giro A | D5 | +| Encoder B | Giro B | D6 | +| Botón Centro | Start | D7 | +| Botón Back | Reversa | D8 | +| Botón Confirm | Adelante | D3 | +| Driver IN1 | Motor A | D0 | +| Driver IN2 | Motor B | D4 | + +### 3.4 Driver de Potencia + +- Modelo: DRV8871 +- Control PWM bidireccional +- Polaridad definida por IN1 / IN2 --- -## 3. Conexiones y Pinout +## 4. Lógica de Control -### 3.1 Módulo de Interfaz a ESP8266 +### 4.1 Máquina de Estados -| Etiqueta PCB | Función | Pin ESP8266 | Observaciones | -| ------------ | --------------------- | ----------- | --------------------------------------- | -| CON | Confirmar / Adelante | D3 (GPIO0) | Pin de boot. No presionar al encender | -| SDA | I2C Data (OLED) | D2 (GPIO4) | — | -| SCL | I2C Clock (OLED) | D1 (GPIO5) | — | -| PSH | Botón Encoder (Start) | D7 (GPIO13) | — | -| RTA | Encoder Fase A | D5 (GPIO14) | — | -| TRB | Encoder Fase B | D6 (GPIO12) | — | -| BAK | Atrás / Reversa | D8 (GPIO15) | Pin de boot. Debe estar LOW al encender | -| GND | Tierra | G (GND) | — | -| VCC | Alimentación | 3V (3.3V) | — | +| Estado | Descripción | +|------|-------------| +| WAITING_START | Sistema armado, motor bloqueado | +| SELECT_DIR | Selección explícita de sentido | +| RUNNING | Operación normal | +| CHANGING_DIR | Inversión controlada | + +### 4.2 Arranque Seguro + +El motor permanece apagado hasta confirmación explícita del usuario. Previene reinicios peligrosos tras fallas de energía. + +### 4.3 Selección de Sentido + +- BACK → Reversa +- CONFIRM → Adelante + +No existe sentido por defecto. + +### 4.4 Control de Velocidad Adaptativo + +- Giro lento → ±1 % +- Giro rápido (< 50 ms) → ±5 % +- Rango: 0–100 % + +### 4.5 Rampa de Aceleración + +Variables desacopladas: + +- `targetSpeed` → intención del usuario +- `currentSpeed` → acción física + +Incremento/decremento: 0.5 % por ciclo (~2.5 s a plena escala). + +### 4.6 Inversión Inteligente + +Secuencia obligatoria: + +1. Rampa descendente hasta 0 % +2. Pausa mecánica +3. Cambio de polaridad +4. Rampa ascendente --- -### 3.2 Driver DRV8871 a ESP8266 +## 5. Firmware -| Etiqueta PCB | Función | Pin ESP8266 | Observaciones | -| ------------ | ------------- | ----------- | ------------------------- | -| IN1 | Entrada PWM A | D0 (GPIO16) | PWM limitado por hardware | -| IN2 | Entrada PWM B | D4 (GPIO2) | Comparte LED azul interno | +### 5.1 Inicialización (`setup()`) + +- Configuración de GPIO +- Motor apagado por defecto +- Botones con pull-up interno +- Inicialización OLED + +### 5.2 Bucle Principal (`loop()`) + +- Lectura del encoder +- Ejecución de máquina de estados +- Control de rampa +- Render de interfaz + +### 5.3 Funciones Críticas + +- `readEncoderSpeed()` → velocidad adaptativa +- `handleDirectionButtons()` → solicitud de inversión +- `updateMotorRamp()` → núcleo de control +- `drawInterface()` → visualización --- -## 4. Lógica de Operación +## 6. Interfaz de Usuario -### 4.1 Máquina de Estados de Seguridad +- Flechas grandes indican sentido +- Porcentaje central = velocidad real +- Indicador secundario = velocidad objetivo -Para prevenir daños y accidentes, el sistema implementa cuatro estados definidos: - -* **WAITING_START (Armado):** - - * Motor bloqueado - * Pantalla solicita pulsar **PSH** para iniciar - -* **SELECT_DIR (Dirección):** - - * Espera selección del sentido - * **CON** = Adelante - * **BAK** = Reversa - -* **RUNNING (Operación):** - - * Motor en giro - * Encoder ajusta `targetSpeed` - -* **CHANGING_DIR (Transición):** - - * Rampa baja progresivamente a 0% - * Pausa de seguridad - * Rampa sube en el nuevo sentido +Diseño orientado a lectura inmediata. --- -### 4.2 Control de Velocidad Adaptativo +## 7. Limitaciones Actuales -* **Giro lento:** Ajustes finos de ±1% -* **Giro rápido:** Ajustes de ±5% (intervalo entre pulsos < 50 ms) -* **Rampa de seguridad:** Incrementos de 0.5% por ciclo +- GPIO al límite +- Sin indicadores periféricos +- Expansión restringida --- -## 5. Interfaz de Usuario (HMI) +## 8. Versión 2 (V2) -La pantalla OLED presenta información crítica del sistema: +### 8.1 Migración a ESP32 -* **Iconos de sentido:** Flechas grandes indicando el giro -* **Velocidad real:** Porcentaje actual aplicado por la rampa -* **Velocidad objetivo:** Valor configurado por el usuario -* **Alertas del sistema:** +- Mayor número de GPIO +- PWM por hardware +- Escalabilidad - * `SISTEMA BLOQUEADO` - * `CAMBIANDO GIRO` +### 8.2 LED RGB de Estado + +| Color | Significado | +|------|-------------| +| Amarillo | Standby / 0 % | +| Verde | Adelante | +| Rojo | Reversa | +| Rojo/Verde | Inversión | + +### 8.3 Cambios en Firmware + +- Nueva función `updateLeds()` +- GPIO dedicados (25, 26, 27) +- Mezcla de color por PWM --- -## 6. Hoja de Ruta — Versión 2 (V2) +## 9. Conclusión -### 6.1 Migración a ESP32 - -* Sustitución por **ESP32 DevKit V1** -* Eliminación de restricciones de GPIO -* PWM por hardware con mayor resolución - -### 6.2 Indicadores de Estado Visuales (LED RGB) - -| Color | Estado del sistema | -| ------------------ | ----------------------------- | -| 🟡 Amarillo | Sistema en espera (0%) | -| 🟢 Verde | Motor girando adelante | -| 🔴 Rojo | Motor girando en reversa | -| 🔵 Azul (parpadeo) | Cambio de dirección / frenado | - ---- - -## 7. Limitaciones Técnicas Actuales - -* **D3 / D8:** Pines sensibles al arranque por resistencias internas -* **PWM en D0:** Frecuencia no estándar, compensada por software - ---- - -**Desarrollado por:** Marco Gallegos +El sistema establece una base sólida para control seguro y profesional de motores DC, con una arquitectura preparada para evolucionar hacia una solución embebida más robusta en ESP32. diff --git a/drill_vanity.ino b/drill_vanity.ino new file mode 100644 index 0000000..e068657 --- /dev/null +++ b/drill_vanity.ino @@ -0,0 +1,253 @@ +/* + * Proyecto: Control de Motor DC - Consola Digital v1.0 + * Archivo: drill_vanity.ino + * Autor: Marco Gallegos + * Hardware: ESP8266 (HW-364A), DRV8871, OLED SSD1306, Encoder + */ + +#include +#include +#include + +// --- DEFINICIÓN DE PINES --- +// Nota: Usamos la notación Dx del NodeMCU para claridad, mapeada a GPIO +#define PIN_CON 0 // D3 (GPIO0) - Adelante / Confirmar (Active LOW) +#define PIN_SDA 4 // D2 (GPIO4) - I2C Data +#define PIN_SCL 5 // D1 (GPIO5) - I2C Clock +#define PIN_PSH 13 // D7 (GPIO13) - Botón Encoder (Start/Stop) +#define PIN_RTA 14 // D5 (GPIO14) - Encoder A +#define PIN_TRB 12 // D6 (GPIO12) - Encoder B +#define PIN_BAK 15 // D8 (GPIO15) - Atrás / Reversa (Active LOW) +#define PIN_IN1 16 // D0 (GPIO16) - PWM A +#define PIN_IN2 2 // D4 (GPIO2) - PWM B + +// --- CONFIGURACIÓN OLED --- +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); + +// --- MÁQUINA DE ESTADOS --- +enum SystemState { + WAITING_START, // Sistema armado, motor bloqueado + SELECT_DIR, // Esperando selección de sentido + RUNNING, // Motor operando + CHANGING_DIR // Transición de seguridad +}; + +SystemState currentState = WAITING_START; +bool directionForward = true; // true = Adelante, false = Atrás + +// --- VARIABLES DE CONTROL MOTOR --- +int targetSpeed = 0; // Velocidad deseada (0-255) +float currentSpeed = 0; // Velocidad real (Rampa) +const float RAMP_STEP = 0.8; // Ajuste de suavidad (menor = más suave) +unsigned long lastRampTime = 0; +const int RAMP_INTERVAL = 5; // ms + +// --- VARIABLES ENCODER --- +volatile int encoderValue = 0; +volatile unsigned long lastInterruptTime = 0; +unsigned long lastEncoderChangeTime = 0; + +// --- DEBOUNCE BOTONES --- +unsigned long lastDebounceTime = 0; +const int DEBOUNCE_DELAY = 200; + +// --- ISR (Interrupción Encoder) --- +ICACHE_RAM_ATTR void handleEncoder() { + unsigned long interruptTime = millis(); + if (interruptTime - lastInterruptTime > 5) { + if (digitalRead(PIN_RTA) == digitalRead(PIN_TRB)) { + encoderValue--; + } else { + encoderValue++; + } + lastInterruptTime = interruptTime; + } +} + +void setup() { + Serial.begin(115200); + + // Configuración de Pines + // D3 y D8 son pines de BOOT. Se configuran INPUT_PULLUP en setup, + // pero el hardware debe asegurar sus estados correctos al encender. + pinMode(PIN_CON, INPUT_PULLUP); + pinMode(PIN_BAK, INPUT_PULLUP); + pinMode(PIN_PSH, INPUT_PULLUP); + + pinMode(PIN_RTA, INPUT_PULLUP); + pinMode(PIN_TRB, INPUT_PULLUP); + + pinMode(PIN_IN1, OUTPUT); + pinMode(PIN_IN2, OUTPUT); + + // Motor apagado al inicio + digitalWrite(PIN_IN1, LOW); + digitalWrite(PIN_IN2, LOW); + + attachInterrupt(digitalPinToInterrupt(PIN_RTA), handleEncoder, CHANGE); + + // Inicializar OLED + Wire.begin(PIN_SDA, PIN_SCL); + if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { + Serial.println(F("Error OLED")); + for(;;); + } + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + display.display(); +} + +void loop() { + handleStateMachine(); + updateMotorOutput(); + updateDisplay(); + delay(10); +} + +void handleStateMachine() { + unsigned long now = millis(); + + bool btnCon = (digitalRead(PIN_CON) == LOW); + bool btnBak = (digitalRead(PIN_BAK) == LOW); + bool btnPsh = (digitalRead(PIN_PSH) == LOW); + + // Filtro de rebotes + if ((btnCon || btnBak || btnPsh) && (now - lastDebounceTime < DEBOUNCE_DELAY)) return; + if (btnCon || btnBak || btnPsh) lastDebounceTime = now; + + switch (currentState) { + case WAITING_START: + targetSpeed = 0; + currentSpeed = 0; + if (btnPsh) currentState = SELECT_DIR; + break; + + case SELECT_DIR: + if (btnCon) { + directionForward = true; + targetSpeed = 60; // Arranca suave + currentState = RUNNING; + } else if (btnBak) { + directionForward = false; + targetSpeed = 60; + currentState = RUNNING; + } else if (btnPsh) { + currentState = WAITING_START; // Cancelar + } + break; + + case RUNNING: + // Control de velocidad con encoder + if (encoderValue != 0) { + int change = encoderValue; + // Aceleración adaptativa según velocidad de giro del encoder + int step = (millis() - lastEncoderChangeTime < 50) ? 10 : 2; + + targetSpeed += (change * step); + targetSpeed = constrain(targetSpeed, 0, 255); + encoderValue = 0; + lastEncoderChangeTime = now; + } + + // Parada + if (btnPsh) { + targetSpeed = 0; + if (currentSpeed < 10) currentState = WAITING_START; + } + + // Cambio de sentido "al vuelo" + if ((directionForward && btnBak) || (!directionForward && btnCon)) { + currentState = CHANGING_DIR; + } + break; + + case CHANGING_DIR: + targetSpeed = 0; + // Esperar a que el motor se detenga casi por completo + if (currentSpeed <= 5) { + delay(300); // Pequeña pausa mecánica + directionForward = !directionForward; + currentState = RUNNING; + targetSpeed = 60; + } + break; + } +} + +void updateMotorOutput() { + unsigned long now = millis(); + + // Lógica de Rampa + if (now - lastRampTime >= RAMP_INTERVAL) { + if (currentSpeed < targetSpeed) { + currentSpeed += RAMP_STEP; + if (currentSpeed > targetSpeed) currentSpeed = targetSpeed; + } else if (currentSpeed > targetSpeed) { + currentSpeed -= RAMP_STEP; + if (currentSpeed < targetSpeed) currentSpeed = targetSpeed; + } + lastRampTime = now; + } + + int pwmOutput = (int)currentSpeed; + + if (currentState == WAITING_START) { + digitalWrite(PIN_IN1, LOW); + digitalWrite(PIN_IN2, LOW); + } else { + // DRV8871 Control + if (directionForward) { + analogWrite(PIN_IN1, pwmOutput); + digitalWrite(PIN_IN2, LOW); + } else { + digitalWrite(PIN_IN1, LOW); + analogWrite(PIN_IN2, pwmOutput); + } + } +} + +void updateDisplay() { + static unsigned long lastDisp = 0; + if (millis() - lastDisp < 100) return; + lastDisp = millis(); + + display.clearDisplay(); + display.setTextSize(1); + display.setCursor(0, 0); + + switch (currentState) { + case WAITING_START: + display.println(F(">> SISTEMA LISTO <<")); + display.setCursor(25, 25); + display.setTextSize(2); + display.print(F("PULSAR")); + display.setCursor(35, 45); + display.print(F("START")); + break; + + case SELECT_DIR: + display.println(F(" SELECCIONAR GIRO")); + display.setCursor(10, 30); + display.setTextSize(2); + display.print(F("<< O >>")); + break; + + case RUNNING: + case CHANGING_DIR: + display.print(currentState == CHANGING_DIR ? F("! INVIRTIENDO !") : (directionForward ? F("GIRO: ADELANTE >") : F("GIRO: < ATRAS"))); + + display.setTextSize(3); + int pct = map((int)currentSpeed, 0, 255, 0, 100); + display.setCursor(35, 20); + display.print(pct); + display.setTextSize(2); + display.print(F("%")); + + display.drawRect(5, 58, 118, 6, SSD1306_WHITE); + display.fillRect(7, 60, map(pct,0,100,0,114), 2, SSD1306_WHITE); + break; + } + display.display(); +} \ No newline at end of file