前言
个人认为,ADC是stm32中最核心的功能之一,因为stm32所处理的信号是数字信号,而现实生活中所接触的大部分是模拟信号,因此需要对模拟信号进行采样使其变成数字信号后再对其进行处理,也是大部分电子信息相关专业本科所学的信号处理方法得以运用的关键步骤之一,由于本人最近在做AD相关的项目,因此在这里记载一下学习过程和感悟。本次用的单片机是ch32,与stm32相似,希望能为也在学习的朋友提供参考。
一、ADC是什么?
ADC 代表模数转换,它用于将模拟值从现实世界转换为数字值,只有经过AD才能将现实生活中的模拟信号转变为可以经由单片机处理的数字信号,因此对于数字信号处理有着先决作用。
二、ADC的主要功能模块
1.从功能框图开始
首先,要先清除ADC的量化范围,即ADC能把多大范围内的模拟信号转换成数字信号,在stm32中量化范围为:
0
⩽
V
I
N
⩽
V
D
D
A
0 \leqslant V_{I N} \leqslant V_{D D A}
0⩽VIN⩽VDDA,因此常规情况下在设计电路时要将单片机的VDDA引脚接VCC。
每个外部ADC通道通过一个GPIO引出,如上图中有16个ADC通道,此外还有两个内部通道,这18个ADC通道可配置到两种转换组中:规则组和注入组。若想理解这两个转换组的定义,可单独查阅手册,这里做一个不太恰当的比喻,规则组就像是主函数的进程,注入组就像是中断函数的进程,当注入组ADC被触发后,程序开始执行注入组的任务,关闭后又回到规则组。
ADC的时钟是由系统时钟分频得来的,可在初始化ADC结构体中将ADC时钟设置为系统时钟的二分频,四分频,六分频或八分频。ADC 使用若干个 ADCCLK 周期对输入电压采样,总转换时间可以用以下公式来计算:
T
CON
=
采样时间
+
12.5
T
ADCCLK
\mathrm{T}_{\text {CON }}=\text { 采样时间 }+12.5 \mathrm{~T}_{\text {ADCCLK }}
TCON = 采样时间 +12.5 TADCCLK 其中
T
A
D
C
C
L
K
T_{ADCCLK}
TADCCLK即为前面设置的ADC的时钟周期,而采样时间也可在初始化结构体中设置
上述过程每进行一次则表示完成了一次ADC,若将ADC设置为规则组则转换完成后EOC标志位置一,若设置为注入组则转换完成后JEOC标志位置一,通过判断标志位可用来进行ADC的DMA过程或者中断过程等。以上即为ADC过程的概述,接下来具体讨论。
2.触发方式
stm32的ADC有三种触发方式,分别为:内部触发方式(定时器的TRGO事件)、外部触发方式(通过外部中断触发)、软件触发方式,在我所用的这款单片机中,关于触发方式的定义如下:
/* ADC_mode */
#define ADC_Mode_Independent ((uint32_t)0x00000000)
#define ADC_Mode_RegInjecSimult ((uint32_t)0x00010000)
#define ADC_Mode_RegSimult_AlterTrig ((uint32_t)0x00020000)
#define ADC_Mode_InjecSimult_FastInterl ((uint32_t)0x00030000)
#define ADC_Mode_InjecSimult_SlowInterl ((uint32_t)0x00040000)
#define ADC_Mode_InjecSimult ((uint32_t)0x00050000)
#define ADC_Mode_RegSimult ((uint32_t)0x00060000)
#define ADC_Mode_FastInterl ((uint32_t)0x00070000)
#define ADC_Mode_SlowInterl ((uint32_t)0x00080000)
#define ADC_Mode_AlterTrig ((uint32_t)0x00090000)
/* ADC_external_trigger_sources_for_regular_channels_conversion */
#define ADC_ExternalTrigConv_T1_CC1 ((uint32_t)0x00000000)
#define ADC_ExternalTrigConv_T1_CC2 ((uint32_t)0x00020000)
#define ADC_ExternalTrigConv_T2_CC2 ((uint32_t)0x00060000)
#define ADC_ExternalTrigConv_T3_TRGO ((uint32_t)0x00080000)
#define ADC_ExternalTrigConv_T4_CC4 ((uint32_t)0x000A0000)
#define ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO ((uint32_t)0x000C0000)
#define ADC_ExternalTrigConv_T1_CC3 ((uint32_t)0x00040000)
#define ADC_ExternalTrigConv_None ((uint32_t)0x000E0000)
#define ADC_ExternalTrigConv_T3_CC1 ((uint32_t)0x00000000)
#define ADC_ExternalTrigConv_T2_CC3 ((uint32_t)0x00020000)
#define ADC_ExternalTrigConv_T8_CC1 ((uint32_t)0x00060000)
#define ADC_ExternalTrigConv_T8_TRGO ((uint32_t)0x00080000)
#define ADC_ExternalTrigConv_T5_CC1 ((uint32_t)0x000A0000)
#define ADC_ExternalTrigConv_T5_CC3 ((uint32_t)0x000C0000)
其中软件触发方式需要在每次进行ADC转换前通过以下语句使能ADC方可正常使用,此时ADC的触发状态被配置为:ADC_ExternalTrigConv_None
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
若设置成定时器触发模式,则需将触发方式配置为ADC_ExternalTrigConv_T3_TRGO此即为定时器3的TRGO事件,不过需要另外单独配置定时器3,此处给一个案例,关于TRGO接下来会单独写一篇博客讨论:
void TTRGO(void)
{
TIM_OCInitTypeDef TIM_OCInitStructure={0};
GPIO_InitTypeDef GPIO_InitStructure={0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3, ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseInitStructure.TIM_Period = 10-1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 96-1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit( TIM3, &TIM_TimeBaseInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_Pulse = 5;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init( TIM3, &TIM_OCInitStructure );
TIM_OC1PreloadConfig( TIM3, TIM_OCPreload_Enable );
TIM_CtrlPWMOutputs(TIM3, ENABLE );
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); //选择TRGO作为触发源为定时器更新时间
TIM_ARRPreloadConfig( TIM3, ENABLE );
TIM_Cmd(TIM3, ENABLE);
}
外部中断触发方式由于现在还没有接触过,等以后补上。
3.寄存器
ADC的寄存器有如下(以ADC1为例):
以上都可以在芯片手册上详细找到,这里着重说一下状态寄存器
在库函数中可以使能状态寄存器的某一状态为中断标志位,进而检测该位进入中断。
4.库函数
ADC的库函数中主要为:
void ADC_Function_Init(void)
{
ADC_InitTypeDef ADC_InitStructure={0};
GPIO_InitTypeDef GPIO_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_BufferCmd(ADC1, DISABLE); //disable buffer
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
Calibrattion_Val = Get_CalibrationValue(ADC1);
ADC_BufferCmd(ADC1, ENABLE); //enable buffer
}
这个函数中将ADC设置为主频的八分频,开启了ADC和对应GPIO的时钟,将ADC设置为独立模式,连续采集模式,软件触发模式,右对齐模式,最终实现的功能为一个通道ADC连续采集1024次发送给串口打印。
此外若要开启ADC中断则需要如下代码,这里是使用EOC位作为中断标志位:
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
ADC_ExternalTrigConvCmd(ADC1, ENABLE);//开启ADC外部时间启动
NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1 ;
NVIC_InitStructure.NVIC_IRQChannel = ENABLE ;
NVIC_Init(&NVIC_InitStructure);
NVIC_EnableIRQ(ADC1_2_IRQn);
若要更改其他的标志位在上述地方更改即可,最后不要忘记使能ADC中断。文章来源:https://www.toymoban.com/news/detail-754980.html
总结
以上即为ADC的简单介绍。文章来源地址https://www.toymoban.com/news/detail-754980.html
到了这里,关于STM32 ADC使用学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!