Revise README for DC Motor Control project

Updated the README to provide a comprehensive overview of the DC Motor Control project, including hardware setup, functionality, and future enhancements.
This commit is contained in:
Marco Gallegos
2026-01-18 11:30:45 -06:00
committed by GitHub
parent 2c63d3c112
commit ce9f8d9f36

408
README.md
View File

@@ -1,285 +1,189 @@
Proyecto de Control de Motor DC: Explicación Integral # Proyecto de Control de Motor DC
Este proyecto consiste en crear una "consola de mando" digital para un motor de corriente continua. En lugar de usar un potenciómetro simple que cambia la velocidad de golpe, estás construyendo un sistema inteligente que gestiona la aceleración suave, protege el motor en los cambios de giro y te da retroalimentación visual.
1. El Hardware y el Pinout (La base física)
Estamos utilizando la placa HW-364A (ESP8266). Esta placa es el "cerebro". El desafío principal con ella es que tiene pocos pines disponibles, y tu proyecto requiere conectar muchos periféricos.
Diagrama de Conexiones en tu Protoboard
Imagina el flujo de información: Tu mano mueve el encoder -> El ESP8266 procesa la señal -> El ESP8266 manda instrucciones al Driver -> El Driver da energía al Motor -> La Pantalla te muestra qué pasó.
Aquí está la asignación física de pines que debes tener en tu protoboard:
Sistema de Energía:
Todo comparte la tierra (GND).
El Motor recibe su voltaje de una fuente externa (Baterías o Fuente de Poder), NO del USB.
La electrónica (Pantalla, Encoder, ESP) se alimenta con 3.3V.
La Pantalla y el Encoder (Módulo Integrado):
SDA (Datos) -> D2: Por aquí viaja la información para dibujar en la pantalla.
SCL (Reloj) -> D1: Sincroniza la comunicación de la pantalla.
A (Encoder) -> D5: Detecta el giro hacia un lado.
B (Encoder) -> D6: Detecta el giro hacia el otro.
Btn Centro -> D7: Es el "Enter" o arranque.
Btn Back -> D8: Botón para ir hacia atrás (Reversa).
Btn Confirm -> D3: Botón para ir hacia adelante.
El Músculo (Driver DRV8871):
IN1 -> D0: Controla la potencia en una polaridad.
IN2 -> D4: Controla la potencia en la polaridad opuesta.
2. Lógica de Funcionamiento (El Comportamiento)
El programa no solo "gira y ya". Tiene una máquina de estados que define su comportamiento seguro:
A. Estado de Inicio (Seguridad)
Cuando enciendes el sistema, el motor no arranca. La pantalla te pide presionar el botón central. Esto evita que, si se fue la luz y vuelve, el motor arranque solo y cause un accidente.
B. Selección de Sentido
Una vez activado, el sistema te pregunta: ¿Hacia dónde vamos? Debes presionar el botón de la izquierda (BACK) para Reversa o el de la derecha (CONFIRM) para Adelante.
C. Control de Velocidad (El Encoder)
Aquí hay una lógica interesante llamada "Velocidad Adaptativa":
Giro Fino: Si giras la perilla despacio, la velocidad sube de 1% en 1%. Ideal para ajustes precisos.
Giro Rápido: Si giras la perilla rápidamente (menos de 50ms entre clicks), el sistema detecta la urgencia y sube la velocidad de 5% en 5%.
D. La Rampa de Aceleración (Física Suave)
Si estás al 0% y pides ir al 100%, el motor no salta instantáneamente.
El código tiene una variable targetSpeed (lo que tú quieres) y una variable currentSpeed (lo que el motor está haciendo). El programa ajusta la currentSpeed poco a poco hasta alcanzar a la targetSpeed. Esto toma 2.5 segundos. Esto protege los engranajes del motor y evita picos de corriente.
E. Cambio de Sentido (Inversión Inteligente)
Si el motor va hacia adelante al 100% y presionas "Atrás", el sistema hace esto automáticamente:
Baja la velocidad suavemente de 100% a 0% (Rampa de desaceleración).
Espera un momento (zona muerta) para asegurar que el eje se detuvo.
Cambia la polaridad eléctrica interna.
Sube la velocidad suavemente de 0% a 100% en el nuevo sentido.
3. Explicación de las Funciones del Código
Aquí te explico qué hace cada parte del programa que verás más abajo:
setup(): Prepara los pines. Define cuáles son entradas (botones) y salidas (motor). Inicia la pantalla OLED y muestra el mensaje de bienvenida.
loop(): Es el cerebro principal que se repite infinitamente. Usa un switch (systemState) para saber si está esperando, eligiendo dirección o corriendo.
readEncoderSpeed(): Lee el encoder. Calcula el tiempo entre giros para decidir si suma 1 o 5 a la velocidad.
handleDirectionButtons(): Vigila si presionas BACK o CONFIRM. Si lo haces, cambia el estado del sistema a CHANGING_DIR.
updateMotorRamp(): Esta es la función más importante. Compara la velocidad real con la deseada. Si son diferentes, suma o resta una pequeña fracción (0.5) en cada ciclo. Luego convierte ese número en una señal eléctrica (PWM) para el motor.
drawInterface(): Dibuja en la pantalla OLED las flechas (<- o ->), el número de porcentaje grande y el objetivo pequeño.
4. El Código (Versión 1 - Hardware Actual)
Copia y pega esto en tu Arduino IDE. Está diseñado para ser estable en tu HW-364A.
code
C++
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h> // Si la pantalla se ve con lluvia, cambia a Adafruit_SSD1306
#include <RotaryEncoder.h>
// --- ASIGNACIÓN DE PINES (HW-364A) --- ## 1. Descripción General
#define PIN_IN1 D0 // Salida Motor A
#define PIN_IN2 D4 // Salida Motor B
#define PIN_SDA D2 // Pantalla
#define PIN_SCL D1 // Pantalla
#define PIN_ENC_A D5 // Encoder Giro A 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:
#define PIN_ENC_B D6 // Encoder Giro B
#define PIN_BTN_CENTER D7 // Botón encoder
#define PIN_BTN_BACK D8 // Botón Izquierdo
#define PIN_BTN_CONFIRM D3 // Botón Derecho
// --- OBJETOS --- - Máquina de estados para evitar arranques accidentales.
#define OLED_RESET -1 - Aceleración y desaceleración progresiva (rampa).
Adafruit_SH1106 display(OLED_RESET); - Inversión de giro con secuencia controlada.
// Nota: Si usas SSD1306 descomenta la línea de abajo y comenta la de arriba: - Interfaz hombremáquina con encoder rotatorio, botones y pantalla OLED.
// #include <Adafruit_SSD1306.h>
// Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
// Configuración del encoder para evitar rebotes El sistema se desarrolla inicialmente sobre **HW-364A (ESP8266)**, con una ruta clara de migración a **ESP32 DevKit V1**.
RotaryEncoder encoder(PIN_ENC_A, PIN_ENC_B, RotaryEncoder::LatchMode::TWO03);
// --- VARIABLES GLOBALES --- ---
int targetSpeed = 0; // La velocidad que TÚ quieres
float currentSpeed = 0; // La velocidad que el MOTOR tiene ahora (float para suavidad)
int lastEncoded = 0;
unsigned long lastEncoderTime = 0;
// Máquina de Estados ## 2. Arquitectura del Sistema
enum State { WAITING_START, SELECT_DIR, RUNNING, CHANGING_DIR };
State systemState = WAITING_START;
enum Direction { FWD, REV }; ### 2.1 Flujo Funcional
Direction currentDir = FWD;
Direction nextDir = FWD;
// --- CONFIGURACIÓN INICIAL --- ```
void setup() { Usuario (Encoder / Botones)
Serial.begin(115200); // Para depurar errores en PC
ESP8266
Driver DRV8871
Motor DC
OLED (Feedback)
```
// Configurar Motor (Apagado al inicio) ---
pinMode(PIN_IN1, OUTPUT);
pinMode(PIN_IN2, OUTPUT);
digitalWrite(PIN_IN1, LOW);
digitalWrite(PIN_IN2, LOW);
// Configurar Botones con resistencia interna (Pullup) ## 3. Hardware
pinMode(PIN_BTN_CENTER, INPUT_PULLUP);
pinMode(PIN_BTN_BACK, INPUT_PULLUP);
pinMode(PIN_BTN_CONFIRM, INPUT_PULLUP);
// Iniciar Pantalla ### 3.1 Controlador Principal
display.begin(SH1106_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(10, 20);
display.println("INICIANDO SISTEMA...");
display.display();
delay(1000);
}
// --- BUCLE PRINCIPAL --- - Placa: HW-364A
void loop() { - MCU: ESP8266
encoder.tick(); // Revisar si el encoder se movió - Lógica: 3.3 V
- Limitación crítica: GPIO reducidos
switch (systemState) { ### 3.2 Sistema de Energía
// 1. ESTADO DE ESPERA
case WAITING_START:
displayCentered("PRESIONA CENTRO", "PARA ACTIVAR");
if (digitalRead(PIN_BTN_CENTER) == LOW) { // Al presionar (LOW)
delay(300); // Pequeña espera para evitar doble click
systemState = SELECT_DIR;
}
break;
// 2. ESTADO DE SELECCIÓN - Tierra común (GND) para todo el sistema
case SELECT_DIR: - Motor alimentado por fuente externa independiente
display.clearDisplay(); - Electrónica alimentada a 3.3 V
display.setCursor(0,0); display.println("ELIGE SENTIDO:");
display.setCursor(0,30); display.println("<- BACK (REV)");
display.setCursor(0,50); display.println("CONFIRM (FWD) ->");
display.display();
if (digitalRead(PIN_BTN_BACK) == LOW) { ### 3.3 Asignación de Pines
currentDir = REV;
systemState = RUNNING;
delay(300);
}
if (digitalRead(PIN_BTN_CONFIRM) == LOW) {
currentDir = FWD;
systemState = RUNNING;
delay(300);
}
break;
// 3. ESTADO DE CARRERA (NORMAL) | Función | Señal | Pin |
case RUNNING: |------|------|-----|
readEncoderSpeed(); // Leer perilla | OLED SDA | Datos I2C | D2 |
handleDirectionButtons(); // Leer botones de cambio | OLED SCL | Reloj I2C | D1 |
updateMotorRamp(); // Mover motor suavemente | Encoder A | Giro A | D5 |
drawInterface(); // Dibujar | Encoder B | Giro B | D6 |
break; | 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 |
// 4. ESTADO DE CAMBIO DE DIRECCIÓN ### 3.4 Driver de Potencia
case CHANGING_DIR:
targetSpeed = 0; // Ordenar freno total
updateMotorRamp(); // Ejecutar freno (respetando la rampa)
drawInterface();
// Cuando la velocidad real llegue casi a 0 - Modelo: DRV8871
if (currentSpeed <= 1.0) { - Control PWM bidireccional
delay(100); // Pausa de seguridad mecánica - Polaridad definida por IN1 / IN2
currentDir = nextDir; // Cambiar polaridad interna
targetSpeed = lastEncoded; // Recuperar la velocidad que teníamos
systemState = RUNNING; // Volver a correr
}
break;
}
delay(10); // Estabilidad del procesador
}
// --- FUNCIONES AUXILIARES --- ---
void readEncoderSpeed() { ## 4. Lógica de Control
int newPos = encoder.getPosition();
if (newPos != lastEncoded) {
int diff = newPos - lastEncoded;
unsigned long now = millis();
// Si giras muy rápido (<50ms), salta de 5 en 5. Si no, de 1 en 1. ### 4.1 Máquina de Estados
int step = (now - lastEncoderTime < 50) ? 5 : 1;
if (diff > 0) targetSpeed += step; | Estado | Descripción |
else targetSpeed -= step; |------|-------------|
| WAITING_START | Sistema armado, motor bloqueado |
| SELECT_DIR | Selección explícita de sentido |
| RUNNING | Operación normal |
| CHANGING_DIR | Inversión controlada |
// Limitar entre 0 y 100 ### 4.2 Arranque Seguro
targetSpeed = constrain(targetSpeed, 0, 100);
encoder.setPosition(targetSpeed);
lastEncoded = targetSpeed;
lastEncoderTime = now;
}
}
void handleDirectionButtons() { El motor permanece apagado hasta confirmación explícita del usuario. Previene reinicios peligrosos tras fallas de energía.
// Si presiono BACK y voy hacia Adelante -> Cambiar
if (digitalRead(PIN_BTN_BACK) == LOW && currentDir == FWD) {
nextDir = REV;
systemState = CHANGING_DIR;
}
// Si presiono CONFIRM y voy hacia Atrás -> Cambiar
if (digitalRead(PIN_BTN_CONFIRM) == LOW && currentDir == REV) {
nextDir = FWD;
systemState = CHANGING_DIR;
}
}
void updateMotorRamp() { ### 4.3 Selección de Sentido
// Acercar currentSpeed a targetSpeed paso a paso
if (currentSpeed < targetSpeed) {
currentSpeed += 0.5; // Acelerar
if (currentSpeed > targetSpeed) currentSpeed = targetSpeed;
} else if (currentSpeed > targetSpeed) {
currentSpeed -= 0.5; // Frenar
if (currentSpeed < targetSpeed) currentSpeed = targetSpeed;
}
// Convertir porcentaje (0-100) a PWM (0-1023) - BACK → Reversa
int pwmVal = map((int)currentSpeed, 0, 100, 0, 1023); - CONFIRM → Adelante
// Zona muerta: Si es muy poco voltaje, mejor apagar para evitar zumbido No existe sentido por defecto.
if (pwmVal < 50) pwmVal = 0;
// Aplicar al driver DRV8871 ### 4.4 Control de Velocidad Adaptativo
if (currentDir == FWD) {
analogWrite(PIN_IN1, pwmVal);
analogWrite(PIN_IN2, 0);
} else {
analogWrite(PIN_IN1, 0);
analogWrite(PIN_IN2, pwmVal);
}
}
void drawInterface() { - Giro lento → ±1 %
display.clearDisplay(); - Giro rápido (< 50 ms) → ±5 %
- Rango: 0100 %
// Dibujar flechas de dirección ### 4.5 Rampa de Aceleración
display.setTextSize(2);
display.setCursor(10, 0);
if (currentDir == FWD) display.print("FWD ->");
else display.print("<- REV");
// Dibujar velocidad gigante Variables desacopladas:
display.setTextSize(3);
display.setCursor(35, 30);
display.print((int)currentSpeed);
display.setTextSize(1);
display.print("%");
// Dibujar objetivo pequeño (si estamos en rampa) - `targetSpeed` → intención del usuario
if ((int)currentSpeed != targetSpeed) { - `currentSpeed` → acción física
display.setCursor(0, 55);
display.print("Rampa: "); display.print(targetSpeed);
}
display.display();
}
void displayCentered(String line1, String line2) { Incremento/decremento: 0.5 % por ciclo (~2.5 s a plena escala).
display.clearDisplay();
display.setTextSize(1); ### 4.6 Inversión Inteligente
display.setCursor(0, 20);
display.println(line1); Secuencia obligatoria:
display.println(line2);
display.display(); 1. Rampa descendente hasta 0 %
} 2. Pausa mecánica
5. Visión Futura: La Versión 2 (V2) 3. Cambio de polaridad
Has notado que con la placa HW-364A estamos "ahorcados" de pines. No pudimos poner el LED RGB cómodamente. Para la Versión 2, el plan es migrar el hardware. 4. Rampa ascendente
El Hardware Recomendado: ESP32 DevKit V1
Esta placa es un poco más larga, pero tiene 30 pines. Con esto podemos conectar todo lo anterior y agregar luces de estado profesionales. ---
Implementación del LED RGB en V2
La idea es usar un LED RGB para dar información periférica (para que sepas qué pasa sin mirar la pantalla OLED): ## 5. Firmware
Luz Amarilla Fija: El sistema está encendido pero en "Standby" o velocidad 0%.
Luz Verde: El motor gira hacia ADELANTE. La intensidad del verde podría subir con la velocidad. ### 5.1 Inicialización (`setup()`)
Luz Roja: El motor gira en REVERSA.
Parpadeo Rojo/Verde: El sistema está invirtiendo el giro automáticamente (fase de peligro/atención). - Configuración de GPIO
Cambios en Código para V2 - Motor apagado por defecto
En el futuro, solo tendrás que agregar una función llamada updateLeds() dentro del loop(). Al tener pines de sobra (como GPIO 25, 26, 27), podrás usar analogWrite en el LED Rojo y Verde para mezclar colores (Rojo + Verde = Amarillo) y crear una interfaz mucho más profesional. - 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
---
## 6. Interfaz de Usuario
- Flechas grandes indican sentido
- Porcentaje central = velocidad real
- Indicador secundario = velocidad objetivo
Diseño orientado a lectura inmediata.
---
## 7. Limitaciones Actuales
- GPIO al límite
- Sin indicadores periféricos
- Expansión restringida
---
## 8. Versión 2 (V2)
### 8.1 Migración a ESP32
- Mayor número de GPIO
- PWM por hardware
- Escalabilidad
### 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
---
## 9. Conclusión
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.