Objetivo
Implementar UART en un STM32F103 accediendo directamente a los registros del periférico, sin usar el HAL de ST. Entender qué hace el hardware realmente.
¿Por qué sin HAL?
El HAL es útil para producción rápida, pero oculta detalles críticos como:
- Configuración del clock tree (APB1/APB2)
- El registro BRR y cómo calcular el baudrate
- El bit RXNE y cómo detectar datos recibidos
Configuración del clock
UART2 está en APB1 (36 MHz en el STM32F103). Necesitamos habilitar el clock antes de tocar cualquier registro del periférico:
/* Habilitar clock de GPIOA y USART2 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
Configurar pines TX/RX
PA2 es TX, PA3 es RX para USART2. TX en modo alternate function push-pull, RX como input flotante:
/* PA2: AF push-pull (TX) */
GPIOA->CRL &= ~(0xF << 8);
GPIOA->CRL |= (0xB << 8); /* AF push-pull, 50 MHz */
/* PA3: input flotante (RX) */
GPIOA->CRL &= ~(0xF << 12);
GPIOA->CRL |= (0x4 << 12); /* Input floating */
Calcular y configurar el baudrate
Para 115200 baud con APB1 = 36 MHz:
/* BRR = fCLK / baudrate = 36000000 / 115200 ≈ 312.5 */
USART2->BRR = 0x0139; /* Mantissa=312, Fraction=8 → 312.5 */
Habilitar el periférico
USART2->CR1 = USART_CR1_UE /* UART enable */
| USART_CR1_TE /* TX enable */
| USART_CR1_RE; /* RX enable */
Enviar y recibir un byte
void uart_send(uint8_t byte) {
while (!(USART2->SR & USART_SR_TXE)); /* esperar TX buffer vacío */
USART2->DR = byte;
}
uint8_t uart_recv(void) {
while (!(USART2->SR & USART_SR_RXNE)); /* esperar dato recibido */
return (uint8_t)USART2->DR;
}
Resultado
Con este código tenemos UART funcional sin ninguna dependencia de ST. El mismo patrón (habilitar clock → configurar pines → calcular BRR → habilitar) aplica a todos los periféricos del STM32.