一、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.635Sys.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_NONEadc_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 == 1g_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文件

DMA简单理解和分享相关推荐

  1. android 点击事件消费,Android View事件分发和消费源码简单理解

    Android View事件分发和消费源码简单理解 前言: 开发过程中觉得View事件这块是特别烧脑的,看了好久,才自认为看明白.中间上网查了下singwhatiwanna粉丝的读书笔记,有种茅塞顿开 ...

  2. git的简单理解及基础操作命令

    前端小白一枚,最近开始使用git,于是花了2天看了廖雪峰的git教程(偏实践,对于学习git的基础操作很有帮助哦),也在看<git版本控制管理>这本书(偏理论,内容完善,很不错),针对所学 ...

  3. php _call call_user_func_array,PHP call_user_func和call_user_func_array函数的简单理解与应用分析...

    本文实例讲述了PHP call_user_func和call_user_func_array函数的简单理解与应用.分享给大家供大家参考,具体如下: call_user_func():调用一个回调函数处 ...

  4. 一次网络世界的旅行-简单理解网络通信

    一次网络世界的旅行 前言 简单理解网络通信 网络通信 mac地址 IP地址和子网掩码和网关 DHCP服务器 DNS服务器 前言 简要概述网络通信的简单原理,新手向,分享一下自己的理解 简单理解网络通信 ...

  5. Introduction To AMBA 简单理解

    文章目录 前言 简介 AMBA 的演进 AMBA 演进图解 AMBA specifications AMBA1.0 1.1 Introduction to AMBA 1.2 AMBA Specific ...

  6. route map: 转发一个博客,附上自己的简单理解

    最近在做实验需要用到route map,搜到了一个博客感觉挺不错,分享一下: https://blog.csdn.net/ZhangPengFeiToWinner/article/details/85 ...

  7. Spring Security并没有那么难嗷 简单理解OAuth2.0

    文章目录 1. 基本概念 1.1 什么是认证 1.2 什么是会话 1.3 什么是授权 1.4 授权的数据模型 1.5 RBAC 1.5.1 基于角色的访问控制 1.5.2 基于资源的访问控制 2. 基 ...

  8. 【转载】Deep learning:十九(RBM简单理解)

    Deep learning:十九(RBM简单理解) 这篇博客主要用来简单介绍下RBM网络,因为deep learning中的一个重要网络结构DBN就可以由RBM网络叠加而成,所以对RBM的理解有利于我 ...

  9. 学习:双机热备、集群、负载均衡、SQL故障转移群集简单理解(转)

    双机热备.集群.负载均衡.SQL故障转移群集简单理解平常,大家常提到几个技术名词:双机热备.集群.负载均衡.SQL故障转移群集.这里,就我的理解,和大家简单探讨下,有不足或错误之处还请各位指出! 这些 ...

最新文章

  1. php高版本不再使用mysql_connect()来连接数据库
  2. mac攻略(1) -- 简单配置php开发环境
  3. Ubuntu常用服务器ftp、ssh
  4. 选定内容没有属性页_宣传单页的效果和作用
  5. android auto note 8,三星Galaxy Note 8.0支持flash吗
  6. 数据库安装时的log路径
  7. 10投屏后没有声音_10年后,学区房有没有可能成为“负资产”?这位专家说了实话...
  8. ppt设置外观样式_ppt如何设置幻灯片的样式
  9. android 仿饿了么地图,微信小程序仿饿了么地址定位、筛选与回传
  10. 四相五线步进电机定时器驱动
  11. ubuntu安装pytorch3d
  12. 共筑未来 | 思腾合力加入光合组织,完成国产信创兼容适配
  13. 从数据治理到数据应用,制造业企业如何突破数字化转型困境丨行业方案
  14. 转行程序员日记---2020-09-18【,勿忘国耻】【回忆青春】
  15. 201771010137赵栋《第九周学习总结》
  16. linux 列转行函数,GP行转列、列转行函数
  17. LINUX基础 第四次课 10月15日
  18. OpenCL学习笔记(三):OpenCL安装,编程简介与helloworld
  19. [日推荐]『健康管理』为你的健康保驾护航
  20. 干货 | 10分钟给上万客服排好班,携程大规模客服排班算法实践

热门文章

  1. 字节与字,以及字和字长的关系
  2. GRE作文——看门见山直述观点
  3. 学习英文,从日常生活开始
  4. 阿里云易立:以增效促降本,容器服务全面进入智能化时代
  5. 性能优化之@Contended减少伪共享
  6. JDK之伪分享的情况下该使用填充还是@Contended
  7. vscode设置python版本_如何在vscode使用指定版本的python_
  8. 400元左右的蓝牙耳机啥牌子好?400元价位蓝牙耳机推荐
  9. windows下ITOP安装
  10. LabVIEW编程基础:事件结构框架编程