DMA简单理解和分享

一、DMA基本概念

直接存储器访问(Direct Memory Access),简称DMA。DMA是CPU一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件,数据拷贝过程不需CPU干预,数据拷贝结束则通知CPU处理。因此,大量数据拷贝时,使用DMA可以释放CPU资源。DMA数据拷贝过程,典型的有:

  • 内存—>内存,内存间拷贝
  • 外设—>内存,如uart、spi、i2c等总线接收数据过程
  • 内存—>外设,如uart、spi、i2c等总线发送数据过程

二、DMA一些应用场景

1、ADC与DMA

(1)AD单次启动+软件启动+查询/中断方式

-------------------------------------------------------------------------
var = Get_Adc_Average(ADC_CHANNEL_0, 10); //放大倍数为0.635
 Sys.adc1 = var * 3300 / 4096 * 1.574 + 10;
 ----------------------------------------------------------------------------
 u16 Get_Adc(u32 ch) 
{
 ADC_ChannelConfTypeDef ADC1_ChanConf;
 ADC1_ChanConf.Channel=ch; 
 ADC1_ChanConf.Rank=1; 
 ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_480CYCLES; 
 ADC1_ChanConf.Offset=0; 
 HAL_ADC_ConfigChannel(&hadc1,&ADC1_ChanConf); 
 HAL_ADC_Start(&hadc1); 
 HAL_ADC_PollForConversion(&hadc1,10); 
return (u16)HAL_ADC_GetValue(&hadc1); 
}
u16 Get_Adc_Average(u32 ch,u8 times)
{
  u32 temp_val=0;
  u8 t;
  for(t=0;t<times;t++)
  {
  temp_val+=Get_Adc(ch);
  osDelay(5);
  }
  return temp_val/times;
}

(2)连续转换+DMA+手动启动/定时器启动

在方法1里面,每次转换完成,需要我们手动去读一下AD值;启动DMA之后,完全省掉了这个过程,只需要等待设定好的值全部转换完成之后触发一个中断,再进行数据处理。

/**
 * @brief adc01的配置 规则通道并行   扫描和连续转换模式 
 */
static void bsp_adc01_cfg(void)
{    
    /* enable ADC0 and ADC1 clock */
    rcu_periph_clock_enable(RCU_ADC0);
    rcu_periph_clock_enable(RCU_ADC1);
    /* config ADC clock */
    rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV16);
    
    /* ADC continous function enable */
    adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
    adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); 
    adc_special_function_config(ADC1, ADC_SCAN_MODE, ENABLE);
    adc_special_function_config(ADC1, ADC_CONTINUOUS_MODE, ENABLE); 
    
    /* ADC trigger config */
    adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_EXTTRIG_REGULAR_NONE);//ADC0_1_EXTTRIG_REGULAR_NONE
    adc_external_trigger_source_config(ADC1, ADC_REGULAR_CHANNEL, ADC0_1_EXTTRIG_REGULAR_NONE);//ADC0_1_EXTTRIG_INSERTED_NONE     
        
    /* ADC data alignment config */
    adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
    adc_data_alignment_config(ADC1, ADC_DATAALIGN_RIGHT);
    
    /* ADC mode config,使用规则同步模式,ADC0是主,ADC1是从, 同时转换两个通道(同时转换的通道不能相同) */
    adc_mode_config(ADC_DAUL_REGULAL_PARALLEL); 
    
    /* ADC分辨率 12B */
    adc_resolution_config(ADC0,ADC_RESOLUTION_12B);
    adc_resolution_config(ADC1,ADC_RESOLUTION_12B);

    /* ADC channel length config */
    adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 6);
    adc_channel_length_config(ADC1, ADC_REGULAR_CHANNEL, 0);    
    
    /* ADC regular channel config,一个通道转换时长是2.06us */
    adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_6, ADC_SAMPLETIME_239POINT5);
    adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_7, ADC_SAMPLETIME_239POINT5);
    adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_14, ADC_SAMPLETIME_239POINT5);
    //adc_regular_channel_config(ADC0, 3, ADC_CHANNEL_7, ADC_SAMPLETIME_55POINT5);
    //adc_regular_channel_config(ADC0, 4, ADC_CHANNEL_8, ADC_SAMPLETIME_55POINT5);
        
    adc_regular_channel_config(ADC0, 3, ADC_CHANNEL_15, ADC_SAMPLETIME_239POINT5);    
    adc_regular_channel_config(ADC0, 4, ADC_CHANNEL_8, ADC_SAMPLETIME_239POINT5);
    adc_regular_channel_config(ADC0, 5, ADC_CHANNEL_9, ADC_SAMPLETIME_239POINT5);
   // adc_regular_channel_config(ADC1, 3, ADC_CHANNEL_15, ADC_SAMPLETIME_55POINT5);
    //adc_regular_channel_config(ADC1, 4, ADC_CHANNEL_5, ADC_SAMPLETIME_55POINT5);
    
    /* ADC external trigger enable */
    adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);    
    adc_external_trigger_config(ADC1, ADC_REGULAR_CHANNEL, ENABLE);    

    /* enable ADC0 interface */
    adc_enable(ADC0);      
    //delay_ms(0xFFFF);
    /* ADC0 calibration and reset calibration */
    adc_calibration_enable(ADC0);
    
    /* enable ADC1 interface */
    adc_enable(ADC1);      
    //delay_ms(0xFFFF);
    /* ADC1 calibration and reset calibration */
    adc_calibration_enable(ADC1);  /* ADC校准复位 */  
    
    /* ADC DMA function enable */
    adc_dma_mode_enable(ADC0);        
    
    /* ADC0 software trigger enable */
     adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
      adc_software_trigger_enable(ADC1, ADC_REGULAR_CHANNEL);
}

uint16_t adc01_fifo[100];
/**
  * @brief ADC01 DMA配置
  * @retval none
  * @author Mr.W
  * @date 2020-11-10
  */
void bsp_adc01_dma_cfg(void)
{
    /* ADC_DMA_channel configuration */
    dma_parameter_struct dma_data_parameter;
    /* enable DMA0 clock */
    rcu_periph_clock_enable(RCU_DMA0); 
    /* ADC DMA_channel configuration */
    dma_deinit(DMA0, DMA_CH0);
   
    /* initialize DMA data mode */
    dma_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
    dma_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_data_parameter.memory_addr = (uint32_t)(g_sys.adcs.fifo);
    dma_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
    dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;  
    dma_data_parameter.direction = DMA_PERIPHERAL_TO_MEMORY;
    dma_data_parameter.number = ADCFILTER_NUM*ADCCHANNEL_NUM*2;    /* 搬运的数据个数,完成后会触发中断*/
    dma_data_parameter.priority = DMA_PRIORITY_HIGH;
    dma_init(DMA0, DMA_CH0, &dma_data_parameter);
    dma_circulation_enable(DMA0, DMA_CH0);
    //设置dma中断优先级
    nvic_irq_enable(DMA0_Channel0_IRQn, 0, 0);
    /* enable DMA transfer complete interrupt */
    dma_interrupt_enable(DMA0,DMA_CH0, DMA_INT_HTF|DMA_INT_FTF);//半传输/全传输中断都开启
    /* enable DMA channel */
    dma_channel_enable(DMA0, DMA_CH0);
}

void DMA0_Channel0_IRQHandler(void)
{
    if(dma_interrupt_flag_get(DMA0,DMA_CH0, DMA_INT_HTF))//半传输完成
    {    
        g_sys.adcs.dma_half1=1; 
    } 
    if(dma_interrupt_flag_get(DMA0,DMA_CH0, DMA_INT_FTF))//全部传输完成
    {
        g_sys.adcs.dma_half2=1;        
    } 
    //清除全部标志位
    dma_interrupt_flag_clear(DMA0,DMA_CH0, DMA_INT_FLAG_G);
    rt_sem_release(&sem_adc_dma); /* 释放信号量 */
}

void calcaulate_anlog(void)
{
   uint32_t sum_data[ADCCHANNEL_NUM] = {0};
   uint16_t i = 0;
   uint16_t j = 0;
   // int16_t var1=0;
   static uint8_t cnt_tsk = 0;
   static uint8_t cnt_cluth = 0;
   static uint16_t cnt2_cluth = 0;
   static int16_t judge_cluth_data_before = 0;
   static int16_t judge_cluth_data_now = 0;
   if (g_sys.adcs.dma_half1 == 1)
   {
      for (i = 0; i < ADCFILTER_NUM; i++)
      {
         for (j = 0; j < ADCCHANNEL_NUM; j++)
            sum_data[j] += g_sys.adcs.fifo[j + i * 6];
      }
      for (j = 0; j < ADCCHANNEL_NUM; j++)
      {
         g_sys.adcs.average_adc[j] = sum_data[j] / ADCFILTER_NUM;
         sum_data[j] = 0;
      }
   }
   if (g_sys.adcs.dma_half2 == 1) // half2数据暂时舍弃
   {
      // for(i=0;i<ADCFILTER_NUM;i++)
      //   {
      //       for(j=0;j<ADCCHANNEL_NUM;j++)
      //       sum_data[j]+=g_sys.adcs.fifo[j+i*6+ADCCHANNEL_NUM*ADCFILTER_NUM];
      //   }
      //   for(j=0;j<ADCCHANNEL_NUM;j++)
      //   {
      //     g_sys.adcs.average_adc[j]= sum_data[j]/ADCFILTER_NUM;
      //     sum_data[j]=0;
      //   }
   }
      }
}

2、串口与DMA

串口(uart)是一种低速的串行异步通信,适用于低速通信场景,通常使用的波特率小于或等于115200bps。对于小于或者等于115200bps波特率的,而且数据量不大的通信场景,一般没必要使用DMA,或者说使用DMA并未能充分发挥出DMA的作用。
对于数量大,或者波特率提高时,必须使用DMA以释放CPU资源,因为高波特率可能带来这样的问题:

  • 对于发送,使用循环发送,可能阻塞线程,需要消耗大量CPU资源“搬运”数据,浪费CPU
  • 对于发送,使用中断发送,不会阻塞线程,但需浪费大量中断资源,CPU频繁响应中断;以115200bps波特率,1s传输11520字节,大约69us需响应一次中断,如波特率再提高,将消耗更多CPU资源
  • 对于接收,如仍采用传统的中断模式接收,同样会因为频繁中断导致消耗大量CPU资源
    如下为某项目的环形DMA传输;
for(;;)
  {  
      xResult = xSemaphoreTake(g_xSemCntUart1_rxHandle, portMAX_DELAY) ;     // 任务代码中获取二值信号量  成功
      if(xResult == pdTRUE)
      {
          taskENTER_CRITICAL() ;              
          memset((uint8_t *)g_uart1_temp_buf,0,U1_BUFSIZE);  
          printf("串口1的接收数据位置 :  %d  %d\r\n",g_uart1_recv.tail, g_uart1_recv.head);                 
          size= ring_used_buf_get(&g_uart1_recv,(uint8_t *)g_uart1_temp_buf);             
          taskEXIT_CRITICAL() ; 
          //printf("串口1接收数据为 : %s\r\n",g_uart1_temp_buf);                       
          uart_send_data(&huart3,(uint8_t *)g_uart1_temp_buf,size);          
      } 
      else
      {
           Error_Handler();
      }       
  }
-----------------------------------------------------------------------
/**
串口1相关DMA部分初始化代码
**/
void init_related_usart1(void)
{
    /* USART1 DMA Init */
    /* USART1_RX Init */
    hdma_usart1_rx.Instance = DMA2_Stream2;
    hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
    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_CIRCULAR;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
    {
      Error_Handler();
    }
    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
    /* USART1_TX Init */
    hdma_usart1_tx.Instance = DMA2_Stream7;
    hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4;
    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;
    hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
    {
      Error_Handler();
    }
 __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
 
   #if UART1_RECV_EN == 1
    g_uart1_recv.rxbuf = (uint8_t *)g_uart1_dma_rxbuf;            /* STM32 串口设备 */
    g_uart1_recv.totalleng =U1_RXSIZE;
    g_uart1_recv.head =0;
    g_uart1_recv.tail =0;
    g_uart1_recv.used =0;
    HAL_UART_Receive_DMA(&huart1, g_uart1_recv.rxbuf , U1_RXSIZE) ;
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); 
    __HAL_UART_CLEAR_IDLEFLAG(&huart1) ;   //清除空闲标志 
  #endif
 }
 
 --------------------------------------------------------
void USART1_IRQHandler(void)
{
  usart_recv_idle(&huart1, &g_uart1_recv,&g_xSemCntUart1_rxHandle) ;
  HAL_UART_IRQHandler(&huart1);
}
/** 
  *********************************************************************************************************
  * 函数功能:   串口1 接收   环形DMA+空闲中断 
  *********************************************************************************************************
  */
 typedef struct
{
  uint8_t *rxbuf; 
  uint16_t totalleng;      //  数据总长
  uint16_t head;      //  这次接收数据 队列头
  uint16_t tail;      //  这次接收数据 队列尾
  uint16_t used;      //   接收数据 解析标志     // 
//  UART_STATUSE    statuse;
}USART_RECV;
void usart_recv_idle(UART_HandleTypeDef *huart,USART_RECV *pUart,  osSemaphoreId_t *pSem)
{
   BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  
   BaseType_t xSaveIntStatus ;
  
  if((__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET))
  {
    __HAL_UART_CLEAR_IDLEFLAG(huart) ;   //清除空闲标志 
     
      
    xSaveIntStatus = taskENTER_CRITICAL_FROM_ISR() ;      /* 进入中断服务程序里面临界区 */
    
     pUart->tail=pUart->head; 
    
     pUart->head=pUart->totalleng - __HAL_DMA_GET_COUNTER(huart->hdmarx);   
    //printf("si is %d\r\n",( pUart->head));
     taskEXIT_CRITICAL_FROM_ISR(xSaveIntStatus) ;     /* 退出中断服务程序里面临界区 */   
    
      xSemaphoreGiveFromISR(*pSem, &xHigherPriorityTaskWoken) ;   //用于中断服务程序中释放 二值 信号量。
      
    if(xHigherPriorityTaskWoken == pdTRUE)     /* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
    {
      portYIELD_FROM_ISR(xHigherPriorityTaskWoken) ;
    }
  }
}
/**  *********************************************************************************************************
  * 函数功能:   从环形缓冲区中  从used到 head 长度的数据 
  *********************************************************************************************************
  */
uint16_t ring_used_buf_get(USART_RECV *pUart,uint8_t *buf)
{
    uint16_t lenght=0 ;
    while(pUart->used!=pUart->head)
     {         
         *buf++= pUart->rxbuf[pUart->used++];
         lenght++;
         if(pUart->used>=pUart->totalleng)  
             pUart->used= 0;
    }
    return  lenght ;
}

/** 
  *********************************************************************************************************
  * 函数功能:   从环形缓冲区中  从tail到 head 长度的数据 
  *********************************************************************************************************
  */
uint16_t ring_tail_buf_get( USART_RECV *pUart,uint8_t *buf)
{
    uint16_t lenght=0 ;
    while(pUart->tail!=pUart->head)
     {         
         *buf++= pUart->rxbuf[pUart->tail++];
          lenght++;
         if(pUart->tail>=pUart->totalleng)  
             pUart->tail= 0;
    }
    return  lenght ;
}

3、其他

  • 如在一些大量数据需要多个算法处理,如摄像头数据需要分别进行二值化处理、几何变换、色度变换,则必须使用到DMA进行数据搬运来提升效率;
  • 再如越来越多的多核的处理器,xilinx ZYNQ 7000: Cortex-A9 + fpga;Ti TMS320C6678:8核;STM32MP1/iMX7 : Cortex-A7 和 Cortex-M4 等其核心间通信除了共享的部分空间和外设,大部分的数据量都是要靠DMA进行传送来达到协程。
  • DMA与网口。
    HAL_ETH_DMATxDescListInit();
    HAL_ETH_DMARxDescListInit();
  • DMA与SPI、SDIO
    如下图为stm32f4的DMA请求映射

在这里插入图片描述

三、DMA几个相关误区

1、DMA与cahe一致性问题

五- 高性能的微控制器会在主存储器和 CPU 之间增加高速缓冲存储器(Cache),目的是提高对存储器的平均访问速度,从而提高存储系统的性能。

  • CPU在访问内存时,首先判断所要访问的内容是否在Cache中,如果在,就称为“命中(hit)”,此时CPU直接从Cache中调用该内容;否则,就 称为“ 不命中”,CPU只好去内存中调用所需的子程序或指令了。CPU不但可以直接从Cache中读出内容,也可以直接往其中写入内容。由于Cache的存取速 率相当快,使得CPU的利用率大大提高,进而使整个系统的性能得以提升。
  • Cache的一致性就是Cache中的数据,与对应的内存中的数据是一致的。 DMA是直接操作总线地址的,这里先当作物理地址来看待吧(系统总线地址和物理地址只是观察内存的角度不同)。如果cache缓存的内存区域不包括DMA分配到的区域,那么就没有一致性的问题。但是如果cache缓存包括了DMA目的地址的话,会出现什么什么问题呢?问题出在,经过DMA操作,cache缓存对应的内存数据已经被修改了,而CPU本身不知道(DMA传输是不通过CPU的),它仍然认为cache中的数 据就是内存中的数据,以后访问Cache映射的内存时,它仍然使用旧的Cache数据。这样就发生Cache与内存的数据“不一致性”错误。
  • 一般而言要么禁用此项功能,要么在使用过程中注意该段空间是否存在多个host的操作,如有,则实时需要刷新cahe 和memory之间的交互。

2、DMA与CCM内存单元

相较于F2,F4新加的一个特殊内部SRAM。64 KB CCM (内核耦合存储器)数据 RAM 不属于总线矩阵请参见图 1 : STM32F405xx/07xx和 STM32F415xx/17xx 器件的系统架构)。
在这里插入图片描述
因此,如果DMA的源地址或者目标地址分配到的是CCM部分的空间,则无法完成搬运。解决方式是要么禁用该段空间,要么修改分散加载sct文件

  • 20
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值