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.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简单理解和分享相关推荐
- android 点击事件消费,Android View事件分发和消费源码简单理解
Android View事件分发和消费源码简单理解 前言: 开发过程中觉得View事件这块是特别烧脑的,看了好久,才自认为看明白.中间上网查了下singwhatiwanna粉丝的读书笔记,有种茅塞顿开 ...
- git的简单理解及基础操作命令
前端小白一枚,最近开始使用git,于是花了2天看了廖雪峰的git教程(偏实践,对于学习git的基础操作很有帮助哦),也在看<git版本控制管理>这本书(偏理论,内容完善,很不错),针对所学 ...
- 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():调用一个回调函数处 ...
- 一次网络世界的旅行-简单理解网络通信
一次网络世界的旅行 前言 简单理解网络通信 网络通信 mac地址 IP地址和子网掩码和网关 DHCP服务器 DNS服务器 前言 简要概述网络通信的简单原理,新手向,分享一下自己的理解 简单理解网络通信 ...
- Introduction To AMBA 简单理解
文章目录 前言 简介 AMBA 的演进 AMBA 演进图解 AMBA specifications AMBA1.0 1.1 Introduction to AMBA 1.2 AMBA Specific ...
- route map: 转发一个博客,附上自己的简单理解
最近在做实验需要用到route map,搜到了一个博客感觉挺不错,分享一下: https://blog.csdn.net/ZhangPengFeiToWinner/article/details/85 ...
- Spring Security并没有那么难嗷 简单理解OAuth2.0
文章目录 1. 基本概念 1.1 什么是认证 1.2 什么是会话 1.3 什么是授权 1.4 授权的数据模型 1.5 RBAC 1.5.1 基于角色的访问控制 1.5.2 基于资源的访问控制 2. 基 ...
- 【转载】Deep learning:十九(RBM简单理解)
Deep learning:十九(RBM简单理解) 这篇博客主要用来简单介绍下RBM网络,因为deep learning中的一个重要网络结构DBN就可以由RBM网络叠加而成,所以对RBM的理解有利于我 ...
- 学习:双机热备、集群、负载均衡、SQL故障转移群集简单理解(转)
双机热备.集群.负载均衡.SQL故障转移群集简单理解平常,大家常提到几个技术名词:双机热备.集群.负载均衡.SQL故障转移群集.这里,就我的理解,和大家简单探讨下,有不足或错误之处还请各位指出! 这些 ...
最新文章
- php高版本不再使用mysql_connect()来连接数据库
- mac攻略(1) -- 简单配置php开发环境
- Ubuntu常用服务器ftp、ssh
- 选定内容没有属性页_宣传单页的效果和作用
- android auto note 8,三星Galaxy Note 8.0支持flash吗
- 数据库安装时的log路径
- 10投屏后没有声音_10年后,学区房有没有可能成为“负资产”?这位专家说了实话...
- ppt设置外观样式_ppt如何设置幻灯片的样式
- android 仿饿了么地图,微信小程序仿饿了么地址定位、筛选与回传
- 四相五线步进电机定时器驱动
- ubuntu安装pytorch3d
- 共筑未来 | 思腾合力加入光合组织,完成国产信创兼容适配
- 从数据治理到数据应用,制造业企业如何突破数字化转型困境丨行业方案
- 转行程序员日记---2020-09-18【,勿忘国耻】【回忆青春】
- 201771010137赵栋《第九周学习总结》
- linux 列转行函数,GP行转列、列转行函数
- LINUX基础 第四次课 10月15日
- OpenCL学习笔记(三):OpenCL安装,编程简介与helloworld
- [日推荐]『健康管理』为你的健康保驾护航
- 干货 | 10分钟给上万客服排好班,携程大规模客服排班算法实践