Implementación de PWM No Bloqueante mediante micros() para el Control de Motores DC

 Implementación de PWM No Bloqueante mediante micros() para el Control de Motores DC

En esta publicación analizaremos en detalle la estructura de un programa diseñado para generar una señal PWM personalizada mediante software no bloqueante. Veremos por qué este enfoque supera la implementación básica con la función delay() y explicaremos minuciosamente el rol que cumple la función micros() dentro del ciclo de ejecución.

El Problema del Método Básico con delay()

En una implementación sencilla, el ancho de pulso suele generarse alternando digitalWrite(pin, HIGH) y digitalWrite(pin, LOW) separados por llamadas a delay().

Aunque este método es intuitivo, presenta un inconveniente crítico: la función delay() es bloqueante. Mientras el microcontrolador ejecuta una pausa por retardo, la CPU detiene por completo el procesamiento de instrucciones. Esto significa que el sistema no puede:

  • Leer el estado de botones o pulsadores externos.

  • Procesar lecturas de sensores de corriente o velocidad.

  • Atender comandos enviados por puerto serie o conectividad WiFi.

Además, la función delay() trabaja con una precisión limitada a milisegundos, lo cual impide ajustar frecuencias con alta finura en comparación con contadores basados en microsegundos.

Código del Programa No Bloqueante

C++
// Pin de salida PWM (Pin 3 de Arduino Uno -> Pin D3 / GPIO0 en ESP8266)
const int pinPWM = 3; 

// Frecuencia deseada: 100 Hz -> Período total = 10,000 microsegundos (10 ms)
const unsigned long periodoTotalUs = 10000; 

// Ciclo de trabajo (Duty Cycle) de 0 a 255 (128 = 50%)
int valorPWM = 50; 

// Variables de tiempo
unsigned long tiempoON = 0;
unsigned long ultimoTiempoMicros = 0;

void setup() {
  pinMode(pinPWM, OUTPUT);
  // Calcular el tiempo en ALTO según el valor de PWM (0-255)
  tiempoON = (periodoTotalUs * valorPWM) / 255;
}

void loop() {
  unsigned long tiempoActual = micros();
  unsigned long tiempoTranscurrido = tiempoActual - ultimoTiempoMicros;

  if (tiempoTranscurrido < tiempoON) {
    digitalWrite(pinPWM, HIGH); // Período activo (ON)
  } else if (tiempoTranscurrido < periodoTotalUs) {
    digitalWrite(pinPWM, LOW);  // Período inactivo (OFF)
  } else {
    ultimoTiempoMicros = tiempoActual; // Reiniciar ciclo de 10 ms
  }
}

Análisis y Explicación de Funciones e Instrucciones

  • micros(): Esta función devuelve el número de microsegundos transcurridos desde que la tarjeta ejecutó el programa actual. Dado que se desborda y vuelve a cero aproximadamente cada 70 minutos, permite realizar mediciones relativas de tiempo de altísima precisión.

  • unsigned long: Es el tipo de dato utilizado para las variables de tiempo (tiempoActual, ultimoTiempoMicros, periodoTotalUs). Es indispensable usar enteros largos sin signo para almacenar los grandes valores numéricos que genera micros() y evitar errores al evaluar la resta de tiempos.

  • tiempoON = (periodoTotalUs * valorPWM) / 255;: Realiza una regla de tres directa dentro de setup(). Convierte la escala estándar de PWM (0 a 255) en un equivalente en microsegundos. Con un valor de 50 sobre 255 y un período total de 10,000 microsegundos, tiempoON se fija automáticamente en 1,960 microsegundos (aproximadamente un 19.6% del ciclo de trabajo).

  • Evaluación del tiempo en loop(): La resta tiempoActual - ultimoTiempoMicros calcula cuántos microsegundos han pasado desde el inicio del período actual:

    • Si el tiempo es menor a tiempoON, el pin conmuta a HIGH.

    • Si el tiempo supera a tiempoON pero no ha completado los 10,000 microsegundos del período, la salida conmuta a LOW.

    • Al cumplirse o superar el periodoTotalUs (10 ms), se actualiza ultimoTiempoMicros = tiempoActual, reiniciando el ciclo de forma transparente.

Ventajas Principales

  1. Ejecución Asíncrona: Al no existir ningún delay(), el bucle loop() se repite miles de veces por segundo. Esto deja la CPU completamente disponible para intercalar otras tareas de control en tiempo real.

  2. Estabilidad en Simuladores: Evita los problemas de compatibilidad de los temporizadores por hardware nativos (analogWrite()) durante simulaciones en entornos como Proteus.

  3. Control Dinámico: Permite modificar la variable valorPWM al vuelo desde cualquier otra sección del código (por ejemplo, al detectar la pulsación de un botón) y recalcular tiempoON de manera instantánea.

Justificación Técnica: ¿Por qué usamos este programa en lugar de analogWrite() para la simulación?

En esta etapa del proyecto, el uso de la función nativa analogWrite() presenta un inconveniente específico dentro del entorno de simulación (Proteus). Al seleccionar una placa de desarrollo basada en AVR (como Arduino Uno) dentro del IDE para compilar y generar el archivo ejecutable .hex que luego lee el modelo virtual del NodeMCU/ESP8266, ocurre un problema de compatibilidad en la abstracción de hardware.

La instrucción analogWrite() intenta configurar y depender directamente de los registros internos de los temporizadores por hardware (Hardware Timers) del microcontrolador. Sin embargo, la librería gráfica y el modelo esquemático del ESP8266 en Proteus no logran mapear los canales de estos temporizadores AVR hacia el pin físico D3 (GPIO0). Como resultado, la salida dentro del simulador queda en un estado lógico bajo constante o flotante, impidiendo observar la conmutación en el osciloscopio virtual.

El programa basado en micros() resuelve este problema al implementar un PWM por software no bloqueante. Al controlar manualmente el pin con digitalWrite() y medir el tiempo transcurrido en microsegundos, forzamos a Proteus a conmutar el estado del puerto de manera precisa y visual a 100 Hz, manteniendo al mismo tiempo la CPU libre para ejecutar otras instrucciones dentro del loop().

Programa Equivalente para Implementación Física en el ESP8266 (Laboratorio Real)

Cuando se trabaje directamente sobre el módulo NodeMCU (ESP8266) físico en el laboratorio, se debe prescindir del PWM por software y utilizar la función nativa por hardware. Al seleccionar la tarjeta ESP8266 en el IDE de Arduino y compilar con su core oficial, la función analogWrite() usará los temporizadores reales del chip de forma completamente autónoma.

C++
// Pin de salida PWM (D3 / GPIO0 en la serigrafía del NodeMCU ESP8266)
const int pinPWM = 0; 

// Ciclo de trabajo (Duty Cycle) de 0 a 1023 en el core del ESP8266
// 512 representa el 50% del ciclo de trabajo
int valorPWM = 512; 

void setup() {
  pinMode(pinPWM, OUTPUT);
  
  // Opcional: Ajusta la frecuencia del PWM a 100 Hz para coincidir con la simulación
  analogWriteFreq(100); 

  // Inicia la generación de la señal PWM por hardware
  analogWrite(pinPWM, valorPWM);
}

void loop() {
  // El ciclo loop() queda completamente libre. 
  // La señal PWM se genera en segundo plano mediante los temporizadores del ESP8266.
}

Diferencias clave al cargar en el ESP8266 real:

  • Resolución por defecto: En la arquitectura nativa del ESP8266, la función analogWrite() trabaja por defecto con una resolución de 10 bits (valores de 0 a 1023), donde 512 equivale al 50% de ancho de pulso.

  • Ajuste de frecuencia: La función analogWriteFreq(100) permite definir explícitamente la frecuencia en Hz, logrando replicar exactamente los 100 Hz utilizados en la simulación sin cargar el procesador.

Comentarios

Entradas populares de este blog

FUENTE DC CON PROTECCIÓN ELECTRÓNICA E INDICADORES LED

Control de velocidad y sentido de giro de un motor de corriente continua

Controlador de giro para motor paso a paso unipolar mediante 555, contador y lógica combinacional