[STM32F1] 使用 HAL_UART_Transmit_IT() 实现 printf

[复制链接]
99|12
该如何用HAL_UART_Transmit_IT()实现 UART 上的 printf 输出。
下面是阻塞版本,工作正常:

void PRINT_MESG_UART(const char * format, ... )
{
    va_list ap;
    uint8_t buffer [128];
    int n;
    va_start(ap, format);
    n = vsnprintf ((char*)buffer, 128, format, ap);
    va_end(ap);
    if(HAL_UART_Transmit(&UartHandle, (uint8_t*)buffer, n, 300) != HAL_OK) {
        Error_Handler();
    }
}
现在希望改成非阻塞、基于中断发送。我写了下面代码:
uint8_t buffer [128];
void PRINT_MESG_UART(const char * format, ... )
{
    va_list ap;
    int n;

    while (UartHandle.gState != HAL_UART_STATE_READY);

    va_start(ap, format);
    n = vsnprintf ((char*)buffer, 128, format, ap);
    va_end(ap);
    HAL_UART_Transmit_IT(&UartHandle, (uint8_t*)buffer, n);
}
可以跑,但缺点明显:UART 处于发送状态时,函数依然会原地阻塞自旋等待,算不上真正非阻塞。
理想方案是传统的软件环形发送缓冲区,逐个字节入环,直接操作 TXE 中断标志:

bool u_put_byte(uint8_t data)
{
    bool ok = FALSE;
    if ((uart_buffer.txin + 1) % S_BUF_SIZE != uart_buffer.txout)
    {
        uart_buffer.txbuf[uart_buffer.txin] = data;
        uart_buffer.txin = (uart_buffer.txin + 1) % S_BUF_SIZE;
        ok = TRUE;
    }
    SET_BIT(UartHandle.Instance->CR1, USART_CR1_TXEIE);
    return ok;
}

void PRINT_MESG_UART(const char * format, ... )
{
    va_list ap;
    uint8_t buffer[S_BUF_SIZE];
    int n;
    va_start(ap, format);
    n = vsnprintf ((char*)buffer, S_BUF_SIZE, format, ap);
    va_end(ap);
    int i = 0;
    while ((buffer[i] != 0) && (i < S_BUF_SIZE) && (i < n))
    {
        while (!u_put_byte(buffer[i]));
        i++;
    }
}
但是 HAL 库 UART 接口,并不直接支持这种底层环形缓冲区的工作模式。

flycamelaaa 发表于 2026-8-7 15:11 | 显示全部楼层
你的非阻塞版本质还是阻塞,因为while循环等gState就绪时,其他任务根本没法用串口。
jcky001 发表于 2026-8-7 15:43 | 显示全部楼层
真正非阻塞要把vsnprintf和入队操作拆开,用队列+状态机,在发送完成中断里继续取数。
onlycook 发表于 2026-8-7 16:14 | 显示全部楼层
HAL库的Transmit_IT内部会锁住gState,此时你往CR1写TXEIE使能会被HAL覆盖掉,导致中断冲突。
powerantone 发表于 2026-8-7 16:45 | 显示全部楼层
推荐重写中断服务函数,只开TC(发送完成)中断,不要用HAL的IT发送API,自己维护头尾指针。
probedog 发表于 2026-8-7 19:15 | 显示全部楼层
用DMA加环形缓冲区才是终极方案,CPU只在缓冲区空时触发一次DMA,中断开销最小。
solty 发表于 2026-8-7 19:48 | 显示全部楼层
你现在的方案里vsnprintf每次执行时间不定,如果栈压力大,128字节buffer放栈上容易爆。
spicy 发表于 2026-8-7 20:19 | 显示全部楼层
可以借鉴FreeRTOS+队列方式,把要发的字符串丢进队列,空闲任务或低优先级任务慢慢往外吐。
七毛钱 发表于 2026-8-7 20:52 | 显示全部楼层
你的u_put_byte函数里如果TXEIE没被正确使能,第一字节发不出去,得检查USART_CR1状态。
今天会画卧蚕吗 发表于 2026-8-7 21:21 | 显示全部楼层
建议放弃HAL的Transmit_IT,直接用寄存器版IT发送,配合双缓冲(前台+后台)切换,效率高且可控。
在海边聆听 发表于 2026-8-7 21:23 | 显示全部楼层
可以把buffer从栈改成全局静态数组,防止PRINT_MESG_UART嵌套调用时覆盖数据,造成乱码。
没有太阳的晴天 发表于 2026-8-7 22:10 | 显示全部楼层
HAL库的gState在发送完成中断里会被重置为READY,但如果你手动开了TXEIE,两者会抢控制权。
茉璃夏 发表于 2026-8-7 22:45 | 显示全部楼层
别纠结纯非阻塞了,实际项目中printf只用于调试,跑阻塞版配合DMA空闲中断,足够99%场景。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

205

主题

208

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部
0