DMA详解和应用实例

DMA详解和应用实例

1.头大的常规概念

DMA是Direct Memory Access(直接内存访问)的缩写,它是一种计算机系统的特性,允许某些硬件子系统在不经过中央处理器(CPU)的情况下直接访问主系统内存。使用DMA,CPU可以在传输过程中执行其他操作,而不必占用整个读写周期。这样可以提高数据传输的效率,减少CPU的负担。12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发。对应的使用DMA的类型有,外设->存储器,存储器->外设,存储器->存储器

作者云”:DMA一般都是和外设,存储器搭配使用,而目前市面上主流的开发板配套例程(某火,某原)都是和串口或者ADC一起呈现,当然后续我们也会用上,但本文中我仅单独进行写, STM32在硬件上设计了这么多的DMA通道是煞费苦心的,用好DMA控制器,可以大

大地提高系统的吞吐量。在PC上,将硬盘数据复制到内存,就采用DMA模式,把复制数

据的任务交给DMA来执行,CPU就可以去做别的事情了。在STM32下也是如此,在编程中

不采用DMA模式,全靠CPU去辛苦劳作,是巨大的损失。

2.勉强的结构讲解

虽然学到STM你可以直接学HAL库这些封装程度比较高的库,但是我们想想,有多长时间没有体会到真正恍然大悟感觉,那很奇妙。所以结构应该去了解。

从这个图我们可以看到我们使用DMA需要配置哪些参数,我们学习每个外设最后都要配置对应的结构体,这个图不是官方的图,是江科大的(他们的视频真的不错)

DMA_InitTypeDef结构体:用于定义DMA通道的初始化参数,包括以下字段:

DMA_PeripheralBaseAddr:外设基地址,用于指定DMA源或目的地址。DMA_MemoryBaseAddr:存储器基地址,用于指定DMA源或目的地址。DMA_DIR:DMA传输方向,可以是DMA_DIR_PeripheralSRC(外设作为源)或DMA_DIR_PeripheralDST(外设作为目的)。DMA_BufferSize:DMA传输的数据量,单位为字节。DMA_PeripheralInc:外设地址寄存器递增模式,可以是DMA_PeripheralInc_Enable(使能)或DMA_PeripheralInc_Disable(禁止)。DMA_MemoryInc:存储器地址寄存器递增模式,可以是DMA_MemoryInc_Enable(使能)或DMA_MemoryInc_Disable(禁止)。DMA_PeripheralDataSize:外设数据宽度,可以是DMA_PeripheralDataSize_Byte(8位)、DMA_PeripheralDataSize_HalfWord(16位)或DMA_PeripheralDataSize_Word(32位)。DMA_MemoryDataSize:存储器数据宽度,可以是DMA_MemoryDataSize_Byte(8位)、DMA_MemoryDataSize_HalfWord(16位)或DMA_MemoryDataSize_Word(32位)。DMA_Mode:DMA传输模式,可以是DMA_Mode_Circular(循环模式)或DMA_Mode_Normal(正常模式)。DMA_Priority:DMA通道优先级,可以是DMA_Priority_VeryHigh(很高)、DMA_Priority_High(高)、DMA_Priority_Medium(中)或DMA_Priority_Low(低)。DMA_M2M:存储器到存储器传输模式,可以是DMA_M2M_Enable(使能)或DMA_M2M_Disable(禁止)。3.常用函数插入

DMA_Init函数:用于根据指定的参数初始化DMA通道,原型如下:

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

其中,DMAy_Channelx是要初始化的DMA通道,可以是DMA1_Channel1到DMA1_Channel7或DMA2_Channel1到DMA2_Channel5。DMA_InitStruct是指向一个DMA_InitTypeDef结构体的指针,用于指定DMA通道的初始化参数。

DMA_Cmd函数:用于使能或禁止指定的DMA通道,原型如下:

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

其中,DMAy_Channelx是要操作的DMA通道,可以是DMA1_Channel1到DMA1_Channel7或DMA2_Channel1到DMA2_Channel5。NewState是要设置的DMA通道的新状态,可以是ENABLE(使能)或DISABLE(禁止)。

DMA_ITConfig函数:用于配置指定的DMA通道的中断,原型如下

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);

其中,DMAy_Channelx是要操作的DMA通道,可以是DMA1_Channel1到DMA1_Channel7或DMA2_Channel1到DMA2_Channel5。DMA_IT是要配置的DMA通道的中断源,可以是以下值的组合:

* DMA_IT_TC:传输完成中断

* DMA_IT_HT:半传输中断

* DMA_IT_TE:传输错误中断

NewState是要设置的DMA通道的中断的新状态,可以是ENABLE(使能)或DISABLE(禁止)。

DMA_GetFlagStatus函数:用于检查指定的DMA标志是否被设置,原型如下:

FlagStatus DMA_GetFlagStatus(uint32_t DMA_FLAG);

4.代码哐当登场

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA的时钟

/*DMA初始化*/

DMA_InitTypeDef DMA_InitStructure; //定义结构体变量

DMA_InitStructure.DMA_PeripheralBaseAddr = ; //外设基地址,给定形参

DMA_InitStructure.DMA_PeripheralDataSize = ; //外设数据宽度,选择字节

DMA_InitStructure.DMA_PeripheralInc = ; //外设地址自增,选择使能

DMA_InitStructure.DMA_MemoryBaseAddr =; //存储器基地址,给定形参

DMA_InitStructure.DMA_MemoryDataSize = ; //存储器数据宽度,选择字节

DMA_InitStructure.DMA_MemoryInc = ; //存储器地址自增,选择使能

DMA_InitStructure.DMA_DIR = ; //数据传输方向,选择由外设到存储器

DMA_InitStructure.DMA_BufferSize = ; //转运的数据大小(转运次数)

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //模式,选择正常模式

DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //存储器到存储器,选择使能

DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等

DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1

/*DMA使能*/

DMA_Cmd(DMA1_Channel1, DISABLE); //这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始

#include "stm32f10x.h"

#define BUFFER_SIZE 32 //定义缓冲区大小

uint32_t aSRC_Const_Buffer[BUFFER_SIZE]; //定义源缓冲区

uint32_t aDST_Buffer[BUFFER_SIZE]; //定义目标缓冲区

void RCC_Configuration(void); //时钟配置函数

void GPIO_Configuration(void); //GPIO配置函数

void DMA_Configuration(void); //DMA配置函数

void NVIC_Configuration(void); //中断配置函数

void Error_Handler(void); //错误处理函数

int main(void)

{

RCC_Configuration(); //配置时钟

GPIO_Configuration(); //配置GPIO

DMA_Configuration(); //配置DMA

NVIC_Configuration(); //配置中断

/* 填充源缓冲区 */

for (uint32_t i = 0; i < BUFFER_SIZE; i++)

{

aSRC_Const_Buffer[i] = 0x55555555 + i;

}

/* 使能DMA通道1 */

DMA_Cmd(DMA1_Channel1, ENABLE);

/* 等待传输完成 */

while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);

/* 清除传输完成标志 */

DMA_ClearFlag(DMA1_FLAG_TC1);

/* 比较源缓冲区和目标缓冲区是否相同 */

for (uint32_t i = 0; i < BUFFER_SIZE; i++)

{

if (aSRC_Const_Buffer[i] != aDST_Buffer[i])

{

/* 缓冲区不匹配 */

Error_Handler();

}

}

/* 缓冲区匹配,程序正常运行 */

while (1)

{

/* 点亮LED */

GPIO_SetBits(GPIOC, GPIO_Pin_13);

}

}

/* 时钟配置函数 */

void RCC_Configuration(void)

{

/* 使能GPIOC时钟 */

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

/* 使能DMA1时钟 */

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

}

/* GPIO配置函数 */

void GPIO_Configuration(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

/* 配置PC13为推挽输出模式 */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOC, &GPIO_InitStructure);

/* 关闭LED */

GPIO_ResetBits(GPIOC, GPIO_Pin_13);

}

/* DMA配置函数 */

void DMA_Configuration(void)

{

DMA_InitTypeDef DMA_InitStructure;

/* 复位DMA1通道1 */

DMA_DeInit(DMA1_Channel1);

/* 配置DMA1通道1的初始化参数 */

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer; //外设基地址为源缓冲区地址

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer; //存储器基地址为目标缓冲区地址

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作为源

DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; //传输数据量为缓冲区大小

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //外设地址递增

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址递增

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //外设数据宽度为32位

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //存储器数据宽度为32位

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常模式

DMA_InitStructure.DMA_Priority = DMA_Priority_High; //高优先级

DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //存储器到存储器模式

DMA_Init(DMA1_Channel1, &DMA_InitStructure); //初始化DMA1通道1

/* 配置DMA1通道1的传输完成中断 */

DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);

}

/* 中断配置函数 */

void NVIC_Configuration(void)

{

NVIC_InitTypeDef NVIC_InitStructure;

/* 配置NVIC优先级分组为4 */

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

/* 配置DMA1通道1中断 */

NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn; //DMA1通道1中断

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级为0

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级为0

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断

NVIC_Init(&NVIC_InitStructure); //初始化NVIC

}

/* DMA1通道1中断服务函数 */

void DMA1_Channel1_IRQHandler(void)

{

/* 判断DMA1通道1是否发生传输完成中断 */

if (DMA_GetITStatus(DMA1_IT_TC1) == SET)

{

/* 清除DMA1通道1传输完成中断标志 */

DMA_ClearITPendingBit(DMA1_IT_TC1);

/* 关闭LED */

GPIO_ResetBits(GPIOC, GPIO_Pin_13);

}

}

/* 错误处理函数 */

void Error_Handler(void)

{

/* 用户可以在这里添加自己的代码 */

while(1)

{

/* 闪烁LED */

GPIO_ToggleBits(GPIOC, GPIO_Pin_13);

Delay(1000); //延时1秒

}

}

这个代码实例的功能是将一个32字节的源缓冲区的内容复制到一个目标缓冲区,然后比较两个缓冲区是否相同。如果相同,程序正常运行,点亮LED,如果不同,程序进入错误处理函数,闪烁LED。

这个代码实例的步骤如下:

包含所需的头文件,定义缓冲区大小,声明源缓冲区和目标缓冲区,声明配置函数和错误处理函数。在main函数中,调用配置函数,配置时钟、GPIO、DMA和中断,填充源缓冲区的内容,使能DMA通道1,等待传输完成,清除传输完成标志,比较源缓冲区和目标缓冲区的内容,根据结果执行不同的操作。定义一个时钟配置函数,使能GPIOC时钟和DMA1时钟。定义一个GPIO配置函数,配置PC13为推挽输出模式,关闭LED。定义一个DMA配置函数,复位DMA1通道1,配置DMA1通道1的初始化参数,配置DMA1通道1的传输完成中断。定义一个中断配置函数,配置NVIC优先级分组为4,配置DMA1通道1中断。定义一个DMA1通道1中断服务函数,判断DMA1通道1是否发生传输完成中断,清除传输完成中断标志,关闭LED。定义一个错误处理函数,用户可以在这里添加自己的代码,闪烁LED。 5.总结

注意DMA的传输方向:DMA的传输方向是指数据是从外设到存储器,还是从存储器到外设,还是从存储器到存储器。根据不同的传输方向,需要配置相应的源地址和目的地址,以及DMA请求的类型。如果传输方向配置错误,会导致数据传输失败或混乱。注意DMA的数据宽度:DMA的数据宽度是指外设和存储器的数据位数,可以是8位、16位或32位。根据不同的数据宽度,需要配置相应的数据对齐方式,以及源地址和目的地址的增量模式。如果数据宽度配置错误,会导致数据传输不完整或错误。注意DMA的传输模式:DMA的传输模式是指DMA是以循环模式还是正常模式进行数据传输。循环模式是指当传输完成后,DMA会自动重新开始传输,直到停止或中断。正常模式是指当传输完成后,DMA会自动停止传输,等待下一次启动。根据不同的传输模式,需要配置相应的传输数量和中断事件。如果传输模式配置错误,会导致数据传输过多或过少,或者无法停止或启动。注意DMA的优先级:DMA的优先级是指当多个DMA通道同时有传输请求时,哪个通道优先进行传输。DMA的优先级分为四级,分别是很高、高、中和低。根据不同的优先级,需要配置相应的控制寄存器。如果优先级配置错误,会导致数据传输的延迟或冲突。注意DMA的服务范围:DMA的服务范围是指DMA可以访问的外设和存储器的范围。不同的DMA控制器和通道有不同的服务范围,需要根据芯片的总线架构和功能框图来确定。如果服务范围配置错误,会导致数据传输无法进行或异常。注意DMA的缓存问题:DMA的缓存问题是指当使用带有缓存的内存区域作为DMA的源端或目的端时,可能会出现数据不一致或不更新的问题。这是因为CPU和DMA访问内存的方式不同,CPU可能会使用缓存来加速访问,而DMA直接访问实际的存储器。这样会导致缓存和存储器之间的数据不同步,或者缓存的数据被DMA覆盖。为了解决这个问题,需要在适当的时机清除或失效缓存,或者使用透写或共享的缓存策略,或者禁用缓存。

尊享推荐

28365-365 中国球迷世界杯现场求婚,用DR钻戒承诺像梅西一样一生只爱一人!
365bet资讯网 2025十大宠物店品牌排行榜 宠物店排行榜前十名
365娱乐app官方版下载106平台 手机来电秀怎么开启

手机来电秀怎么开启

📅 07-15 👑 596
28365-365 老君查岗

老君查岗

📅 07-24 👑 14