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

[复制链接]
 楼主| 逢dududu必shu 发表于 2021-12-30 22:26 | 显示全部楼层 |阅读模式
AD, DM, IDL, ST
接着上一篇关于STM32H7串口收发问题,继续说,上一篇里边提供了中断接收方式,最大的缺点就是中断过于频繁,为了解决这个问题那就把DMA搬过来,它不就是专门搬用数据的嘛,不用多可惜。

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

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

评论

dma很占优势  发表于 2021-12-31 07:28
 楼主| 逢dududu必shu 发表于 2021-12-30 22:27 | 显示全部楼层
还是按照上一篇形式大概看一下,HAL库中DMA是怎么和串口配合的。

  1. /**
  2.   * [url=home.php?mod=space&uid=247401]@brief[/url] Receive an amount of data in DMA mode.
  3.   * [url=home.php?mod=space&uid=536309]@NOTE[/url]   When the UART parity is enabled (PCE = 1), the received data contain
  4.   *         the parity bit (MSB position).
  5.   * @param huart UART handle.
  6.   * @param pData Pointer to data buffer.
  7.   * @param Size  Amount of data to be received.
  8.   * @retval HAL status
  9.   */
  10. HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
  11. {
  12.   /* 其他关系不大的代码省略 有兴趣可以自己看 下同 */
  13.   //1.检查参数 判断串口接收状态为就绪 把缓存区参数传递到串口句柄  修改某些状态
  14.   //2.判断和串口句柄关联的DMA句柄地址不为空  然后给DMA句柄注册一些回调函数
  15.       /* Enable the DMA channel */
  16.       if (HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->RDR, (uint32_t)huart->pRxBuffPtr, Size) != HAL_OK)
  17.     {
  18.       ......
  19.     }
  20. }
 楼主| 逢dududu必shu 发表于 2021-12-30 22:31 | 显示全部楼层
HAL_UART_Receive_DMA这个函数里最主要的就是调用了HAL_DMA_Start_IT这个函数,你看他连参数都没怎么变,就把句柄换了,其他三个原封不动的传递过去了。所以函数内其他内容几乎不用考虑了。直接往下看这个函数。
 楼主| 逢dududu必shu 发表于 2021-12-30 22:31 | 显示全部楼层
  1. /**
  2.   * @brief  Start the DMA Transfer with interrupt enabled.
  3.   * @param  hdma:       pointer to a DMA_HandleTypeDef structure that contains
  4.   *                     the configuration information for the specified DMA Stream.
  5.   * @param  SrcAddress: The source memory Buffer address
  6.   * @param  DstAddress: The destination memory Buffer address
  7.   * @param  DataLength: The length of data to be transferred from source to destination
  8.   * @retval HAL status
  9.   */
  10. HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
  11. {
  12.   //1.检查参数 给句柄上锁 判断状态 只有在就绪状态下才允许配置
  13.   //2.判断就绪状态
  14.   {
  15.     ......
  16.     /* Configure the source, destination address and the data length */
  17.     DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
  18.     ......
  19.     //这里会开启一堆中断  完事要用  暂时可以不关心
  20.   }
  21. }
 楼主| 逢dududu必shu 发表于 2021-12-30 22:33 | 显示全部楼层
上面这个函数也差不多还是那个意思,都懒的看了,主要是调用了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 */
  }  
}
 楼主| 逢dududu必shu 发表于 2021-12-30 22:34 | 显示全部楼层
各位同志你们自己看吧这个函数最主要的目的就是配置DMA寄存器。以上就是串口DMA接收函数中最重要的部分。好像没什么意义一样,因为我要用到其中的一点点东西,所以必须要分析源码。

我们只是说了调用接收函数,参数里边指定了接收缓存,也指定了大小,似乎和我们的目标不符合,假设我们的DMA和串口之间的配置已经配置好了,那现在能做到的就是把数据收集到这个缓冲区里,可是并不能告诉主程序接收到了数据,和接收了多少,想要知道这个,有两种方法,一种是CPU定时去问DMA接收了多少数据,然后自己定义一个记录变化的变量,配合,另一种是中断方式,我们通过查询得知,DMA只有接收完成和接收半完成两个中断。
 楼主| 逢dududu必shu 发表于 2021-12-30 22:36 | 显示全部楼层
最最主要的串口的空闲中断不要忘了呀,这个可以告诉你,一帧数据接收完了,你可以根据这个中断去搞,接收了多少,在什么地方。

因为这个编辑器不知道咋回事,贴代码总是挂,上边那个函数就贴了我很长时间,很生气,所以我就无耻的把源码整到这里了,整个工程哦(暂时还没有上传)。
 楼主| 逢dududu必shu 发表于 2021-12-30 22:42 | 显示全部楼层
再试试贴代码吧,方便你我他,要积分的都无耻。改天

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

  1. #include "Uart.h"
  2. #include "stm32h7xx_hal.h"

  3. #define RxBufSize   1024

  4. UART_HandleTypeDef hUart1 = {0};
  5. DMA_HandleTypeDef hDmaUart1Tx = {0};
  6. DMA_HandleTypeDef hDmaUart1Rx = {0};

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

  10. void Uart1Init(uint32_t BaudRate,void (*RxCompleteCallback)(uint8_t *pData,uint16_t *Count))
  11. {
  12. hUart1.Instance = USART1;
  13. hUart1.Init.BaudRate = BaudRate;
  14. hUart1.Init.ClockPrescaler = UART_PRESCALER_DIV2;
  15. hUart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  16. hUart1.Init.Mode = UART_MODE_TX_RX;
  17. hUart1.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED;
  18. hUart1.Init.OverSampling = UART_OVERSAMPLING_8;
  19. hUart1.Init.Parity = UART_PARITY_NONE;
  20. hUart1.Init.StopBits = UART_STOPBITS_1;
  21. hUart1.Init.WordLength = UART_WORDLENGTH_8B;
  22. hUart1.FifoMode = UART_FIFOMODE_DISABLE;

  23. HAL_UART_Init(&hUart1);

  24. __HAL_RCC_DMA1_CLK_ENABLE();

  25. hDmaUart1Tx.Instance = DMA1_Stream0;
  26. hDmaUart1Tx.Init.Request = DMA_REQUEST_USART1_TX;
  27. hDmaUart1Tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
  28. hDmaUart1Tx.Init.PeriphInc = DMA_PINC_DISABLE;
  29. hDmaUart1Tx.Init.MemInc = DMA_MINC_ENABLE;
  30. hDmaUart1Tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  31. hDmaUart1Tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  32. hDmaUart1Tx.Init.Mode = DMA_NORMAL;
  33. hDmaUart1Tx.Init.Priority = DMA_PRIORITY_MEDIUM;

  34. HAL_DMA_Init(&hDmaUart1Tx);
  35. __HAL_LINKDMA(&hUart1,hdmatx,hDmaUart1Tx);

  36. hDmaUart1Rx.Instance = DMA1_Stream1;
  37. hDmaUart1Rx.Init.Request = DMA_REQUEST_USART1_RX;
  38. hDmaUart1Rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  39. hDmaUart1Rx.Init.PeriphInc = DMA_PINC_DISABLE;
  40. hDmaUart1Rx.Init.MemInc = DMA_MINC_ENABLE;
  41. hDmaUart1Rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  42. hDmaUart1Rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  43. hDmaUart1Rx.Init.Mode = DMA_NORMAL;
  44. hDmaUart1Rx.Init.Priority = DMA_PRIORITY_MEDIUM;

  45. HAL_DMA_Init(&hDmaUart1Rx);
  46. __HAL_LINKDMA(&hUart1,hdmarx,hDmaUart1Rx);

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

  48. __HAL_UART_ENABLE_IT(&hUart1,UART_IT_IDLE);
  49. HAL_NVIC_EnableIRQ(USART1_IRQn);
  50. HAL_NVIC_SetPriority(USART1_IRQn,14,0);

  51. HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);//Tx
  52. HAL_NVIC_SetPriority(DMA1_Stream0_IRQn,14,0);

  53. HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);//Rx
  54. HAL_NVIC_SetPriority(DMA1_Stream1_IRQn,14,0);

  55. Uart1RxCompleteCallback = RxCompleteCallback;
  56. }

  57. void HAL_UART_MspInit(UART_HandleTypeDef *huart)
  58. {
  59. if(huart == &hUart1)//串口1
  60. {
  61.   GPIO_InitTypeDef GPIO_InitStruct;

  62.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  63.   GPIO_InitStruct.Pull = GPIO_NOPULL;
  64.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
  65.   GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
  66.   GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_9;

  67.   __HAL_RCC_GPIOA_CLK_ENABLE();
  68.   __HAL_RCC_USART1_CLK_ENABLE();

  69.   HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
  70. }
  71. }

  72. void Uart1TxData(uint8_t *pData,uint16_t Count)
  73. {
  74. if(Count)
  75.   HAL_UART_Transmit_DMA(&hUart1,pData,Count);
  76. }

  77. void USART1_IRQHandler(void)
  78. {
  79. if(__HAL_UART_GET_FLAG(&hUart1,UART_FLAG_IDLE))
  80. {
  81.   static uint16_t count;
  82.   __HAL_UART_CLEAR_IDLEFLAG(&hUart1);
  83.   if(Uart1RxCompleteCallback)
  84.   {
  85.    hUart1.RxState = HAL_UART_STATE_READY;
  86.    hDmaUart1Rx.State = HAL_DMA_STATE_READY;
  87.    HAL_UART_RxCpltCallback(&hUart1);
  88.   }
  89. }
  90. else
  91.   HAL_UART_IRQHandler(&hUart1);
  92. }

  93. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  94. {
  95. if(huart == &hUart1)
  96. {
  97.   static uint16_t count;
  98.   count = RxBufSize - __HAL_DMA_GET_COUNTER(&hDmaUart1Rx);
  99.   if(count == 0)return;
  100.   hDmaUart1Rx.Lock = HAL_UNLOCKED;
  101.   if(huart->pRxBuffPtr < RxBuf[1])
  102.   {
  103.    Uart1RxCompleteCallback(RxBuf[0],&count);
  104.    HAL_UART_Receive_DMA(&hUart1,RxBuf[1],RxBufSize);
  105.   }
  106.   else
  107.   {
  108.    Uart1RxCompleteCallback(RxBuf[1],&count);
  109.    HAL_UART_Receive_DMA(&hUart1,RxBuf[0],RxBufSize);
  110.   }
  111.   hDmaUart1Rx.Lock = HAL_LOCKED;
  112. }
  113. }

  114. void DMA1_Stream0_IRQHandler(void)
  115. {
  116. HAL_DMA_IRQHandler(&hDmaUart1Tx);
  117. }

  118. void DMA1_Stream1_IRQHandler(void)
  119. {
  120. HAL_DMA_IRQHandler(&hDmaUart1Rx);
  121. }
 楼主| 逢dududu必shu 发表于 2021-12-30 22:43 | 显示全部楼层
这个是Uart.h文件

  1. #ifndef __Uart_H_
  2. #define __Uart_H_

  3. #include "stdint.h"

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

  6. #endif /* End __Uart_H_ */
 楼主| 逢dududu必shu 发表于 2021-12-30 22:48 | 显示全部楼层
这个是应用文件,实现了把收到的数据在发回去的功能。

  1. #include "Uart1Task.h"
  2. #include "limits.h"//ULONG_MAX
  3. #include "Uart.h"
  4. #include "string.h"
  5. #include "SystemConfTask.h"

  6. #define Uart1RxCompleteFlag  0x01

  7. static uint8_t *Uart1RxData;
  8. static uint16_t Uart1RxCount;
  9. TaskHandle_t Uart1TaskHandle;

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

  11. TaskHandle_t *GetUart1TaskHandle(void)
  12. {
  13. return &Uart1TaskHandle;
  14. }

  15. void Uart1Task(void *pvParameter)
  16. {
  17. Uart1Init(115200,Uart1RxIRQ);
  18. while(1)
  19. {
  20.   uint32_t NotifyValue = 0;
  21.   xTaskNotifyWait(pdFALSE,ULONG_MAX,&NotifyValue,portMAX_DELAY);
  22.   Uart1TxData(Uart1RxData,Uart1RxCount);
  23. }
  24. }

  25. void Uart1RxIRQ(uint8_t *pData,uint16_t *Count)
  26. {
  27. Uart1RxData = pData;
  28. Uart1RxCount = *Count;
  29. BaseType_t pxHigherPriorityTaskWoken;
  30. xTaskNotifyFromISR(*GetUart1TaskHandle(),Uart1RxCompleteFlag,eSetBits,&pxHigherPriorityTaskWoken);
  31. portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
  32. }
 楼主| 逢dududu必shu 发表于 2021-12-30 22:49 | 显示全部楼层
串口驱动里做了一个双缓冲的,防止大量数据出问题,有问题欢迎联系我。
 楼主| 逢dududu必shu 发表于 2021-12-30 22:49 | 显示全部楼层

————————————————
版权声明:本文为CSDN博主「wait_for_STM32」的原创**,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wait_for_STM32/article/details/109245683
caoenq 发表于 2021-12-31 09:00 | 显示全部楼层
逢dududu必shu 发表于 2021-12-30 22:48
这个是应用文件,实现了把收到的数据在发回去的功能。

进入空闲中断ISR后,清完ilde flag后,应该立即停掉DMA,否者容易出错。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

69

主题

493

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部

69

主题

493

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部