完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
电子发烧友论坛扫一扫,分享给好友
|
|
|
相关推荐
1个回答
|
|
|
DMA传输
看过我这篇博文《STM32 | 基于NRF24L01串口透传(不定长数据无线串口双向传输)》的应该就清楚我为什么要写DMA传输,最终目的就是为了让nRF24L01采用串口DMA+SPI DMA方式实现串口的透传,所以本文来讲讲DMA传输。还是老样子,我认真写好本文,希望更多人了解并且会用并且了解DMA传输,希望大家多多支持,可以参与评论或点个赞,谢谢! 那么就进入正题! 测试平台:STM32F103C8T6一、查询和中断不爽吗?为什么要使用串口DMA?(借串口DMA引入) 如果真的是刚刚接触单片机或嵌入式,肯定会问,因为按照难易程度:DMA>中断>查询。这里我提几个反问句回答。
二、DMA介绍 学习只有积跬步,才能至千里。可能废话有点多,当然这是写给还不知道DMA的同学看的。已经了解的,只想学怎么配置的可以直接跳转到后面的内容。 1、什么是DMA? 不太官方的理解:DMA只是个搬运工,帮助老板(CPU)搬运东西(数据),可以帮老板(CPU)把东西(数据)从家搬运到家门外(存储器→外设);可以帮老板(CPU)把到东西(数据)从家门外搬运到家里(外设→存储器);还可以帮老板(CPU)把东西(数据)从家里卧室1搬到卧室2(存储器→存储器)。 官方一点的表达:DMA,全称为:Direct Memory Access,即直接存储器访问。直接存储器存取( DMA )用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须 CPU 干预,数据可以通过 DMA 快速地移动,这就节省了 CPU 的资源来做其他操作。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。 2、STM32上的DMA资源 STM32 最多有 2 个 DMA 控制器( DMA2 仅存在大容量产品中),12个独立的可配置的通道(请求), DMA1 有 7 个通道。DMA2 有 5 个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。 小容量产品是指闪存存储器容量在16K至32K字节之间的微控制器。 中容量产品是指闪存存储器容量在64K至128K字节之间的微控制器。 大容量产品是指闪存存储器容量在256K至512K字节之间的微控制器。 互联型产品是指STM32F105xx和STM32F107xx微控制器。 3、DMA主要特征
后续要以串口2的接收举例,由以下内容可以知道USART2_RX请求在DMA1通道6。 (1)DMA1控制器 从外设(TIMx[x=1、2 、3、4] 、ADC1 、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。参见下图的DMA1请求映像。 外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭。 DMA1 请求映像 各个通道的DMA1请求一览 (1)DMA2控制器 从外设(TIMx[5、6、7、8]、ADC3、SPI/I2S3、UART4、DAC通道1、2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,这意味着同时只能有一个请求有效。参见下图的DMA2请求映像。 外设的DMA请求,可以通过设置相应外设寄存器中的DMA控制位,被独立地开启或关闭。 注意: DMA2控制器及相关请求仅存在于大容量产品和互联型产品中。 DMA2 请求映像 各个通道的DMA2请求一览 注意:ADC3、SDIO和TIM8的DMA请求只在大容量的产品中存在。 5、DMA寄存器介绍 DMA寄存器的介绍主要参照参考手册,这里列出来方便大家阅读,使用DMA的时候可以使用库函数,方便阅读;也可直接配置寄存器,方便使用(快捷)。 注意: 在以下列举的所有寄存器中,所有与通道6和通道7相关的位,对DMA2都不适用,因为DMA2只 有5个通道。 (1)DMA中断状态寄存器(DMA_ISR) [tr]数据位 功能 [/tr]
[tr]数据位 功能 [/tr]
[tr]数据位 功能 [/tr]
[tr]数据位 功能 [/tr]
[tr]数据位 功能 [/tr]
[tr]数据位 功能 [/tr]
库函数比较好的就是便于查看,有的人会说库函数和寄存器学一个就好了,但是有时候你会发现库函数和寄存器结合起来使用的时候回比较方便。例如,你要修改一些配置的时候,寄存器会很方便,原本库函数需要几条代码才能搞定,而寄存器可能一条就解决了。所以,我建议使用DMA的时候可以库函数结合寄存器一起使用。在DMA配置的时候,使用 V3.5 库函数操作的话,我们只需要调用 DMA_Init() 函数就可以了。下面介绍一些主要的函数。 [tr] 函数名 描述 [/tr]
[tr] 函数名 DMA_DeInit [/tr]
/* 将 DMA 的通道 6 寄存器重设为缺省值 */ DMA_DeInit(DMA_Channel6); (2)函数DMA_Init [tr] 函数名 DMA_Init [/tr]
DMA_InitTypeDef 定义于文件“stm32f10x_dma.h”: typedef struct { u32 DMA_PeripheralBaseAddr; u32 DMA_MemoryBaseAddr; u32 DMA_DIR; u32 DMA_BufferSize; u32 DMA_PeripheralInc; u32 DMA_MemoryInc; u32 DMA_PeripheralDataSize; u32 DMA_MemoryDataSize; u32 DMA_Mode; u32 DMA_Priority; u32 DMA_M2M; } DMA_InitTypeDef; DMA_PeripheralBaseAddr:该参数用以定义 DMA 外设基地址 DMA_MemoryBaseAddr:该参数用以定义 DMA 内存基地址 DMA_DIR:规定外设是作为数据传输的目的地还是来源(数据传输方向),该参数的取值范围如下表。 [tr] DMA_DIR 值 描述 [/tr]
DMA_PeripheralInc:用来设定外设地址寄存器递增与否。该参数的取值范围如下表。 [tr] DMA_PeripheralInc 值 描述 [/tr]
[tr] DMA_MemoryInc 值 描述 [/tr]
[tr] DMA_PeripheralDataSize 值 描述 [/tr]
[tr] DMA_MemoryDataSize 值 描述 [/tr]
[tr] DMA_Mode 值 描述 [/tr]
DMA_Priority:设定 DMA 通道 x 的软件优先级。该参数的取值范围如下表。 [tr] DMA_Priority 值 描述 [/tr]
[tr] DMA_M2M 值 描述 [/tr]
[tr] 函数名 DMA_Cmd [/tr]
/* 使能DMA通道6 */ DMA_Cmd(DMA_Channel6, ENABLE); (4)函数 DMA_ITConfig [tr] 函数名 DMA_ITConfig [/tr]
[tr] DMA_IT 值 描述 [/tr]
/* 使能DMA通道6传输完成中断 */ DMA_ITConfig(DMA_Channel6, DMA_IT_TC, ENABLE); 三、DMA配置 下面是配置DMA通道x的过程(x代表通道号):
上述便是DMA初始化的配置过程,为什么前文说配置寄存器方便使用(快捷)?从上述过程可以发现实际只需配置四个寄存器即可完成DMA的配置。但是不好的地方就是不便于阅读,通常大家都要对照使用手册才知道什么位配置成‘0’或‘1’。这里为什么要将,有一个很重要的因素就是快。 先使能要使用的DMA时钟,这里使用DMA1,USART2_RX(即使用通道6)。 RCC->AHBENR |= 1<<0 ; //DMA1时钟使能 1 接着对照流程,配置寄存器方法如下(以通道6为例): u8 u1rxbuf[100]; //接收数据缓冲区1 //DMA_USART2_RX USART2->RAM的数据传输 DMA1_Channel6->CPAR = (u32)(&USART2->DR) ; //1.设置外设寄存器地址,注意PSIZE DMA1_Channel6->CMAR = (u32)u1rxbuf ; //2.设置数据存储器的地址,注意MSIZE DMA1_Channel6->CNDTR = buffersize ; //3.设置要传输的数据量buffersize个 DMA1_Channel6->CCR |= 2<<12 ; //4.设置通道的优先级高(处于位13:12) DMA1_Channel6->CCR &= ~( 1<<4 ) ; //5(1).设置数据传输的方向从外设读取到内存 DMA1_Channel6->CCR &= ~( 1<<5 ) ; //5(2).设置不循环 DMA1_Channel6->CCR &= ~( 1<<6 ) ; //5(3).不执行外设地址增量模式 DMA1_Channel6->CCR |= 1<<7 ; //5(4).存储器地址增量模式 DMA1_Channel6->CCR &= ~( 3<<8 ) ; //5(5).外设数据宽度8bi DMA1_Channel6->CCR &= ~( 3<<10 ) ; //5(6).存储器数据宽度8bit DMA1_Channel6->CCR &= ~( 1<<14 ) ; //5(7).非存储器到存储器模式 DMA1_Channel6->CCR |= 1<<1 ; //5(8).允许传输完成中断 DMA1_Channel6->CCR |= 1 << 0 ; //6.开启DMA通道6 因为上述允许传输完成中断,所以还要配置相应的NVIC,这里使用回库函数 NVIC_InitTypeDef NVIC_InitStructure; //DMA1通道6 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; //NVIC通道设置 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 当然,使用串口DMA的话还需开启串口接收中断(通道6是串口2接收通道) USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); //开启串口DMA接收 2、库函数版 库函数最大的优势就是便于阅读。根据固件库手册可以写出以下配置。 要先使能要使用的DMA时钟,这里使用DMA1,USART2_RX(即使用通道6)。相应库函数前面有讲解,直接上代码(注释很清楚)。 DMA_InitTypeDef DMA1_Init; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //使能DMA1时钟 //DMA1通道6 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; //NVIC通道设置 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //DMA_USART2_RX USART2->RAM的数据传输 DMA_DeInit(DMA1_Channel6); //将DMA的通道6寄存器重设为缺省值 DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); //启动传输前装入实际RAM地址 DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf; //设置接收缓冲区首地址 DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取到内存 DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN; //DMA通道的DMA缓存的大小 DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变 DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位 DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA1_Init.DMA_Mode = DMA_Mode_Normal; //工作在正常模式 DMA1_Init.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级 DMA1_Init.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输 DMA_Init(DMA1_Channel6,&DMA1_Init); //对DMA通道6进行初始化 DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE); //开USART2 Rx DMA中断 DMA_Cmd(DMA1_Channel6,ENABLE); //使DMA通道6开始工作 USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); //开启串口DMA接 四、DMA使用 1、查询方式 查询方式可以不使能DMA中断,通过DMA_GetFlagStatus函数判断标志位来辨别是否传输完成或过半以及出错,然后关闭DMA通道,用DMA_SetCurrDataCounter函数重设缓存大小,完成相应操作后要记得清除标志位再使能DAM通道。如果使能了循环模式,会自动重装载计数。 2、中断方式 DMA中断一般用于定长数据传输,以传输完成中断为例。 (1)当产生DMA传输完成中断后,清除中断标志位、传输完成标志位; (2)关闭DMA通道; (3)处理数据; (4)重新设置DMA通道的DMA缓存的大小(可以省去); (5)开启DMA通道 3、不定长数据传输 以串口为例,不定长数据传输的时候,可以通过串口空闲中断来判断传输是否完成(传输缓存大小要大于传输的数据大小),数据长度可以通过DMA_GetCurrDataCounter函数来计算,然后关闭DMA通道,重设DMA缓存的大小,再启用DMA通道。 4、双缓冲方式 设置两个缓冲区,设置一个缓冲区标志(用来指示当前处在哪个缓冲区),每完成一次传输就通过重新配置DMA_MemoryBaseAddr的缓冲区地址,下次传输数据就会保存到新的缓冲区中,可以通过自定义缓存区标志来判断和切换,这样可以避免缓冲区数据来不及处理就被覆盖的情况,也能为处理数据留出更多地时间(指到下次传输完成)。 五、实战串口DMA 串口DMA的配置以及使用请查看此文:《STM32 | 串口DMA很难?其实就是如此简单!(超详细、附代码)》 六、总结 其实DMA的配置并不难,按照上面的配置过程配置即可。在使用DMA的时候,如果启用了DMA传输中断,还要写相应的中断函数,在中断函数中切记要清除中断标志位。DMA只有在传输完成或传输过半或者传输出错才会产生中断(前提打开了中断),对于DMA定长数据传输的时候,建议使用中断,对于不定长数据,以串口为例,可以结合串口空闲中断来使用。因为中断不是本文内容,这里不做讲解。**还是要说一下的就是DMA通道一旦使能便开始传输数据,如果是接收数据,可以先使能DMA通道再等待数据接收;如果是发送数据,最好配置好发送缓存再使能DMA通道。**最后说明一下,本文为了后续讲解串口DMA和SPI DMA时候方便写下,所以对于各类的DMA运用还需大家结合实际运用。本文若有不足之处,欢迎指出,可在下方评论。 |
|
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
4130 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
3221 浏览 1 评论
2747 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
2175 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
14939 浏览 2 评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
3084浏览 4评论
stm32f4下spi+dma读取数据不对是什么原因导致的?
1892浏览 3评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
2064浏览 3评论
1976浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
2165浏览 3评论
/9
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-12-1 22:33 , Processed in 0.642274 second(s), Total 72, Slave 55 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191

淘帖
1719