实验目的

当我们看到ADC的时候是不是想起来躲在辅助后面输出的ADC,但是此ADC非彼ADC。那么我们来看看32的ADC吧。

STM32集成有ADC模数转换器,本章学习利用其采集电压,通过串口打印出来,数据手册请参看第11章。

实验简介

ADC(Analog to Digital Converter),模/数转换器。在模拟信号需要以数字形式处理,存储或传输时,模/数转换器几乎必不可少。

STM32在片上集成的ADC外设非常强大。在STM32F103xC,STM32F103xD和STM32F103xE增强型产品,内嵌3个12位的ADC,每个ADC共用多达21个外部通道,可以实现单次或多次扫描转换。星光STM32开发板用的是STM32F103VET6,属于增强型的CPU,它有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次,连续,扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。

模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阈值。

STM32的ADC最大的转换速率为1MHz,也就是转换时间为1us(在 ADCCLK = 14M,采样周期为1.5个ADC时钟下得到),不要让ADC的时钟超过14M,否则将导致结果准确度下降。

STM32将ADC的转换分为2个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的,同这个类似,注入通道的转换可以打断规则通道的转换,在注入通道被转换完成之后,规则通道才得以继续转换。

通过一个形象的例子可以说明:假如你在家里的院子内放了5个温度探头,室内放了3个温度探头;你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组循环扫描室外的5个探头并显示AD转换结果,当你想看室内的温度时,通过一个按钮启动注入转换组(3个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则和注入组的规划,当你按下按钮后,需要从新配置AD循环扫描通道,然后在释放按钮后需再次配置AD循环扫描的通道。

stm32f103 CubeMx ADC配置_#endif


stm32f103 CubeMx ADC配置_初始化_02


从图中可知,它的参考电压负极是要接地的,即VREF- = 0V。而参考电压正常的范围为 2.4 <= VREF+ <=3.6V,所以STM32的ADC是不能直接测量负电压的,而且其输入的电压信号的范围为: VREF- <= VIN <= VREF+.当需要测量负电压或测量的电压信号超出范围时,要先经过运算电路进行平移或利用电阻分压。

电路设计

星光STM32F103开发板板载1个可调电阻,连接到STM32的PA7引脚,对应ADC的IN7,实现AD转换进行电压采集,电路如下图所示:

stm32f103 CubeMx ADC配置_#include_03

代码

main.c

#include "MyIncludes.h"

char buff[100];
//用来存放电位器电压值的数组
uint16_t Vol;
//电位器电压的变量

u16 sys_cnt = 0;
void systick_isr(void)
{
  if(sys_cnt < 1000)
  {
    sys_cnt++;
  }
  else
  {
    sys_cnt = 0;
    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_4|GPIO_PIN_5);
  }
}


int main(void)
{
  System_Init();
  LED_Init();
  SysTick_Init(systick_isr);
  USART1_Init(115200,NULL,NULL);
  
  STM32_ADC_Init(ADC1,ADC_CHANNEL_7,NULL);
  //ADC初始化(ADC外设1,ADC输入通道7,中断处理回调函数NULL)
  delay_ms(1000);
  while(1)
  {
    Vol = Vol_Sample();
         //电压采样函数
    sprintf(buff,"VOL: %d.%d%dV\n",Vol/1000,Vol%1000/100,Vol%100/10);
    printf(buff);
    delay_ms(1000);
  }
}

adc.h

#ifndef __ADC_H_
#define __ADC_H_

#include "stm32f1xx.h"
#include "stm32_types.h"
#include "stm32f1xx_hal.h"

typedef struct
{
  void(*isr_op)(void);
  //ADC中断处理
}_ADC_ISR_;

extern ADC_HandleTypeDef AdcHandle;

void STM32_ADC_Init(ADC_TypeDef *ADCx,uint32_t Channel,void(*ISR)(void));
//ADC初始化  (ADC外设,ADC输入通道,中断处理回调指针)
uint32_t Vol_Sample(void);
//电压采样
#endif

adc.c

#include "adc.h"

ADC_HandleTypeDef AdcHandle;
//ADC句柄结构变量声明
DMA_HandleTypeDef hdma_adc;
//DMA句柄结构变量声明
_ADC_ISR_  adc_isr;
//ADC中断结构变量声明
#ifdef ADC_DMA_ENABLE 
//使能DMA;
uint32_t aADCxConvertedValues;
#endif

//在HAL_ADC_Init中调用
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  //GPIO初始化结构变量声明
  RCC_PeriphCLKInitTypeDef PeriphClkInit;
  //RCC扩展时钟结构变量声明
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  //要配置的扩展时钟  ADC外围时钟
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
  //ADC时钟源,可以是预分频器的值  PCLK(IO接口时钟)2/6
  HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
  //RCC外围时钟源配置
  __ADC1_CLK_ENABLE();
  //使能ADC1时钟
  __GPIOA_CLK_ENABLE();
   //使能GPIOA时钟
  GPIO_InitStruct.Pin = GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
  //模拟输入模式
  HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
   //GPIO初始化
  #ifdef ADC_DMA_ENABLE
   
  
  __HAL_RCC_DMA1_CLK_ENABLE();
   //使能DMA1时钟
  hdma_adc.Instance = DMA1_Channel1;
  //寄存器基址 
  hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
  //外设到内存方向
  hana_adc.Init.PeriphInc = DMA_PINC_DISABLE;
  //外围增量模式禁用
  hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
  //内存增量模式启用
  hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALING_HALFWORD;
  //外围数据对其 半字
  hdma_adc.Init.MemDataAlignment = DMA_PDATAALINg_HALFWORD;
   //外围数据对其 半字
  hdma_adc.Init.Mode = DMA_CIRCULAR;
  //圆形模式
  hdma_adc.Init.Priority = DMA_PRIORITY_HIGH;
  // 优先级 高
  HAL_DMA_Init(&hdma_adc);
   //DMA初始化
  __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc);
   //将初始化的DMA句柄与ADC句柄关联
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,0,0);
  //设置优先级
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn)
  //设置中断源
  #endif
}

void STM32_ADC_Init(ADC_TypeDef *ADCx,uint32_t Channel,void (*ISR)(void))
{
   ADC_ChannelConfTypeDef sConfig;
   //ADC通道结构变量声明
   adc_isr.isr_op = ISR;
   //挂载中断处理函数
   
   //配置ADC外设 
   AdcHandle.Instance = ADCx;
   //寄存器基址 ADC1
   AdcHandle.Init.ScanConvMode = ADC_SCAN_DISABLE;
   //禁止扫描模式
   AdcHandle.Init.ContinuousConvMode = ENABLE;
   //使能连续转换
   AdcHandle.Init.DiscontinuousConvMode = DISABLE;
   //禁止常规通道的不连续采样模式
   //AdcHandle.Init.NbrOfDiscConversion = 0;
   //不连续采样模式下的转换常规通道数
   AdcHandle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
   //软件启动
   AdcHandle.Init.DataAlign = ADC_DATAALING_RIGHT;
   //右对齐
   AdcHandle.Init.NbrOfConversion = 1;
   //常规通道序列长度,2次转换
    
   HAL_ADC_Init(&AdcHandle);
   //ADC初始化
   
   //配置ADC通道
   sConfig.Channel = Channel;
   //转换通道
   sConfig.Rank = 1;
   //指定常规组序列器的列组
   sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
    //要为选定通道设置的采样时间值
   HAL_ADC_ConfigChannel(&AdcHandle,&sConfig);
    //ADC配置通道
   HAL_ADCEx_Calibration_Start(&AdcHandle);
    //ADC校准
   #ifdef ADC_DMA_ENABLE//使能DMA
   HAL_ADC_Start_DMA(&AdcHandle,&aADCxConvertedValues,sizeof(aADCxConvertedValues));
   #else
   HAL_ADC_Start_IT(&AdcHandle);
   //使能ADC中断,启动ADC
   #endif
}

void ADC_IRQHandle(void)
//ADC中断服务函数
{
 HAL_ADC_IRQHandler(&AdcHandle);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
//转换完成回调,在HAL_ADC_IRQHandler中调用
{
  if(adc_isr.isr_op != NULL )
   adc_isr.isr_op();
}

//转换完成一半回调
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
 
}

//ADC错误回调
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc)
{
 
}

#ifdef ADC_DMA_ENABLE
//使能DMA

void DMA1_Channel1_IRQHandler(void)
DMA中断
{
  HAL_DMA_IRQHandler(&hdma_adc);
}
#endif

//电压采样
uint32_t Vol_Sample(void)
{
  #ifdef ADC_DMA_ENABLE
  //使能DMA
  return (aADCxConvertedValues&0xfff)*3300/4096;
  #else
  uint32_t Vol_ADC_Val;
  Vol_ADC_Val = HAL_ADC_GetValue(&AdcHandle);
  return Vol_ADC_Val*3300/4096;
  //返回电压值扩大1000倍
  #endif
}

实验现象

D5 D6 LED灯闪烁

stm32f103 CubeMx ADC配置_#include_04

寄存器代码

test.c

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include  "adc.h"


int main(void)
{
	u16 adcx;

	Stm32_Clock_Init(9);	
	uart_init(72,115200);	
	delay_init(72);	   	 	
	LED_Init();		  		
  Adc_Init();
	
	while(1)
	{
		LED3 = !LED3;
		adcx = Get_Adc_Average(ADC_CH7,10);
		
		
		
		LED1 = !LED1;
		printf("%d:\r\n",adcx);
		LED2 = !LED2;
		printf("OK\r\n");
	
		delay_ms(250);
	}
}

adc.h

#ifndef __ADC_H
#define __ADC_H	
#include "sys.h"

								  
#define ADC_CH1  1  				
#define ADC_CH7  7 //通道7(连接在PA1)

void Adc_Init(void); 
//ADC通道初始化				
u16  Get_Adc(u8 ch);
//获得某个通道值 				
u16 Get_Adc_Average(u8 ch,u8 times);
//得到某个通道采样的平均值  
#endif

adc.c

#include "adc.h"
#include "delay.h"					   
													   
void  Adc_Init(void)
{    
	//初始化IO口
 	RCC->APB2ENR|=1<<2; 
 	//  使能PORTA时钟
	GPIOA->CRL&=0X0FFFFFFF;
	//PA7 输入
	RCC->APB2ENR|=1<<9;  
	//ADC1时钟使能 
	RCC->APB2RSTR|=1<<9;  
	//ADC1复位
	RCC->APB2RSTR&=~(1<<9);
	//复位结束
	RCC->CFGR&=~(3<<14); 
    //分频因子清零
    //SYSCLK/DIV2=12M ADC时钟设置为12M
    //ADC最大时钟不能超过14M,否则准确度下降 
	RCC->CFGR|=2<<14;   	 
	ADC1->CR1&=0XF0FFFF;
	//工作模式清零  
	ADC1->CR1|=0<<16;   
	//独立工作模式  
	ADC1->CR1&=~(1<<8);  
	//非扫描模式
	ADC1->CR2&=~(1<<1);   
	//单次转换模式 
	ADC1->CR2&=~(7<<17);	   
	ADC1->CR2|=7<<17;	
	//软件控制转换   
	ADC1->CR2|=1<<20; 
	//使用用外部触发,必须使用一个事件来触发     
	ADC1->CR2&=~(1<<11); 
	//右对齐 
	ADC1->SQR1&=~(0XF<<20);
	ADC1->SQR1|=0<<20; 
	//1个转换在规则序列中,也就是只转换规则序列1   	   
	//设置通道1的采样时间
	ADC1->SMPR2&=~(3*1);  
	//通道1采样时间清空
 	ADC1->SMPR2|=7<<(3*1);
 	//通道1 239.5周期,提高采样时间可以提高准确度	 
	ADC1->CR2|=1<<0;	
	//开启AD转换器   
	ADC1->CR2|=1<<3;
	//使能复位校准      
	while(ADC1->CR2&1<<3);
	//等待校准结束
	//该位由软件设置并由硬件清除,在校准寄存器
	//被初始化后该位也被清除 
	ADC1->CR2|=1<<2;   
	//开启AD校准    
	while(ADC1->CR2&1<<2); 
	//等待校准结束
	//该位由软件设置以开始校准,并在校准结束时由硬件清除。
}				  

u16 Get_Adc(u8 ch)   
{
	 //设置转换序列
	ADC1->SQR3&=0XFFFFFFE0;
	//规则序列1 通道ch
	ADC1->SQR3|=ch;		  			    
	ADC1->CR2|=1<<22; 
	//启动规则转换通道     
	while(!(ADC1->SR&1<<1));  
	//等待转换结束
	return ADC1->DR;
	//返回adc值		
}

//获取通道ch的转换值,然后平均
u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);
		delay_ms(5);
	}
	return temp_val/times;
}