stm32串口dma发送/接收程序

发布者:SereneWanderer最新更新时间:2024-04-22 来源: elecfans关键字:stm32  串口  dma  发送  接收 手机看文章 扫描二维码
随时随地手机看文章

  串口可以配置成用DMA的方式接收数据,不过DMA需要定长才能产生接收中断,如何接收可变长度的数据呢?

  方法有以下3种:

  1.将RX脚与一路时钟外部引脚相连,当串口一帧发完,即可利用此定时器产生超时中断。这个实时性较高,可以做到1个字节实时监测。


  2.不改变硬件,开启一个定时器监控DMA接收,如果超时则产生中断。这个实时性不高,因为超时时间必须要大于需要接收帧的时间,精度不好控制。

  3.STM32单片机有的串口可以监测总线是否处于空闲,如果空闲则产生中断。可以用它来监测DMA接收是否完毕。这种方式实时性很高。

  串口DMA发送:

  发送数据的流程:

  前台程序中有数据要发送,则需要做如下几件事

  1. 在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。

  2. 将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(串口发送DMA和串口接收DAM不是同一个DMA通道)

  3. 开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管Keil 是什么状态,DMA总是发送数据。

  4. 等待发送完成标志位,即下面的终端服务函数中的第3点设置的标志位。或者根据自己的实际情况来定,是否要一直等待这个标志位,也可以通过状态机的方式来循环查询也可以。或者其他方式。 判断数据发送完成:

  启动DMA并发送完后,产生DMA发送完成中断,在中断函数中做如下几件事:

  1. 清DMA发送完成中断标志位 2. 关闭串口发送DMA通道

  3. 给前台程序设置一个软件标志位,说明数据已经发送完毕

  串口DMA接收:

  接收数据的流程:

  串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。

  判断数据数据接收完成:

  这里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。这个中断里面做如下几件事:

  1.关闭串口接收DMA通道,2点原因:1.防止后面又有数据接收到,产生干扰。2.便于DMA的重新配置赋值,下面第4点。

  2. 清除DMA 所有标志位

  3. 从DMA寄存器中获取接收到的数据字节数

  4.重新设置DMA下次要接收的数据字节数,注意,这里是给DMA寄存器重新设置接收的计数值,这个数量只能大于或者等于可能接收的字节数,否则当DMA接收计数器递减到0的时候,又会重载这个计数值,重新循环递减计数,所以接收缓冲区的数据则会被覆盖丢失。

  5. 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如第4条的写入计数值,必须要在关闭DMA的条件进行,否则操作无效。

  说明一下,STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。

  串口用DMA方式发送和接收,分以下几步:

  1)串口初始化

  2)DMA初始化

  3)发送数据

  4)接收数据

  我们按部就班:

  1) 串口初始化 — 使用串口一

  #define DMASIZE 1024

  // 配置串口一的发送和接收的GPIO口功能,以及中断

  static void _uart1_gpio_init(void)

  {

  NVIC_InitTypeDef NVIC_InitStructure;

  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |

  RCC_APB2Periph_USART1 |

  RCC_APB2Periph_AFIO, ENABLE) ;

  GPIOA-》CRH&=0XFFFFF00F;

  GPIOA-》CRH|=0X000008B0;//IO状态设置 10pin_上拉输入 9pin_推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  /* Configure USART1 Rx as input floating */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Configure USART1 Tx as alternate function push-pull */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Enable the USART1 Interrupt */

  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

  USART_ClearFlag(USART1, USART_FLAG_TC); /* 清发送外城标志,Transmission Complete flag */

  USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);// 采用空闲中断,目的是在产生空闲中断时,说明接收或者发送已经结束,此时可以读取DMA中的数据了。

  //USART_ITConfig(USART1, USART_IT_TC, ENABLE);

  //USART_ITConfig(USART1, USART_IT_FE, ENABLE);

  }

  // 设置对应串口的波特率

  static void _uart_setbaudrate(USART_TypeDef* USARTx,u32 value)

  {

  USART_InitTypeDef USART_InitStructure;

  USART_InitStructure.USART_BaudRate =value;

  USART_InitStructure.USART_WordLength = USART_WordLength_8b;

  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(USARTx, &USART_InitStructure);

  USART_Cmd(USARTx, ENABLE);

  2)初始化DMA

  u8 sendbuf[1024];

  u8 receivebuf[1024];

  static void _uart1_dma_configuration()

  {

  DMA_InitTypeDef DMA_InitStructure;

  /* DMA1 Channel6 (triggered by USART1 Rx event) Config */

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 ,

  ENABLE);

  /* DMA1 Channel5 (triggered by USART1 Rx event) Config */

  DMA_DeInit(DMA1_Channel5);

  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;// 初始化外设地址,相当于“哪家快递”

  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf;// 内存地址,相当于几号柜

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据来源,即为收快递

  DMA_InitStructure.DMA_BufferSize = DMASIZE ;// 缓存容量,即柜子大小

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增,即柜子对应的快递不变

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 内存递增

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节宽度,即快递运输快件大小度量(按重量算,还是按体积算)

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存字节宽度,即店主封装快递的度量(按重量,还是按体质进行封装)

  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,即满了就不在接收了,而不是循环存储

  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;// 优先级很高,对应快递就是加急

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 内存与外设通信,而非内存到内存

  DMA_Init(DMA1_Channel5, &DMA_InitStructure);// 把参数初始化,即拟好与快递公司的协议

  DMA_Cmd(DMA1_Channel5, ENABLE);// 启动DMA,即与快递公司签订合同,正式生效

  /* DMA1 Channel4 (triggered by USART1 Tx event) Config */

  DMA_DeInit(DMA1_Channel4);

  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; // 外设地址,串口1, 即发件的快递

  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf;// 发送内存地址

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 外设为传送数据目的地,即发送数据,即快递是发件

  DMA_InitStructure.DMA_BufferSize = 0; //发送长度为0,即未有快递需要发送

  DMA_Init(DMA1_Channel4, &DMA_InitStructure);//初始化

  USART_ITConfig(USART1, USART_IT_TC, ENABLE);// 使能串口发送完成中断

  USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求

  }

  }


  数据发送

  流程:串口发送数据,全部数据发送完毕后,会产生一个发送中断,所以

  发送数据分为两部分,

  A、发送数据

  B、中断处理

  A、发送数据

  u16 Uart_Send_Data(void* buffer, u16 size)

  {

  if(!size) return 0;// 判断长度是否有效

  while (DMA_GetCurrDataCounter(DMA1_Channel4));// 检查DMA发送通道内是否还有数据

  if(buffer) memcpy(sendbuf, buffer,(size 》 1024?1024:size));

  //DMA发送数据-要先关 设置发送长度 开启DMA

  DMA_Cmd(DMA1_Channel4, DISABLE);

  DMA1_Channel4-》CNDTR = size;// 设置发送长度

  DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送

  return size;

  }

  B、中断处理

  1)中断处理相关准备工作

  typedef enum _UartEvent_

  {

  E_uart_0 = 0,// 没有事件

  E_uart_tc=0x40, //发送完成

  E_uart_idle=0x80, //接收完成

  }UartEvent;

  u16 receivelen = 0;// 声明接收数据长度

  UartEvent event;//申明一个事件参数

  //清除DMA 缓存,并终止DMA

  void Uart_Dma_Clr(void)

  {

  DMA_Cmd(DMA1_Channel4, DISABLE);

  DMA1_Channel4-》CNDTR=0;

  DMA_Cmd(DMA1_Channel5, DISABLE);

  DMA1_Channel5-》CNDTR=DMASIZE ;

  DMA_Cmd(DMA1_Channel5, ENABLE);

  }

  // 获取一个事件,事件分为发送完成事件和接收完成事件,可以根据事件进行进行处理

  UartEvent Uart_Get_Event(void)

  {

  UartEvent e;

  if(!DMA1_Channel5-》CNDTR) Uart_Dma_Clr();// 如果产生一个事件后,接收数据通道已经没有了缓存空间,进行清除DMA清空

  return event;

  }

  // 清除对应的事件

  void Uart_Clr_Event(UartEvent event_in)

  {

  event&=~event_in;

  }

  2) 中断处理,当所有数据发送完毕,串口1产生一个发送完成中断

  void Uatr1_Back_IRQHandler()

  {

  u8 tem;

  if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)

  {

  tem=USART1-》SR;//先读SR,然后读DR才能清除

  tem=USART1-》DR;

  tem=tem;

  Uart_Set_Event(E_uart_idle);

  receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度

  USART_ClearITPendingBit(USART1, USART_IT_IDLE);

  }

  **if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记**

  {

  USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记

  DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA

  DMA1_Channel4-》CNDTR=0; // 清除数据长度

  Uart_Set_Event(E_uart_tc); //设置发送完成事件

  }

  }

  4、接收数据

 stm32串口dma发送/接收程序

  根据上图描述,流程如下:

  1、串口接收到数据

  2、DMA自动取走数据

  3、DMA把数据存到内存receive[1024]中

  

  4、串口接收完毕后会产生一个空闲中断

  根据上面流程,我们接收数据需要做到两步:

  1)串口产生一个空闲中断后,设置一个接收完成事件

  中断处理:

  void Uatr1_Back_IRQHandler()

  {

  u8 tem;

  **if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)**

  {

  tem=USART1-》SR;//先读SR,然后读DR才能清除

  tem=USART1-》DR;// 清除DR

  tem=tem; // 防止编译器警告

  Uart_Set_Event(E_uart_idle);// 设置接收完成(空闲)事件

  receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度

  USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除空闲中断

  }

  if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记

  {

  USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记

  DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA

  DMA1_Channel4-》CNDTR=0; // 清除数据长度

  Uart_Set_Event(E_uart_tc); //设置发送完成事件

  }

  }

  2)接收数据函数检测事件,如果发现是接收完成事件,取走数据,并且做相关清除操作

  u8 Uart_Receive_Data(u8*recbuf u16 *revLen)

  {

  u8 *str;

  if( event & E_uart_idle) // 是否产生空闲中断

  {

  str = Uart_Get_Data(revLen);

  memcpy(recbuf,receivebuf,*revLen);

  Uart_Clr_Event(E_uart_idle);

  Uart_Dma_Clr();

  return TRUE;

  }

  else

  {

  revLen = 0;

  return FALSE;

  }

  }


关键字:stm32  串口  dma  发送  接收 引用地址:stm32串口dma发送/接收程序

上一篇:基于STM32的简易四轴飞行器系统的设计实现
下一篇:STM32中GPIO是如何工作的?想知道吗?

推荐阅读最新更新时间:2026-03-20 19:42

STM32 USART串口DMA接收发送模式
串口DMA发送: 发送数据的流程: 前台程序中有数据要发送,则需要做如下几件事 1. 在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。 2. 将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(串口发送DMA和串口接收DAM不是同一个DMA通道) 3. 开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管Keil 是什么状态,DMA总是发送数据。 4. 等待发送完成标志位,即下面的终端服务函数中的第3点设置的标志位。或者根据自己的实际情况来定,是否要一直等待这个标志位,也可以通过状态机的方式来循
[单片机]
<font color='red'>STM32</font> USART<font color='red'>串口</font><font color='red'>DMA</font><font color='red'>接收</font>和<font color='red'>发送</font>模式
GD32F4单片机实现接收超时中断+DMA实现串口的不定长接收DMA发送
1、通常的实现方式介绍 环形缓冲区+定时器超时中断的方式 设备任务比较繁重时,使用中断接收可能会丢失数据。尤其是在长时间关闭中断或者串口中断优先级不高时 频繁进出中断。在使用 RTOS 的系统中,每收到一个数据就会进行一次任务到中断的切换和中断到任务的切换 环形缓冲区可以接收多帧数据 数据帧超时间隔可以设置 优点 缺点 使用串口接收空闲中断+DMA 的方式 空闲中断的时间对于同一个波特率来说是固定的,但某些时候 1 个字节的接收时间太短,不能作为数据帧接收完成的标志 不会频繁在任务和中断之间切换,效率会更高 一般不会丢失数据 优点 缺点 2、接收超时中断的相关内容 GD32F4 系列的单片机串口除
[单片机]
GD32F4单片机实现<font color='red'>接收</font>超时中断+<font color='red'>DMA</font>实现<font color='red'>串口</font>的不定长<font color='red'>接收</font>和<font color='red'>DMA</font><font color='red'>发送</font>
串口DMA方式发送&接收
串口DMA方式收发 笔者使用的是STM32F407VET6,共包含6路串口,页尾处程序已将全部串口的DMA收发配置完成,本文仅以串口1为例进行讲解。(查看代码可直接跳至第二节或页尾处下载) 1 STM32F4 DMA 简介 DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。 STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用
[单片机]
<font color='red'>串口</font><font color='red'>DMA</font>方式<font color='red'>发送</font>&<font color='red'>接收</font>
【调试记录】STM32 HAL库硬件I2C从机Seq DMA方式不定长接收发送
序言 久闻STM32硬件I2C坑多,之前做的项目浅尝主机通信就偶尔遇到总线锁死的bug,网上解决方案也很多,用着也还行。然而作为从机就是另一个大坑了,官方例程少,网上资料少,api也说的不明不白。本文整合各位博主分享的资料,记录和分享调试linux主机与STM32的I2C通信过程中遇到的问题和解决方案,最终在STM32L051C8T单片机实现DMA方式的I2C从机。 单片机资源紧张,性能低但是实时性高。要充分发挥单片机实时特性,在处理低速IO时应该尽量用硬件方式实现,尽可能利用硬件处理数据。DMA就是解放CPU负载的利器。 HAL库API分析 一般来说HAL库的通信io类API分为polling阻塞,IT和DMA方式。而I2C分
[单片机]
STM32实现USART+DMA接收未知长度的数据和发送
前言:开始学USART+DMA的时候看到帖子《STM32 UART DMA实现未知数据长度接收》,觉得方法妙极了。此下出自此帖子——(整体的思路是这样的,一开始设置好DMA接收,可以把缓冲区长度设置为帧最大长度,我们可以把RX连接到定时器的管脚输入端,并且一开始设置输入并且使能引脚下降沿中断,当帧的第一个字节发送时,因为起始位为低电平,空闲时UART为高电平,满足条件,进入中断,禁止中断,并且在中断中开启定时器,该定时器工作在复位模式,上升沿复位,并且设置好定时器输出比较值为超时时间,比如20ms,这样,在传输后面字节时,肯定会有高低电平出现,即便是传输的是0x00,0xFF,虽然UART数据区不变,但是都为1,或都为0,但是因为
[单片机]
STM32 DMA串口发送模式配置及使用简单分享
  赠人玫瑰,手有余香;很感谢网上其他楼主的分享;我这个贴子是关于STM32 DMA USART 发送模式的调试分享,我为什么要建这个帖子呢,因为我不想看到还有其他人跟我一样,为了调通DMA串口,而花上大半天的时间,这很不利于大家高效率的开发,网上的经验都是从头教到尾的,内容极多,对于有点小经验的人,或者是想很快达到目的的人,这个很不适合他们;我的这个很简单(本帖不适合不熟悉配置STM32串口的玩家),他只是说串口的DMA怎么配置,还有我用的是DMA1_通道4,因为我的是串口1的TX长话短说:直接po代码:   DMA_InitTypeDef DMA_InitStruct; //DMA类型声明   RCC_AHBPeriphC
[单片机]
STM32串口采用DMA方式发送数据测试
环境: 主机:WIN7 开发环境:MDK4.23 MCU:STM32F103CBT6 源代码: 配置: //---------------------串口功能配置--------------------- //打开串口对应的外设时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); //启动DMA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //DMA发送中断设置 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
[单片机]
STM32串口DMA发送中断配置
本文以STM32F1xx的串口1为例,简单呈现下使用DMA中断连续发送的代码 串口DMA配置通常可以分为2个部分: 1.串口配置 2.DMA配置 串口配置 void UART1_Init(u32 bound){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved