DMA的基本知识
DMA(Direct Memory Access)直接存储器存取
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发
STM32F103C8T6 DMA资源:DMA1(7个通道)
CPU或DMA对FLASH只能读不能写,对SRAM能读能写
传输计数器为0后,地址会跳回原转运地址
启动要求:开关控制,硬件触发,计数器不为0
使用硬件触发需要选择对应通道,软件可以使用所有通道
当传输两侧数据宽度不一致时
是否自增会决定中间箭头的对应关系
存储器映像模式的验证
普通变量存在SRAM区,2000可由下列代码检验
#include "stm32f10x.h" // Device header
#include "Delay.h"//需要引用延时函数
#include "LED.h"
#include "Key.h"
#include "OLED.h"
uint8_t value=0x66;
int main()
{
LED_Init();
Key_Init();
OLED_Init();
OLED_ShowHexNum(1,1,value,2);
OLED_ShowHexNum(2,1,(uint32_t)&value,8);//加了&是指针变量,应转换
while(1)
{
}
}
上述程序显示地址为2000开头
const声明的常量存在于FLASH中
使用下面的一段程序
#include "stm32f10x.h" // Device header
#include "Delay.h"//需要引用延时函数
#include "LED.h"
#include "Key.h"
#include "OLED.h"
const uint8_t value=0x66;
int main()
{
LED_Init();
Key_Init();
OLED_Init();
OLED_ShowHexNum(1,1,value,2);
OLED_ShowHexNum(2,1,(uint32_t)&value,8);//加了&是指针变量,应转换
while(1)
{
}
}
显示0800开头的地址,由于flash里面有程序内容,所以有偏移
变量和常量的地址由编译器确定,通常是不固定的
外设寄存器的地址是固定的
用ADC1->DR可以访问ADC1外设的DR寄存器
OLED_ShowHexNum(2,1,(uint32_t)(&ADC1->DR),8);//加了&是指针变量,应转换
在数据手册中,用寄存器地址+偏移可以查到寄存器地址
利用结构体指针访问寄存器可以实现结构体相对地址和寄存器绝对地址的重合
DMA程序
由于不涉及外设,在System文件夹中添加即可
存储器到存储器的单次转运
#ifndef __DMA_H
#define __DMA_H
void MyDMA_Init(uint32_t ADDRA,uint32_t ADDRB,uint16_t size);//从A传递到B,传递次数为SIZE次
#endif
#include "stm32f10x.h" // Device header
void MyDMA_Init(uint32_t ADDRA,uint32_t ADDRB,uint16_t size)//从A传递到B,传递次数为SIZE次
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备,注意这类芯片选择OTHER DEVICE
//DMA转运三个条件:计数器大于0,有触发源,DMA使能
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;//外设站点作为源头还是目的地,DST目的地,SRC源头
DMA_InitStructure.DMA_BufferSize=size;//传输计数器,传输几次,最大值65535
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,又选择了通道,软件触发可以任意选择通道
DMA_Cmd(DMA1_Channel1,ENABLE);//初始化后自动工作
}
#include "stm32f10x.h" // Device header
#include "Delay.h"//需要引用延时函数
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "MyDMA.h"
uint8_t DataA[]={0x01,0x02,0x03,0x04};
uint8_t DataB[]={0,0,0,0};
int main()
{
LED_Init();
Key_Init();
OLED_Init();
OLED_ShowHexNum(1,1,DataA[0],2);
OLED_ShowHexNum(2,1,DataB[1],2);
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);
OLED_ShowHexNum(3,1,DataA[0],2);
OLED_ShowHexNum(4,1,DataB[1],2);
while(1)
{
}
}
手动控制多次转运
#ifndef __DMA_H
#define __DMA_H
void MyDMA_Init(uint32_t ADDRA,uint32_t ADDRB,uint16_t size);//从A传递到B,传递次数为SIZE次
void MyDMA_Transfer(void);
#endif
#include "stm32f10x.h" // Device header
uint16_t My_DMA_Size;
void MyDMA_Init(uint32_t ADDRA,uint32_t ADDRB,uint16_t size)//从A传递到B,传递次数为SIZE次
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备,注意这类芯片选择OTHER DEVICE
//DMA转运三个条件:计数器大于0,有触发源,DMA使能
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;//外设站点作为源头还是目的地,DST目的地,SRC源头
DMA_InitStructure.DMA_BufferSize=size;//传输计数器,传输几次,最大值65535
My_DMA_Size=size;//保存一下这个变量便于Transfer赋值
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,又选择了通道,软件触发可以任意选择通道
DMA_Cmd(DMA1_Channel1,DISABLE);//初始化后不自动工作
}
void MyDMA_Transfer(void)//再转运一次
{
DMA_Cmd(DMA1_Channel1,DISABLE);//使DMA失能以便给传输计数器赋值
DMA_SetCurrDataCounter(DMA1_Channel1,My_DMA_Size);
DMA_Cmd(DMA1_Channel1,ENABLE);//重新满足三个条件
while (DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);//没有转运完成,一直等待,这个FLAG转到变量里有不同的返回值,对应完成,失败等
DMA_ClearFlag(DMA1_FLAG_TC1);//手动清除标志位
}
#include "stm32f10x.h" // Device header
#include "Delay.h"//需要引用延时函数
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "MyDMA.h"
uint8_t DataA[]={0x01,0x02,0x03,0x04};
uint8_t DataB[]={0,0,0,0};
int main()
{
LED_Init();
Key_Init();
OLED_Init();
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);
OLED_ShowString(1,1,"DataA");
OLED_ShowHexNum(1,8,(uint32_t)DataA,8);
OLED_ShowString(3,1,"DataB");
OLED_ShowHexNum(3,8,(uint32_t)DataB,8);//显示数据保存的地址
while(1)
{
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);
DataA[0]++;
DataA[1]++;
DataA[2]++;
DataA[3]++;
Delay_ms(1000);
MyDMA_Transfer();
}
}
DMA和ADC的四通道配合
#ifndef __AD_H
#define __AD_H
extern uint16_t AD_Value[4];//外部可调用数组
void AD_Init(void);
void AD_GetValue(void);
#endif
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启这个ADC对应的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备,注意这类芯片选择OTHER DEVICE
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择分频,现在是12MHz
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;//调节成AD专属模式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);//ADC1的模式,选择通道,选择规则组列表序列号(第几个菜),采样时间(随便)
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);
//4个通道对应四个序列
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;//ADC1或ADC2单模式
ADC_InitStructure.ADC_NbrOfChannel=4;//通道数,扫描模式,4
ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描还是非扫描
ADC_Init(ADC1,&ADC_InitStructure);
//我找不出来问题的初始代码
/*
//配置DMA
//DMA转运三个条件:计数器大于0,有触发源,DMA使能
DMA_InitTypeDef DMA_InitStructure;
//起始站点(外设)的起始地址,数据宽度,是否自增
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//源头是ADC的寄存器
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//半字,低16位数据
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//不自增,始终转运同一个地址的数据
//寄存器的起始地址,数据宽度,是否自增
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//起始地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//存储器地址自增
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//外设站点作为源头还是目的地,DST目的地,SRC源头
DMA_InitStructure.DMA_BufferSize=4;//传输计数器,4个通道,给4个
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular ;//是否自动重装(循环),注意软件触发和自动重装不能同时用
//一般存储器到存储器不重装,外设到存储器可能要重装
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//是否从存储器到存储器转运(软件触发),这里不用软件触发
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);//第一个参数既选择了是哪个DMA,又选择了通道
//ADC硬件触发只接在了DMA1的通道1上
*/
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStructure; //定义结构体变量
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设基地址,给定形参AddrA
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址自增,选择失能,始终以ADC数据寄存器为源
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器基地址,给定存放AD转换结果的全局数组AD_Value
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //存储器数据宽度,选择半字,与源数据宽度对应
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能,每次转运后,数组移到下一个位置
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
DMA_InitStructure.DMA_BufferSize = 4; //转运的数据大小(转运次数),与ADC通道数一致
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //模式,选择循环模式,与ADC的连续转换一致
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1
DMA_Cmd(DMA1_Channel1,ENABLE);//初始化后,等有触发源满足时工作
ADC_DMACmd(ADC1,ENABLE);//还需开启ADC到DMA的触发源,DMA的1通道有3个触发源
ADC_Cmd(ADC1,ENABLE);//开启ADC
ADC_ResetCalibration(ADC1);//复位校准开始,把某一位置1
while (ADC_GetResetCalibrationStatus(ADC1)==SET);//这个函数为SET返回1,校准进行中,返回0校准完成
ADC_StartCalibration(ADC1);//正式开始校准
while (ADC_GetCalibrationStatus(ADC1)==SET);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//循环模式,需要软件触发一次
}
void AD_GetValue(void)//不需要参数了,转换完成之后,放在AD_value数组里,用于单次扫描,循环没啥用
{
DMA_Cmd(DMA1_Channel1,DISABLE);//使DMA失能以便给传输计数器赋值
DMA_SetCurrDataCounter(DMA1_Channel1,4);
DMA_Cmd(DMA1_Channel1,ENABLE);//重新满足三个条件
//等待ADC转换完成就不需要了
//等待转运完成
while (DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);//没有转运完成,一直等待,这个FLAG转到变量里有不同的返回值,对应完成,失败等
DMA_ClearFlag(DMA1_FLAG_TC1);//手动清除标志位
}
#include "stm32f10x.h" // Device header
#include "Delay.h"//需要引用延时函数
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0,AD1,AD2,AD3;
int main()
{
AD_Init();
OLED_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);
}
}