《嵌入式-STM32开发指南》第二部分 基础篇 - 第7章DMA(HAL库)

标准库3.5实现:
《嵌入式-STM32开发指南》第二部分 基础篇 - 第7章 DMA

7.1 DMA工作原理

7.1.1 DMA介绍

DMA (Direct Memory Access,直接存储器存取),是一种可以大大减轻 CPU 工作量的数据存取方式,DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输,因而被广泛地使用。早在 8086 的应用中就已经有 Intel 的 8237 这种典型的 DMA 控制器,而 STM32 的 DMA 则是以类似外设的形式添加到 Cortex 内核之外的。

在硬件系统中,主要由 CPU(内核)、外设、内存(SRAM)、总线等结构组成,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,数据经常要在内存与外设之间转移,或从外设 A 转移到外设 B。例如:当 CPU 需要处理由 ADC 外设采集回来的数据时,CPU 首先要把数据从 ADC外设的寄存器读取到内存中(变量),然后进行运算处理,这是一般的处理方法。

图1 DMA数据传输示意图

DMA数据传输主要涉及三种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。三种情况的数据传输分别时:外设到内存、内存到外设、内存到内存。


7.1.2 STM32F1 的DMA主要特征

STM32F1有两个DMA控制器,一共有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

● 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
● 支持循环的缓冲器管理
● 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
● 存储器和存储器间的传输
● 外设和存储器、存储器和外设之间的传输
● 闪存、SRAM、外设的SRAM、APB1 、APB2和AHB外设均可作为访问的源和目标。
● 可编程的数据传输数目:最大为65535

7.1.3 STM32F1 的DMA请求映像

从外设(TIMx[x=1、 2、 3、 4]、 ADC1、 SPI1、 SPI/I2S2、 I2Cx[x=1、 2]和USARTx[x=1、 2、 3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。参见下图的DMA1请求映像。

外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭。

表1 各个通道的DMA1通道
图2 DMA1请求映射
表2 各个通道的DMA2通道
图3 DMA2请求映射

从外设(TIMx[5、 6、 7、 8]、 ADC3、 SPI/I2S3、 UART4、 DAC通道1、 2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,这意味着同时只能有一个请求有效。参见上图的DMA2请求映像。
要使用 DMA,需要确定一系列的控制参数,如外设数据的地址、内存地址、传输方向等,在开启 DMA 传输前还要先发出 DMA 请求。外设的DMA请求,可以通过设置相应外设寄存器中的DMA控制位,被独立地开启或关闭。

注意: DMA2控制器及相关请求仅存在于大容量产品和互联型产品中。


7.1.4 STM32F1 的DMA工作过程

如图4所示为,STM32F1的DMA的系统框图。

图4 STM32F1 DMA框图

我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。

如果不使用DMA,CPU传输数据还要以内核作为中转站,比如要将USART1的数据转移到到SRAM中,这个过程是这样的:

第一步:内核通过DCode经过总线矩阵协调,从获取AHB存储的外设USART1的数据。
第二步:内核再通过DCode经过总线矩阵协调把数据存放到内存SRAM中。

图5 STM32F1不使用DMA工作工作过程

如果使用DMA的话,数据传输需要以下步骤:

1.DMA传输时外设对DMA控制器发出请求。DMA控制器收到请求,触发DMA工作。
2.DMA控制器从AHB外设获取USART1的数据,存储到DMA通道中
3.DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设USART1的数据经由DMA通道存放到

SRAM中,这个数据的传输过程中,完全不需要内核的参与,也就是不需要CPU的参与。

图6 STM32F1使用DMA工作工作过程

在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。DMA传输结束,如果有更多的请求时,外设可以启动下一个周期。

总之,每次DMA传送由3个操作组成:

1.从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
2.存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
3.执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

好了,接下来我们看看如何实现DMA进行数据传输。


7.2 STM32Cube生成工程

和以前一样,还是使用GPIO流程灯的工程进行修改,当然也可重新新建工程。主要配置项如下。
1.配置RCC

图7 RCC配置

RCC和以前一样,使用设置高速外部时钟HSE。

2.配置串口

本文使用USART1,配置如下。设置MODE为异步通信(Asynchronous)。基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能。

图8 USART配置

设置GPIO引脚自动设置 USART1_RX/USART_TX,默认即可。

图9 USART1 GPIO配置

另外还要使能USART中断,在NVIC Settings 一栏使能接收中断。

图10 USART1 中断使能

根据DMA通道预览可以知道,我们用的USART1 的TX RX 分别对应DMA1 的通道4和通道5。

图11 USART1 DMA配置

点击DMASettings 点击 Add 添加通道,选择USART_RX USART_TX 传输速率设置为中速,DMA传输模式为正常模式,DMA内存地址自增,每次增加一个Byte(字节)。

【注1】DMA传输方式
方法1:DMA_Mode_Normal,正常模式,
当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
方法2:DMA_Mode_Circular ,循环传输模式

当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。也就是多次传输模式。
Channel DMA传输通道设置

  • DMA1 : DMA1 Channel 0~DMA1 Channel 7
  • DMA2: DMA2 Channel 1~DMA1 Channel 5

【注2】DMA的传输方向
DMA的传输方向在前文也已经说过了,对应于STM32cubeMX的三种传输配置如下:外设到内存 Peripheral To Memory,内存到外设 Memory To Peripheral,内存到内存 Memory To Memory。

【注3】指针递增模式
Src Memory 表示外设地址寄存器
功能:设置传输数据的时候外设地址是不变还是递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,

Dst Memory 表示内存地址寄存器
功能:设置传输数据时候内存地址是否递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,这个Src Memory一样,只不过针对的是内存。
串口发送数据是将数据不断存进固定外设地址串口的发送数据寄存器(USARTx_TDR)。所以外设的地址是不递增。而内存储器存储的是要发送的数据,所以地址指针要递增,保证数据依次被发出。串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte。

3.DMA配置
右侧点击System Core 点击DMA。

图12 DMA配置界面

注意:上图显示了我们刚才添加的串口外设,如果你是在DMA设置界面添加DMA 而没有开启对应外设的话 ,默认为MENTOMEN,也只有此选择。如下图所示。

图13 DMA add界面

4.时钟源设置
笔者使用外部8M时钟源,DMA1挂在AHP上的,USART1是挂在APB2上的。具体配置如下。

图14 时钟配置

其他默认即可,然后点击GENERATE CODE 创建工程


7.3 DMA具体代码分析

7.3.1 USART+ DMA数据发送

在具体实现实现代码之前,先看看USART1+DMA发送数据流程,如下图示所示。我们直接通过DMA通道将要发送的数据放入串口发送寄存器,数据经过串口发送到数据接收设备,笔者使用的是上位机接收。

图15 USART1 发送数据流

在分析代码之前,先看看USART+ DMA数据发送是如何实现的,打开工程,我们新建一个变量。

uint8_t sendBuff[] = "USART test by DMA\r\n";  

然后在man.c中的主循环添加以下代码:

HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)sendBuff, sizeof(sendBuff));    
HAL_Delay(1000);

然后编译,将程序编译好后下载到板子中,通过串口助手可以看到在接收区有数据不断的打印输出,同时LED1不停闪烁。

图16 USART1 发送数据实验现象


【注】在本例中串口是DMA操作的,而LED的闪烁是CPU控制,请读者朋友注意。

下面讲讲代码,流程图见上图14。通过DMA+USART数据发送和直接使用USART整体流程编程差不多,主要在于串口的初始化配置不同,数据发送函数不同。可以和上一章比较。DMA+USART流程如下:

1.初始化硬件,配置时钟
2.GPIO初始化(仅仅LED),DMA初始化,串口初始化参数配置(USART的RX和TX);
3.填充数据,发送数据。

这里主要关注DMA初始化,串口初始化参数配置,以下函数都是STM32cudeMX自动生成的,函数如下:

MX_DMA_Init();
MX_USART1_UART_Init();

先看MX_DMA_Init()函数,函数原型如下:

static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  /* DMA1_Channel5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);

}

很简单,主要打开DMA1的通道4和5的中断开关,也就是串口的RX和TX。

再看看MX_USART1_UART_Init()函数。函数原型如下:

static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

前面就不说了,在上一章已经交接过了,这里重点讲解HAL_UART_Init()函数

HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
  /* Check the UART handle allocation */
  if (huart == NULL)
  {
    return HAL_ERROR;
  }

  /* Check the parameters */
  if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
  {
    /* The hardware flow control is available only for USART1, USART2 and USART3 */
    assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance));
    assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl));
  }
  else
  {
    assert_param(IS_UART_INSTANCE(huart->Instance));
  }
  assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));
#if defined(USART_CR1_OVER8)
  assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling));
#endif /* USART_CR1_OVER8 */

  if (huart->gState == HAL_UART_STATE_RESET)
  {
    /* Allocate lock resource and initialize it */
    huart->Lock = HAL_UNLOCKED;

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    UART_InitCallbacksToDefault(huart);

    if (huart->MspInitCallback == NULL)
    {
      huart->MspInitCallback = HAL_UART_MspInit;
    }

    /* Init the low level hardware */
    huart->MspInitCallback(huart);
#else
    /* Init the low level hardware : GPIO, CLOCK */
    HAL_UART_MspInit(huart);
#endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */
  }

  huart->gState = HAL_UART_STATE_BUSY;

  /* Disable the peripheral */
  __HAL_UART_DISABLE(huart);

  /* Set the UART Communication parameters */
  UART_SetConfig(huart);

  /* In asynchronous mode, the following bits must be kept cleared:
     - LINEN and CLKEN bits in the USART_CR2 register,
     - SCEN, HDSEL and IREN  bits in the USART_CR3 register.*/
  CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN));
  CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN));

  /* Enable the peripheral */
  __HAL_UART_ENABLE(huart);

  /* Initialize the UART state */
  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->gState = HAL_UART_STATE_READY;
  huart->RxState = HAL_UART_STATE_READY;

  return HAL_OK;
}

重点关注HAL_UART_MspInit()函数,

void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(huart->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 DMA Init */
    /* USART1_RX Init */
    hdma_usart1_rx.Instance = DMA1_Channel5;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_NORMAL;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);

    /* USART1_TX Init */
    hdma_usart1_tx.Instance = DMA1_Channel4;
    hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_tx.Init.Mode = DMA_NORMAL;
    hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
    if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(huart,hdmatx,hdma_usart1_tx);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }

}

这里就是DMA的USART初始化。有个重要的结构体:DMA_HandleTypeDef。

typedef struct __DMA_HandleTypeDef
{
  DMA_Channel_TypeDef   *Instance;                       /*!< Register base address                  */
  
  DMA_InitTypeDef       Init;                            /*!< DMA communication parameters           */ 
  
  HAL_LockTypeDef       Lock;                            /*!< DMA locking object                     */  
  
  HAL_DMA_StateTypeDef  State;                           /*!< DMA transfer state                     */
  
  void                  *Parent;                                                      /*!< Parent object state                    */  
  
  void                  (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);     /*!< DMA transfer complete callback         */
  
  void                  (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*!< DMA Half transfer complete callback    */
  
  void                  (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);    /*!< DMA transfer error callback            */

  void                  (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);    /*!< DMA transfer abort callback            */  
  
  __IO uint32_t         ErrorCode;                                                    /*!< DMA Error code                         */

  DMA_TypeDef            *DmaBaseAddress;                                             /*!< DMA Channel Base Address               */
  
  uint32_t               ChannelIndex;                                                /*!< DMA Channel Index                      */  

} DMA_HandleTypeDef;

 Instance:DMA通道配置,也就是寄存器基地址;
 Init:也是一个结构体,主要初始化DMA通讯参数,后面会有讲解;
 Lock:对资源操作增加操作锁;
 State:DMA的传输状态,通过设置不同的状态设置,实现传输方式;
 剩余几个参数是回调的指针和变量,用于实现回调函数。
再看看DMA_InitTypeDef结构体,其原型如下;


typedef struct
{
  uint32_t Direction;                 /*!< Specifies if the data will be transferred from memory to peripheral, 
                                           from memory to memory or from peripheral to memory.
                                           This parameter can be a value of @ref DMA_Data_transfer_direction */

  uint32_t PeriphInc;                 /*!< Specifies whether the Peripheral address register should be incremented or not.
                                           This parameter can be a value of @ref DMA_Peripheral_incremented_mode */

  uint32_t MemInc;                    /*!< Specifies whether the memory address register should be incremented or not.
                                           This parameter can be a value of @ref DMA_Memory_incremented_mode */

  uint32_t PeriphDataAlignment;       /*!< Specifies the Peripheral data width.
                                           This parameter can be a value of @ref DMA_Peripheral_data_size */

  uint32_t MemDataAlignment;          /*!< Specifies the Memory data width.
                                           This parameter can be a value of @ref DMA_Memory_data_size */

  uint32_t Mode;                      /*!< Specifies the operation mode of the DMAy Channelx.
                                           This parameter can be a value of @ref DMA_mode
                                           @note The circular buffer mode cannot be used if the memory-to-memory
                                                 data transfer is configured on the selected Channel */

  uint32_t Priority;                  /*!< Specifies the software priority for the DMAy Channelx.
                                           This parameter can be a value of @ref DMA_Priority_level */
} DMA_InitTypeDef;

 Direction:DMA传输方向,有三种,外设到存储器,存储器到外设,存储器到存储器,根据工程要求来配置,这里选择DMA_MEMORY_TO_PERIPH参数;
 PeriphInc:配置外设地址寄存器是否自动增加,这里配置为不递增;
 MemInc:配置内存地址自动增加,一般都是使能自动增加;
 PeriphDataAlignment:外设数据长度,分别有字节、字、半字,这里配置为字。
 MemDataAlignment:内存数据长度,和外设的类似;
 Mode:配置传输模式,这里是常规模式。
 Priority:优先权,笔者配置的为中优先级。


以上的结构体配置参数就是通过STM32cudeMX配置得到的,和前文是的配置是一一对应的。DMA+USART1的数据发送就到这里了,如果还有什么迷惑,请读者朋友结合USART1 发送数据流程与代码在仔细琢磨吧。

7.3.2 USART+ DMA数据接收

使用USART+DMA数据接收不需要再配置STM32cubeMX,只是在发送的基础上添加代码即可。下面还是先将如何USART+ DMA数据接收实现。

在main.c添加如下变量:

uint8_t recvBuff;  //接收数据缓存

在MX_USART1_UART_Init()函数添加以下代码:

//DMA接收初始化函数,此句一定要加,不加接收不到第一次传进来的实数据,是空的,且此时接收到的数据长度为缓存器的数据长度
HAL_UART_Receive_DMA(&huart1, &recvBuff, 1);

然后添加回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
      HAL_UART_Receive_DMA(&huart1, &recvBuff, 1);  
      HAL_UART_Transmit_DMA(&huart1, &recvBuff, 1); 
}

然后将程序编译好后下载到板子中,发送一个数据我们设置连续发送,如下图所示。

图17DMA+USART1接收数据


这样就完了吗,我们换个数据测试下。

图18 DMA+USART1接收数据


有的朋友说,是我设置的接收数据是一个字符,换一下就好了,那么你可以是试试换成2个或者多个试试。我这里就不展示了,接下来笔者将带领大家实现任意字符串接收并输出。这里有种最简单的方式,就是使用DMA+USART1接收数据,通过USART1的普通方式发送数据。
也就是将

HAL_UART_Transmit_DMA(&huart1, &recvBuff, 1);   

换成

HAL_UART_Transmit(&huart1,&recvBuff,1,0);

然后编译,下载程序,看下结果。

图19 DMA+USART1接收数据

当然,只要是实现了,怎样都可以,笔者这章讲的是DMA,那么接收发送都得用吧,来吧,展示!
在main.c中添加以下变量:

uint8_t recvBuff[BUFFER_SIZE] ;  //接收数据缓存数组
volatile uint8_t recvLength = 0;  //接收一帧数据的长度
volatile uint8_t recvDndFlag = 0; //一帧数据接收完成标志

在函数添加以下代码:

__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);

在main.h中添加以下宏定义与变量:

#define BUFFER_SIZE 16
extern uint8_t recvBuff[BUFFER_SIZE];  //接收数据缓存
extern volatile uint8_t recvLength;  //接收一帧数据的长度
extern volatile uint8_t recvDndFlag; //一帧数据接收完成标志


接下来就是重点,我们使用IDLE 接收空闲中断+DMA接收数据,然后发送数据。STM32的IDLE的中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断。我们修改USART1_IRQHandler()函数:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
    uint32_t tmpFlag = 0;
    uint32_t temp;
    tmpFlag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
    if((tmpFlag != RESET))//idle标志被置位
    { 
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
        
        HAL_UART_DMAStop(&huart1); //
        temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数   
        recvLength  =  BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
        recvDndFlag  = 1;   // 接受完成标志位置1    
        HAL_UART_Transmit_DMA(&huart1, recvBuff, recvLength);
        recvLength = 0;//清除计数
        recvDndFlag = 0;//清除接收结束标志位

        memset(recvBuff,0,recvLength);
        HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);//重新打开DMA接收,不然只能接收一次数据
     }
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

好了,编译下,重新将程序下载到板子里。

图20 DMA+USART1接收数据


现在就完美了,当然在实际工程中,最基本的就是要实现功能,然后再谈效率。最后再总结下USART+ DMA数据接收过程,和USART中断接收流程差不多,只是这里增加了DMA,流程图我看的画了。这里重点将讲解STM32 的IDLE中断,也就是STM32的接收不定长度字节数据的方法。由于STM32单片机带IDLE中断,所以利用这个中断,可以接收不定长字节的数据。

IDLE中断什么时候发生?IDLE就是串口收到一帧数据后,发生的中断。什么是一帧数据呢?比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。关于STM32F1的IDLE可以看参考手册的27.6.4节的内容。

USART_CRT寄存器

这是串口CR1寄存器,其中,对bit4写1开启IDLE中断,对bit5写1开启接收数据中断。(注意:不同系列的STM32,对应的寄存器位可能不同,清理中断标志位的方法也不同,具看参考手册)

关于更多IDLE中断,有时间再讲吧,今天就到这里了。


代码获取方式
1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[STM32F1]获取资料


欢迎访问我的网站:

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269