一、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. TF之DCGAN:基于TF利用DCGAN测试自己的数据集并进行生成过程全记录
  2. eclipse 搭建Android 开发环境(ADT安装和sdk下载,选择)
  3. 看风水用什么罗盘最好_兰花用什么花盆栽植最好?
  4. URLshorting网址短链接PHP源码 开源源码
  5. 60-100-032-使用-MySQL大小写敏感的解决方法
  6. db2 控制台执行创建函数语句_DB2 从命令行创建SQL存储过程
  7. C语言x86汇编指令理解volatile(三十五)
  8. 前景检测算法(一)--综述
  9. Sequelize-nodejs-5-Querying
  10. Google Earth Studio黑科技,教你一键制作震撼的地球俯冲航拍等视频素材
  11. 【分享】VISIO 2003下载
  12. 聊一聊我在腾讯的外包同事
  13. bulma css 中文,Bulma CSS – 开始
  14. 人到中年,没事多休息,有空多赚钱!
  15. Peter Norvig:十年学会编程
  16. 算法设计 - 01背包问题
  17. js 中有endswith_函数 ENDSWITH()函数 - 闪电教程JSRUN
  18. Python的replace()方法
  19. iOS开发:字符串处理:截取字符串、匹配字符串、分割字符串
  20. thinkpad装linux无线网卡驱动,ThinkPad E530 Fedora 20 下无线网卡驱动的安装

热门文章

  1. docear使用教程_如何使用Docear进行学术研究
  2. 命令行解决svn版本冲突
  3. 【C++】C++基础 —— 变量和基本类型
  4. MySQL分组查找最早(大)或最晚(小)记录
  5. Python 安装特定版本的 Opencv
  6. vue+vuetify项目
  7. 论文:Flow Reconstruction for Data-Driven Traffic Animation
  8. Bluetooth MESH探究 --- (6) BLE core spec之广播信道防冲突与数据信道选择
  9. BGP实验2(IBGPRR)
  10. Linux 基金会执行董事 Jim Zemlin:开源如何成为创新的关键推动力