目录
ADC作为一种模数转换功能,在实际应用中非常常用,那么也常常有各种个性化需求,包括对其转换开始时间、顺序等的要求等。STM32也提供了多种ADC触发方式来满足要求,包括软件触发、定时器触发和外部触发等等。
本文简单介绍软件触发,重点说明定时器触发。同时,网上的资料多数是介绍规则组的配置,而本文把规则组和注入组都进行说明,方便大家学习和比较。过程中也会提及自己的一些思考。
1. ADC简介
1.1ADC通道和转换时间
STM32f103有3个AD模数转换器,每个ADC都有18个通道,可以测量16个外部和2个内部模拟量。最大理论转换频率为1Mhz,也就是转换时间为1us(在 ADCCLK = 14Mhz,采样周期为1.5个时钟周期时,因为总时间是采样时间+12.5个时钟周期)。而实际用的时候最短转换时间约为1.17 ,这是因为挂载的时钟频率为72MHz,分频为6时ADC时钟为12MHz,可以满足低于14MHz,最大时钟超过14Mhz会导致ADC转换准确度降低。
每个ADC的各个通道对应接口如图:
STM32的ADC是12位精度的,即数据最大为4096
STM32的ADC转换有两种通道,规则通道和注入通道,注入通道可以抢占式地打断规则通道的采样,执行注入通道采样后,再执行之前的规则通道采样,和中断类似。
1.2ADC触发方式、模式等的设置(ADC_CR2)
STM32的ADC可以由外部事件触发(例如定时器更新、捕获,EXTI线)和软件触发(即在配置相关寄存器时,直接开启采样)。这里可以看一下参考手册里的说明:
在配置转换的时候有一些模式,这里进行一下说明:
单次模式、连续模式:ADC单通道要求进行一次ADC转换时配置为单次模式使能,这样ADC的这个通道转换一次后就停止转换。要求进行连续ADC转换时配置为连续模式使能,这样ADC的这个通道转换一次后接着进行下一次。
扫描模式:对应多个ADC通道的情况,每个通道依次进行转换。
上面的各种内容,包括触发方式、转换模式等的设置,都在ADC_CR2寄存器里体现,详情可以去参考手册里看看寄存器的说明。
1.3ADC的数据存储(ADC_DR和ADC_JDRx)
1.3.1 规则通道
ADC在规则通道存储数据时,只有一个数据寄存器ADC_DR
寄存器中有16位来存放数据,而ADC精度是12位,所以涉及到数据左或右u对齐的问题。
在连续多次ADC转换时,由于只有一个数据寄存器,规则通道的数据会被下一次转换的数据覆盖,所以ADC常常和DMA一起使用,利用DMA模式将转换的数据,传输在一个数组中,程序对数组读操作就可以得到转换的结果。
1.3.2 注入通道
注入通道的每个通道都有自己的数据寄存器,不过同样涉及到数据对齐方式的设置。
1.4工作流程
STM32的ADC在单次转换模式下,只执行一次转换,首先要置为ADC_CR2 寄存器的ADON 位,该模式可以通过软件触发启动,也可以通过外部触发启动(均适用于规则通道和注入通道),这时CONT 位为0。以规则通道为例,转换开始后,SWSTART位(开始规则通道转换位)自动清除,一旦所选择的通道转换完成,转换结果将被存在ADC_DR 寄存器,EOC (转换结束)标志将被置位,如果设置了EOCIE ,则会产生中断。然后ADC将停止,直到下次启动。
2 软件触发方式
软件触发方式是最基本的ADC触发方式,这里我用规则组来做。配置的时候配置成不使用外部触发,并且在单次转换下,每次转换时都需要对启动规则组转换对应的位置位即调用对应的函数,这个后面结合着代码来说。这里我把ADC部分的配置贴上来。
void Adc_Init()
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1,ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频因子为6
//RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
//DMACmd(ADC1, ENABLE);//开启ADC的DMA支持
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
以上就是单通道、软件触发模式下规则组的配置情况,基本说明在注释里都包括了。那么配置完毕以后,什么时候开始一次转换呢?我们看调用的函数
u16 Get_Adc(u8 ch)
{
ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_239Cycles5);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
我们每调用一次这个函数,就执行一次ADC转换,并返回转换的结果值。那么我们来分析一下这个函数里的语句:
ADC_RegularChannelConfig这个函数决定了在多通道采样的情况下,对每个通道的采样顺序、采样时间的配置,ch即通道名,1是第一个进行转换。
ADC_SoftwareStartConvCmd这个函数对寄存器ADC_CR2的SWSTART位进行操作,执行完此句以后就真正开始一次ADC转换。
仔细思考的一些同学可能会有一些疑惑,比如ADC_SoftwareStartConvCmd函数使能和ADC_Cmd是什么关系?情况是这样的,前者操作的是ADC_CR2的SWSTART位,后者操作的是ADC_CR2的ADON位,后者相当于电源,当ADON低位时,操作SWSTART位无效;当ADON高位时,置SWSTART位可以开启一次转换。
其实这里我在实验的时候遇到了两个问题,一个是执行完ADC_SoftwareStartConvCmd这条语句后寄存器被置位从而执行转换,但是却没有将对应的位清零的语句,那么为什么只执行了一次而不是反复在转换呢?另一个问题是当转换完成后EOC被置位,同样没有该位清零的语句,那么这个转换完成位就一直是高位,后面再判断的时候就总是转换完成了,这显然是不合逻辑的,问题出在哪里呢?
这个问题我上网查了很多教程和咨询,都没有被提及,经过了很长时间的思索以后,最后在参考手册的寄存器描述里找到了非常细节的答案,手册里是这么描述的:
第一行写到,转换开始后硬件马上清除此位。也就是说为了让转换开始而置的位在转换一开始就被清楚了,方便后面的配置。就让我豁然开朗。
对于第二个问题,在参考手册里也找到了答案:
EOC转换结束标志位在读取本次ADC转换结果数据的时候被自动清除,这样同样有利于后面程序的执行。
3定时器TRGO触发ADC
3.1使用规则组配置
在规则组配置过程时,ADC配置部分基本内容和软件触发相同,仅有下面这些改动
- 触发方式修改:ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; 也就是把原本的ADC_ExternalTrigConv_None改掉
- 使能外部触发: ADC_ExternalTrigConvCmd(ADC1,ENABLE);
- 同时我发现,这时不用配置ADC_SoftwareStartConvCmd就也可以正常工作了,我不太清除这个函数到底是使能软件转换开启的还是使能整个规则组转换开启的,现在倾向于前者,有比较清楚的大佬可以给解释一下。
然后就是配置定时器的内容了,注意选择让定时去产生更新触发。
整个配置部分代码如下:
void Adc_Init()
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1,ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频因子改为6
//RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_ExternalTrigConvCmd(ADC1,ENABLE);
// ADC_DMACmd(ADC1, ENABLE);//开启ADC的DMA支持
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
// ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
}
void TIM3_init(u16 arr,u16 psc)
{ NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 80K
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
//TIM_ITConfig(TIM3, TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
// TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清中断以免一启用中断后立即产生中断
TIM_Cmd(TIM3, ENABLE); //使能TIM1
}
然后调用ADC_GetConversionValue(ADC1)函数就可以得到ADC转换的值了。通过配置定时器的arr和psc值,可以实现任意时间间隔的ADC转换。
这里要注意的一个点就是,如果你不需要定时器中断,那就不要使能定时器中断,否则你使能了中断却又没有写中断服务函数的话,程序会跑飞。我看很多人分享的代码里都使能了定时器中断但也不用,实在不能理解。
3.2使用注入组配置
一般来说,用到注入组了肯定是之前已经有了规则组了,不然就没必要让它作为注入组了。在规则组和注入组混合使用的情况下,规则组的配置没有任何变化,注入组的配置如下:
ADC_InjectedSequencerLengthConfig(ADC1, 2); //注入组有两个通道
ADC_InjectedChannelConfig(ADC1, ADC_Channel_3, 1,ADC_SampleTime_13Cycles5);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_6, 2,ADC_SampleTime_13Cycles5);//对两个注入组的通道进行设置
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO);
ADC_ExternalTrigInjectedConvCmd(ADC1, ENABLE); //使能 ADC1的经外部触发启动注入组转换功能
//ADC_ITConfig(ADC1,ADC_IT_JEOC,ENABLE); //注入组ADC转换结束的中断
ADC_AutoInjectedConvCmd(ADC1, DISABLE); //使能或者失能指定 ADC 在规则组转化后自动开始注入组转换
如果是软件触发方式的注入组,就把触发方式ADC_ExternalTrigInjectedConvConfig改变一下,同时不用使能外部触发启动功能,而使能ADC_SoftwareStartInjectedConvCmd(ADC1, ENABLE)即可。
同时也别忘了,每个通道对应的GPIO端口也要进行初始化配置。
在读取注入组的转换数据时,使用的函数变成了ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1);
它包括了两个入口参数,一个是ADCx,另一个是注入通道的顺序序号,这时因为注入组每个通道都有自己的数据寄存器,所以可以读指定通道的数据。
以上就是关于ADC触发部分介绍的内容了,希望大家读了会有些收获!
最后参考了一些别人的总结,列出了ADC部分的各种函数
ADC_DeInit 将外设 ADCx 的全部寄存器重设为缺省值
ADC_Init 根据 ADC_InitStruct 中指定的参数初始化外设 ADCx 的寄存器
ADC_StructInit 把 ADC_InitStruct 中的每一个参数按缺省值填入
ADC_Cmd 使能或者失能指定的 ADC
ADC_DMACmd 使能或者失能指定的 ADC 的 DMA 请求
ADC_ITConfig 使能或者失能指定的 ADC 的中断
ADC_ResetCalibration 重置指定的 ADC 的校准寄存器
ADC_GetResetCalibrationStatus 获取 ADC 重置校准寄存器的状态
ADC_StartCalibration 开始指定 ADC 的校准程序
ADC_GetCalibrationStatus 获取指定 ADC 的校准状态
ADC_SoftwareStartConvCmd 使能或者失能指定的 ADC 的软件转换启动功能
ADC_GetSoftwareStartConvStatus 获取 ADC 软件转换启动状态
ADC_DiscModeChannelCountConfig 对 ADC 规则组通道配置间断模式
ADC_DiscModeCmd 使能或者失能指定的 ADC 规则组通道的间断模式
ADC_RegularChannelConfig 设置指定 ADC 的规则组通道,设置它们的转化顺序和采样时间
ADC_ExternalTrigConvConfig 使能或者失能 ADCx 的经外部触发启动转换功能
ADC_GetConversionValue 返回最近一次 ADCx 规则组的转换结果
ADC_GetDuelModeConversionValue 返回最近一次双 ADC 模式下的转换结果
ADC_AutoInjectedConvCmd 使能或者失能指定 ADC 在规则组转化后自动开始注入组转换
ADC_InjectedDiscModeCmd 使能或者失能指定 ADC 的注入组间断模式
ADC_ExternalTrigInjectedConvConfig 配置 ADCx 的外部触发启动注入组转换功能
ADC_ExternalTrigInjectedConvCmd 使能或者失能 ADCx 的经外部触发启动注入组转换功能
ADC_SoftwareStartinjectedConvCmd 使能或者失能 ADCx 软件启动注入组转换功能
ADC_GetsoftwareStartinjectedConvStatus 获取指定 ADC 的软件启动注入组转换状态
ADC_InjectedChannleConfig 设置指定 ADC 的注入组通道,设置它们的转化顺序和采样时间
ADC_InjectedSequencerLengthConfig 设置注入组通道的转换序列长度
ADC_SetinjectedOffset 设置注入组通道的转换偏移值
ADC_GetInjectedConversionValue 返回 ADC 指定注入通道的转换结果
ADC_AnalogWatchdogCmd 使能或者失能指定单个/全体,规则/注入组通道上的模拟看门狗
ADC_AnalogWatchdongThresholdsConfig 设置模拟看门狗的高/低阈值
ADC_AnalogWatchdongSingleChannelConfig对单个 ADC 通道设置模拟看门狗
ADC_TampSensorVrefintCmd 使能或者失能温度传感器和内部参考电压通道
ADC_GetFlagStatus 检查制定 ADC 标志位置 1 与否
ADC_ClearFlag 清除 ADCx 的待处理标志位
ADC_GetITStatus 检查指定的 ADC 中断是否发生
ADC_ClearITPendingBit 清除 ADCx 的中断待处理位