注:本文属博主学习时所作笔记,内容源大参考于野火的《零死角玩转STM32F103》以及部分网络资料,笔记内容仅作为自己参考,免去频繁查询参考手册的麻烦,如有错误,还请指出!
ADC简介 STM32f103 系列有 3 个 ADC,精度为 12 位,每个 ADC 最多有 16 个外部通道,可测量16个外部和2个内部信号源。其中ADC1 和 ADC2 都有 16 个外部通道,ADC3 根据 CPU 引脚的不同通道数也不同,一般都有8 个外部通道。
ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。
电压输入范围 输入电压:$V_{REF-} \leq V_{IN} \leq V_{REF+}$,决定输入电压的引脚:$V_{REF-}, V_{REF+},VDDA,VSSA$,把$VSSA$和$V_{REF-}$接地,$V_{REF+}$和$VDDA$接$3.3V$,得到ADC的输入电压范围为:==0~3.3V==。
VDD: D即device 表示器件的意思,是器件内部的工作电压。
VDDA(A表示 Analog 【模拟】)是模拟电源,当使用到模拟信号的时候,比如AD(模数)或者DA(数模)的时候,系统会使用VDDA的电压作为参考电压来。不要求精准使用的话,可以直接把VDDA和VDD同时接入$3.3V$就行。如果要求精准,则需要做一个稳压电路,再接入VDDA 。
VSS:S即series 表示公共连接的意思,通常指电路公共接地端电压 ,VSSA同理为模拟部分的电源。
对于数字电路来说,VCC是电路的供电电压,VDD是芯片的工作电压(通常$VCC>VDD$),$VSS$是接地点。 例如,对于ARM单片机来说,其供电电压$VCC$一般为5V,一般经过稳压模块将其转换为单片机工作电压$VDD = 3.3V$。
定制电压范围 例如将电压范围设成$-10V$~ $10V$,可以通过下图的附加扩展电路实现:
根据基尔霍夫定律(KCL),即节点流入的电流等于流出的电流可以得到: $$ {(V_{IN}-V_{OUT})\over 2 } + {(3.3V-V_{OUT})\over R_1 } = {V_{OUT} \over R_3} $$ 解得: $V_{OUT}={(V_{IN}+ 10)\over 6}$
在附加电路的条件下,当$V_{IN}=-10V$时,$V_{OUT}=0$;当$V_{IN}=10V$时,$V_{OUT}=3.3V$。以此达到测量$-10V$~ $10V$电压的目的。
输入通道 输入通道,顾名思义为将电压输入到ADC的通道,STM32 的 ADC 多达 18 个通道,具体参考下图:
外部的 16 个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有 16路,注入通道最多有 4 路。
规则通道:也就是一般情况下用到的通道;
注入通道:可以在规则通道转换的时候强行插入转换,享有转换特权,有点像中断的操作。
转换顺序
规则序列
转换的优先级并不是根据通道的编号来确定的,需要操作规则序列寄存器SQRx
(x=1,2,3),例如将通道六设置为第十个转换,SQ10这个位写6即可。SQL1中SQL位用于设置转换通道的数量,具体的操作都可以库函数代劳。
注入序列
注入序列的转换顺序稍有不一样,当JL
的值小于4
时,第一次转的顺序是JSQx
(x=4-JL);当JL等于4时,与SQR一致。
触发源 触发源可以看作是系统给ADC这个外设的一个信号,当触发源激活时,ADC就开始进行转换。触发源一般有两种:软件触发和外部事件触发(包括内部定时器和外部IO);
转换时间 ADC时钟 ADC 输入时钟 ADC_CLK 由 PCLK2 经过分频产生,最大是 14M,分频因子由 RCC 时钟配置寄存器 RCC_CFGR
的位 15:14 ADCPRE[1:0]
设置,可以是 2/4/6/8 分频,注意这里没有 1 分频。一般我们设置 $PCLK2=HCLK=72M$。
采样时间 ADC 使用若干个 ADC_CLK
周期对输入的电压进行采样,采样的周期数可通过 ADC 采样时间寄存器 ADC_SMPR1
和 ADC_SMPR2
中的 SMP[2:0]
位设置,ADC_SMPR2 控制的是通道 09,ADC_SMPR1
控制的是通道 1017。每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5个周期,这里说的周期就是$ 1\over ADC_{CLK}$。
ADC 的转换时间跟 ADC 的输入时钟和采样时间有关,公式为: $$ Tconv = 采样时间 + 12.5 个周期。 $$ 当 $ADCLK = 14MHZ$ (最高),采样时间设置为 1.5 周期(最快),那么总的转换时间(最短)$T_{conv}$ = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。一般我们设置 $PCLK2=72M$,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us,这个才是最常用的。
数据寄存器 ADC转换后,规则组数据存放在ADC_DR
寄存器中,注入组在JDRx
。
规则数据寄存器 ADC 规则组数据寄存器 ADC_DR
只有一个,是一个 32 位的寄存器,低 16 位在单 ADC时使用,高 16 位是在 ADC1 中双模式下保存 ADC2 转换的规则数据,双模式就是 ADC1 和ADC2 同时使用。在单模式下,ADC1/2/3 都不使用高 16 位。因为 ADC 的精度是 12 位, 无论 ADC_DR
的高 16 或者低 16 位都放不满,只能左对齐或者右对齐,具体是以哪一种方式存放,由 ADC_CR2
的 11 位 ALIGN
设置。
由于数据寄存器只有一个,而通道却有多个,多通道转换时为了避免数据被覆盖,转换完成时应立刻将数据取走 ,一般开启DMA
传输模式,直接将数据传输给变量。
注入数据寄存器 ADC 注入组最多有 4 个通道,刚好注入数据寄存器也有 4 个,每个通道对应着自己的寄存器,不会跟规则寄存器那样产生数据覆盖的问题。ADC_JDRx
是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2
的 11 位 ALIGN
设置。
中断 转换结束中断 数据转换结束后,可以产生中断,中断分为三种:
规则通道转换结束中断, 对应中断位:ADC_IT_EOC
;
注入转换通道转换结束中断, 对应中断位:ADC_IT_JEOC
;
模拟看门狗中断 ,对应中断位:ADC_IT_AWD
。
模拟看门狗中断 当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断 ,其中低阈值和高阈值由 ADC_LTR
和 ADC_HTR
设置。例如我们设置的电压高阈值为3.3V,若接入的模拟电压高于3.3V时,即产生中断。
DMA 请求 规则和注入通道转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据直接存储在内存里面。要注意的是只有 ADC1 和 ADC3 可以产生 DMA 请求 。
双 ADC 模式 AD 转换包括采样阶段和转换阶段,在采样阶段才对通道数据进行采集;而在转换阶段只是将采集到的数据进行转换为数字量输出,此刻通道数据变化不会改变转换结果。独立模式的 ADC 采集需要在一个通道采集并且转换完成后才会进行下一个通道的采集。而双重 ADC 的机制就是使用两个 ADC 同时采样一个或者多个通道。双重 ADC 模式较独立模式一个最大的优势就是提高了采样率,弥补了单个 ADC 采样不够快的缺点。
启用双 ADC 模式的时候,通过配置 ADC_CR1
寄存器的 DUALMOD[3:0]
位,可以有几种不同的模式:
模式
简介
同步注入模式
ADC1 和 ADC2 同时转换一个注入通道组,其中 ADC1 为主,ADC2 为从。转换的数据存储在每个 ADC 接口的ADC_JDRx
寄存器中。
同步规则模式
ADC1 和 ADC2 同时转换一个规则通道组,其中 ADC1 为主,ADC2 为从。ADC1 转换的结果放在 ADC1_DR
的低 16 位,ADC2 转换的结果放在 ADC1_DR
的高十六位。
快速交叉模式
ADC1 和 ADC2 交替采集一个规则通道组(通常为一个通道)。当ADC2 触发之后,ADC1 需要等待 ==7==个 ADCCLK
之后才能触发。
慢速交叉模式
ADC1 和 ADC2 交替采集一个规则通道组(只能为一个通道)。当ADC2 触发之后,ADC1 需要等待 ==14== 个 ADCCLK
之后才能触发。
交替触发模式
ADC1 和 ADC2 轮流采集注入通道组,当 ADC1 所有通道采集完毕之后再采集 ADC2 的通道,如此循环。跟交叉采集不一样。
混合的规则/注入同步模式
规则组同步转换被中断,以启动注入组的同步转换。分开两个模式来理解就可以了,区别就是注入组可以中断规则组的转换。
混 合 的 同 步 规则+交替触发模式
规则组同步转换被中断,以启动注入组交替触发转换。分开两个模式来理解就可以了,区别就是注入组可以中断规则组的转换。
混合同步注入+交叉模式
交叉转换可以被同步注入模式中断。这种情况下,交叉转换被中断,注入转换被启动。
例程设计 STM32 的 ADC 功能繁多,以下通过三个基础例程尽量展示ADC的功能,代码中均没有使用宏定义,是为了看得更直观。
独立模式单通道 **1) **初始 ADC 用到的 GPIO,这里我们以GPIOC1
为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 void ADCx_GPIO_Config (void ) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); }
2) 设置 ADC 的工作参数并初始化(以ADC1为例);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void ADCx_Mode_Config (void ) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); 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); RCC_ADCCLKConfig(RCC_PCLK2_Div8); ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 1 , ADC_SampleTime_55Cycles5); ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }
**3) **配置中断源和中断优先级;
1 2 3 4 5 6 7 8 9 10 11 12 void ADC_NVIC_Config (void ) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
4) 中断服务函数
1 2 3 4 5 6 7 8 void ADC1_2_IRQHandler (void ) { if (ADC_GetITStatus(ADC1,ADC_IT_EOC)==SET) { ADC_ConvertedValue = ADC_GetConversionValue(ADC1); } ADC_ClearITPendingBit(ADC1,ADC_IT_EOC); }
5) main()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 extern __IO uint16_t ADC_ConvertedValue;float ADC_ConvertedValueLocal; int main (void ) { USART_Config(); ADCx_GPIO_Config(); ADCx_Mode_Config(); ADC_NVIC_Config(); printf ("\t\n ----这是一个串口发送实验----\r\n" ); ADC_ConvertedValueLocal =(float ) ADC_ConvertedValue/4096 *3.3 ; printf ("\r\n The current AD value = 0x%04X \r\n" ,ADC_ConvertedValue); printf ("\r\n The current AD value = %f V \r\n" ,ADC_ConvertedValueLocal); printf ("\r\n\r\n" ); }
独立模式多通道 因为与单通道大多配置都相同,因此只列出差异部分。
**1) **初始 ADC 用到的 GPIO;
1 GPIO_InitStructure.GPIO_Pin = ADC_PIN1 | ADC_PIN2 | ADC_PIN3 | ADC_PIN4 | ADC_PIN5 | ADC_PIN6;
2) 设置 ADC 的工作参数并初始化,还需要对DMA进行配置;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC_x->DR)); DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 6 ; 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_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1 , ENABLE); ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_NbrOfChannel = 6 ; ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1 , ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2 , ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3 , ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 4 , ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 5 , ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 6 , ADC_SampleTime_55Cycles5); ADC_DMACmd(ADC1, ENABLE);
3) main()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 extern __IO uint16_t ADC_ConvertedValue[6 ];float ADC_ConvertedValueLocal[6 ]; int main (void ) { USART_Config(); ADCx_GPIO_Config(); ADCx_Mode_Config();; printf ("\t\n ----这是一个串口发送实验----\r\n" ); ADC_ConvertedValueLocal[0 ] =(float ) ADC_ConvertedValue[0 ]/4096 *3.3 ; ADC_ConvertedValueLocal[1 ] =(float ) ADC_ConvertedValue[1 ]/4096 *3.3 ; ADC_ConvertedValueLocal[2 ] =(float ) ADC_ConvertedValue[2 ]/4096 *3.3 ; ADC_ConvertedValueLocal[3 ] =(float ) ADC_ConvertedValue[3 ]/4096 *3.3 ; ADC_ConvertedValueLocal[4 ] =(float ) ADC_ConvertedValue[4 ]/4096 *3.3 ; ADC_ConvertedValueLocal[5 ] =(float ) ADC_ConvertedValue[5 ]/4096 *3.3 ; printf ("\r\n CH0 value = %f V \r\n" ,ADC_ConvertedValueLocal[0 ]); printf ("\r\n CH1 value = %f V \r\n" ,ADC_ConvertedValueLocal[1 ]); printf ("\r\n CH2 value = %f V \r\n" ,ADC_ConvertedValueLocal[2 ]); printf ("\r\n CH3 value = %f V \r\n" ,ADC_ConvertedValueLocal[3 ]); printf ("\r\n CH4 value = %f V \r\n" ,ADC_ConvertedValueLocal[4 ]); printf ("\r\n CH5 value = %f V \r\n" ,ADC_ConvertedValueLocal[5 ]); printf ("\r\n\r\n" ); }
双ADC同步规则 **1) **初始 ADC 用到的 GPIO;
1 2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
2) 设置 ADC 的工作参数并初始化,还需要对DMA进行配置;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult; 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 = 1 ; ADC_Init(ADCx_1, &ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div8); ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1 , ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_SoftwareStartConvCmd(ADCx_1, ENABLE); ADC_Init(ADC2, &ADC_InitStructure); ADC_RegularChannelConfig(ADC2, ADC_Channel_14, 1 , ADC_SampleTime_239Cycles5); ADC_ExternalTrigConvCmd(ADC2, ENABLE);
3) main()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 extern __IO uint32_t ADC_ConvertedValue[1 ];float ADC_ConvertedValueLocal[NOFCHANEL*2 ]; int main (void ) { uint16_t temp0=0 ,temp1=0 ; USART_Config(); ADCx_GPIO_Config(); ADCx_Mode_Config(); printf ("\r\n ----这是一个双ADC规则同步采集实验----\r\n" ); temp0 = (ADC_ConvertedValue[0 ]&0XFFFF0000 ) >> 16 ; temp1 = (ADC_ConvertedValue[0 ]&0XFFFF ); ADC_ConvertedValueLocal[0 ] =(float ) temp0/4096 *3.3 ; ADC_ConvertedValueLocal[1 ] =(float ) temp1/4096 *3.3 ; printf ("\r\n ADCx_1 value = %f V \r\n" , ADC_ConvertedValueLocal[1 ]); printf ("\r\n ADCx_2 value = %f V \r\n" , ADC_ConvertedValueLocal[0 ]); printf ("\r\n\r\n" ); } }