DMA
2023-08-28 23:18:48来源:博客园
存储器到存储器一般使用软件触发,外设到存储器用硬件触发(特定硬件)
存储器映像
运行从主闪存Flash中开始
(资料图片)
选项字节:刷新程序时可以保持不变,存的主要是同Flash的读保护、写保护,看门狗等
内核外设:NVIC 和 SysTick
DMA框图
总线矩阵左边是主动单元,右边是被动单元
DCode总线专门访问Flash(CPU或DMA直接访问的话一般是只读的,但可以通过配置其接口控制器进行写入【对FLASH按页进行擦除,再写入】),系统总线访问其他
DMA每个通道都可以设置他们的源地址和目的地址,仲裁器根据优先级决定哪个通道使用唯一的一条DMA总线
总线上也有一个仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问。以防冲突,不过总线仲裁器。仍然会保证CPU得到一半的总线带宽,使CPU正常工作
AHB从设备,是DMA自身的寄存器,这样就可以被CPU配置,既是主动单元也是被动单元;DMA的硬件触发源可触发DMA,比如APB2里面的外设数据准备完成就触发DMA执行数据转运
数据宽度分为8(Byte) 16(HalfWord) 32位(Word)
外设寄存器(只是个名字,这里面的配置可以是外设也可以是存储器【flash SRAM】) 反正两个地址转运数据 怎么设置都行
传输计数器:自减 (写几就转运计次,为0时就不再转运,而且自增的地址也会恢复到起始位置)
自动重装器:计数器值到0后是否恢复到最初设置给计数器的值(比如计数器从5计数到0,是结束呢还是重新让他从5再次计数)
M2M 存储器到存储器,
给M2M位置1时,选择软件触发(以最快的速度,连续不断触发DMA,将计数值清零,完成一轮转换),不能和循环模式一起用,一般用在存储器到存储器
0硬件触发,源可以选择ADC 定时器 串口等,与外设有关,这些转运需要一定的时机
三个条件:CMD使能,计数器大于0,有触发条件。 当计数器等于0且没有自动重装时,无论是都触发都不再转运,此时需要CMD关闭DMA,再给计数器写个值,再次启动DMA
每个通道的硬件触发源都不一样(特定的硬件触发),软件触发的话都一样可任意选择
自动重装与软件触发不能同时使用
#include "stm32f10x.h" // Device headeruint16_t MyDMA_Size;void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size){ MyDMA_Size = Size; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//起始地址 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//是否自增 DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向 DMA_InitStructure.DMA_BufferSize = Size; //缓冲区大小:计数器 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); //选择DMAx的Channelx DMA_Cmd(DMA1_Channel1, DISABLE);}void MyDMA_Transfer(void){ DMA_Cmd(DMA1_Channel1, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size); DMA_Cmd(DMA1_Channel1, ENABLE); while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); DMA_ClearFlag(DMA1_FLAG_TC1);}View Code
DMA转运的三个条件:计数器不为0,触发源有触发信号(软件触发或硬件触发),使能
DMA单次运转:RCCDMA_Init DMA使能
DMA连续运转:在单次的基础上:DMA失能,重新设置DMA计数器,使能DMA,等待转运完成后清除其标志位(为了下次转运)
若使用ADC1 则有个库函数 ADC1_DMACmd() 用来开启ADC1的这一路触发源 (开启某个外设的DMA输出)
通道号越小,优先级越高,也可以自定义配置
数据宽度与对其
转运双方的数据宽度一样的话就正常转运,不一样的话就看下表(不够就补0,超了就舍弃高位)
案例:复制转运,数组数据转运(左自增右不自增的时候)
ADC扫描+DMA (外设地址不自增,存储器地址自增)
在每个单独的通道转换完成后,把其产生在ADC_DR(外设地址)里面的值转运到DMA目的地(存储器地址【可在SRAM中定义一个数组暂存】),并且目的地址自增
触发选择:DMA转运的时机。需要和ADC单个通道转换完成同步,所以选择ADC的硬件触发
单个通道转换完成后没有任何标志位可供查询,但是会触发单通道的DMA请求
这里采用4个ADC通道,依次转换,结果会存在同一个DR寄存器中,然后由DMA及时转运出去(源地址固定,目的地递增)避免数据覆盖
步骤: 开启DMA ADC和对应的GPIO时钟并配置CLK,初始化4个GPIO口,选择ADC4个通道,ADC初始化,DMA初始化(注意配置时候的硬件关系对照),使能
ADC和DMA都是单次的时候需要每次触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLEDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
当调用这个函数,ADC开始转换,连续扫描4个通道,数值依次写到DR寄存器中,DMA在数据覆盖之前会将每次的数据及时转运出去,目的地自增
都是循环模式的话就自动不用管
uint16_t AD_Value[4];void AD_Init(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//点菜,通道0放在序列1的位置 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //开启扫描模式(从序列1-序列4) ADC_InitStructure.ADC_NbrOfChannel = 4;//前4个序列有效 ADC_Init(ADC1, &ADC_InitStructure); DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//要DR寄存器低16位的数据 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//源地址地址不变 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//目的地地址自增 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 4; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//可一般模式,可循环 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //不使用软件触发,使用硬件ADC触发 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_Init(DMA1_Channel1, &DMA_InitStructure); // ADC1硬件固定对应DMA1的通道1 DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE);//开启ADC到DMA的输出 ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); //校准ADC while (ADC_GetResetCalibrationStatus(ADC1) == SET); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1) == SET); ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件触发ADC开始连续转换,DMA也连续转运}View Code
此外,还可以加个定时器,定时器触发ADC,ADC触发DMA (硬件自动化)
TODU: 串口数据,使用DMA进行存储器到外设的转运
位段:用来单独操作寄存器或SRAM的某一位,给他新编一个地址(在新开辟的地址区域,有大把空闲地址)
在程序中的临时变量放在RAM区,地址以20开头;使用const修饰的常量放在flash区内,地址以08开头(常用来修饰不变的数据)
SMT32中利用结构体来访问寄存器,结构体内的成员顺序(内存)与各个寄存器的地址(内存)一一对应,比如指定结构体A的起始地址为ADC1外设寄存器的起始地址,访问结构体成员就相当于访问外设的某个寄存器; &ADC1->DR ADC1的结构体指针指向的是ADC1外设的起始地址,访问结构体成员就相当于加一个偏移,这样也是可以访问对应的寄存器
需要DMA的中断就调用DMA_ITConfig,配置NVIC
关键词: