版权信息
常用方式
有时我们需要串口打印信息到上位机,最便捷的方法是重定向 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; }
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) { 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。这个字符信息就丢失了。。。。