目录
1.1 STM32 DMA简介
1.2 STM32 DMA的操作
1.DMA的初始化
2. 初始化代码
3. 主函数代码
本文将向大家介绍 STM32 的 DMA。(如有错误,欢迎批评指正) 在本章中,我们将利用 STM32 的 DMA 来实现ADC1通道1内数据传送,并在 TFTLCD 模块上显示传送数据AD的电压值。本章分为以下学习目标:
1.了解什么是DMA
2.懂得操作STM32的DMA
1.1 STM32 DMA简介
DMA,全称为: Direct Memory Access,即直接存储器访问, DMA 传输将数据从一个地址空间复制到另外一个地址空间。 当 CPU 初始化这个传输动作,传输动作本身是由DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。 DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
STM32 最多有 2 个 DMA 控制器( DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。
STM32的DMA有以下一些特性:
- 每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
- 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。
- 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。
- 支持循环的缓冲器管理。
- 每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。
- 存储器和存储器间的传输。
- 外设和存储器,存储器和外设的传输
- 闪存、 SRAM、外设的 SRAM、 APB1 APB2 和 AHB 外设均可作为访问的源和目标。
- 可编程的数据传输数目:最大为 65536
STM32F103ZET6 有两个 DMA 控制器, DMA1 和 DMA2, 本章我们仅针对 DMA1 进行介绍。
从外设( TIMx、 ADC、 SPIx、 I2Cx 和 USARTx)产生的 DMA 请求,通过逻辑或输入到DMA 控制器,这就意味着同时只能有一个请求有效。外设的 DMA 请求,可以通过设置相应的外设寄存器中的控制位,被独立地开启或关闭。
接下来我们来看一下,DMA 1主要有哪些通道:
这里解释一下上面说的逻辑或,例如通道 1 的几个 DMA1 请求( ADC1、 TIM2_CH3、 TIM4_CH1),这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个。其他通道也是类似的。
接下来我们来看一下,DMA 2主要有哪些通道:
从上图我们看出 STM32 的 DMA 很多,但大致操作时一样的,这里我们主要讲述ADC DMA 的操作方式。
1.2 STM32 DMA的操作
1.DMA的初始化
DMA配置通道的过程为:
不过呢,我们使用 V3.5 库函数操作的话,我们只需要调用 DMA_Init()函数就可以了。
DMA_Init()函数有两个输入参数:
1) DMA_Channelx:用来选择要初始化的通道参数。我们这里以ADC1为例,查看上
面的 DMA1 的通道表,我们知道ADC 1的 DMA 通道是是通道 1,所以打开ADC1的通道:DMA1_Channel1。
2) DMA_InitStruct:这个是用来设置 DAM 参数的结构,它一共有 11 个成员,它的成员如下:
- DMA_PeripheralBaseAddr:外设基地址,也就是你 DMA 要传输数据 的目的地,我们是要使用到ADC1,所以设置为ADC1 的地址:(u32)&ADC1->DR(注意是地址)。
- DMA_MemoryBaseAddr:内存基地址,也就是你 DMA 要搬运数据的存放的首地址,如果你是定义了一组数组发送,那么就设置为你的数据首地址。
- DMA_BufferSize:DMA 通道的缓存大小。即设置一次传输数据量的大小,这个很容易理解。
- DMA_PeripheralInc:是否使能外设地址递增。我们直接使用ADC1的 发 送 寄 存 器 , 地 址 不 递 增 , 所 以 设 置 为 : DMA_PeripheralInc_Disable。
- DMA_MemoryInc:是否使能内存地址递增。我们发送的数据发送完 一 位 , 肯 定 是 发 送 下 一 位 , 所 以 这 里 肯 定 要 使 能 : DMA_MemoryInc_Enable。
- DMA_PeripheralDataSize:设置外设数据宽度,ADC保存数组内数据是16位,所以设置为:DMA_PeripheralDataSize_HalfWord。
- MA_MemoryDataSize : 内 存 数 据 宽 度 , 这 里 也 设 置 为 :DMA_MemoryDataSize_HalfWord。
- DMA_Mode:传送模式。DMA 的工作模式又两种,第 1 种是工作在 循环缓存模式,也就是将数据一直重复发送,第 2 种工作在正常缓存模式,也就是只将数据传送一次。我们这里设置为循环缓存模式:DMA_Mode_Circular。
- DMA_Priority:DMA 的传输顺序是有优先级的,当有两个 DMA 同时使用的时候, 优先级高的先发送。 而 DMA 的等级一共有四种等级:
a) 最高优先级
b) 高优先级
c) 中优先级
d) 低优先级 我们这里只使用一个 DMA,随便设置就好。
- DMA_M2M : 是 否 使 用 存 储 器 到 存 储 器 模 式 , 我 们 设 置 为 DMA_M2M_Disable。
2. 初始化代码
void dma_init() //DMA初始化
{
//结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_ADC1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//12M 最大14M 设置ADC时钟(ADCCLK)
ADC_DeInit(ADC1);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//ADC //1
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); //A
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_239Cycles5); //1
//内部温度传感器是在ADC1通道16的。
// ADC_RegularChannelConfig(ADC1,ADC_Channel_16,1,ADC_SampleTime_239Cycles5);
// ADC_TempSensorVrefintCmd(ENABLE);//打开内部温度传感器使能
ADC_DMACmd(ADC1,ENABLE);//将ADC与DMA链接在一起
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);//重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1));//获取ADC重置校准寄存器的状态
ADC_StartCalibration(ADC1);//开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1));//获取指定ADC的校准程序
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能或者失能指定的ADC的软件转换启动功能
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&ADC1->DR;//DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr=(u32)&adc_data;//DMA内存地址
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//外设作为数据传输的来源
DMA_InitStructure.DMA_BufferSize=1;//指定DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址寄存器递增
DMA_InitStructure.DMA_MemoryInc=DMA_PeripheralInc_Enable;//内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//外设数据宽度16
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//存储数据宽度16
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;//工作在循环缓存模式
DMA_InitStructure.DMA_Priority=DMA_Priority_High;//DMA通道x拥有高优先级
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel1,&DMA_InitStructure); //ADC1在DMA1通道1内
DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA1
}
3. 主函数代码
int main()
{
u16 value;
u8 i,j,dat[6];
float ad;
dma_init(); //DMA初始化
TFT_Init(); //TFT彩屏初始化
LED_Init(); //端口初始化
printf_init(); //printf初始化
TFT_ClearScreen(BLACK); //清屏
GUI_Show12ASCII(10,10,"This is a ADC1-DMA1 Check!",YELLOW,BLACK);
GUI_Show12ASCII(10,27,"PA1 is AD Input!",YELLOW,BLACK);
GUI_Show12ASCII(10,100,"The AD Volage is:",YELLOW,BLACK);
while(1)
{
value=0;
for(i=0;i<10;i++)//读取10次的AD数值取其平均数较为准确
{
value=value+adc_data[0];
}
delay_ms(500);
value=value/10;
ad=value*3.3/4095;
value=(u16)(ad*100);
dat[0]=value/100+0x30;
dat[1]='.';
dat[2]=value%100/10+0x30;
dat[3]=value%100%10+0x30;
dat[4]='V';
dat[5]='\0';
GUI_Show12ASCII(160,100,dat,YELLOW,BLACK);
if(j>1)
{
j=0;
GPIO_SetBits(GPIOC,GPIO_Pin_0);
}
else
{
j++;
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
}
}
}