Un controlador PID evalúa constantemente el Error (La diferencia entre la meta que quieres alcanzar y el estado actual real) y ajusta un actuador (como la apertura de una válvula o la potencia de un motor) basado en tres enfoques temporales:
En la universidad te enseñan la fórmula continua con derivadas e integrales ($\int e(t)dt$). Pero un procesador digital no puede calcular un área continua infinita; funciona a "saltos" dictados por su reloj (ej. cada 10 milisegundos). Debemos transformar el cálculo (Cálculo Numérico) a una Ecuación Discreta:
Suma += Error * delta_t.(Error_actual - Error_anterior) / delta_t.Los ingenieros que vienen de Arduino suelen usar enteros multiplicados por 100 para evadir la matemática con decimales, porque un AVR de 8 bits tarda cientos de ciclos en hacer una división flotante (Soft-Float). Tu STM32F401RE cuenta con un Procesador Matemático de Hardware (FPU Cortex-M4F). Puede sumar, multiplicar y dividir números `float` en un solo ciclo de reloj. Por primera vez en nuestra arquitectura Bare-Metal, declararemos variables float con la confianza de que el hardware las absorberá sin pestañear.
Ejercicio 1: Programaremos la estructura de nuestro Controlador PID en lenguaje C. Incluiremos las ganancias (K), las memorias del pasado (Integrador y Error Previo) y una protección industrial vital: el Anti-Windup. Esta protección evita que la memoria integral acumule números astronómicos si la válvula se queda trabada mecánicamente, impidiendo un fallo sistémico grave.
/**
* @file pid_controller.c
* @brief Algoritmo PID Discreto Bare-Metal con soporte FPU y Anti-Windup
*/
#include "stm32f401xe.h"
#include <stdbool.h>
// =========================================================================
// ESTRUCTURA DEL CONTROLADOR
// =========================================================================
typedef struct {
// Ganancias de Afinación (Tuning)
float Kp; // Proporcional
float Ki; // Integral
float Kd; // Derivativo
// Memorias del estado anterior
float error_previo;
float integrador;
// Límites de seguridad del actuador (Ej. PWM de 0% a 100%)
float out_min;
float out_max;
// Límite Anti-Windup para el integrador
float windup_limit;
} PID_Controller_t;
/**
* @brief Inicializa el controlador con los parámetros físicos de la planta
*/
void PID_Init(PID_Controller_t *pid, float kp, float ki, float kd) {
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->error_previo = 0.0f;
pid->integrador = 0.0f;
pid->out_min = 0.0f; // 0% PWM (Válvula totalmente cerrada)
pid->out_max = 100.0f; // 100% PWM (Válvula totalmente abierta)
// Si la válvula solo puede abrir al 100%, no tiene sentido que el
// integrador acumule ordenando un 500% de apertura.
pid->windup_limit = 100.0f;
}
/**
* @brief Ecuación principal del PID. Debe ejecutarse a intervalos de tiempo EXACTOS.
*
* @param pid Puntero a la estructura del controlador
* @param setpoint El objetivo a alcanzar (Ej. 20.00 Litros)
* @param medicion La lectura actual del sensor (Ej. 18.50 Litros)
* @param dt El "Delta Time", tiempo transcurrido en segundos desde la última llamada.
* @return float Salida de control (Ej. Porcentaje de apertura de la válvula)
*/
float PID_Update(PID_Controller_t *pid, float setpoint, float medicion, float dt) {
// Evitar división por cero si el timer falla
if (dt <= 0.0f) return 0.0f;
// 1. Calcular el Error actual (Qué tan lejos estamos de la meta)
float error = setpoint - medicion;
// 2. Término PROPORCIONAL
float P = pid->Kp * error;
// 3. Término INTEGRAL (Acumulación del error en el tiempo)
pid->integrador += error * dt;
// PROTECCIÓN INDUSTRIAL: Anti-Windup
// Evita que el integrador acumule hasta el infinito si la válvula
// está al máximo y el fluido no acelera (ej. falta de presión de bomba).
if (pid->integrador > pid->windup_limit) {
pid->integrador = pid->windup_limit;
} else if (pid->integrador < -pid->windup_limit) {
pid->integrador = -pid->windup_limit;
}
float I = pid->Ki * pid->integrador;
// 4. Término DERIVATIVO (Tasa de cambio del error)
float derivada = (error - pid->error_previo) / dt;
float D = pid->Kd * derivada;
// 5. Salida Total del Bucle (Ecuación PID)
float salida = P + I + D;
// 6. Saturar la salida a los límites físicos del actuador (Clamp)
if (salida > pid->out_max) salida = pid->out_max;
if (salida < pid->out_min) salida = pid->out_min;
// 7. Guardar el estado para la próxima iteración del bucle
pid->error_previo = error;
return salida;
}
// =========================================================================
// USO EN EL SISTEMA (SIMULACIÓN DEL SUPER BUCLE)
// =========================================================================
PID_Controller_t pid_valvula;
extern bool bandera_timer_10ms;
extern float leer_sensor_litros();
int main(void) {
// FPU Enable (Obligatorio en STM32F4 para cálculos de 1-ciclo)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));
// Las ganancias K se afinan experimentalmente (Ziegler-Nichols o empírico)
PID_Init(&pid_valvula, 15.0f, 2.5f, 0.5f);
float meta_despacho = 20.0f; // 20 Litros
while(1) {
// El SysTick nos avisa exactamente cada 10 milisegundos (dt = 0.01s)
if (bandera_timer_10ms) {
// 1. Leer sensor de caudal (Actualizado por interrupción EXTI en el Día 128)
float litros_actuales = leer_sensor_litros();
// 2. Calcular algoritmo PID
float apertura = PID_Update(&pid_valvula, meta_despacho, litros_actuales, 0.01f);
// 3. Aplicar al actuador físico
// set_pwm_duty_cycle(apertura); // Configura el Timer de la Válvula
// Si la apertura llega a 0.0, cerramos todo mecánicamente y terminamos.
if (apertura <= 0.0f) {
// finalizar_venta();
}
bandera_timer_10ms = false;
}
}
return 0;
}
Objetivo del día: Precisión Volumétrica Exacta (Cero Sobretiro).
El principal problema de los dispensadores mecánicos antiguos es que la inercia del fluido provoca errores de hasta +150 mililitros al final de la carga. Para el dueño de la estación, regalar 150ml en 1,000 ventas diarias equivale a miles de dólares perdidos al año.
Al introducir el algoritmo **PID Discreto**, has transformado tu Despachador en un robot de altísima precisión. Cuando falte medio litro para llegar al objetivo, la ecuación PID ordenará al hardware reducir la apertura de la electroválvula de manera logarítmica, cerrándola al 20%, luego al 10%, luego al 5%... permitiendo que el gas entre por "goteo" exacto en los milisegundos finales. Tu máquina entregará 20.000 Litros. Ni una gota más, ni una gota menos. El uso de la **FPU (Hardware Floating Point)** garantiza que estos complejos cálculos de derivadas se realicen sin congelar las comunicaciones RS485 o la impresora que corren en paralelo.
dt) entre ciclos.SCB->CPACR), el uso de variables float deja de ser un "cuello de botella" y se convierte en la forma más rápida y precisa de manejar cálculos algorítmicos.