STM32F103ADC配置(ADC1和ADC2)
STM32F103拥有2~3个ADC,这些ADC各含16各通道。虽然数量繁多但是在配置上很费时间,为了让读者更好的调用这些ADC,我将ADC1和ADC2归并到同一个函数里面,这样读者在使用ADC时直接调用即可!
头文件代码如下
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
/*
STM32F103RC系列只有adc1/2
ADC1/2_IN0 PA0
ADC1/2_IN1 PA1
ADC1/2_IN2 PA2
ADC1/2_IN3 PA3
ADC1/2_IN4 PA4
ADC1/2_IN5 PA5
ADC1/2_IN6 PA6
ADC1/2_IN7 PA7
ADC1/2_IN8 PB0
ADC1/2_IN9 PB1
ADC1/2_IN10 PC0
ADC1/2_IN11 PC1
ADC1/2_IN12 PC2
ADC1/2_IN13 PC3
ADC1/2_IN14 PC4
ADC1/2_IN15 PC5
*/
#define PX(PX_n) (PX_n>>4) //获取ADC组号号
#define PN(PX_n) (PX_n&0x0f)//获取ADC通道号
typedef enum
{
ADC1_CH0,
ADC1_CH1,
ADC1_CH2,
ADC1_CH3,
ADC1_CH4,
ADC1_CH5,
ADC1_CH6,
ADC1_CH7,
ADC1_CH8,
ADC1_CH9,
ADC1_CH10,
ADC1_CH11,
ADC1_CH12,
ADC1_CH13,
ADC1_CH14,
ADC1_CH15,//16
ADC2_CH0,
ADC2_CH1,
ADC2_CH2,
ADC2_CH3,
ADC2_CH4,
ADC2_CH5,
ADC2_CH6,
ADC2_CH7,
ADC2_CH8,
ADC2_CH9,
ADC2_CH10,
ADC2_CH11,
ADC2_CH12,
ADC2_CH13,
ADC2_CH14,
ADC2_CH15,//32
} ADCx_CHn;
//void Adc_Init(void);
void ADC_Init(ADCx_CHn n);
u16 Get_Adc(ADCx_CHn n);
u16 Get_Adc_Average(ADCx_CHn n,u8 times);
float ADC_Result(ADCx_CHn n);
#endif
头文件主要在ADCx_CHn结构体里面包含了所有的adc通道,方便源文件的调用,这里不多做介绍
初始化代码如下:
u16 ADC_PN[16]={2,2,2,2,2,2,2,2,3,3,4,4,4,4,4,4};
void ADC_Init(ADCx_CHn n)
{
u8 PX,PN;
//获取ADC组、通道
PX=PX(n);//获取组ADCn PX_n/16
PN=PN(n);//获取通道CHn PX_n%16
RCC->APB2ENR|=1<<ADC_PN[PN];//初始化引脚时钟
//引脚初始化,模拟输入模式
if(PN>=0 && PN<=7)//PA
{
GPIOA->CRL&=~(3<<PN);
}
else if(PN==8 || PN==9)//PB
{
GPIOB->CRL&=~(3<< (PN-8) );
}
else //PC
{
GPIOC->CRL&=~(3<< (PN-10) );
}
//ADC初始化 设置
RCC->APB2ENR|=1<<(PX+9);//ADC时钟使能
RCC->APB2RSTR|=1<<(PX+9);//ADC复位
RCC->APB2RSTR&=~( 1<<(PX+9) );//复位结束
RCC->CFGR&=~(3<<14); //分频因子清零//SYSCLK/DIV2=12M ADC时钟设置为12M,ADC最大时钟不能超过14M!//否则将导致ADC准确度下降!
RCC->CFGR|=2<<14; //6分频bit14,bit15
switch(PX)
{
case 0:
{
ADC1->CR1&=0XF0FFFF; //中文参考手册P171:位19:16 DUALMOD[3:0]:双模式选择
ADC1->CR1|=0<<16; //选择独立工作模式 0000:独立模式 (其实这里没必要在重复设置,为了严谨有必要加上这个)
ADC1->CR1&=~(1<<8); //非扫描模式 位8 SCAN:扫描模式 (Scan mode) 1:使用扫描模式
ADC1->CR2&=~(1<<1); //P174 位1 CONT:连续转换 1:连续转换模式
ADC1->CR2&=~(7<<17); // 位19:17 EXTSEL[2:0]:选择启动规则通道组转换的外部事件
ADC1->CR2|=7<<17; //软件控制转换 111: SWSTART
ADC1->CR2|=1<<20; //使用用外部触发 1:使用外部事件启动转换
ADC1->CR2&=~(1<<11); //右对齐
ADC1->SQR1&=~(0XF<<20);//位23:20 L[3:0]:规则通道序列长度 1111: 16个转换
ADC1->SQR1|=0<<20; //1个转换在规则序列中 也就是只转换规则序列1
//设置通道1的采样时间
ADC1->SMPR2&=~(7<<3); //通道1采样时间清空
ADC1->SMPR2|=7<<3; //通道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); //等待校准结束
//该位由软件设置以开始校准,并在校准结束时由硬件清除
break;
}
case 1:
{
ADC2->CR1&=0XF0FFFF;
ADC2->CR1|=0<<16; //选择独立工作模式
ADC2->CR1&=~(1<<8); //非扫描模式
ADC2->CR2&=~(1<<1); //单次转换模式
ADC2->CR2&=~(7<<17);
ADC2->CR2|=7<<17; //软件控制转换
ADC2->CR2|=1<<20; //使用用外部触发(SWSTART)!!! 必须使用一个事件来触发
ADC2->CR2&=~(1<<11); //右对齐
ADC2->SQR1&=~(0XF<<20);
ADC2->SQR1|=0<<20; //1个转换在规则序列中 也就是只转换规则序列1
//设置通道1的采样时间
ADC2->SMPR2&=~(7<<3); //通道1采样时间清空
ADC2->SMPR2|=7<<3; //通道1 239.5周期,提高采样时间可以提高精确度
ADC2->CR2|=1<<0; //开启AD转换器
ADC2->CR2|=1<<3; //使能复位校准
while(ADC2->CR2&1<<3); //等待校准结束
//该位由软件设置并由硬件清除。在校准寄存器被初始化后该位将被清除。
ADC2->CR2|=1<<2; //开启AD校准
while(ADC2->CR2&1<<2); //等待校准结束
//该位由软件设置以开始校准,并在校准结束时由硬件清除
break;
}
}
}
这时adc.c文件中的初始化部分,此部分可以初始化adc1/2的所有通道,(简例:ADC_Init(ADC1_CH0);这里直接初始化了adc1的通道0),这样就完成了初始化,读者调用起来十分方便,这样一来减少了开发时的不必要麻烦,把时间精力都集中在算法开发上。这段代码的讲解在注释上有所体现就不一一讲解。
下面是adc获取函数:
u16 Get_Adc(ADCx_CHn n)
{
u8 PX,PN;
//获取ADC组、通道
PX=PX(n);//获取组ADCn PX_n/16
PN=PN(n);//获取通道CHn PX_n%16
//设置转换序列
switch(PX)
{
case 0:
{
ADC1->SQR3&=0XFFFFFFE0;//规则序列1 通道ch
ADC1->SQR3|=PN;
ADC1->CR2|=1<<22; //启动规则转换通道
while(!(ADC1->SR&1<<1));//等待转换结束
return ADC1->DR; //返回adc值
break;
}
case 1:
{
ADC2->SQR3&=0XFFFFFFE0;//规则序列1 通道ch
ADC2->SQR3|=PN;
ADC2->CR2|=1<<22; //启动规则转换通道
while(!(ADC2->SR&1<<1));//等待转换结束
return ADC2->DR; //返回adc值
break;
}
}
}
调用这段函数可以直接获取adc通道的值。
在这里我们鉴介正点原子写的取平均函数,如下
u16 Get_Adc_Average(ADCx_CHn n,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(n);
delay_ms(5);
}
return temp_val/times;
}
最后,我们直接将上述两端函数直接集成到一个函数中
float ADC_Result(ADCx_CHn n)
{
u16 adcx;
float temp;
adcx=Get_Adc_Average(n,10);
temp=(float)adcx*(3.3/4096);//此时temp就是电压值
return temp;
}
在工程中调用此函数即可立即获取adc值,不过值得注意的时该函数返回的是float型的值!!!
在主函数这样调用即可:
float temp;
ADC_Init(ADC1_CH0);//初始化
while(1)
{
temp=ADC_Result(ADC1_CH1);//将adc储存在temp中
}