En el mundo embebido existen dos tipos de zumbadores:
No vamos a usar el procesador principal para encender y apagar el pin velozmente (eso es *Bit-banging* y quema CPU). Usaremos un periférico de hardware del STM32 llamado Timer PWM. Le decimos al Timer: "Ajusta tu Auto-Reload Register (ARR) a 2000 Hz, y genera una onda cuadrada". A partir de ahí, el Timer opera solo, en paralelo a la CPU.
Una alarma como "Beep-Silencio-Beep-Silencio" dura 2 segundos. Si haces HAL_Delay(500) cuatro veces, el RTOS de tu STM32 suspende la tarea actual. Si pusiste esto en la misma tarea de Metrología o Interfaz, congelaste la máquina.
La solución es crear un "Reproductor de Audio (Music Player) por FSM". Consiste en guardar las notas en un Array (Matriz). Una tarea de FreeRTOS revisa el milisegundo actual (osKernelGetTickCount()); si ya pasó el tiempo de la nota, cambia a la siguiente, programa el nuevo PWM en el hardware, y se va a dormir instantáneamente. **Cero bloqueos.**
El Circuito Físico: Un zumbador industrial necesita de 30mA a 100mA para sonar fuerte. Un pin del STM32 solo puede dar 20mA. Si conectas el buzzer directo al microcontrolador, lo quemarás. Debes intercalar un Transistor (ej. BJT 2N2222 o MOSFET BSS138). El pin PWM del STM32 se conecta a la Base/Gate del transistor a través de una resistencia de 1k, y el transistor funciona como un interruptor de potencia conectado a la línea de 5V o 12V del Buzzer. Además, coloca un Diodo Flyback en paralelo al Buzzer para evitar que el retroceso inductivo de la bobina mate al transistor.
Ejercicio 1: El Reproductor de Arreglos (Music FSM). Programaremos la estructura C que permite definir una sinfonía como un simple arreglo, y la máquina de estados que la interpreta. Cumpliremos estrictamente MISRA-C con la manipulación de los punteros del arreglo.
/**
* @file buzzer_fsm.c
* @brief Sintetizador de Alarmas No Bloqueante por PWM de Hardware
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
#include <stdint.h>
#include <stdbool.h>
// =========================================================================
// DEFINICIONES MUSICALES (Estructuras de Partitura)
// =========================================================================
// Cada "Nota" se compone de una Frecuencia (Hz) y una Duración (ms).
// Si la Frecuencia es 0, significa un Silencio.
typedef struct {
uint16_t frecuencia_hz;
uint16_t duracion_ms;
} NotaBuzzer_t;
// Identificadores de las diferentes melodías del sistema
typedef enum {
BUZZER_IDLE = 0U,
BUZZER_MELODY_OK = 1U,
BUZZER_MELODY_ERR = 2U,
BUZZER_MELODY_LEAK = 3U
} AlarmaBuzzer_t;
// =========================================================================
// ARREGLOS DE MELODÍAS (Almacenados en Flash / .rodata)
// =========================================================================
// Un doble Beep corto y agudo para "Lectura RFID Correcta"
static const NotaBuzzer_t MELODIA_OK[] = {
{2000U, 100U}, // Beep 2KHz por 100ms
{0U, 50U}, // Silencio 50ms
{2500U, 150U}, // Beep 2.5KHz por 150ms
{0U, 0U} // Terminador de arreglo (End Of Array)
};
// Una sirena policial alternante para "Alarma Fuga/Sabotaje"
static const NotaBuzzer_t MELODIA_FUGA[] = {
{800U, 400U},
{1200U, 400U},
{800U, 400U},
{1200U, 400U},
{0U, 0U}
};
// =========================================================================
// MÁQUINA DE ESTADOS Y HARDWARE ABSTRACTION
// =========================================================================
static AlarmaBuzzer_t alarma_activa = BUZZER_IDLE;
static const NotaBuzzer_t* puntero_melodia = NULL;
static uint16_t indice_nota_actual = 0U;
static uint32_t timestamp_inicio_nota = 0U;
// Hardware (Supongamos TIM2 Canal 1)
extern TIM_HandleTypeDef htim2;
/**
* @brief Cambia la frecuencia del PWM en el Hardware (Bajo Nivel)
*/
static void Set_Frecuencia_PWM(uint16_t frecuencia) {
if (frecuencia == 0U) {
// Apagar sonido (Duty Cycle = 0)
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);
} else {
// STM32 Timer Math: ARR = (TimerClock / (Prescaler * Freq)) - 1
// Asumiendo TimerClock=84MHz, Prescaler=84 (1 MHz timer clock)
uint32_t arr = (1000000U / frecuencia) - 1U;
__HAL_TIM_SET_AUTORELOAD(&htim2, arr);
// 50% Duty Cycle (Volumen máximo para piezo)
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, (arr / 2U));
}
}
// =========================================================================
// TAREA RTOS (El Reproductor Asíncrono)
// =========================================================================
/**
* @brief Tarea Background. No usa Delays. Solo verifica la hora actual.
*/
void Task_Buzzer_Player(void *argument) {
// Iniciar Generador PWM Base
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
Set_Frecuencia_PWM(0U); // Silencio inicial
for(;;) {
if (alarma_activa != BUZZER_IDLE) {
// Si la melodía acaba de cargarse
if (puntero_melodia != NULL) {
uint32_t tiempo_actual = osKernelGetTickCount();
uint16_t freq_actual = puntero_melodia[indice_nota_actual].frecuencia_hz;
uint16_t dura_actual = puntero_melodia[indice_nota_actual].duracion_ms;
// ¿Es el fin de la canción?
if ((freq_actual == 0U) && (dura_actual == 0U)) {
Set_Frecuencia_PWM(0U);
alarma_activa = BUZZER_IDLE; // Apagar reproductor
}
else {
// ¿Ya pasó el tiempo de esta nota?
if ((tiempo_actual - timestamp_inicio_nota) >= dura_actual) {
// Avanzar a la siguiente nota
indice_nota_actual++;
freq_actual = puntero_melodia[indice_nota_actual].frecuencia_hz;
if ((freq_actual != 0U) || (puntero_melodia[indice_nota_actual].duracion_ms != 0U)) {
Set_Frecuencia_PWM(freq_actual);
timestamp_inicio_nota = tiempo_actual;
}
}
}
}
}
// Polling del motor de audio (Refresco cada 10ms es suficiente para oído humano)
osDelay(pdMS_TO_TICKS(10U));
}
}
// API PARA OTRAS TAREAS:
// Simplemente se llama "Disparar_Melodia(BUZZER_MELODY_OK)" y la función regresa al instante.
Objetivo del día: Interfaz HMI Acústica y Arquitectura Concurrente Multi-Hilo.
Un error clásico de un ingeniero novato es programar todas sus funciones en un solo hilo de ejecución, asumiendo que el humano interactuará linealmente. *"Si ocurre un error, enciendo el zumbador, espero 1 segundo con un delay, lo apago, y luego sigo leyendo el sensor de gas"*. Ese delay de 1 segundo causará un "salto" en el motor de metrología. Si el flujo es de 50 litros por minuto, 1 segundo sin leer sensores regalará casi un litro de Gas LP a presión al cliente.
Al implementar el **Motor PWM No Bloqueante (State Machine Audio Player)** impulsado por la API de tiempo de FreeRTOS (`osKernelGetTickCount`), transformaste el buzzer en un periférico puramente asíncrono. Ahora, si la tarea de Detección de Fugas (Día 239) invoca la alarma, simplemente mueve un puntero en la RAM (`puntero_melodia = MELODIA_FUGA`) y continúa con su cacería de anomalías en el mismo microsegundo. El hardware PWM del STM32 y la Tarea Auxiliar de Audio hacen sonar el altavoz ensordecedoramente de fondo, logrando un paralelismo perfecto real, vital para máquinas de grado industrial.
delay() o HAL_Delay()) para la sincronización de audio (esperar a que acabe un beep) destruye el determinismo del RTOS, ocasionando pérdida de datos en la metrología y retrasos en la criptografía.