一、实验现象
在配置 ADC 功能时,我错误地将 GPIO 引脚 P1 配置为 P2,但仍然能够得到电压数据。这让我感到困惑,为了进一步排查问题,我进行了以下步骤:
首先一步步排查:
- 引脚更正:既然引脚弄错了,那引脚改为正确的引脚,仍然能够得到数据。
- 注释 GPIO 初始化:通过第一步判断,GPIO配置似乎是不起作用的,那么将GPIO初始化全部注释掉,直接进入ADC初始化,也是能得到数据。
- **更换 ADC 通道:**保证引脚正确,改ADC通道,能得到电压数据,但是数据不正确是其它的引脚的电压数据(改了ADC通道,对应的引脚自然也发生了变化)。
那么经过这么逐一验证后,此时有个猜想,这意味着在不进行任何GPIO配置的情况下,ADC可能仍然能够从这些引脚读取数据。但是查了官方例程,以及别人写的代码,都是需要进行GPIO配置
官方历程 ADC_DMA
static void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* ADC1 DeInit */
ADC_DeInit(ADC1);
/* GPIOC Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
/* ADC1 Periph clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
/* Configure ADC Channel11 and channel10 as analog input */
#ifdef USE_STM320518_EVAL
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;
#else
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;
#endif /* USE_STM320518_EVAL */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Initialize ADC structure */
ADC_StructInit(&ADC_InitStructure);
/* Configure the ADC1 in continuous mode withe a resolution equal to 12 bits */
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Backward;
ADC_Init(ADC1, &ADC_InitStructure);
/* Convert the ADC1 Channel11 and channel10 with 55.5 Cycles as sampling time */
#ifdef USE_STM320518_EVAL
ADC_ChannelConfig(ADC1, ADC_Channel_11 , ADC_SampleTime_55_5Cycles);
#else
ADC_ChannelConfig(ADC1, ADC_Channel_10 , ADC_SampleTime_55_5Cycles);
#endif /* USE_STM320518_EVAL */
/* Convert the ADC1 temperature sensor with 55.5 Cycles as sampling time */
ADC_ChannelConfig(ADC1, ADC_Channel_TempSensor , ADC_SampleTime_55_5Cycles);
ADC_TempSensorCmd(ENABLE);
/* Convert the ADC1 Vref with 55.5 Cycles as sampling time */
ADC_ChannelConfig(ADC1, ADC_Channel_Vrefint , ADC_SampleTime_55_5Cycles);
ADC_VrefintCmd(ENABLE);
/* Convert the ADC1 Vbat with 55.5 Cycles as sampling time */
ADC_ChannelConfig(ADC1, ADC_Channel_Vbat , ADC_SampleTime_55_5Cycles);
ADC_VbatCmd(ENABLE);
/* ADC Calibration */
ADC_GetCalibrationFactor(ADC1);
/* ADC DMA request in circular mode */
ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);
/* Enable ADC_DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable the ADC peripheral */
ADC_Cmd(ADC1, ENABLE);
/* Wait the ADRDY flag */
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY));
/* ADC1 regular Software Start Conv */
ADC_StartOfConversion(ADC1);
}
[想看简略分析给跳过这段直接看二、原因总结分析]
以下是详细分析:
尽管我没有初始化GPIO,却发现ADC仍然能够使用。这让我想起,GPIO有某种默认的初始状态。然后查找手册发现手册中写了 ❤️ 在复位期间及复位刚刚完成后,复用功能尚未激活,I/O 端口被配置为输入浮空模式 ❤️。
原来如此,了解到I/O端口在复位后被配置为输入浮空模式,这让我想到在配置ADC时,或许可以不将GPIO专门设置为模拟输入模式,而是保持其默认状态。
然而,我们在学习过程中常被告诫,在设置输入模式时,应避免使用浮空模式,因为它可能会导致不稳定的电平状态。
那么,让我们回顾一下GPIO的不同输入模式,以便更好地理解为什么浮空模式通常不被推荐:
浮空输入与上下拉输入的框图与特征:
此时,输出缓冲被禁用;图中的施密特触发器被启用;IO脚的数据被采样到数据输入寄存器可被读取。上/下拉电阻根据需要可以被打开或禁用。当上下拉电阻同时被禁用时即为浮空输入模式。
对于STM32芯片,复位后GPIO默认为浮空输入状态。对于不使用的GPIO,不建议让GPIO处于浮空输入模式。当它没有外接信号时,那个施密特触发器往往在随机地做电平翻转跳变,从而带来噪声并增加芯片的功耗。同时浮空脚对外部噪声或干扰敏感,对过ESD也极为不利。
GPIO的Analog模式(GPIO_AN)的功能框图如下:
这个Analog模式的GPIO状态跟上面的带可配置上下拉输入的GPIO状态主要有两个明显差别:
- 施密特触发器被关闭了,该触发器输出恒为0.
- 内部的上下拉电阻被关闭了。
这个施密特触发器被关闭了,GPIO的数字输入功能被取消。如果此时读取输入数据寄存器的值,结果恒为0。由于关闭了施密特触发器,上面提到的因它而起的电平跳变噪声和相应的额外功耗就没有了。
换句话说,当GPIO状态由浮空或上下拉输入状态改为Analog状态时,既消除了因为施密特触发器带来的噪声,同时又因它的关闭而降低了芯片动态功耗。
另外,配置在GPIO_Analog状态的GPIO属于高阻态,这点也有利于保持模拟信号的真实性。
GPIO可能被配置为某些模拟外设的复用脚,比如ADC,DAC的复用功能脚等,但也完全可能不做任何模拟外设的复用脚,只是配置在Analog模式而已。比方对于那些不用的管脚,我们都可以将其配置为GPIO_Analog状态。做过STM32芯片低功耗应用的人可能会在ST官方例程里发现过,在进低功耗模式之前对不用外设的对应GPIO都配置为Analog状态了。
二、原因总结分析
- I/O 端口的默认状态
在复位期间及复位刚刚完成后,复用功能尚未激活,I/O 端口被配置为输入浮空模式。这意味着即使没有进行 GPIO 配置,ADC 仍然可以从这些引脚读取数据。然而,这种模式下,引脚的电平状态可能不稳定,容易受到外部干扰。 - 浮空输入模式的特性
浮空输入模式下,引脚未连接到任何外部电路,处于高阻态,电平状态不确定。这种方式下,引脚可能因为干扰信号的存在而产生误触发,因此应尽量避免使用。 - 模拟输入模式的特性
模拟输入模式下,引脚被配置为模拟输入,适用于 ADC 等模拟外设的使用。在这种模式下,引脚的施密特触发器被关闭,内部的上下拉电阻被关闭,引脚处于高阻态,有助于保持模拟信号的真实性。
三、解决方法
- 配置 GPIO 为模拟输入模式
为了确保 ADC 读取的数据准确无误,建议将 GPIO 配置为模拟输入模式 - 避免使用浮空模式
对于不使用的 GPIO 引脚,建议将其配置为模拟输入模式或上下拉输入模式,以避免不必要的功耗和干扰。例如:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &GPIO_InitStructure);
四、注意事项
- 确保 GPIO 配置正确:在配置 ADC 时,务必确保将 GPIO 引脚配置为模拟输入模式,否则容易因为干扰信号的存在而产生误触发。
- 避免浮空模式:对于不使用的 GPIO 引脚,建议将其配置为模拟输入模式或上下拉输入模式,以避免不必要的功耗和干扰。
- 检查引脚配置:在配置引脚模式时,务必确保引脚的模式和功能配置正确,否则可能导致引脚无法正常工作。
五、扩展
在GPIO输入模式下,不同的输入方式会影响GPIO引脚的电信号特性,具体的区别如下:
4. 模拟输入:模拟输入是指将模拟信号连接到GPI0引脚上,这种输入方式需要使用ADC(模数转换器)将模拟信号转换为数字信号进行处理。
5. 浮空输入:浮空输入是指GPIO引脚未连接到任何外部电路,此时引脚处于高阻态,电平状态不确定。这种方式下,GPIO引脚可能因为干扰信号的存在而产生误触发,因此应尽量避免使用。
6. 下拉输入:下拉输入是指将GPIO引脚通过一个下拉电阳连接到地,当外部电路未连接时,GPIO引脚被下拉到低电平。这种方式下,当外部电路未连接时,引脚会保持低电平,避免误触发。
7. 上拉输入:上拉输入是指将GPI0引脚通过一个上拉电阻连接到3.3V电源,当外部电路未连接时,GPIO引脚被上拉到高电平。这种方式下,当外部电路未连接时,引脚会保持高电平,避免误触发。
需要注意的是,下拉输入和上拉输入一般用于数字信号的输入,而模拟输入则用于模拟信号的输入。同时,在使用上拉输入和下拉输入时,需要选择合适的电阻值,以确保输入信号稳定。
总结:ADC配置时,还是要注意将引脚的GPIO配置成模拟输入,否则容易因为干扰信号的存在而产生误触发。