En el lenguaje C, cuando un archivo necesita usar una función que está en otro lado, necesita saber su "firma" (qué recibe y qué devuelve) antes de que el compilador la acepte. El archivo `.h` (Header File) actúa como un contrato público o un menú de restaurante. Te dice qué platillos (funciones) puedes pedir, pero no te dice cómo se cocinaron.
El archivo `.c` (Source File) es la cocina. Ahí es donde vive el código real (los bucles `while`, los IFs y el acceso a los registros del hardware).
Si el archivo `modbus.h` incluye a `valvula.h`, y el `main.c` incluye a ambos, el compilador procesará el texto de `valvula.h` dos veces. Esto generará un error fatal de "Redefinición de Estructura". Para evitarlo, todo archivo `.h` del planeta debe estar envuelto en macros de seguridad llamadas Include Guards (#ifndef, #define, #endif). Ellas garantizan que el compilador solo copie el contrato una sola vez por compilación.
En lenguajes como C++ o Java usamos la palabra private para esconder variables peligrosas. En C, usamos la palabra static a nivel global. Si declaras static float totalizador_litros = 0; dentro de `metrologia.c`, esa variable se vuelve invisible para el resto del proyecto. Nadie, ni siquiera usando `extern`, podrá leerla o modificarla desde `main.c`. Esto evita que un programador novato borre el totalizador por error al usar el mismo nombre de variable en otro archivo.
Ejercicio 1: Vamos a refactorizar el código de nuestro controlador PID (Día 151). Lo sacaremos del `main.c` y crearemos su propia Unidad de Traducción: pid_valvula.h y pid_valvula.c. Protegeremos las variables internas del PID usando el modificador static, exponiendo únicamente las funciones de Inicialización y Actualización para que el Súper Bucle las utilice.
pid_valvula.h (El Contrato Público)
/**
* @file pid_valvula.h
* @brief Interfaz Pública para el controlador PID de Válvulas Proporcionales
*/
// 1. LAS GUARDAS DE INCLUSIÓN (Include Guards)
// Evitan que este archivo se copie más de una vez si varios .c lo incluyen.
#ifndef PID_VALVULA_H
#define PID_VALVULA_H
#include <stdint.h>
// Opcional: Estructuras de datos públicas que otros módulos necesiten conocer
typedef struct {
float kp;
float ki;
float kd;
} PID_Config_t;
// 2. PROTOTIPOS DE FUNCIONES PÚBLICAS (API)
// El main() podrá invocar estas funciones.
void PID_Valvula_Init(PID_Config_t *configuracion);
float PID_Valvula_Update(float setpoint, float medicion, float dt);
#endif /* PID_VALVULA_H */
pid_valvula.c (La Implementación Oculta)
/**
* @file pid_valvula.c
* @brief Implementación y lógica privada del Controlador PID
*/
// Incluimos nuestro propio contrato para que el compilador verifique firmas
#include "pid_valvula.h"
// 1. VARIABLES PRIVADAS (Encapsulamiento)
// Al añadir 'static', estas variables SOLO existen dentro de este archivo.
// Ningún otro archivo en el proyecto puede leerlas o sobrescribirlas.
static float error_previo = 0.0f;
static float integrador = 0.0f;
static float limite_vueltas = 100.0f;
static PID_Config_t params_internos;
// 2. FUNCIONES PRIVADAS (Ocultas)
// Funciones de uso interno que el Súper Bucle no necesita conocer.
static float aplicar_anti_windup(float val_integral) {
if (val_integral > limite_vueltas) return limite_vueltas;
if (val_integral < -limite_vueltas) return -limite_vueltas;
return val_integral;
}
// 3. IMPLEMENTACIÓN DE FUNCIONES PÚBLICAS
void PID_Valvula_Init(PID_Config_t *config) {
// Copiamos la configuración a nuestra estructura privada
params_internos.kp = config->kp;
params_internos.ki = config->ki;
params_internos.kd = config->kd;
error_previo = 0.0f;
integrador = 0.0f;
}
float PID_Valvula_Update(float setpoint, float medicion, float dt) {
if (dt <= 0.0f) return 0.0f;
float error = setpoint - medicion;
float P = params_internos.kp * error;
integrador += error * dt;
integrador = aplicar_anti_windup(integrador); // Llamada a func privada
float I = params_internos.ki * integrador;
float D = params_internos.kd * ((error - error_previo) / dt);
error_previo = error;
float salida = P + I + D;
// Clamp de seguridad 0-100%
if(salida > 100.0f) salida = 100.0f;
if(salida < 0.0f) salida = 0.0f;
return salida;
}
main.c (Aplicación Limpia)
/**
* @file main.c
* @brief Archivo Maestro (Súper Bucle) refactorizado
*/
#include "stm32f401xe.h"
#include "pid_valvula.h" // Incluimos el contrato del PID
// #include "modbus.h"
// #include "lcd_i2c.h"
int main(void) {
// El main() ahora está inmaculado. Solo coordina a los gerentes.
PID_Config_t calibracion = { .kp = 15.0f, .ki = 2.5f, .kd = 0.5f };
PID_Valvula_Init(&calibracion);
while(1) {
// El main() no sabe qué es el Anti-Windup ni le importan
// las variables internas de error_previo. Solo pasa datos.
float salida = PID_Valvula_Update(20.0f, 18.5f, 0.01f);
// aplicar_pwm(salida);
}
return 0;
}
Objetivo del día: Mantenibilidad del Código a Nivel Empresarial.
Si alguna vez intentas que tu placa de circuito impreso (PCB) sea revisada por una empresa certificadora o si contratas a otro ingeniero para que te ayude a programar una nueva función de cobro bancario, lo primero que exigirán será un código legible. Nadie invertirá dinero en una máquina donde el controlador de la válvula, el protocolo Modbus RS485 y el dibujado de la pantalla LCD están todos amontonados en un archivo `main.c` infinito.
Al implementar la **Refactorización en Headers y Módulos**, has creado un "Firmware Base". En el futuro, si decides cambiar la pantalla LCD por una Táctil a color, simplemente borras `lcd_i2c.c`, programas un nuevo `pantalla_tactil.c`, y al incluir su Header en el `main()`, tu Despachador seguirá funcionando. El Súper Bucle principal ha dejado de ser un albañil, y se ha convertido en el verdadero Arquitecto: solo da órdenes a sus gerentes delegados y coordina la música industrial.
.c emparejados con .h). Esto permite que distintos ingenieros trabajen en distintos módulos (Ej. Modbus y Metrología) sin chocar (Conflictos de Merge de Git).#ifndef) son obligatorias en absolutamente todos los archivos .h. Previenen que el preprocesador de C caiga en bucles infinitos de copiado si los módulos se referencian mutuamente.static dentro del archivo .c. Esto limita el alcance (Scope) del símbolo al archivo local, protegiendo las entrañas del módulo de ser manipuladas accidentalmente por código externo.main.c debe estar lo más vacío posible, conteniendo idealmente solo la máquina de estados principal (Súper Bucle) e invocando APIs públicas declaradas en los Headers de los módulos delegados.