STM32H7 串口 空闲中断 DMA 任意长接收 Hal库 IDLE

本文探讨了如何使用STM32H7的DMA来优化串口接收,以减少中断频率。通过DMA,数据在后台传输,只有在接收完整帧数据时才通知主程序,从而降低中断对系统的影响。文章详细分析了HAL库中的`HAL_UART_Receive_DMA`函数和`HAL_DMA_Start_IT`函数,展示了配置DMA传输参数的过程,以及如何利用串口空闲中断来指示一帧数据接收完成。此外,还展示了双缓冲区在串口驱动中的应用,确保数据传输的连续性和稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接着上一篇关于STM32H7串口收发问题,继续说,上一篇里边提供了中断接收方式,最大的缺点就是中断过于频繁,为了解决这个问题那就把DMA搬过来,它不就是专门搬用数据的嘛,不用多可惜。

首先我们需要大致了解,DMA和外设传送数据,例如串口,我们希望,当一帧数据接收完毕了,有个东西告诉主程序,串口接收到了一帧n个字节的数据存在某个地方,接收过程中你丫别打搅我。

DMA就能胜任这个工作,他可以以中断的形式告诉你这些信息。相比中断接收方式,是不是省了很多中断,主程序被打断的次数也就少了。

还是按照上一篇形式大概看一下,HAL库中DMA是怎么和串口配合的。

/**
  * @brief Receive an amount of data in DMA mode.
  * @note   When the UART parity is enabled (PCE = 1), the received data contain
  *         the parity bit (MSB position).
  * @param huart UART handle.
  * @param pData Pointer to data buffer.
  * @param Size  Amount of data to be received.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* 其他关系不大的代码省略 有兴趣可以自己看 下同 */
  //1.检查参数 判断串口接收状态为就绪 把缓存区参数传递到串口句柄  修改某些状态
  //2.判断和串口句柄关联的DMA句柄地址不为空  然后给DMA句柄注册一些回调函数
      /* Enable the DMA channel */
      if (HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->RDR, (uint32_t)huart->pRxBuffPtr, Size) != HAL_OK)
    {
      ......
    }
}

HAL_UART_Receive_DMA这个函数里最主要的就是调用了HAL_DMA_Start_IT这个函数,你看他连参数都没怎么变,就把句柄换了,其他三个原封不动的传递过去了。所以函数内其他内容几乎不用考虑了。直接往下看这个函数。

/**
  * @brief  Start the DMA Transfer with interrupt enabled.
  * @param  hdma:       pointer to a DMA_HandleTypeDef structure that contains
  *                     the configuration information for the specified DMA Stream.
  * @param  SrcAddress: The source memory Buffer address
  * @param  DstAddress: The destination memory Buffer address
  * @param  DataLength: The length of data to be transferred from source to destination
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  //1.检查参数 给句柄上锁 判断状态 只有在就绪状态下才允许配置
  //2.判断就绪状态
  {
    ......
    /* Configure the source, destination address and the data length */
    DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
    ......
    //这里会开启一堆中断  完事要用  暂时可以不关心
  }
}

上面这个函数也差不多还是那个意思,都懒的看了,主要是调用了DMA_SetConfig这个函数,追踪过去,它才是真正配置寄存器的函数,HAL库就是这么繁琐。

/**
  * @brief  Sets the DMA Transfer parameter.
  * @param  hdma:       pointer to a DMA_HandleTypeDef structure that contains
  *                     the configuration information for the specified DMA Stream.
  * @param  SrcAddress: The source memory Buffer address
  * @param  DstAddress: The destination memory Buffer address
  * @param  DataLength: The length of data to be transferred from source to destination
  * @retval None
  */
static void DMA_SetConfig(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  /* calculate DMA base and stream number */
  //获取DMA控制器对应流的基地址
  DMA_Base_Registers  *regs_dma  = (DMA_Base_Registers *)hdma->StreamBaseAddress;
  BDMA_Base_Registers *regs_bdma = (BDMA_Base_Registers *)hdma->StreamBaseAddress;
  
  /* Clear the DMAMUX synchro overrun flag */
  hdma->DMAmuxChannelStatus->CFR = hdma->DMAmuxChannelStatusMask;

  if(hdma->DMAmuxRequestGen != 0U)
  {
    /* Clear the DMAMUX request generator overrun flag */
    hdma->DMAmuxRequestGenStatus->RGCFR = hdma->DMAmuxRequestGenStatusMask;
  }

  if(IS_DMA_STREAM_INSTANCE(hdma->Instance) != 0U) /* DMA1 or DMA2 instance */
  {
    /* Clear all interrupt flags at correct offset within the register */
    regs_dma->IFCR = 0x3FUL << (hdma->StreamIndex & 0x1FU);

    /* Clear DBM bit */
    ((DMA_Stream_TypeDef *)hdma->Instance)->CR &= (uint32_t)(~DMA_SxCR_DBM);

    /* Configure DMA Stream data length */
    //这里设置搬用的目标数据长度
    ((DMA_Stream_TypeDef *)hdma->Instance)->NDTR = DataLength;

    /* Peripheral to Memory */
    //整个这个if判断是在设置DMA搬运数据的目标地址和源地址
    if((hdma->Init.Direction) == DMA_MEMORY_TO_PERIPH)
    {
      /* Configure DMA Stream destination address */
      ((DMA_Stream_TypeDef *)hdma->Instance)->PAR = DstAddress;

      /* Configure DMA Stream source address */
      ((DMA_Stream_TypeDef *)hdma->Instance)->M0AR = SrcAddress;
    }
    /* Memory to Peripheral */
    else
    {
      /* Configure DMA Stream source address */
      ((DMA_Stream_TypeDef *)hdma->Instance)->PAR = SrcAddress;

      /* Configure DMA Stream destination address */
      ((DMA_Stream_TypeDef *)hdma->Instance)->M0AR = DstAddress;
    }
  }
  else if(IS_BDMA_CHANNEL_INSTANCE(hdma->Instance) != 0U) /* BDMA instance(s) */
  {
    /* Clear all flags */
    regs_bdma->IFCR = (BDMA_ISR_GIF0) << (hdma->StreamIndex & 0x1FU);

    /* Configure DMA Channel data length */
    ((BDMA_Channel_TypeDef *)hdma->Instance)->CNDTR = DataLength;

    /* Peripheral to Memory */
    if((hdma->Init.Direction) == DMA_MEMORY_TO_PERIPH)
    {
      /* Configure DMA Channel destination address */
      ((BDMA_Channel_TypeDef *)hdma->Instance)->CPAR = DstAddress;

      /* Configure DMA Channel source address */
      ((BDMA_Channel_TypeDef *)hdma->Instance)->CM0AR = SrcAddress;
    }
    /* Memory to Peripheral */
    else
    {
      /* Configure DMA Channel source address */
      ((BDMA_Channel_TypeDef *)hdma->Instance)->CPAR = SrcAddress;

      /* Configure DMA Channel destination address */
      ((BDMA_Channel_TypeDef *)hdma->Instance)->CM0AR = DstAddress;
    }
  }
  else
  {
    /* Nothing To Do */
  }  
}

各位同志你们自己看吧这个函数最主要的目的就是配置DMA寄存器。以上就是串口DMA接收函数中最重要的部分。好像没什么意义一样,因为我要用到其中的一点点东西,所以必须要分析源码。

我们只是说了调用接收函数,参数里边指定了接收缓存,也指定了大小,似乎和我们的目标不符合,假设我们的DMA和串口之间的配置已经配置好了,那现在能做到的就是把数据收集到这个缓冲区里,可是并不能告诉主程序接收到了数据,和接收了多少,想要知道这个,有两种方法,一种是CPU定时去问DMA接收了多少数据,然后自己定义一个记录变化的变量,配合,另一种是中断方式,我们通过查询得知,DMA只有接收完成和接收半完成两个中断。

最最主要的串口的空闲中断不要忘了呀,这个可以告诉你,一帧数据接收完了,你可以根据这个中断去搞,接收了多少,在什么地方。

因为这个编辑器不知道咋回事,贴代码总是挂,上边那个函数就贴了我很长时间,很生气,所以我就无耻的把源码整到这里了,整个工程哦(暂时还没有上传)。

再试试贴代码吧,方便你我他,要积分的都无耻。改天

uart.c文件就这么多代码,

#include "Uart.h"
#include "stm32h7xx_hal.h"

#define RxBufSize   1024

UART_HandleTypeDef hUart1 = {0};
DMA_HandleTypeDef hDmaUart1Tx = {0};
DMA_HandleTypeDef hDmaUart1Rx = {0};

//数组后边的那个限定跟你的内存分配有关 如果你的主RAM在512K的那个片内存就可以不加 这是AC6编译器用法
uint8_t RxBuf[2][RxBufSize] __attribute__((section (".RAM_D1")));
void (*Uart1RxCompleteCallback)(uint8_t *pData,uint16_t *Count);

void Uart1Init(uint32_t BaudRate,void (*RxCompleteCallback)(uint8_t *pData,uint16_t *Count))
{
 hUart1.Instance = USART1;
 hUart1.Init.BaudRate = BaudRate;
 hUart1.Init.ClockPrescaler = UART_PRESCALER_DIV2;
 hUart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
 hUart1.Init.Mode = UART_MODE_TX_RX;
 hUart1.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED;
 hUart1.Init.OverSampling = UART_OVERSAMPLING_8;
 hUart1.Init.Parity = UART_PARITY_NONE;
 hUart1.Init.StopBits = UART_STOPBITS_1;
 hUart1.Init.WordLength = UART_WORDLENGTH_8B;
 hUart1.FifoMode = UART_FIFOMODE_DISABLE;

 HAL_UART_Init(&hUart1);

 __HAL_RCC_DMA1_CLK_ENABLE();

 hDmaUart1Tx.Instance = DMA1_Stream0;
 hDmaUart1Tx.Init.Request = DMA_REQUEST_USART1_TX;
 hDmaUart1Tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
 hDmaUart1Tx.Init.PeriphInc = DMA_PINC_DISABLE;
 hDmaUart1Tx.Init.MemInc = DMA_MINC_ENABLE;
 hDmaUart1Tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
 hDmaUart1Tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
 hDmaUart1Tx.Init.Mode = DMA_NORMAL;
 hDmaUart1Tx.Init.Priority = DMA_PRIORITY_MEDIUM;

 HAL_DMA_Init(&hDmaUart1Tx);
 __HAL_LINKDMA(&hUart1,hdmatx,hDmaUart1Tx);

 hDmaUart1Rx.Instance = DMA1_Stream1;
 hDmaUart1Rx.Init.Request = DMA_REQUEST_USART1_RX;
 hDmaUart1Rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
 hDmaUart1Rx.Init.PeriphInc = DMA_PINC_DISABLE;
 hDmaUart1Rx.Init.MemInc = DMA_MINC_ENABLE;
 hDmaUart1Rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
 hDmaUart1Rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
 hDmaUart1Rx.Init.Mode = DMA_NORMAL;
 hDmaUart1Rx.Init.Priority = DMA_PRIORITY_MEDIUM;

 HAL_DMA_Init(&hDmaUart1Rx);
 __HAL_LINKDMA(&hUart1,hdmarx,hDmaUart1Rx);

 HAL_UART_Receive_DMA(&hUart1,RxBuf[0],RxBufSize);

 __HAL_UART_ENABLE_IT(&hUart1,UART_IT_IDLE);
 HAL_NVIC_EnableIRQ(USART1_IRQn);
 HAL_NVIC_SetPriority(USART1_IRQn,14,0);

 HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);//Tx
 HAL_NVIC_SetPriority(DMA1_Stream0_IRQn,14,0);

 HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);//Rx
 HAL_NVIC_SetPriority(DMA1_Stream1_IRQn,14,0);

 Uart1RxCompleteCallback = RxCompleteCallback;
}

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
 if(huart == &hUart1)//串口1
 {
  GPIO_InitTypeDef GPIO_InitStruct;

  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
  GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
  GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_9;

  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_USART1_CLK_ENABLE();

  HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
 }
}

void Uart1TxData(uint8_t *pData,uint16_t Count)
{
 if(Count)
  HAL_UART_Transmit_DMA(&hUart1,pData,Count);
}

void USART1_IRQHandler(void)
{
 if(__HAL_UART_GET_FLAG(&hUart1,UART_FLAG_IDLE))
 {
  static uint16_t count;
  __HAL_UART_CLEAR_IDLEFLAG(&hUart1);
  if(Uart1RxCompleteCallback)
  {
   hUart1.RxState = HAL_UART_STATE_READY;
   hDmaUart1Rx.State = HAL_DMA_STATE_READY;
   HAL_UART_RxCpltCallback(&hUart1);
  }
 }
 else
  HAL_UART_IRQHandler(&hUart1); 
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
 if(huart == &hUart1)
 {
  static uint16_t count;
  count = RxBufSize - __HAL_DMA_GET_COUNTER(&hDmaUart1Rx);
  if(count == 0)return;
  hDmaUart1Rx.Lock = HAL_UNLOCKED;
  if(huart->pRxBuffPtr < RxBuf[1])
  {
   Uart1RxCompleteCallback(RxBuf[0],&count);
   HAL_UART_Receive_DMA(&hUart1,RxBuf[1],RxBufSize);
  }
  else
  {
   Uart1RxCompleteCallback(RxBuf[1],&count);
   HAL_UART_Receive_DMA(&hUart1,RxBuf[0],RxBufSize);
  }
  hDmaUart1Rx.Lock = HAL_LOCKED;
 }
}

void DMA1_Stream0_IRQHandler(void)
{
 HAL_DMA_IRQHandler(&hDmaUart1Tx);
}

void DMA1_Stream1_IRQHandler(void)
{
 HAL_DMA_IRQHandler(&hDmaUart1Rx);
}

这个是Uart.h文件

#ifndef __Uart_H_
#define __Uart_H_

#include "stdint.h"

void Uart1Init(uint32_t BaudRate,void (*RxCompleteCallback)(uint8_t *pData,uint16_t *Count));
void Uart1TxData(uint8_t *pData,uint16_t Count);

#endif /* End __Uart_H_ */

这个是应用文件,实现了把收到的数据在发回去的功能。

#include "Uart1Task.h"
#include "limits.h"//ULONG_MAX
#include "Uart.h"
#include "string.h"
#include "SystemConfTask.h"

#define Uart1RxCompleteFlag  0x01

static uint8_t *Uart1RxData;
static uint16_t Uart1RxCount;
TaskHandle_t Uart1TaskHandle;

void Uart1RxIRQ(uint8_t *pData,uint16_t *Count);

TaskHandle_t *GetUart1TaskHandle(void)
{
 return &Uart1TaskHandle;
}

void Uart1Task(void *pvParameter)
{
 Uart1Init(115200,Uart1RxIRQ);
 while(1)
 {
  uint32_t NotifyValue = 0;
  xTaskNotifyWait(pdFALSE,ULONG_MAX,&NotifyValue,portMAX_DELAY);
  Uart1TxData(Uart1RxData,Uart1RxCount);
 }
}

void Uart1RxIRQ(uint8_t *pData,uint16_t *Count)
{
 Uart1RxData = pData;
 Uart1RxCount = *Count;
 BaseType_t pxHigherPriorityTaskWoken;
 xTaskNotifyFromISR(*GetUart1TaskHandle(),Uart1RxCompleteFlag,eSetBits,&pxHigherPriorityTaskWoken);
 portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
}

串口驱动里做了一个双缓冲的,防止大量数据出问题,有问题欢迎联系我。

<think>嗯,用户想了解STM32H7系列中如何用HAL实现串口DMA空闲中断,或者解决相关问题。首先,我得回忆一下STM32的UART和DMA的相关知识。HAL虽然封装了很多函数,但有时候配置起来还是容易出问题。 首先,空闲中断Idle Interrupt)的作用是在串口检测到总线空闲时触发中断,通常用于接收不定度的数据。结合DMA的话,可以自动接收数据,当空闲中断触发时,就知道一帧数据接收完了。这在处理如Modbus等协议时很有用。 那HAL中如何配置呢?可能需要先启用UART的空闲中断,然后配置DMA传输。步骤大概包括初始化UART,启用空闲中断,配置DMA接收,然后在中断处理函数中处理空闲事件。不过HAL本身可能没有直接处理空闲中断的函数,可能需要自己写回调或者覆盖某些处理函数。 接着,用户可能遇到哪些问题呢?比如空闲中断无法触发,DMA传输不停止,数据不完整,或者中断冲突。这时候需要检查中断是否使能,DMA配置是否正确,是否有清除中断标志位,或者优先级设置的问题。 然后想到参考内容中的优先级继承,虽然主要在内核层面,但可能在中断处理中也有优先级的问题需要注意。比如确保空闲中断的优先级设置合理,避免被其他中断阻塞。不过这部分可能更多是硬件配置的问题。 具体到实现步骤,可能需要先初始化UART,设置波特率等参数,然后使能空闲中断。接着配置DMA,设置内存地址、传输度等。在中断服务函数中,检测是否是空闲中断,然后处理数据,重置DMA传输。同时要注意,HAL中断处理可能需要用户自己添加空闲中断的处理,因为默认可能不包含。 可能还需要使用__HAL_UART_ENABLE_IT(&huart, UART_IT_IDLE)来使能空闲中断,然后在HAL_UART_IRQHandler中处理。或者使用回调函数,比如HAL_UARTEx_RxEventCallback,这个可能需要查看HAL的文档。 调试时,如果发现空闲中断没有触发,可能需要检查是否使能了全局中断,或者是否正确配置了NVIC。另外,DMA的配置是否正确,比如循环模式还是单次模式,传输完成中断是否影响等。 可能还需要注意缓存一致性问题,尤其是STM32H7有DCache,DMA传输的数据可能需要手动失效缓存,确保CPU读取的是最新数据。这点有时候容易被忽略,导致数据错误。 总结一下,步骤大概是配置UART和DMA,使能空闲中断,处理中断,处理数据,然后重置DMA。常见问题包括中断未使能、缓存问题、DMA配置错误等。需要仔细检查各个步骤的配置,并利用调试工具查看寄存器状态。</think>在STM32H7系列中,基于HAL实现串口DMA空闲中断需要结合空闲中断检测(IDLE)与DMA传输的特性。以下是具体实现步骤和注意事项: --- ### 一、实现步骤 1. **初始化UART与DMA配置** ```c UART_HandleTypeDef huart2; DMA_HandleTypeDef hdma_usart2_rx; // UART参数配置 huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart2); // 绑定DMA到UART接收 __HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx); hdma_usart2_rx.Instance = DMA1_Stream0; hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_CIRCULAR; // 循环模式或单次模式 HAL_DMA_Init(&hdma_usart2_rx); ``` 2. **启用空闲中断** ```c // 使能UART的空闲中断 __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); // 启动DMA接收 HAL_UART_Receive_DMA(&huart2, rx_buffer, BUFFER_SIZE); ``` 3. **重写中断处理函数** ```c void USART2_IRQHandler(void) { HAL_UART_IRQHandler(&huart2); // 调用HAL默认处理 // 检测空闲中断标志 if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart2); // 必须手动清除标志 // 自定义处理逻辑 uint16_t data_length = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx); process_received_data(rx_buffer, data_length); // 处理接收到的数据 HAL_UART_Receive_DMA(&huart2, rx_buffer, BUFFER_SIZE); // 重新启动DMA } } ``` --- ### 二、常见问题及解决方法 1. **空闲中断未触发** - 检查UART和DMA中断使能状态,确保`UART_IT_IDLE`已启用。 - 确认NVIC中UART全局中断已配置优先级并启用。 2. **DMA传输数据不完整** - 使用`DMA_CIRCULAR`模式时需注意缓冲区溢出问题。 - 对于STM32H7的缓存一致性,需在访问DMA接收缓冲区前调用`SCB_InvalidateDCache_by_Addr()`[^1]。 3. **中断冲突或优先级问题** - 参考优先级继承机制,合理配置UART和DMA中断的优先级,避免被高优先级任务阻塞[^1]。 --- ### 三、优化建议 1. 使用`HAL_UARTEx_RxEventCallback()`替代手动中断处理,简化代码结构。 2. 结合双缓冲区(Double Buffer)减少数据处理延迟。 ---
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值