1. 常用方式
    1. 这样做的缺点
  2. 使用非阻塞的方式:灵活运用DMA
    1. 1. 定义缓冲区。
    2. 2. 重定向 fputc(),使用缓冲区
    3. 3. 定义DMA 发送函数
    4. 4. DMA 传输完成回调
  3. 一些疑惑的解答

串口重定向的非阻塞方法

版权信息

本文章遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


常用方式

有时我们需要串口打印信息到上位机,最便捷的方法是重定向 printf(),网上的常用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

int fputc(int ch, FILE *f){
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);

return ch;
}

int fgetc(FILE *f){

uint8_t ch = 0;

HAL_UART_Receive(&huart1, &ch, 1, 0xffff);

return ch;
}

这样做的缺点

  • 这是 阻塞模式,每次 printf() 都要等待数据发送完毕,影响 CPU 执行效率。
  • 如果数据较多,会降低实时性,阻塞主循环。

使用非阻塞的方式:灵活运用DMA

我们使用DMA非阻塞发送。具体步骤如下:

1. 定义缓冲区。

根据fputc()的特性(这里先按下不表),使用 DMA 发送时,printf() 需要先把数据存到 一个缓冲区,然后一次性使用 DMA 发送出去。

1
2
3
4
5
6

#define TX_BUFFER_SIZE 256 // 发送缓冲区大小
uint8_t uart_tx_buffer[TX_BUFFER_SIZE]; // 串口发送缓冲区
volatile uint16_t tx_len = 0; // 当前缓冲区数据长度
volatile uint8_t tx_busy = 0; // 发送状态标志

2. 重定向 fputc(),使用缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

int fputc(int ch, FILE *f)
{
// 确保缓冲区不会溢出
if (tx_len < TX_BUFFER_SIZE - 1)
{
uart_tx_buffer[tx_len++] = (uint8_t)ch;
}

// 如果遇到换行符,或者缓冲区接近满,就启动 DMA 发送
// 注意咯,如果缓冲区没满的情况下,只有检测到/n才会触发DMA的发送。所以在缓
// 冲区没满的情况下,如果你想要发送字符串"abc",printf("abc")是没用的,
// printf("abc\n")才有用哦。
if (ch == '\n' || tx_len >= TX_BUFFER_SIZE - 1)
{
uart_dma_transmit();
}

return ch;
}


3. 定义DMA 发送函数

1
2
3
4
5
6
7
8
9
10

void uart_dma_transmit(void)
{
// 如果 DMA 还在忙,则不启动新的传输
if (tx_busy) return;

tx_busy = 1; // 标记为发送中
HAL_UART_Transmit_DMA(&huart1, uart_tx_buffer, tx_len);
}

4. DMA 传输完成回调

1
2
3
4
5
6
7
8
9
10

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) // 确保是目标串口
{
tx_len = 0; // 清空缓冲区
tx_busy = 0; // 标记为可用
}
}

一些疑惑的解答

Q: 茂神茂神,你的说的非阻塞方式的确很不错,但是还是太吃操作了,我就不能直接在 fputc()里写 HAL_UART_Transmit_DMA吗?

A: 可以的兄弟可以的。fputc() 是发送一个字符对吧,理论上我们只需要把DMA传输的字节数改为1就行了对吧?nonono,首先DMA是非阻塞的对吧,也就是激活了它,他就只管发,不管你完没完成,你都可以再次激活它,如果像你这样做,当发送字符流时大概率是这种情况:

fputc()打电话给DMA的秘书说我要发这个字符,然后他就认为我已经给DMA说了叫它发送这个字符啦,所以这个字符就发出去啦,剩下就不关我的事啦。结果DMA秘书看到DMA此时还在忙于上次发送,于是判断DMA为HAL_BUSY没有办法把字符交给DMA。这个字符信息就丢失了。。。。