DMA:直接储存区访问,DMA传输将数据从一个地址空间复制到另一个空间。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM何IO设备开辟一条直接传输数据的通道,从而可以提高CPU的效率。一般用到DMA,主要作用为CPU减负。
我们用STM32CubeMX来配置一下串口的DMA实现收发。
选择芯片:
根据自己的电路设计情况配置时钟,我用的外部晶振25M
然后配置串口及DMA这里我们配置两个串口串口1和串口2,让这两个串口实现互通,两个串口的配置一样,这里展示串口1的
其他配置和我们平时用串口配置一样,就是在DMA这里开启收发就可以,点击Add添加,收发都添加进去,下面mode设置可以选择Normal表单次传输,传输一次后终止传输,Circular表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。此处选择单次传输。
Increment Address表示地址指针递增。串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte。
最后这里中断要开启,DMA的是默认开启的,如果不开启串口中断,则程序只能发送一次数据,程序不能判断DMA传输是否完成,USART一直处于busy状态。这样我们就配置好了,串口2也如此配置,
然后设置工程名称及存放地址和使用的编译器,点击生成。生成后点击打开工程,可以先编译一下正常的是没有错误的
发送比较简单 用这个函数直接就可发送,
HAL_UART_Transmit_DMA();
uint8_t TXbuff[] = "\r\n**** UART-Hyperterminal communication based on DMA ***\r\n ";
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)TXbuff, sizeof(TXbuff)-1);
我们定义了一个数组,然后在while里面循环每隔一秒发一下。
接收就比较麻烦,尤其是不定长的接受,这里我们用到串口的空闲中断,
首先我们要在库函数添加一点代码
/* UART in mode Idle -------------------------------------------------*/
if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
HAL_UART_IdleCpltCallback(huart);
return;
}
将上述代码添加到stm32f4xx_hal_uart.c中,该文件在工程的Drivers\STM32F4xx_HAL_Driver中添加位置:添加在
/* UART in mode Transmitter ------------------------------------------------*/的前面,可ctrl+F弹出查找,输入点击查找。
__weak void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
}
将上述代码添加到
__weak void HAL_UART_AbortTransmitCpltCallback(UART_HandleTypeDef *huart)函数的前面。
再接着
在stm32f4xx_hal_uart.h中添加
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart);
最后一步在在usart.c添加回调函数
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
}
然后这里我们在回调函数里面直接做一个串口1和串口2的数据中转
//定义一些变量和数据缓冲区
uint16_t RecCount1=0 ; //接收数据个数
uint16_t RecCount2=0 ; //接收数据个数
uint8_t UART_1_RxBuffer[1024];
uint8_t UART_2_RxBuffer[1024];
#define LENGTH 1024 //接收缓存区大小,该值需要大于一帧数据的总字符数
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_DMAStop(&huart1);
//发生空闲中断时,已接收数据个数等于数据总量减去DMA数据流中待接收的数据个数
RecCount1 = LENGTH - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
HAL_UART_Transmit_DMA(&huart2,(uint8_t*)UART_1_RxBuffer,RecCount1);
HAL_UART_Receive_DMA(&huart1,(uint8_t*)UART_1_RxBuffer,LENGTH); //启动DMA接收
}
if(huart->Instance == USART2)
{
HAL_UART_DMAStop(&huart2);
//发生空闲中断时,已接收数据个数等于数据总量减去DMA数据流中待接收的数据个数
RecCount2 = LENGTH - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
HAL_UART_Transmit_DMA(&huart1,(uint8_t*)UART_2_RxBuffer,RecCount2);
HAL_UART_Receive_DMA(&huart2,(uint8_t*)UART_1_RxBuffer,LENGTH); //启动DMA接收
}
}
然后在main函数的里启动DMA接收
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart1,(uint8_t*)UART_1_RxBuffer,LENGTH); //启动DMA接收
__HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart2,(uint8_t*)UART_2_RxBuffer,LENGTH); //启动DMA接收
编译程序然后下载通过串口发数据,我们会看到串口1发的数据串口2会收到,同时串口2发的数据串口1会收到。
当然这个方法有个弊端,就是我们直接修改的是库函数 ,那么再次用STM32CubeMX生成代码时stm32f4xx_hal_uart.h和stm32f4xx_hal_uart.c中修改的内容就没有了,需要重新修改,其实库函数的文件在你每次改动STM32CubeMX后生成代码时是不会变的,所以先把改好的这两个文件复制一下,然后在生成后直接粘贴过来替代就行。