ADC采样
ADC采样有一下几种方式:
- 积分型
- 逐次比较型
- 并行比较型
- 电容阵列逐次比较型
- 压频变换型
stm32的adc采样
我们stm32的adc采样使用的是逐次比较型的方式进行采样的
配置过程也很容易实现,思路很简单:
- 配置初始化
- 配置软件/硬件采样方式
- 设置对齐方式
逐次逼近法
通过地址锁存和译码器
选择ADC转换的输入通道。
然后进行比较,比较时使用一个DAC输入的电压作为参考,如果DAC电压比输入的电压大,就调小DAC的电压,DAC在改变的时候通过12位的比较逐次比较,直到DAC的电压达到分辨率的最大值,此时与外部输入的电压近似相等,把此时的DAC编码值作为外部输入电压的编码值。
所以过程可以分为:采样与保持
,量化与编码
。
在stm32f103里,转换时间 = 采样时间 + 12.5个ADC时钟周期
比如在ADCCLK为14Mhz的情况下 采样时间 = 1.5个ADC时钟周期(前提是采样时间寄存器相关控制位配置为000),转换时间为14个ADC时钟周期,总时间为1us。
stm32的ADC采样框图如下。
它有两种转换方式,一种规则转换,最大16个ADC通道同时转换,但是规则通道数据寄存器只有16位,所以只能存下最新的数据,故在读取数据时要注意时序,不过也可以一组规则转换里只转换一个通道;另一种是注入转换,它每次最多可以转换4个通道的数据,并且存放时也是保留了全部的数据。如果需要使用规则组的多通道转换一般需要搭配DMA来完成任务。
转换的开始信号可以由软件触发(即调用代码出发转换),也可以由触发源触发也就是图上EXTI_15和EXTI_11那里的触发源(一个是注入组的转换,一个是规则组的转换)
右边的ADCCLK是用来驱动ADC逐次比较的时钟,它的时钟来源于ADC预分频器,它又来自于RCC时钟的APB2,但是ADCCLK的最大频率是14MHz,而APB2的时钟频率是72MHz,所以在设置分频系数的时候取值是有限制的,要么取6获得12Mhz的ADCCLK,要么取8获得9MHz的ADCCLK。
在stm32的库函数里是这样定义分频系数的取值的:
#define RCC_PCLK2_Div2 ((uint32_t)0x00000000)
#define RCC_PCLK2_Div4 ((uint32_t)0x00004000)
#define RCC_PCLK2_Div6 ((uint32_t)0x00008000)
#define RCC_PCLK2_Div8 ((uint32_t)0x0000C000)
当ADC转换完成之后,会对转换完成标志位置位,如果是规则组转换完成,那么EOC标志位置位,如果是注入组转换完成,那么JEOC标志位置位(在图的最上面有说明),如果配置了转换完成的中断,那么结束后还可以触发相关的NVIC中断。
ADC通道的相关引脚如下:
ADC转换的顺序是可以配置的,比如我希望先转换通道3里的数据,然后再转换通道0里的数据,这些顺序是可以由使用者自行决定的,以下对转换顺序里的每个内容的位次称作序列,比如希望最先转换通道3里的数据,那么序列1所对应的内容就是通道3,希望第2位转换的数据来自通道0,那么序列2所对应的内容就是通道0。然后下面开始讨论ADC转换的4种转换模式。
ADC转换的4种转换模式
-
单次转换,非扫描模式
由触发信号触发转换,并且只转换序列0里的数据,然后停止,想要再次转换需要再次给入触发转换的信号。如果希望换一个通道转换数据,那么需要在开始转换前把序列0里的内容更改为其它通道。 -
连续转换,非扫描模式
和上一种模式的转换方式差不多,只是在给入转换触发信号后,不需要在转换结束后再次给入触发信号,ADC转换会持续进行。 -
单次转换,扫描模式
你需要告诉单片机需要转换的序列数量,然后单片机会依次转换序列里内容所指向的通道,并且只转换一次,当转换结束后EOC置位,如果需要再次转换,就需要再次给入触发转换的信号。 -
连续转换,扫描模式
和上一种转换的方式差不多,但是不需要在转换结束后再次给入触发信号,ADC转换会持续进行。
对齐模式
在采集数据之前,需要配置数据的左右对齐模式,由于数据寄存器是16位的,而stm32的采集分辨率是12位的,会有4位的数据空出来,左对齐就是把数据从高位到低位放在数据寄存器的高12位上,右对齐就是把数据从高位到低位放在数据寄存器的低12位上,一般用右对齐就好了。
校准
在stm32f103进行ADC采样之前需要进行校准步骤,并且启动校准前ADC需要处于关电状态超过2个ADC时钟周期,这个校准过程虽然复杂,但是在代码里却是固定的,只需要把相关代码写上去就行了。
寄存器
所涉及寄存器:
ADC状态寄存器(主要关注转换结束标志位)
ADC控制寄存器1(配置ADC的各种模式、是否允许中断等)
ADC控制寄存器2(主要是配置ADC转换的触发方式、数据对齐等)
ADC采样时间寄存器1(配置ADC的采样时间)
ADC规则组序列寄存器1/2/3(配置转换几个规则组序列,转换规则组的通道顺序)
ADC注入组序列寄存器(同上)
ADC注入数据寄存器x(存放注入组x的转换结果)
ADC规则数据寄存器(存放规则组的转换结果)
函数
主要用到的库函数:
ADC_RegularChannelConfig( ADCx, ADC_Channel_x , Rank_x , ADC_SampleTime);
上面的函数主要用于配置规则组转换的序列和采样时间,填入所使用的ADC,然后指定某个通道,再告诉函数你希望把这个通道配置的序列值是多少,最后指定该通道的采样时间。
ADC_SoftwareStartConvCmd(ADCx,ENABLE);
调用这个函数就可以通过软件来触发ADC采样。
然后等待采样完成即可读取采样结果
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == 0);//等待采样结束
采样完毕之后调用函数获取采样结果
val = ADC_GetConversionValue(ADCx);
示例代码
代码
// 使用PC0作为采样引脚
void ADC_Init_PC0(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); // 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADCCLK的选择分频系数
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // ÅäÖÃΪģÄâÊäÈë
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1,ADC_Channel_10,1,ADC_SampleTime_239Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; // Ö¸¶¨ADC¹¤×÷ÔÚ¶ÀÁ¢Ä£Ê½
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; // Ö¸¶¨ADCת»»½á¹ûÊý¾ÝΪÓÒ¶ÔÆëģʽ
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; // Á¬Ðøת»»ÉèÖùرգ¬¼´µ¥´Îת»»Ä£Ê½
ADC_InitStructure.ADC_ScanConvMode=DISABLE; // ·ÇÁ¬ÐøɨÃè
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; // ²»Ê¹ÓÃÍⲿ´¥·¢Ô´£¬Ê¹ÓÃÈí¼þ´¥·¢
ADC_InitStructure.ADC_NbrOfChannel=1; // Ö¸¶¨×ª»»µÄÐòÁÐÊýÄ¿£¬±¾½ÚÀïֻʹÓÃÁËÒ»¸öIO¿Ú£¬ËùÒÔÖ»Òª1¸ö¾Í¹»ÁË
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE); // ¿ªÆôADCµçÔ´
/*У׼*/
ADC_ResetCalibration(ADC1);
while(SET==ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(SET==ADC_GetCalibrationStatus(ADC1));
}
u16 get_adc_val(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE); // ¸øADC1Ò»¸ö´¥·¢×ª»»µÄÐźÅ
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == 0); // µÈ´ýת»»Íê³É
return ADC_GetConversionValue(ADC1);
}
// 主函数
u16 ad_val = 0;
int main(void)
{
OLED_Init();
OLED_Clear();
OLED_ShowString(1, 1, "ADC");
OLED_ShowString(2, 1, "Initializing...");
OLED_ShowString(3, 1, "val:----");
ADC_Init_PC0();
OLED_ShowString(2, 1, "Initialized ");
OLED_ShowString(3, 1, "val: ");
while (1)
{
ad_val = get_adc_val();
OLED_ShowNum(3,5,ad_val,4);
}
}
效果
【STM32】ADC