El UART (Universal Asynchronous Receiver/Transmitter) es un componente fundamental en sistemas embebidos para la comunicación serie. RT-Thread proporciona un marco de controladores (drivers) estandarizado que simplifica la interacción con estos periféricos, permitiendo una portabilidad eficiente entre diferentes plataformas de hardware.
Configuración del periférico
Antes de interactuar con el puerto serie en el código, es necesario asegurar su habilitación en el sistema:
- Device Tree (DTS): En plataformas que lo soportan, se deben definir los nodos UART especificando registros, relojes y configuración de pines (pinmux).
- Herramienta de configuración (Menuconfig): A través de la herramienta Env o Kconfig, se debe navegar a
Hardware Drivers Config -> On-chip Peripheral Driversy activar la casillaEnable UART, seleccionando el índice específico (UART1, UART2, etc.) y definiendo el modo de operación (Polling, Interrupt o DMA).
Localización y apertura del dispositivo
Para utilizar un puerto serie, primero se debe obtener su manejador mediante el nombre registrado en el sistema y luego configurar el modo de acceso.
#include <rtdevice.h>
int inicializar_uart(void)
{
rt_device_t dev_uart;
const char *nombre_uart = "uart1";
/* Localizar el dispositivo en el kernel */
dev_uart = rt_device_find(nombre_uart);
if (dev_uart == RT_NULL)
{
rt_kprintf("Error: No se encontró el dispositivo %s\n", nombre_uart);
return -RT_ERROR;
}
/* Abrir en modo lectura/escritura con recepción por interrupción */
if (rt_device_open(dev_uart, RT_DEVICE_FLAG_INT_RX) != RT_EOK)
{
rt_kprintf("Error: No se pudo abrir el dispositivo %s\n", nombre_uart);
return -RT_ERROR;
}
return RT_EOK;
}
Los indicadores de apertura más comunes incluyen:
RT_DEVICE_FLAG_INT_RX: Habilita la recepción mediante interrupciones.RT_DEVICE_FLAG_DMA_RX: Utiliza acceso directo a memoria para la recepción.RT_DEVICE_FLAG_RDWR: Permite operaciones de lectura y escritura.
Ajuste de parámetros de comunicación
Es posible modificar la configuración en tiempo de ejecución (baudios, bits de parada, paridad) utilizando la estructura serial_configure.
struct serial_configure config_uart = RT_SERIAL_CONFIG_DEFAULT;
config_uart.baud_rate = BAUD_RATE_9600;
config_uart.data_bits = DATA_BITS_8;
config_uart.stop_bits = STOP_BITS_1;
config_uart.parity = PARITY_NONE;
/* Aplicar los cambios al dispositivo */
rt_device_control(dev_uart, RT_DEVICE_CTRL_CONFIG, &config_uart);
Transmisión de datos
La escritura de datos se realiza de forma síncrona por defecto meidante la función rt_device_write.
const char *msg = "RT-Thread UART Operativo\n";
rt_size_t size_msg = rt_strlen(msg);
/* Escritura en el dispositivo serie */
rt_device_write(dev_uart, 0, msg, size_msg);
Recepción de datos mediante interrupciones
Para evitar el bloqueo de la CPU en ciclos de espera, se recomienda el uso de callbacks. RT-Thread notificará a la aplicación cuando lleguen nuevos datos al buffer.
/* Función callback que se ejecuta al recibir datos */
static rt_err_t rx_callback(rt_device_t dev, rt_size_t size)
{
/* Notificar a un hilo o liberar un semáforo aquí */
return RT_EOK;
}
/* Registro del callback después de abrir el dispositivo */
rt_device_set_rx_indicate(dev_uart, rx_callback);
Ejemplo práctico: Eco de caracteres con semáforos
El siguiente ejemplo demuestra cómo integrar UART con un semáforo para procesar datos en un hilo dedicado, incrementando el valor ASCII del carácter recibido antes de devolverlo.
#include <rtthread.h>
#define UART_DEV_NAME "uart2"
static struct rt_semaphore sem_uart_rx;
static rt_device_t uart_handler;
/* Callback de recepción */
static rt_err_t uart_rx_handler(rt_device_t dev, rt_size_t size)
{
rt_sem_release(&sem_uart_rx);
return RT_EOK;
}
static void uart_processing_thread(void *param)
{
char byte_in;
while (1)
{
/* Intentar leer un byte */
while (rt_device_read(uart_handler, -1, &byte_in, 1) != 1)
{
/* Esperar hasta que el callback libere el semáforo */
rt_sem_take(&sem_uart_rx, RT_WAITING_FOREVER);
}
/* Lógica de procesamiento: Eco con desplazamiento */
char byte_out = byte_in + 1;
rt_device_write(uart_handler, 0, &byte_out, 1);
}
}
int main_uart_demo(void)
{
/* 1. Inicializar semáforo */
rt_sem_init(&sem_uart_rx, "sem_rx", 0, RT_IPC_FLAG_FIFO);
/* 2. Buscar y abrir el dispositivo */
uart_handler = rt_device_find(UART_DEV_NAME);
if (!uart_handler) return -RT_ERROR;
rt_device_open(uart_handler, RT_DEVICE_FLAG_INT_RX);
rt_device_set_rx_indicate(uart_handler, uart_rx_handler);
/* 3. Crear hilo de procesamiento */
rt_thread_t tid = rt_thread_create("u_task", uart_processing_thread, RT_NULL, 1024, 20, 10);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
return 0;
}
MSH_CMD_EXPORT(main_uart_demo, Demo de comunicacion UART);
Consideraciones técnicas y depuración
- Contexto de Interrupción: El callback de recepción se ejecuta en el contexto de la ISR. No se deben realizar operaciones pesadas o bloqueantes (como
rt_thread_delay) dentro de esta función. - Desbordamiento de Buffer: Si el flujo de entrada es muy alto, el buffer de software definido en Kconfig (RX Buffer Size) puede llenarse. Incremente su tamaño si observa pérdida de datos.
- Modo DMA: Para transferencias de alta velocidad o grandes volúmenes de datos, el modo DMA es preferible ya que reduce drásticamente la carga de interrupciones sobre la CPU.
- Comandos de Consola: Utilice el comando
list_deviceen la consola FinSH para verificar que el puerto UART está regisrtado correctamente y ver su estado actual.