STM32 HAL库使用笔记(四)DMA传输方式详解:内存到内存/内存到外设

目录

一、简介

1.DMA简介

2.一些概念

3.工作原理

二、HAL库的配置

1.时钟树的设置

2.DMA配置

2.1 内存到内存(代码对应3.1)

2.2 内存到外设(代码对应3.2)

三、代码编写


一、简介

1.DMA简介

    DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发。

2.一些概念

关于DMA的一些概念,个人认为比较基础,必须要知道,欢迎补充。

地址:DMA的传输过程中,必须要确定源地址和目的地址,在后面代码会有明显体现。

传输模式:内存到内存,内存到外设,外设到内存。本次实验主要验证前两个,原理在实验前会有说明,只做简单验证。

数据宽度:字节,半字,字(4字节)。一般数据传输都用一个字节宽度。

DMA工作模式:正常(Normal)/循环(Circular)。个人认为,除非数据的传输的时间是无法预测,比如串口接收,大部分用正常模式(传输一次后自动关闭)即可,下次需要传递数据时再启动DMA传输。

DMA流的优先级别:非常高/高/中等/低。跟DMA中断非常类似,但不要混淆,流的优先级别越高,会优先进行DMA传输。

    其余的概念会在需要补充的地方补充。

3.工作原理

    这张图将DMA的工作原理描述的非常清楚,不再赘述。补充一点,外设/内存的地址是否自增看实际情况,像第一个实验,内存到内存,两个内存块的大小大于一个数据宽度,显然需要自增;第二个实验中,将内存的数据通过串口发出,内存的数据块需要自增,而串口发送字寄存器是固定的,不需要自增,理解就好。

二、HAL库的配置

1.时钟树的设置

STM32 hal库使用笔记(一)GPIO的使用—流水灯_乱码小伙的博客-CSDN博客

参考这篇文章中的配置即可。

2.DMA配置
2.1 内存到内存(代码对应3.1)

    按照如图所示配置,本次实验通过按键来触发DMA传输并通过LED验证,所以选择正常模式。流的优先级随便,只有一个DMA传输。

配置完成后生成代码即可。

2.2 内存到外设(代码对应3.2)

   主要记录关于DMA的配置,串口相关参数默认即可。流的级别也随便,与2.1是两个实验,如果在同一个工程中可按照需要配置;模式是正常模式,本次实验通过按键触发DMA传输;外设非自增,寄存器自增模式,原因前边已经解释。

配置完成后,生成代码即可。

三、代码编写

3.1

用户主要完成的工作就是在每次DMA传输的时候开启DMA传输即可。

以下代码均在dam.c中编写

添加两个数组:

   uint8_t src_buf[10] = {0x0a, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
   uint8_t dest_buf[10] = {0};

void MX_DMA_Init(void) 
{

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

  /* Configure DMA request hdma_memtomem_dma1_channel1 on DMA1_Channel1 */
  hdma_memtomem_dma1_channel1.Instance = DMA1_Channel1;
  hdma_memtomem_dma1_channel1.Init.Direction = DMA_MEMORY_TO_MEMORY;
  hdma_memtomem_dma1_channel1.Init.PeriphInc = DMA_PINC_ENABLE; // 增量模式
  hdma_memtomem_dma1_channel1.Init.MemInc = DMA_MINC_ENABLE;//增量模式,指针自动向下,否则只会传输/收到第一个字节的数据
  hdma_memtomem_dma1_channel1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  hdma_memtomem_dma1_channel1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  hdma_memtomem_dma1_channel1.Init.Mode = DMA_NORMAL;
  hdma_memtomem_dma1_channel1.Init.Priority = DMA_PRIORITY_HIGH;
  if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)src_buf, (uint32_t)dest_buf, 0);//开启DMA传输,由于初始化不需要传输数据,所以数据传输个数是0

}
void dma_enable_transmit(uint16_t cndtr)//DMA传输完成后,传输个数会清零,使能后赋值并开启
{
    __HAL_DMA_DISABLE(&hdma_memtomem_dma1_channel1);
    
//    DMA1_Channel1->CNDTR = cndtr;
    hdma_memtomem_dma1_channel1.Instance->CNDTR = cndtr;
    
    __HAL_DMA_ENABLE(&hdma_memtomem_dma1_channel1);
}

以下在main.c中编写

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  /* USER CODE BEGIN 2 */
//    memset(dest_buf, 0, 10);
//    dma_enable_transmit(10);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
      
    if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
    {
        HAL_Delay(20);
          if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
       {
            src_buf[0]++;
            src_buf[1]++;
            memset(dest_buf, 0, 10);//初始化dest_buf
            dma_enable_transmit(10);// 由于内存到内存的传输无法循环,所以需要不断重装载传输的个数
           while(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0);

           if(dest_buf[0]==src_buf[0])
           {
           HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
           }
           if(dest_buf[1]==0x03)
           {
            HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
           }
           while (__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1)!=HAL_OK)//如果DMA传输完成,获取标志位,返回值是1
           {
              __HAL_DMA_CLEAR_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1);  //清除标志位
           }           
       }          
    }
  
      
      }
  /* USER CODE END 3 */
}

只做简单验证,按键每次按下,src_buf[0]++,src_buf[1]++,进行验证。

实现现象:

DMA_内存到内存

3.2

定义发送字符串:uint8_t TEXT_TO_SEND[] = {"正点原子 STM32 DMA 串口实验"};

主函数:

int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t TEXT_TO_SEND[] = {"正点原子 STM32 DMA 串口实验"};
  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
    {
      HAL_Delay(20);
      if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)==0)
      {
          
             HAL_UART_Transmit_DMA(&huart1, TEXT_TO_SEND, sizeof(TEXT_TO_SEND));//DMA的发送不是循环模式
              while ( __HAL_DMA_GET_FLAG(&hdma_usart1_tx, DMA_FLAG_TC4)!=HAL_OK)   /* 等待 DMA1_Channel4 传输完成 */
                {
                    __HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TC4);
                }
          while(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == 0);         
          HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
      }          
    }
  
      
      }
  /* USER CODE END 3 */
}

 HAL_UART_Transmit_DMA(&huart1, TEXT_TO_SEND, sizeof(TEXT_TO_SEND));已经完成了DMA启动的功能。

实现现象:

   关于串口发送和接收中使用DMA的实验过于简单,后期会再测试复杂些的实验,例如接收不定长字符串/发送不定长字符串。

欢迎大家交流和指正!!!

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库使用笔记(四)DMA传输方式详解:内存到内存/内存到外设

发表评论