对于STM32F103来说,它的ADC是12位,一共18个通道,其中16个外部通道,2个内部通道。支持单次,连续以及间断模式扫描。
12位ADC
指的是STM32F103的ADC分辨率具有12位,位数越高采集到的ADC越精准。12位是相对于二进制数来说,也就是“111111111111”,转换为十进制就是4095,其实是0-4095,实际上是4096个数,STM32F103的引脚电压是0-3.3V,12位的ADC就会把0-3.3V切割成4096份。这样转换器得到的ADC值便可以转换为相应电压,设转换器采集到的ADC值为x,实际所求电压为y。那么公式为:y=x/4096*3.3V。
16个外部通道:
f103的芯片上有16个引脚是接到模拟电压上可以进行电压检测的,这16个通道会分给3个转换器,这三个转换器是独立的,也就是我们常见的ADC1,ADC2,ADC3。引脚和通道的对应关系可通过手册或者芯片引脚定义查询。
因为手册的引脚定义表太大,我这里就给出一个开发板原理图上的芯片引脚图。在图中可以看到哪个引脚有ADC通道。
这里挑一个典型进行说明:
比如PC0 标注的是ADC123_IN10,说明该引脚可以作为ADC引脚,而123说明该引脚可以被ADC1.ADC2,ADC3三个转化器共用。10对应上面所说的18个通道中的10通道。
ADC的使用:
我在使用该应用的时候是因为项目需要有一个有线遥控器连接控制板,遥控器上按键很多,如果采用外部中断或者按键扫描这种模式,那么每一个按键都需要连接控制板,造成IO口资源的浪费,同时连接线也会更粗,更贵。于是我们采用双排按键,每一排按键对应一个ADC的IO口,根据每个按键按下的电压不同来判断哪个按键被按下。
废话到此结束,让我们开始吧:
我们采用固件库的方式编写代码,那么就先初始化ADC的端口:
- GPIO_InitTypeDef GPIO_InitStructure;
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA ,ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
复制代码
首先还是走老路,初始化所用的GPIO,此次所用的是PA4和PA5。通过手册可以看到这两个口对应的是ADC1和ADC2,对应通道是通道4和通道5。
于是选择ADC1的通道4和通道5对ADC进行初始化:
- ADC_InitTypeDef ADC_InitStructure;
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_ADC1 ,ENABLE);
- RCC_ADCCLKConfig(RCC_PCLK2_Div6);//12M 最大14M
复制代码
- ADC_DeInit(ADC1); //将外设 ADC1 的全部寄存器重设为缺省值
- ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
- ADC_InitStructure.ADC_ScanConvMode = ENABLE; //连续多通道模式
- ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换
- ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换不受外界决定
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐
- ADC_InitStructure.ADC_NbrOfChannel = 2;//扫描通道数
- ADC_Init(ADC1, &ADC_InitStructure);
- /* ADC1 regular channel10 configuration */
- ADC_RegularChannelConfig(ADC1, ADC_Channel_4,1, ADC_SampleTime_71Cycles5);
- ADC_RegularChannelConfig(ADC1, ADC_Channel_5,2, ADC_SampleTime_71Cycles5);
- /* Enable ADC1 */
- ADC_Cmd(ADC1, ENABLE);
- ADC_DMACmd(ADC1, ENABLE);
- /* Enable ADC1 reset calibaration register */
- ADC_ResetCalibration(ADC1); //复位指定的ADC1的校准寄存器
- /* Check the end of ADC1 reset calibration register */
- while(ADC_GetResetCalibrationStatus(ADC1));//获取ADC1复位校准寄存器的状态,设置状态则等待
- /* Start ADC1 calibaration */
- ADC_StartCalibration(ADC1); //开始指定ADC1的校准状态
- /* Check the end of ADC1 calibration */
- while(ADC_GetCalibrationStatus(ADC1)); //获取指定ADC1的校准程序,设置状态则等待
- /* Start ADC1 Software Conversion */
- ADC_SoftwareStartConvCmd(ADC1, ENABLE);
- ADC_SoftwareStartConvCmd(ADC1, ENABLE);
- DMA_Cmd(DMA1_Channel1, ENABLE); //启动DMA通道
复制代码
参数 ADC_Mode :是用来设置 ADC 的模式 ,ADC的模式很多(如下图),这里选择独立模式:ADC_Mode_Independent
参数ADC_ScanConvMode :规定了模数转换工作在扫描模式(多通道)还是单次(单通道)模式。可以设置这个参数为 ENABLE 或者 DISABLE。
参数ADC_ContinuousConvMode :规定了模数转换工作在连续还是单次模式。可以设置这个参数为 ENABLE 或者 DISABLE。
参数ADC_ExternalTrigConv :定义了使用外部触发来启动规则通道的模数转换 ,可取值如下图
参数ADC_DataAlign :规定了 ADC 数据向左边对齐还是向右边对齐。
参数ADC_NbreOfChannel :规定了顺序进行规则转换的 ADC 通道的数目 。我这里只用了双通道,所以为2.
函数ADC_RegularChannelConfig():这一步比较重要,上面我们初始化了ADC,但是并未指定引脚,也就是规则组通道,这里设置有规则组通道和转化顺序以及采样时间。根据上面所说,我们选择ADC1,通道4和5,顺序是通道4第一个采样,通道5第二个采样。ADC_SampleTime 设定了通道的 ADC 采样时间,可选参数如下:
DMA设置:
- DMA_InitTypeDef DMA_InitStructure;
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟
- DMA_DeInit(DMA1_Channel1);
- DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&ADC1->DR; //DMA外设ADC基地
- DMA_InitStructure.DMA_MemoryBaseAddr =(u32)&ADCConvertedValue;//内存地址
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//dma传输方向单向
- DMA_InitStructure.DMA_BufferSize = 100*2;//设置DMA在传输时缓冲区的长度 word
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设置DMA的外设递增模式,一个外设
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//设置DMA的内存递增模式,
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据字长
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存数据字长
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//设置DMA的传输模式:连续不断的循环模式
- DMA_InitStructure.DMA_Priority = DMA_Priority_High;//设置DMA的优先级别
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//设置DMA的2个memory中的变量互相访问
- DMA_Init(DMA1_Channel1, &DMA_InitStructure);
-
- DMA_Cmd(DMA1_Channel1, ENABLE);
复制代码
因为我在代码中做了备注,这里踢几个人比较重要的点:
参数DMA_BufferSize :设置DMA在传输时缓冲区的长度 ,这里要提一下我的数据储存数组u16 ADCConvertedValue[100][2];因为采用两个通道,所以需要两个缓冲保存数据,100*2=200,DMA在运行是就会采集200个ADC值保存在数组中,100个通道4的值,100个通道5的值。取值处理会在后面介绍。
此项设置完之后DMA就回不停地采集这两个ADC通道中的ADC值并保存在数组中。我们只需要在需要的时候读取数组中的值便可。
读取ADC值并做处理
- int sum;
- int sum1=0;
- int sum2=0;
-
- int value1[10];
- int value2[10];
- int isSorted,temp1,temp2,k,p;
- float a;
- int i,j,c;
- float ADC_Value[2];//用来保存经过转换得到的电压值
-
- for(c=0;c<10;c++){
- for(i=0;i<2;i++)
- {
- sum=0;
- for(j=0;j<100;j++)
- {
- sum+=ADCConvertedValue[j][i];
- }
- ADC_Value[i]=(float)sum*3.3/4096/100;//求平均值并转换成电压值
- }
- value1[c]=(int)(ADC_Value[0]*100);//4通道电压值扩大100
- value2[c]=(int)(ADC_Value[1]*100);//5通道电压值扩大100
- }
复制代码
|
C文件:
#include "stm32f10x.h"
#include "adc.h"
//#include "oscilloscope.h"
//#include "ucos_ii.h"
//#include "tft_api.h"
/*-----------------------------------------
声明变量
------------------------------------------*/
//extern WaveType WaveInfo;
//extern WaveMode WaveCtrl;
u16 i;
u16 ADCV1[SAMPDEPTH];
u16 ADCV2[SAMPDEPTH];
volatile u32 ADCConvertedValue[SAMPDEPTH];//AD转换缓冲区,占用RAM 0.8KB ADC1对应低16位,ADC2对应高16位
//uint32_t ADCConvertedValue[SAMPDEPTH];
/*-----------------------------------------
ADC1端口初始化
------------------------------------------*/
void ADC1_GPIO_Init(void)
{
GPIO_InitTypeDef IO_Init;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2|RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置 ADC 分频因子6, 2M/6=12,ADC 最大时间不能超过 14M
//C1模拟输入
IO_Init.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4;
IO_Init.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA,&IO_Init);
}
/* #define ADC1_DR_Address ((unsigned int)0x40012400+0x4c) */
/* unsigned short int ADC1_DMA_Value; */
/*--------------------------------------------------
函数说明:配置ADC1完成AD采集,是本示波器最核心的部分.
配置方案:ADC1由TIM3提供的触发事件进行触发AD转换,控
制TIM3的速率就可以完成采样率的改变.同时采
用DMA完成数据传输,注入通道1完成内部温度传
感器AD采集.
--------------------------------------------------*/
void ADC1_Mode_Config(void)
{
//配置DMA
DMA_InitTypeDef DMA_csh;
ADC_InitTypeDef ADC_csh;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/*
DMA_MemoryDataSize_Byte 数据宽度为 8 位
DMA_MemoryDataSize_HalfWord 数据宽度为 16 位
DMA_MemoryDataSize_Word 数据宽度为32 位
*/
DMA_DeInit(DMA1_Channel1); //DMA复位,通道1
DMA_csh.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC1地址
// DMA_csh.DMA_MemoryBaseAddr = (unsigned int)ADCConvertedValue; //内存地址
DMA_csh.DMA_MemoryBaseAddr = (uint32_t)ADCConvertedValue; //内存地址
DMA_csh.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_csh.DMA_BufferSize = SAMPDEPTH; //缓冲大小为采样深度
DMA_csh.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定
DMA_csh.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址自增
DMA_csh.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //32位
DMA_csh.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //32位
DMA_csh.DMA_Mode = DMA_Mode_Circular; //循环传输
DMA_csh.DMA_Priority = DMA_Priority_High; //DMA优先级高
DMA_csh.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1,&DMA_csh); //写入DMA1配置参数
DMA_Cmd(DMA1_Channel1,ENABLE); //使能DMA1通道1
DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE); //使能DMA CH1中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn; //
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //
NVIC_Init(&NVIC_InitStructure);
//配置TIM3工作在72MHz,为AD提供触发
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); // TIM3时钟使能
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); //
TIM_TimeBaseStructure.TIM_Period = 35; //arr 设置自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = 99; //psc 设置时钟频率除数的预分频值;每格最大值时,不至于溢出
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Update); //使用TIM3事件更新作为ADC触发
//配置ADC1
/*
ADC_Mode_Independent ADC1 和ADC2 工作在独立模式
ADC_Mode_RegInjecSimult ADC1 和ADC2 工作在同步规则和同步注入模式
ADC_Mode_RegSimult_AlterTrig ADC1 和ADC2 工作在同步规则模式和交替触发模式
ADC_Mode_InjecSimult_FastInterl ADC1 和ADC2 工作在同步规则模式和快速交替模式
ADC_Mode_InjecSimult_SlowInterl ADC1 和ADC2 工作在同步注入模式和慢速交替模式
ADC_Mode_InjecSimult ADC1 和ADC2 工作在同步注入模式
ADC_Mode_RegSimult ADC1 和ADC2 工作在同步规则模式
ADC_Mode_FastInterl ADC1 和ADC2 工作在快速交替模式
ADC_Mode_SlowInterl ADC1 和ADC2 工作在慢速交替模式
ADC_Mode_AlterTrig ADC1 和ADC2 工作在交替触发模式
*/
ADC_csh.ADC_Mode = ADC_Mode_RegSimult; //ADC1 和ADC2 工作在同步规则模式
ADC_csh.ADC_ScanConvMode = DISABLE; //关闭扫描模式
ADC_csh.ADC_ContinuousConvMode = DISABLE; //连续AD转换开启
ADC_csh.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; //由TIM3提供的触发事件进行触发AD转换
ADC_csh.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_csh.ADC_NbrOfChannel = 1; //要转换的通道数目1
ADC_Init(ADC1,&ADC_csh); //写入ADC1配置参数
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_1Cycles5);//采样速率1M
// ADC_DMACmd(ADC1,ENABLE); //使能ADC1 DMA
ADC_ExternalTrigConvCmd(ADC1,ENABLE); //打开ADC1外部触发
/******配置ADC2**********/
ADC_csh.ADC_Mode = ADC_Mode_RegSimult; //双通道同时采样
ADC_csh.ADC_ScanConvMode = DISABLE; //关闭扫描模式
ADC_csh.ADC_ContinuousConvMode = DISABLE; //连续AD转换开启
ADC_csh.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //软件触发转换
ADC_csh.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_csh.ADC_NbrOfChannel = 1; //要转换的通道数目1
ADC_Init(ADC2,&ADC_csh); //写入ADC1配置参数
ADC_RegularChannelConfig(ADC2,ADC_Channel_1,1,ADC_SampleTime_1Cycles5);//采样速率1M
ADC_ExternalTrigConvCmd(ADC2,ENABLE); //打开ADC1外部触发
/*******************/
/* ADC_InjectedChannelConfig(ADC1,ADC_Channel_16,1,ADC_SampleTime_239Cycles5); //配置ADC1通道16为注入通道1
ADC_ExternalTrigInjectedConvConfig(ADC1,ADC_ExternalTrigInjecConv_None); //软件触发注入通道的转换
ADC_TempSensorVrefintCmd(ENABLE); //使能温度传感器
*/
ADC_DMACmd(ADC1,ENABLE); //使能ADC1 DMA
ADC_Cmd(ADC1,ENABLE); //使能ADC1
ADC_ResetCalibration(ADC1); //复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); //等待校准寄存器复位完成
ADC_StartCalibration(ADC1); //开始校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准完成
ADC_Cmd(ADC2,ENABLE);
ADC_ResetCalibration(ADC2); //复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC2)); //等待校准寄存器复位完成
ADC_StartCalibration(ADC2); //开始校准
while(ADC_GetCalibrationStatus(ADC2)); //等待校准完成
TIM_Cmd(TIM3,ENABLE); //使能 TIM3
}
/*-----------------------------------------
函数说明:通过注入通道1转换内部温度传感器
AD值,读取10次有效数据取平均返回
------------------------------------------*/
u16 GetTempSensor(void)
{
u16 temp=0,i,k=0;
for(i=0; i<10; )
{
ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE); //软件触发注入通道的转换
k = (0x6EE - ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1)) / 0x05 + 25;
if(k>0 && k<100)
{
temp += k;
i++;
}
}
temp /= 10;
return temp;
}
/*-----------------------------------------
函数说明:ADC1初始化
------------------------------------------*/
void ADC1_Init(void)
{
ADC1_GPIO_Init();
ADC1_Mode_Config();
}
/*-----------------------------------------
函数说明:ADC传输DMA通道1中断服务程序
DMA把AD值传输到缓冲区完成后关闭定
时器3(作为触发AD转换的定时器)同时
置更新完成标志位为1,开定时器3在应
用中开启.
------------------------------------------*/
void DMA1_Channel1_IRQHandler()
{
// OSIntNesting++;
DMA_ClearFlag(DMA1_FLAG_TC1); //清除DMA传输完成中断
TIM_Cmd(TIM3,DISABLE); //关闭TIM3
// WaveCtrl.UpdatTrue = 1; //已经完成一次波形FIFO,可以批量读出数据
// OSIntExit();
for(i=0;i<SAMPDEPTH;i++)
{
ADCV2=ADCConvertedValue>>16;
ADCV1=ADCConvertedValue & 0xFFFF;
}
TIM_Cmd(TIM3,ENABLE); //使能 TIM3
}
/*********************************************************************/
H文件:
/*********************************************************************/
#ifndef __ADC_H
#define __ADC_H
//#define ADC1_DR_Address ((unsigned int)0x40012400+0x4c)
#define ADC1_DR_Address ((uint32_t)0x4001244C)
#define SAMPDEPTH 256
void ADC1_GPIO_Init(void);
void ADC1_Mode_Config(void);
void ADC1_Init(void);
unsigned short int GetTempSensor(void);
void Earse_AD_FIFO(void);
#endif