Manipulación de GPIO
En la familia STM32F103, los registros BSRR y BRR permiten modificar el nivel lógico de un pin de forma atómica.
GPIOB->BSRR |= GPIO_PIN_7: escribe un1en el bit 7 del registro BSRR, lo que pone el pin PB7 a nivel alto.GPIOB->BRR |= GPIO_PIN_7: escribe un1en el bit 7 del registro BRR, lo que pone el pin PB7 a nivel bajo.
El acceso mediante |= es habitual aunque, dado que escribir en BSRR/BRR no requiere leer previamente, también es válido una asignación directa como GPIOB->BSRR = GPIO_PIN_7.
// Activa y desactiva PB7 de forma sencilla
#define LED_PIN GPIO_PIN_7
void led_on(void) { GPIOB->BSRR = LED_PIN; }
void led_off(void) { GPIOB->BRR = LED_PIN; }
Temporizadores (TIM)
Contador y captura de entrada
__HAL_TIM_SetCounter(&htim_x, valor) carga un valor en el contador del temporizador. Usar 0 reinicia la cuenta, lo cual resulta útil para sincronizar capturas o medidas de tiempo.
__HAL_TIM_GET_COUNTER(&htim_x) devuelve el valor actual del contador. Se emplea normalmente para medir anchos de pulso o intervalos.
TIM_HandleTypeDef htim_x;
uint32_t leer_cuenta(void)
{
return __HAL_TIM_GET_COUNTER(&htim_x);
}
void reiniciar_cuenta(void)
{
__HAL_TIM_SetCounter(&htim_x, 0);
}
Frecuencia máxima medible en captura (aproximada):
frecuencia_max ≈ frecuencia_reloj_TIM / resolución. Para un TIM de 16 bits a 168 MHz, el resultado es aproximadamente 2.56 kHz, aunque la configuración real del prescaler y del modo de captura puede modificar este límite.
Gestión de interrupciones periódicas
HAL_TIM_PeriodElapsedCallback() es el callback ejecutado cuando el contador alcanza el valor de recarga automática (ARR) y se produce el evento de actualización.
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
// Tarea periódica de 1 ms, por ejemplo
}
}
Para arrancar o detener el temporizador con interrupciones se usan:
HAL_TIM_Base_Start_IT(&htim_x)HAL_TIM_Base_Stop_IT(&htim_x)
Dentro del ISR se puede comprobar y limpiar el flag de actualización con:
if (__HAL_TIM_GET_FLAG(&htim_x, TIM_FLAG_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_FLAG(&htim_x, TIM_FLAG_UPDATE);
}
Configuración dinámica del periodo
__HAL_TIM_SET_AUTORELOAD(&htim_x, nuevo_arr) cambia el valor de recarga automática. Para que el cambio sea efectivo de inmediato conviene habilitar la precarga del ARR y, si es necesario, reiniciar el contador.
void cambiar_periodo(uint32_t nuevo_arr)
{
__HAL_TIM_SET_AUTORELOAD(&htim_x, nuevo_arr);
__HAL_TIM_SET_COUNTER(&htim_x, 0);
}
__HAL_TIM_SET_PRESCALER(&htim_x, psc) modifica el prescaler, lo que permite ajustar la frecuencia de cuenta sin tocar el ARR. Ambos parámetros definen la frecuencia de salida:
frecuencia = frecuencia_reloj / ((PSC + 1) · (ARR + 1))
Modo encoder
HAL_TIM_Encoder_Start(&htim_x, TIM_CHANNEL_ALL) inicia el contador en modo encoder, útil para leer cuadratura de encoders rotatorios.
Generación de PWM
| Función | Descripción |
|---|---|
HAL_TIM_PWM_Start() |
Inicia la salida PWM sin interrupciones. |
HAL_TIM_PWM_Start_IT() |
Inicia la salida PWM y habilita la interrupción de actualización. |
HAL_TIM_PWM_Stop_IT() |
Detiene la salida PWM y deshabilita la interrupción. |
__HAL_TIM_SetCompare() |
Modifica el registro CCR para variar el ciclo de trabajo. |
Ejemplo de ajuste de duty cycle:
void ajustar_pwm(uint32_t canal, uint32_t ciclo_trabajo)
{
__HAL_TIM_SetCompare(&htim_pwm, canal, ciclo_trabajo);
}
El callback HAL_TIM_PWM_PulseFinishedCallback() se invoca al finalizar un pulso de PWM, lo que resulta útil para actualizar el siguiente ciclo de trabajo de forma síncrona.
Comunicación CAN
El periférico CAN se gestiona con estas funciones clave:
HAL_CAN_Start(&hcan_x): activa el módulo CAN tras configurar el bit-rate.HAL_CAN_Receive_IT(&hcan_x, CAN_FIFO0): habilita la recepción por interrupción.HAL_CAN_ActivateNotification(): activa las notificaciones de interrupción deseadas (mensaje pendiente, FIFO lleno, eror, etc.).HAL_CAN_AddTxMessage(): encola un mensaje para su transmisión.HAL_CAN_GetRxMessage(): extrae un mensaje recibido del FIFO indicado.HAL_CAN_GetRxFifoFillLevel(): indica cuántos mensajes hay pendientes en el FIFO.HAL_CAN_GetTxMailboxLevel(): devuelve el número de buzones de transmisión disponibles.HAL_CAN_GetError(): lee el estado de error del controlador.
Configuración de filtros:
CAN_FilterTypeDef filtro;
filtro.FilterBank = 0;
filtro.FilterMode = CAN_FILTERMODE_IDMASK;
filtro.FilterScale = CAN_FILTERSCALE_32BIT;
filtro.FilterIdHigh = 0x0000;
filtro.FilterIdLow = 0x0000;
filtro.FilterMaskIdHigh = 0x0000;
filtro.FilterMaskIdLow = 0x0000;
filtro.FilterFIFOAssignment = CAN_FILTER_FIFO0;
filtro.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan_x, &filtro);
Comunicación UART
| Función | Modo de operación |
|---|---|
HAL_UART_Receive() |
Bloqueatne con timeout. |
HAL_UART_Receive_IT() |
Por interrupción. |
HAL_UART_Receive_DMA() |
Por DMA. |
HAL_UART_Transmit() |
Transmisión bloqueante. |
Antes de usar una UART es necesario habilitar su reloj:
__HAL_RCC_USART2_CLK_ENABLE();
Para habilitar interrupciones específicas y consultar flags se usan:
__HAL_UART_ENABLE_IT(&huart_debug, UART_IT_RXNE);
if (__HAL_UART_GET_FLAG(&huart_debug, UART_FLAG_RXNE))
{
// Dato disponible en el buffer de recepción
}
Para recepción por DMA con detección de línea inactiva se emplea:
HAL_UARTEx_ReceiveToIdle_DMA(&huart_debug, buffer_rx, TAM_BUFFER);
Comunicación I2C
La HAL ofrece funciones tanto para maestro como para esclavo:
HAL_I2C_Master_Transmit(): envío desde maestro.HAL_I2C_Master_Receive(): recepción como maestro.HAL_I2C_Slave_Transmit(): envío como esclavo.HAL_I2C_Mem_Write(): escritura en un registro/dispositivo con dirección interna.HAL_I2C_Mem_Read(): lectura desde un registro/dispositivo con dirección interna.
uint8_t datos_tx[] = {0xAA, 0x55};
uint8_t datos_rx[4];
HAL_I2C_Master_Transmit(&hi2c_bus, 0xA0, datos_tx, 2, 100);
HAL_I2C_Master_Receive(&hi2c_bus, 0xA0, datos_rx, 4, 100);
Comunicación SPI
Para transferencias full-duplex simultáneas:
uint8_t tx[] = {0x01, 0x02};
uint8_t rx[2];
HAL_SPI_TransmitReceive(&hspi_x, tx, rx, sizeof(tx), HAL_MAX_DELAY);
Para envío simple:
HAL_SPI_Transmit(&hspi_x, tx, sizeof(tx), HAL_MAX_DELAY);
Transferencias DMA
HAL_DMA_Start() inicia una transferencia memoria-memoria o periférico-memoria.
HAL_DMA_Start(&hdma_x,
(uint32_t)origen,
(uint32_t)destino,
sizeof(origen));
Para deshabilitar una interrupción DMA concreta o consultar flags:
__HAL_DMA_DISABLE_IT(&hdma_x, DMA_IT_TC);
if (__HAL_DMA_GET_FLAG(&hdma_x, DMA_FLAG_TC1))
{
__HAL_DMA_CLEAR_FLAG(&hdma_x, DMA_FLAG_TC1);
}
Gestión de interrupciones
Habilitación de una IRQ:
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
El handler de la USART1 se declara como:
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
Control global de interrupciones en Cortex-M:
__set_PRIMASK(1); // Deshabilita todas las interrupciones
__set_PRIMASK(0); // Habilita interrupciones
__set_CONTROL(0x02); // Modo no privilegiado + process stack
__set_CONTROL(0x00); // Modo privilegiado + main stack
FSMC y AFIO
En algunos dispositivos STM32 se dispone de la macro __HAL_AFIO_FSMCNADV_DISCONNECTD(), que desconecta la señal NADV del FSMC para permitir que el pin se reasigne a otra función.
void configurar_fsmc(void)
{
// Configuración previa del FSMC...
__HAL_AFIO_FSMCNADV_DISCONNECTD();
// Resto de configuración de GPIO
}