STM32L052 ADC寄存器配置异常:输入3.3V读取0x00问题排查
排查ADC配置错误导致返回0x00的问题
咱们一步步拆解你代码里的核心问题,这些配置错误和流程混乱直接导致了ADC返回无效值:
1. GPIO模拟模式配置错误
你设置GPIOB14/15为模拟输入时,只置位了MODE14_1和MODE15_1,但STM32的GPIO模拟输入要求MODER对应两位全为1(即MODEx[1:0] = 11)。只设一位的话,引脚处于复用功能模式,根本不是模拟输入状态,自然读不到电压。
修正代码:
// 直接用掩码设置模拟模式,更直观 GPIOB->MODER |= GPIO_MODER_MODE14_Msk | GPIO_MODER_MODE15_Msk; // 等价写法: // GPIOB->MODER |= GPIO_MODER_MODE14_0 | GPIO_MODER_MODE14_1; // GPIOB->MODER |= GPIO_MODER_MODE15_0 | GPIO_MODER_MODE15_1;
2. ADC初始化顺序完全颠倒
ADC的正确启动流程是「使能→等待就绪→校准→启动转换」,你的代码完全搞反了顺序,还手动破坏了硬件标志:
- 提前手动置位
ADRDY标志:ADC1->ISR |= ADC_ISR_ADRDY;,这会覆盖硬件的就绪状态,导致后续等待逻辑失效 - 等待
ADRDY的条件写反:while ((ADC1->ISR & ADC_ISR_ADRDY));是当ADRDY为1时死等,正确应该是等待ADRDY置位:while (!(ADC1->ISR & ADC_ISR_ADRDY)); - 校准放在转换之后:校准必须在ADC首次启动前执行,否则校准无法生效,ADC精度会异常
3. 标志位清除方式错误
你清除EOCAL和AWD标志的写法有语法问题,按位与优先级低于取反,实际执行逻辑不符合预期。正确的清除方式是先将目标标志位按位或,再取反:
ADC1->ISR &= ~(ADC_ISR_EOCAL | ADC_ISR_AWD);
另外,ADRDY标志是写1清除,你注释里的ADC1->CR &= ~ADC_ISR_ADRDY是错误的,正确写法是ADC1->ISR |= ADC_ISR_ADRDY;
4. 转换逻辑混乱
你配置了连续转换模式(CFGR1_CONT),却在循环里反复禁用ADC、重启校准,这完全没必要——连续模式下ADC会自动循环转换,不需要每次重新初始化。同时,你选了3个通道(8、9、17),只在主循环读一次DR只能拿到最后一个通道的值,前面的会被覆盖,建议用中断或DMA来接收多通道数据。
修正后的核心代码示例
int main(void) { // 1. 使能时钟(注意用|=,避免关闭其他已启用的GPIO时钟) RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; RCC->IOPENR |= RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN; // 2. 配置GPIO为模拟输入 GPIOB->MODER |= GPIO_MODER_MODE14_Msk | GPIO_MODER_MODE15_Msk; // 如果使用通道17(内部温度传感器),必须额外使能: ADC->CCR |= ADC_CCR_TSEN; // 3. 配置ADC参数 ADC1->CR &= ~ADC_CR_ADSTART; // 确保ADSTART为0,才能修改采样周期 ADC1->SMPR |= ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 | ADC_SMPR_SMP_2; // 160.5个时钟周期 ADC1->CFGR1 &= ~ADC_CFGR1_SCANDIR; // 向上扫描 // 去掉AUTOFF,连续模式下自动关闭会中断转换流程 ADC1->CFGR1 |= ADC_CFGR1_AWDCH_3 | ADC_CFGR1_AWDEN | ADC_CFGR1_WAIT | ADC_CFGR1_CONT; ADC->CCR |= ADC_CCR_LFMEN | ADC_CCR_VREFEN; ADC1->CHSELR |= ADC_CHSELR_CHSEL8 | ADC_CHSELR_CHSEL9 | ADC_CHSELR_CHSEL17; // 配置中断 ADC1->IER |= ADC_IER_EOCIE | ADC_IER_EOSEQIE | ADC_IER_OVRIE | ADC_IER_EOSMPIE; NVIC_EnableIRQ(ADC1_COMP_IRQn); NVIC_SetPriority(ADC1_COMP_IRQn, 3); // 4. 初始化ADC ADC1->ISR &= ~(ADC_ISR_EOCAL | ADC_ISR_AWD); // 清除初始标志 ADC1->CR |= ADC_CR_ADEN; // 使能ADC while (!(ADC1->ISR & ADC_ISR_ADRDY)); // 等待ADC就绪 ADC1->ISR |= ADC_ISR_ADRDY; // 清除就绪标志 // 5. 执行校准(必须在转换前) ADC1->CR |= ADC_CR_ADCAL; while (!(ADC1->ISR & ADC_ISR_EOCAL)); // 等待校准完成 ADC1->ISR |= ADC_ISR_EOCAL; // 清除校准标志 // 6. 启动连续转换 ADC1->CR |= ADC_CR_ADSTART; // 主循环:专注处理业务,转换数据交给中断 while(1) { delay(100); } } // 中断服务函数:处理多通道转换结果 uint16_t adc_results[3] = {0}; uint8_t adc_channel_idx = 0; void ADC1_COMP_IRQHandler(void) { if(ADC1->ISR & ADC_ISR_EOC){ // 逐个存储通道结果 adc_results[adc_channel_idx++] = ADC1->DR; if(adc_channel_idx >= 3){ adc_channel_idx = 0; // 这里可以处理完整的序列数据,比如打印、存储 } ADC1->ISR |= ADC_ISR_EOC; // 清除EOC标志 } if(ADC1->ISR & ADC_ISR_EOSEQ){ ADC1->ISR |= ADC_ISR_EOSEQ; // 清除序列完成标志 } // 处理溢出等其他中断... }
这些修改应该能解决你返回0x00的问题,核心是纠正GPIO模拟模式配置、ADC初始化顺序和校准时机。如果使用通道17(内部温度传感器),一定要记得使能TSEN位,否则该通道也会返回错误值。
内容的提问来源于stack exchange,提问作者user5713188
相关产品推荐
相关产品推荐

