基础知识点

DMA

DMA(Direct Memory Access),即直接内存存储,在一些数据的传输中,如串口、SPI等,采用DMA方式,传输过程不需要CPU参与,可用让CPU有更多的时间处理其他的事情。

STM32F4的DMA通道选择如下:

接下来的程序思路如下:

编程要点

DMA发送

串口DMA发送配置

由于是发送不定长的数据,先不需要配置发送的长度,在每次的发送时,再配置。

//=======================================
//串口DMA发送配置
//=======================================
void dma_uart_tx_init()
{DMA_InitTypeDef  DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 DMA_DeInit(Uart_Tx_DMAStream);//使用----->DMA2_Stream7while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}//等待DMA可配置 /* 配置 DMA Stream */DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道选择DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //目的:DMA外设地址DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)SendBuff;              //源:DMA存储器0地址DMA_InitStructure.DMA_DIR                = DMA_DIR_MemoryToPeripheral; //方向:存储器到外设模式//DMA_InitStructure.DMA_BufferSize       = BUF_SIZE;                   //长度:数据传输量(先不配置)DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;  //外设非增量模式DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;       //存储器增量模式DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;    //存储器数据长度:8位DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;            //使用普通模式 DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;        //DMA优先级:中等优先级DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存储器单次传输DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外设单次传输DMA_Init(Uart_Tx_DMAStream, &DMA_InitStructure);//初始化DMA Stream//中断配置DMA_ITConfig(Uart_Tx_DMAStream,DMA_IT_TC,ENABLE);  //配置DMA发送完成后产生中断NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;//NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7;//抢占优先级8NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;        //子优先级0NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA发送DMA_Cmd (Uart_Tx_DMAStream,DISABLE);//先不要使能DMA!
}

DMA发送完成中断

DMA发送完成后,触发DMA发送完成中断,这里可用释放自定义的DMA发送完成信号量,表明下次的DMA传输可用进行。

//=======================================
//DMA发送完成中断服务程序
//=======================================
void DMA2_Stream7_IRQHandler(void)
{BaseType_t xHigherPriorityTaskWoken;//printf("ooooooo\r\n");if(DMA_GetITStatus(Uart_Tx_DMAStream,DMA_IT_TCIF7)!= RESET) //检查DMA传输完成中断 DMA_IT_TCIF7{DMA_ClearITPendingBit(Uart_Tx_DMAStream,DMA_IT_TCIF7); //printf("dma tx ok\r\n");if(uartDMATCSemaphore!=NULL){//释放二值信号量xSemaphoreGiveFromISR(uartDMATCSemaphore,&xHigherPriorityTaskWoken);   //释放DMA传输完成二值信号量}portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换}
}

DMA发送函数接口

//=======================================
//串口DMA发送函数
//=======================================
void uart_DMA_send(u8 *str,u16 ndtr)
{u8 i;u8 *p=str;while(xSemaphoreTake(uartDMATCSemaphore,2)!=pdTRUE);//获取信号量,等待DMA发送可用DMA_Cmd(Uart_Tx_DMAStream, DISABLE);                      //关闭DMA传输 while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){} //确保DMA可以被设置  DMA_SetCurrDataCounter(Uart_Tx_DMAStream,ndtr);          //数据传输量 for(i=0;i<ndtr;i++){SendBuff[i]=*p++;}DMA_Cmd(Uart_Tx_DMAStream, ENABLE);                      //开启DMA传输
}

DMA接收

串口DMA接收配置

需要配置一个接收地址和一个接收长度,用于DMA接收数据的暂存。

//=======================================
//串口DMA接收配置
//=======================================
void dma_uart_rx_init()
{DMA_InitTypeDef  DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 DMA_DeInit(Uart_Rx_DMAStream);//使用----->DMA2_Stream5while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}//等待DMA可配置 /* 配置 DMA Stream */DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道选择DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //源:DMA外设地址DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)ReceiveBuff;           //目的:DMA存储器0地址DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralToMemory; //方向:外设到存储器模式DMA_InitStructure.DMA_BufferSize         = BUF_SIZE;                   //长度:数据传输量DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;  //外设非增量模式DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;       //存储器增量模式DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;    //存储器数据长度:8位DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;            //使用普通模式 DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;        //DMA优先级:中等优先级DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存储器单次传输DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外设单次传输DMA_Init(Uart_Rx_DMAStream, &DMA_InitStructure);//初始化DMA StreamUSART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收DMA_Cmd (Uart_Rx_DMAStream,ENABLE);//使能
}

串口空闲中断

串口空闲中断的作用与上一篇FreeRTOS例程3-串口中断接收不定长的数据与二值信号量的使用介绍的一样,都是在发送完一串字符后被触发,这次由于使用了DMA接收,所以接收的数据在DMA缓冲区,且接收的数据长度可用根DMA接收通道的总长度与剩余长度的差值来计算,将接收的数据复制出来使用即可,同时释放自定义的串口空闲信号量,以便其它任务可用及时获取串口接收到的数据。

//=======================================
//串口1空闲中断服务程序,用于DMA接收
//=======================================
void USART1_IRQHandler(void)
{uint8_t data;//接收数据暂存变量BaseType_t xHigherPriorityTaskWoken;if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)//空闲中断{data = USART1->SR;data = USART1->DR;DMA_Cmd(Uart_Rx_DMAStream,DISABLE);//关闭DMA接收while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}  //确保DMA可以被设置 rx_cnt = BUF_SIZE - DMA_GetCurrDataCounter(Uart_Rx_DMAStream);//得到真正接收数据个数  DMA_SetCurrDataCounter(Uart_Rx_DMAStream,BUF_SIZE);//重新设置接收数据个数    //printf("rx_cnt:%d\r\n",rx_cnt);memcpy(rxbuf,ReceiveBuff,rx_cnt);//先复制出来,防止下次的数据来了之后将其覆盖DMA_ClearFlag(Uart_Rx_DMAStream,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//这里的各种标志还没搞懂DMA_Cmd(Uart_Rx_DMAStream,ENABLE); //开启DMA接收if(uartRxIDLESemaphore!=NULL){//printf("nnnnnnn\r\n");//释放二值信号量xSemaphoreGiveFromISR(uartRxIDLESemaphore,&xHigherPriorityTaskWoken);//释放串口空闲中断二值信号量}portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换}
}

串口配置与测试任务

串口配置

基础的GPIO配置,以及串口空闲中断配置,并调用上面的串口DMA发送与接收配置。

//=======================================
//串口配置
//=======================================
void uart_init(u32 bound)
{//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟//串口1对应引脚复用映射GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1//USART1端口配置GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;             //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP;            //推挽复用输出GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;             //上拉GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10//USART1 初始化设置USART_InitStructure.USART_BaudRate            = bound;                         //波特率设置USART_InitStructure.USART_WordLength          = USART_WordLength_8b;           //字长为8位数据格式USART_InitStructure.USART_StopBits            = USART_StopBits_1;              //一个停止位USART_InitStructure.USART_Parity              = USART_Parity_No;               //无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx; //收发模式USART_Init(USART1, &USART_InitStructure); //初始化串口1//DMA Configdma_uart_tx_init();//串口DMA发送配置dma_uart_rx_init();//串口DMA接收配置USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口空闲中断//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;       //串口1中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6; //抢占优先级8NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;       //子优先级0NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器USART_Cmd(USART1, ENABLE);  //使能串口1
}

测试任务

创建DMA发送完成信号量和串口空闲信号量,并先释放DMA发送完成信号量,用于第一次DMA发送时获取信号量。然后测试两条DMA发送不定长字符串,最后测试DMA接收不定长字符串。

//打印任务函数(测试任务)
void print_task(void *pvParameters)
{//创建二值信号量uartDMATCSemaphore = xSemaphoreCreateBinary();uartRxIDLESemaphore = xSemaphoreCreateBinary();xSemaphoreGive(uartDMATCSemaphore);u8 str1[]="ma nong ai xue xi\r\n";uart_DMA_send(str1,sizeof(str1));u8 str2[]="xxpcb.github.io\r\n";uart_DMA_send(str2,sizeof(str2));BaseType_t err = pdFALSE;while(1){err=xSemaphoreTake(uartRxIDLESemaphore,5);    //获取信号量if(err==pdTRUE)                            //获取信号量成功{  uart_DMA_send("receive:",sizeof("receive:"));uart_DMA_send(rxbuf,rx_cnt);uart_DMA_send("\r\n",sizeof("\r\n"));rx_cnt=0;}}
}

实验结果

通过串口助手,可以先接收到DMA发送的两个字符串(第一条hello是测试串口的,不是DMA发的),然后通过串口调试助手发送两次nice to meet you,测试DMA接收。

hello
ma nong ai xue xi
xxpcb.github.io
receive:nice to meet you
receive:nice to meet you

完整工程代码已保存至GitHub:https://github.com/xxpcb/FreeRTOS-STM32F407-examples

觉得文章有用,可以关注我哟~

FreeRTOS例程4-串口DMA收发不定长数据相关推荐

  1. 【32单片机学习】(6)STM32串口+DMA收发不定长数据

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 1.DMA介绍 2.串口接收数据 3.实验现象 1.实验电路图 2.串口收发不定长数据视频演示 3.OLED 显示接收数据 ...

  2. android 串口一直打开_STM32之串口DMA接收不定长数据

    STM32之串口DMA接收不定长数据 引言 在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据是不定长的有如何高效接收呢? 同学A:数据来了就会进入串口 ...

  3. STM32 HAL库 串口DMA接收不定长数据

    STM32 HAL库 串口DMA接收不定长数据 整体思路:我是用的CUBEMX软件生成的工程,使能了两个串口,串口2用来接收不定长的数据,串口1用来发送串口2接收到的数据:串口2我找了一个UBLOX卫 ...

  4. 第九章 AT32F403A基于V2库串口 dma接收不定长数据

    目录 概述 硬件 DMA 软件 流程 初始化 初始化代码: 中断服务函数: DMA1通道5设置函数:(重新使能通道) DMA1通道4发送函数:(设置dma长度和内存地址) 测试 最后 概述 本文主要是 ...

  5. STM32从零到一,从标准库移植到HAL库,UART串口1以DMA模式收发不定长数据代码详解+常见问题 一文解析

    前言 本文的参考资料 感谢提供标准库版本的CSDN同学:这两篇文章至少是我看过的最详细的标准库配置DMA版本.而且代码实测稳定能用. STM32 | DMA配置和使用如此简单(超详细)_...| .. ...

  6. STM32使用串口1配合DMA接收不定长数据,大大减轻CPU载荷

    摘自:http://www.openedv.com/thread-63849-1-1.html 参考:https://blog.csdn.net/heda3/article/details/80602 ...

  7. STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷

    STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷 http://www.openedv.com/thread-63849-1-1.html 实现思路:采 用STM32F103的串口1,并配 ...

  8. STM32单片机串口空闲中断+DMA接收不定长数据

    在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方式有一个问题就是串口每接收到一个字节就会进入一次中断,如果发送的数据比较频繁,那么串口中断就会不停打 ...

  9. MM32F3277空闲中断+DMA接收不定长数据

    摘要:在实际项目中经常用到串口接收一些不定长的数据,怎么判断这一帧数据接收完成了呢?通常使用UART非空中断配合简单的数据协议,在数据中加入帧头.帧尾,在程序中判断是否接收到帧尾来确定数据接收完毕,对 ...

  10. STM32使用串口1配合DMA接收不定长数据,大大减轻CPU载荷。

    最近经常看见坛友在论坛上问串口接收的问题,我之前刚好由于项目需要用到PLC的PPI协议,需要不停地利用串口接收数据,一开始的时候采用单字节中断的方式接收判断.但是用来做通信的时候需要不停的产生串口接收 ...

最新文章

  1. 改动下ICTCLAS4J 0.9.1 提供了分词速度
  2. 外地买房,提取公积金
  3. error while loading shared libraries: xxx.so.x 错误的原因和解决办法
  4. ubuntu 网络配置
  5. java默认异常处理_spring boot 默认异常处理的实现
  6. js 节点和checkbox 使用案例
  7. 蓝桥杯vip答案java_Java实现 蓝桥杯VIP 算法训练 寂寞的数
  8. 视频+案例 | 钟南山院士谈5G医疗
  9. Python源码阅读(一)
  10. 一个独特的简历生成器,开源了!
  11. 机器视觉(9)搞懂机器视觉基本内容,这份PPT就够了!
  12. C++多态的职工管理系统
  13. 公司女同事深夜11点让我去她住处修电脑,原来是C盘爆红,看我一招搞定女同事....的电脑
  14. 国际十大正规现货黄金交易平台排名(2023年优质版)
  15. SQL之累积计算问题--HQL面试题1
  16. 一起来玩U3D之基础物理引擎
  17. java list获取某个字段
  18. 我不爱的那个女人[转]
  19. Linux Signal信号详解
  20. 组合博弈游戏 - SG函数和SG定理

热门文章

  1. 中级java后台开发复习题(最新更新时间20210206)
  2. 命令方块召唤别墅指令_我的世界:指令很好玩,如果你掌握三种技巧,它会变得更加有趣...
  3. 服务器报错Out of memory: Kill process (mysqld)内存溢出
  4. 终端等级UE Category—4G数据传输流程系列(五)
  5. 【计算机视觉基础】最大核聚类,采用Canny等边缘滤波方法对图像边缘提取,提取图像的Harris角点特征并进行沃罗诺伊与德劳内网格划分,显示沃罗诺伊划分的最大核聚类,详细的最大核聚类计算算法
  6. 巴塔木儿歌:陪伴孩子探索更大的世界
  7. ApeForms | C#-WinForm窗体仿Android桌面(左右翻页)
  8. 21岁到华为,一焊就是28年!
  9. OpenAI 文本补全
  10. windows安装mattn/go-oci8遇到的各种问题总结