江科大stm32 p8 DMA直接存储器存取
DMA简介
- 可以直接访问STM32内部的存储器,包括运行内存SRAM,程序存储器Flash和寄存器等等
STM32中的存储器
- ROM:只读存储器,非易失性,掉电不丢失
- 程序存储器,主闪存Flash
- 系统存储器和选项字节的存储介质也是Flash,但是我们一般说的Flash指的是主闪存,而不是这两块区域
- RAM:随机存储器,易失性,掉电丢失
- 外设寄存器介质也是SRAM,但是我们一般把运行内存叫SRAM
- 内核外设就是NVIC和SysTick
DMA结构框图
DMA访问Flash只可读不可写
简化结构图
- DMA的转运可以从外设到存储器也可以反过来,也可以从存储器到存储器 ,但不可以写Flash
- M2M控制触发模式:
- 置1软件触发,一般是存储器到存储器的转运。以最快的速度将传输计数器清0,不能和自动重装一起用,否则DMA会停不下来
- 置0硬件触发,触发源可以选择ADC、串口、定时器等等,一般都是和外设有关的转运
- 写传输计数器时,必须要先关闭DMA
- 复制转运,原数据不会消失
DMA请求映像
- 每个通道都有特定触发源
- 选择哪个触发源取决于开起了哪个外设的DMA输出,比如ADC_DMACmd
数据对齐
例子
- 外设地址:DataA首地址
- 存储器地址:DataB首地址
- 数据宽度:8
- 地址自增:双方地址都要自增
- 方向:DataA到DataB
- 传输计数器:7
- 自动重装:失能
- 触发选择M2M:1软件触发
- 使能DMA:DMA_Cmd
#include "stm32f10x.h" // Device header
uint8_t MyDMA_Size;
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t size)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
MyDMA_Size = size;
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_PeripheralDataSize_Byte; //存储器数据宽度
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //存储器到存储器,软件触发
DMA_InitStructure.DMA_BufferSize = size; //传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //操作模式,是否自动重装,自动重装和软件触发不能同时启用
DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
void MyDMA_Transfer()
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size); //先失能再更改传输计数器
DMA_Cmd(DMA1_Channel1, ENABLE);
while(RESET == DMA_GetFlagStatus(DMA1_FLAG_TC1)); //等待转运完成
DMA_ClearFlag(DMA1_FLAG_TC1); //清除标志位
}
- 外设地址:ADC_DR
- 存储器地址:ADValue首地址
- 数据宽度:16 32bit处理器,它的字是32bit的。半字是16bit;
- 地址自增:外设地址自增,存储器地址自增
- 方向:外设到存储器
- 传输计数器:7
- 自动重装:取决于ADC是连续扫描还是单次扫描
- 触发选择M2M:0硬件触发,ADC单个通道转换完成时,ADC在这种情况下没有标志位,但是有DMA请求,可以触发硬件请求
- 使能DMA:DMA_Cmd
#include "stm32f10x.h" // Device header
uint16_t AD_Value[3];
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); //6分频 72M ADC最高14M 只能选6或8分频
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_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_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续模式启用,只需触发一次,就会一直转换
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式还是双AD模式
ADC_InitStructure.ADC_NbrOfChannel = 3; // 规则组转换列表里通道的数目
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 不使用外部触发源,即软件触发
ADC_Init(ADC1, &ADC_InitStructure);
//DMA转运数据防止覆盖
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // (0x4001244C)
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_M2M = DMA_M2M_Disable; // 不使用软件触发
DMA_InitStructure.DMA_BufferSize = 3; // 传输计数器,3个ADC通道,传输3次
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 操作模式,是否自动重装,否则要手动重装(重新给传输计数器赋值)
DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
//校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件启动ADC转换触发
}