前言:
在上一篇文章里,我采用printf重定向为usart1,但是这样发送,对于MPU的负载比较大,所以本篇文章采用DMA方式,解放MPU资源,去做其他的事情,这里仅做为自己的记录。
正文开始:
Cubemx配置
先是在Cubemx里对单片机进行配置,跟上一篇文章同样的配置
增加DMA通道,并且将RX引脚置为上拉模式,因为本次只是作为发送,所以,这里也可以不把RX置为上拉模式,
配置DMA,保持默认配置即可,可以根据自己的项目需求,将自己的优先级进行调整。
这里已经是默认勾选了DMA中断,这里只将USART1的中断使能勾选上
并且将RX引脚设置为上拉模式,因为在选择异步通信后,CubeMX会自动配置引脚的工作模式。这时的默认配置是不打开上下拉。这可能会使引脚在悬空状态时电平不确定,产生误接收。我们把RX接收引脚,修改为:上拉(Pull-up),给引脚固定一个弱上拉,以避免悬空时产生误接收。
有些同学第一次会找不到如何将RX设置为上拉模式,按照我的箭头指示即可。
至此,我们的配置部分基本完成,点击生辰代码即可。
代码部分:
在KEIL中新建文件夹USART_DMA.c和USAR_DMA.h文件。代码如下
USART_DMA.c
#include "USART_DMA.h"
#include "stdio.h"
#include "stdarg.h"
volatile uint8_t usart_dma_tx_over = 1;
int myprintf(const char *format,...)
{
va_list arg;
static char SendBuff[200] = {0};
int rv;
while(!usart_dma_tx_over);//等待前一次DMA发送完成
va_start(arg,format);
rv = vsnprintf((char*)SendBuff,sizeof(SendBuff)+1,(char*)format,arg);
va_end(arg);
HAL_UART_Transmit_DMA(&huart1,(uint8_t *)SendBuff,rv);
usart_dma_tx_over = 0;//清0全局标志,发送完成后重新置1
return rv;
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)
{
usart_dma_tx_over = 1;
}
}
//要用的时候,直接可以
//myprintf("num:%d\r\n",i++);
//HAL_Delay(1000);
USART_DMA.h
#ifndef __USART_DMA_H
#define __USART_DMA_H
#include "main.h"
#include "usart.h"
int myprintf(const char *format,...);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
#endif
使用的话,只要在while循环里添加myprintf即可。
代码解释:
这里我用到的是DMA传输,用到的函数是
HAL_UART_Transmit_DMA (&huart1, uint8_t *pData, uint16_t Num);
参数分别是选用的串口,数据地址(字符串数字形式的内容),发送的字节数。
与HAL_UART_Transmit_IT (&huart1, uint8_t *pData, uint16_t Num);(中断发送)的区别在于100个字节,会产生100次中断。这个DMA发送函数,全程只产生一次中断。
与HAL_UART_Transmit (&huart1, uint8_t *pData, uint16_t Num, 超时值);(阻塞式发送)区别在于阻塞式发送每发送一个字节,死等,好了继续发下一个,再死等,不断重复。就是以前标准库种那最普通的死等法,只是它增加了一个超时值。其中超时值的功能:如果指定时间内没发送完毕,就直接返回,防止卡死。数据发送通信需时间:1秒 ÷ 波特率 × 字节数 × 10 × 1000ms。举例:115200波特率,100字节,大约用时 9ms
调用后,函数给DMA数据地址,DMA就自动开始搬砖,它会把数据逐字节搬运到串口的DR寄存器上,等串口发送完这个字节了,再自动搬运下一个,过程完全不占用程序运行资源。搬完了,就产生一个中断,给程序打个招呼。通常,我们程序上,把这个“招呼”也省略了,不用理会它。
我们在串口的中断回调函数中,直接将标志位清零,然后等待下次将数据发送出去。