Bootstrap

STM32F4 ADC+DMA单通道采集

背景:对锂电池电压进行采集,由于电池电压为12V,已经提前对12V进行分压,保证ADC采集电压的范围为0~3.3V。对电池电压的采集不用太过频繁,循环模式下的ADC+DMA对一直采集电压浪费资源。于是想着程序设计为在我需要的时候采集一次就可以了。

使用STM32F429,野火的标准库。

主要问题是DMA单次采集之后怎么配合ADC再次启动的问题,网上资料比较多。但尝试了半天,我自己摸索出了一套

.h文件

#ifndef __BSP_ADC_H
#define	__BSP_ADC_H

#include "stm32f4xx.h"
#include <stdbool.h>

#define  FILTER_COUNT  10  //一次采10个数

typedef struct{
__IO uint16_t adc_ConvertedValue[FILTER_COUNT];  //原始数据
		
bool adc_read_f;				 	             //判断完成标志位								 
}VOL_ADC_ST;

// ADC GPIO 
#define VOL_ADC_GPIO_PORT   GPIOC
#define VOL_ADC_GPIO_PIN    GPIO_Pin_3
#define VOL_ADC_GPIO_CLK    RCC_AHB1Periph_GPIOC

// ADC 
#define VOL_ADC          		ADC1
#define VOL_ADC_CLK         RCC_APB2Periph_ADC1
#define VOL_ADC_CHANNEL     ADC_Channel_13

// ADC DR寄存器
#define VOL_ADC_DR_ADDR    ((u32)ADC1+0x4c)

// ADC DMA
#define VOL_ADC_DMA_CLK      RCC_AHB1Periph_DMA2
#define VOL_ADC_DMA_CHANNEL  DMA_Channel_0
#define VOL_ADC_DMA_STREAM   DMA2_Stream0

extern VOL_ADC_ST voltage_adc_data;

void VOL_ADC_Init(void);
void Get_Voltage(void);

#endif /* __BSP_ADC_H */

.c文件

#include "./adc/bsp_adc.h"
#include <stdio.h>

//原始数据结构体
VOL_ADC_ST voltage_adc_data;


static void VOL_ADC_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	// 使能 GPIO 时钟
	RCC_AHB1PeriphClockCmd(VOL_ADC_GPIO_CLK, ENABLE);
		
	GPIO_InitStructure.GPIO_Pin = VOL_ADC_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;	    
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; 
	GPIO_Init(VOL_ADC_GPIO_PORT, &GPIO_InitStructure);		
}

static void VOL_ADC_Mode_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
	
	// ------------------NVIC Init --------------------------
	NVIC_InitStructure.NVIC_IRQChannel=DMA2_Stream0_IRQn; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 2;   //可以自由定义                  
	NVIC_InitStructure.NVIC_IRQChannelSubPriority= 2;                            
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	

  // ------------------DMA Init 初始化配置--------------------------
  // ADC1使用DMA2,数据流0,通道0 
  RCC_AHB1PeriphClockCmd(VOL_ADC_DMA_CLK, ENABLE); 
  // 外设地址
  DMA_InitStructure.DMA_PeripheralBaseAddr = VOL_ADC_DR_ADDR;	
  // 存储器地址	
  DMA_InitStructure.DMA_Memory0BaseAddr = (u32)(voltage_adc_data.adc_ConvertedValue);  
  // 传输方向
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;	
  // 一次传输的数据量
  DMA_InitStructure.DMA_BufferSize = FILTER_COUNT;	
  // 外设地址不自增
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  // 存储器地址自增
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
  // 半字
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 
  // 半字
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;	
  // 单次传输
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; 
  // 只使用DMA一个通道无影响
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  // 禁止使用FIFO
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;  
  // FIFO禁止,不用配置
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
  // DMA通道
  DMA_InitStructure.DMA_Channel = VOL_ADC_DMA_CHANNEL; 
  // 初始化
  DMA_Init(VOL_ADC_DMA_STREAM, &DMA_InitStructure);
  // 使能DMA流
  DMA_Cmd(VOL_ADC_DMA_STREAM, ENABLE);
  // 配置DMA传输完成中断
  DMA_ClearFlag(VOL_ADC_DMA_STREAM,DMA_IT_TC);
  DMA_ITConfig(VOL_ADC_DMA_STREAM,DMA_IT_TC,ENABLE);
	
  // 开启ADC时钟
  RCC_APB2PeriphClockCmd(VOL_ADC_CLK , ENABLE);
  // -------------------ADC Common 结构体初始化------------------------------
  // 独立ADC模式
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  // 时钟分频
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
  // 禁止DMA直接访问	
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  // 采样时间间隔
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;  
  ADC_CommonInit(&ADC_CommonInitStructure);
	
  // ----------------------ADC Init 结构体初始化------------------------------
  ADC_StructInit(&ADC_InitStructure);
  // ADC分辨率
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  // 禁止扫描,多通道使用
  ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
  // 连续转换	
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 
  // 禁止外部边沿触发
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  // 外部触发通道,本例子使用软件触发,此值随便
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
  // 数据右对齐
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  // 转换通道1个
  ADC_InitStructure.ADC_NbrOfConversion = 1;                                    
  ADC_Init(VOL_ADC, &ADC_InitStructure);
  //---------------------------------------------------------------------------
	
  // 配置ADC通道转换顺序为1,第一个转换,采样时间为3个时钟周期
  ADC_RegularChannelConfig(VOL_ADC, VOL_ADC_CHANNEL, 1, ADC_SampleTime_56Cycles);

  // 使能DMA请求 after last transfer (Single-ADC mode)
  ADC_DMARequestAfterLastTransferCmd(VOL_ADC, ENABLE);  //有数据转换完,自动生成DMA请求
  // 使能ADC DMA
  ADC_DMACmd(VOL_ADC, ENABLE);
	
  // 使能ADC
  ADC_Cmd(VOL_ADC, ENABLE);  
  // 开始ADC转换
  ADC_SoftwareStartConv(VOL_ADC);
}


void VOL_ADC_Init(void)
{
	VOL_ADC_GPIO_Config();
	VOL_ADC_Mode_Config();
}

void Get_Voltage(void)
{
	uint32_t avg_vol = 0;
	uint8_t i;
	float ADC_Vol = 0;
	
	if(!voltage_adc_data.adc_read_f) //DMA未转换完毕退出
	{
		return;
	}
	
	for(i = 0; i < FILTER_COUNT; i++) //均值滤波
	{
		avg_vol += voltage_adc_data.adc_ConvertedValue[i];
	}
	avg_vol /= FILTER_COUNT;
	
	
	ADC_Vol =(float) avg_vol/4096*(float)3.3; // 转换为电压值
		
	printf("The current AD value = %f V \n",ADC_Vol); 打印电压值

	//重新配置 DMA and ADC
    //注释的这两行其他网上资料说需要加,我测了不加这里也可以
	//VOL_ADC_DMA_STREAM->M0AR = (u32)(voltage_adc_data.adc_ConvertedValue);
	//DMA_SetCurrDataCounter( VOL_ADC_DMA_STREAM, 5); 	
	DMA_Cmd(VOL_ADC_DMA_STREAM,ENABLE);							
	ADC_Cmd(VOL_ADC, ENABLE);
	ADC_SoftwareStartConv(VOL_ADC);

	voltage_adc_data.adc_read_f = false;

}

中断函数

//DMA转换完毕,需要关闭ADC,并配置标志位
void DMA2_Stream0_IRQHandler(void) 
{
	if(DMA_GetITStatus(VOL_ADC_DMA_STREAM, DMA_IT_TCIF0) == SET)  
	{
		ADC_Cmd(VOL_ADC, DISABLE);

		voltage_adc_data.adc_read_f = true;
		
		DMA_ClearITPendingBit(VOL_ADC_DMA_STREAM, DMA_IT_TCIF0); 
	}
}

main函数

while(1)里面循环调用函数判断标志置即可

static void Delay(__IO uint32_t nCount)	 //延时
{
	for(; nCount != 0; nCount--);
}


int main(void)
{		
  Debug_USART_Config();
  VOL_ADC_Init();	
  while (1)
  {
	 Delay(0x22fffee); 
	 Get_Voltage();
  }
}

其实,这个需求可以不用配置DMA和中断。配置为普通ADC,需要的时候读一下电压就行。

就是突发奇想折腾一下DMA

另外说一点,配置DMA初始化中的 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 将DMA中断相关部分全删掉就是 对ADC的快速循环采集

;