STM32 ADC模块+DMA传输
一、ADC的电压采集范围
ADC 输入范围为: VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、 VDDA 、 VSSA、这四个外部引脚决定。我们在设计原理图的时候一般把 VSSA 和 VREF- 接地,把 VREF+ 和 VDDA 接 3V3,得到 ADC 的输入电压范围为: 0~3.3V
二、输入通道的选择
我们确定好 ADC 输入电压之后,那么电压怎么输入到 ADC?这里我们引入通道的概念, STM32 的ADC 多达 19 个通道,其中外部的 16 个通道就是框图中的 ADCx_IN0、 ADCx_IN1… ADCx_IN5。这 16 个通道对应着不同的 IO 口,具体是哪一个 IO 口可以从手册查询到。
外部的 16 个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有 16 路,注入通道最多有 4 路。
规则通道
规则通道:顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通道,或者应该说我们用到的都是这个通道,没有什么特别要注意的可讲。
注入通道
注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种通道。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。
三、通过ADC采集PC3引脚的电压值,并通过串口发送到串口助手中显示
首先使用到中断函数
ADC初始化代码,我们这里开启转换完成中断,也就是ADC每次转换完成之后,会产生一个中断,在中断里边要及时将转换结果取走,否则下次转换完成时会覆盖本次的转换结果。
GPIO_InitTypeDef gpio_info;
ADC_CommonInitTypeDef adc_common_info;
ADC_InitTypeDef adc_init_info;
NVIC_InitTypeDef NVIC_InitStructure;
//初始化 ADC
//1.初始化引脚 PC3
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
gpio_info.GPIO_Mode = GPIO_Mode_AN;
gpio_info.GPIO_OType = GPIO_OType_PP;
gpio_info.GPIO_Pin = GPIO_Pin_3;
gpio_info.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_info.GPIO_Speed = GPIO_High_Speed;
GPIO_Init(GPIOC,&gpio_info);
//初始化ADC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
//2.初始化ADC common
adc_common_info.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //不适用DMA
adc_common_info.ADC_Mode = ADC_Mode_Independent; //独立模式
adc_common_info.ADC_Prescaler = ADC_Prescaler_Div6;
adc_common_info.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_10Cycles;
ADC_CommonInit(&adc_common_info);
//3.初始化ADC
//使用连续转换模式,MCU会自动进行多次转换
adc_init_info.ADC_ContinuousConvMode = ENABLE;
//数据右对齐
adc_init_info.ADC_DataAlign = ADC_DataAlign_Right;
//外部触发源
adc_init_info.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
//不适用外部触发源
adc_init_info.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//只转换PC3一个通道,所以写1
adc_init_info.ADC_NbrOfConversion = 1;
adc_init_info.ADC_Resolution = ADC_Resolution_12b;
//只有一个采集通道,不用扫描
adc_init_info.ADC_ScanConvMode = DISABLE;
ADC_Init(ADC1,&adc_init_info);
//4.配置转换通道
//PC3引脚对应的是第13通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_13,1,ADC_SampleTime_56Cycles);
//5.开启中断,这样当ADC转换完成之后能立即通知用户
ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE);
//5.1配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//6.使能ADC
ADC_Cmd(ADC1,ENABLE);
//7.开始进行转换
ADC_SoftwareStartConv(ADC1);
四、下面我们使用定时器触发+DMA传输
1.使用定时器触发ADC采集
设置一个定时器,定时器时间到之后自动触发ADC进行一次采集,配置定时器的时间,就可以精确的控制ADC采集的间隔,ADC的触发源可以有多种,如下图黑色框所示:
这里我们使用定时器3进行触发,配置代码如下:
因为使用的是定时器触发,原来的ADC初始化参数需要修改,修改的内容如下:
adc_init_info.ADC_ContinuousConvMode = DISABLE; //因为使用定时器触发,定时器定时时间到就触发一次,所以不使用连续转换
adc_init_info.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; //外部触发源选择定时器3
ADC_ITConfig(ADC1,ADC_IT_EOC,DISABLE); //不使用中断
//ADC_SoftwareStartConv(ADC1); //因为使用定时器触发,所以不再使用软件启动转换
2.使用DMA进行数据传输
2.1DMA(Direct Memory Access, 直接存储区访问) 为实现数据高速在外设寄存器与存储器之间或者存储器与存储器之间传输提供了高效的方法。之所以称之为高效,是因为 DMA 传输实现高速数据移动过程无需任何 CPU 操作控制。从硬件层次上来说,DMA 控制器是独立于 Cortex-M4 内核的,有点类似 GPIO、USART 外设一般,只是 DMA 的功能是可以快速移动内存数据。
2.2 STM32F4xx 系列资源丰富,具有两个 DMA 控制器,同时外设繁多,为实现正常传输,DMA 需要通道选择控制。每个 DMA 控制器具有 8 个数据流,每个数据流对应 8 个外设请求。在实现 DMA传输之前,DMA 控制器会通过 DMA 数据流 x 配置寄存器 DMA_SxCR(x 为 0~7,对应 8 个 DMA数据流) 的 CHSEL[2:0] 位选择对应的通道作为该数据流的目标外设。
2.3我们需要将ADC外设的数据搬运到内存中,ADC对应的是DMA2的通道0,我们要分析声音的频率,就要采集一小段连续的声音,所以我们在内存中设置一个长度为1024的数组,让DMA将ADC转换结果搬运到这个数组中。
我们可以使用DMA传输完成中断,当DMA搬运的数据个数达到了设置的长度,会产生一个DMA传输完成中断:
void DMA2_Stream0_IRQHandler()
{
if(DMA_GetITStatus(DMA2_Stream0,DMA_IT_TCIF0))
{
uint16_t i = 0;
for(i=0;i<1024;i++)
{
printf("%d\r\n",adc_value_buf[i]);
}
DMA_ClearITPendingBit(DMA2_Stream0,DMA_IT_TCIF0);
}
}
作者:物联网应用技术2 韦福将