目录
一,DMA简介
1.DMA(Direct Memory Access)直接存储器存取
2.DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
3.两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自 于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
4.每个通道都支持软件触发和特定的硬件触发
5.DMA主要特性
二,DMA框图
1. DMA总线将DMA的AHB主控接口与总线矩阵相联,总线矩阵协调着CPU的DCode和DMA到
SRAM、闪存和外设的访问。
2.如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优
先权。
三, DMA处理
在发生一个事件后,外设向
DMA
控制器发送一个请求信号。
DMA
控制器根据通道的优先权处理
请求。当
DMA
控制器开始访问发出请求的外设时,
DMA
控制器立即发送给它一个应答信号。当
从
DMA
控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,
DMA
控制
器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。
总之,每次
DMA
传送由
3
个操作组成:
● 从外设数据寄存器或者从当前外设
/
存储器地址寄存器指示的存储器地址取数据,第一次传
输时的开始地址是
DMA_CPARx
或
DMA_CMARx
寄存器指定的外设基地址或存储器单元。
● 存数据到外设数据寄存器或者当前外设
/
存储器地址寄存器指示的存储器地址,第一次传输
时的开始地址是
DMA_CPARx
或
DMA_CMARx
寄存器指定的外设基地址或存储器单元。
● 执行一次
DMA_CNDTRx
寄存器的递减操作,该寄存器包含未完成的操作数目。
四, DMA请求
五,存储器影像
如果想要知道某个寄存器的地址,打开用户手册,在第二章的存储器影像,可以找到该外设的起始地址; 然后看一下该外设寄存器所在的寄存器总表,可以找到它的偏移地址;实际地址就是起始地址加偏移地址
六,DMA数据转运 - keil工程
1.相关函数
//回复缺省配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
//初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
//结构体初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
//使能DMA
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
//中断输出使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
//设置当前数据寄存器(给传输计数器写数据)
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
//获取当前数据寄存器(返回传输计数器的值)
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
//获取标志位,清除标志位
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
void DMA_ClearFlag(uint32_t DMAy_FLAG);
//获取中断状态,清除中断状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
void DMA_ClearITPendingBit(uint32_t DMAy_IT);
2.代码部分
1. MyDMA.c
#include "stm32f10x.h" // Device header
uint16_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_BufferSize = Size; //缓存区大小(传输计数器)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //选择硬件触发还是软件触发
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //传输模式(是否使用自动重装)
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
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);
}
2. MyDMA.h
#ifndef _MYDMA_H
#define _MYDMA_H
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);
#endif
3. main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] = {0, 0, 0, 0};
int main(void)
{
OLED_Init();
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
OLED_ShowString(1, 1, "DataA");
OLED_ShowString(3, 1, "DataB");
OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
while(1)
{
DataA[0]++;
DataA[1]++;
DataA[2]++;
DataA[3]++;
OLED_ShowHexNum(2, 1, DataA[0], 2);
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowHexNum(2, 1, DataA[0], 2);
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
}
}
3.效果图
DMA数据转运
七,DMA+AD多通道
使用ADC连续扫描模式+DMA循环转运
1.代码部分
1. AD.c
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
//对AD初始化
void AD_Init(void)
{
//开启时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//ADC分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //这样ADCCLK=72MHz/6
//配置GPIO
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);
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
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel = 4;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式
ADC_Init(ADC1,&ADC_InitStructure);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设站点的起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度
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_BufferSize = 4; //缓存区大小(传输计数器)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //选择硬件触发还是软件触发
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //传输模式(是否使用自动重装)
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
//开启ADC电源
ADC_Cmd(ADC1, ENABLE);
//校准,当复位校准状态为SET的时候完成复位校准
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //当ADC触发之后,ADC 连续转换,DMA循环转运
}
2. AD.h
#ifndef _AD_H
#define _AD_H
#include "stdint.h"
extern uint16_t AD_Value[4];
void AD_Init(void);
#endif
3. main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0, AD1, AD2, AD3;
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "AD0:");
OLED_ShowString(2, 1, "AD1:");
OLED_ShowString(3, 1, "AD2:");
OLED_ShowString(4, 1, "AD3:");
while(1)
{
OLED_ShowNum(1, 5, AD_Value[0], 4);
OLED_ShowNum(2, 5, AD_Value[1], 4);
OLED_ShowNum(3, 5, AD_Value[2], 4);
OLED_ShowNum(4, 5, AD_Value[3], 4);
Delay_ms(100);
}
}