You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32L476裸机ADC始终输出最大值4095问题排查求助

STM32L476裸机ADC始终输出最大值4095问题排查

我在STM32L476上实现裸机ADC读取GPIO引脚电压(因效率需求未使用HAL库),但无论输入电压如何,每次转换结果都是最大值4095。尝试使用VREFINT、VBAT等内部通道,结果依旧为4095,推测问题出在ADC寄存器配置而非GPIO引脚。以下是相关代码,请求协助排查原因。


adc.c 代码

/*
 * adc.c
 */

#include "adc.h"

//USES ADC1

void adc_init(){
    RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN; //使能ADC时钟
    RCC->CCIPR   &= ~( RCC_CCIPR_ADCSEL ); //清除ADC时钟选择位
    RCC->CCIPR   |=  ( 3 << RCC_CCIPR_ADCSEL_Pos ); //设置ADC时钟为系统时钟
    ADC1->CR    &= ~( ADC_CR_DEEPPWD );//确保ADC不在深度掉电模式
    ADC1->CR |= ADC_CR_ADVREGEN; //使能ADC电压调节器
    nop(10000); //延时等待
    ADC1->CR &= ~(ADC_CR_ADCALDIF); //设置为单端模式
    ADC1->CR |= ADC_CR_ADCAL; //启动ADC校准
    while ((ADC1->CR & ADC_CR_ADCAL) != 0) { } //等待校准完成

}

void adc_enable(){
    ADC1->ISR |= ADC_ISR_ADRDY; // 使能前清除ADRDY标志
    ADC1->CR |= ADC_CR_ADEN; //使能ADC
    while ((ADC1->ISR & ADC_ISR_ADRDY) == 0) { } // 等待ADRDY被硬件置位(原注释错误)
}
void adc_configGPIO(){
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN; //使能GPIOC时钟
    while (GPIOC->OTYPER == 0xFFFFFFFF); //等待时钟使能完成

    GPIOC->OTYPER       &= ~( GPIO_OTYPER_OT0 ); //复位C0引脚的OTYPE位
    GPIOC->PUPDR        &= ~( GPIO_OSPEEDR_OSPEED0 ); // 此处寄存器混淆,应为GPIO_PUPDR_PUPD0
    GPIOC->OSPEEDR      &= ~( GPIO_PUPDR_PUPD0 ); // 此处寄存器混淆,应为GPIO_OSPEEDR_OSPEED0
    GPIOC->MODER        &= ~( GPIO_MODER_MODE0 ); //复位C0引脚的模式位
    GPIOC->ASCR         |=  ( GPIO_ASCR_ASC0 ); //连接C0引脚的模拟开关到ADC通道
    GPIOC->MODER        |=  ( 0x3 << GPIO_MODER_MODE0_Pos ); //设置为模拟模式
}


void adc_setChannel(){
    //采样时间默认,后续修改为最大
    ADC1->SQR1 &= ~( ADC_SQR1_L ); //设置序列长度为1
    ADC1->SQR1 &= ~(ADC_SQR1_SQ1); //复位序列通道选择
    ADC1->SQR1 |= (1 << ADC_SQR1_SQ1_Pos); //设置序列第一个通道为通道1(对应PA1,非PC0)
    ADC1->SMPR1 &= ~( ADC_SMPR1_SMP1 ); //复位通道1的采样时间
    ADC1->SMPR1 |=  ( 0x7 << ADC_SMPR1_SMP1_Pos ); //设置最大采样时间:640.5周期/采样
}

// 执行单次ADC转换
// (假设序列中只有一个通道)
uint16_t adc_singleConversion() {
  // 启动ADC转换
  ADC1->CR  |=  ( ADC_CR_ADSTART );
  // 等待转换完成标志
  while ( !( ADC1->ISR & ADC_ISR_EOC ) ) {};
  // 读取转换值(此操作会清除EOC标志)
  uint16_t adc_val = ADC1->DR;
  // 等待序列完成标志并清除它
  while ( !( ADC1->ISR & ADC_ISR_EOS ) ) {};
  ADC1->ISR |=  ( ADC_ISR_EOS );
  // 返回ADC值
  return adc_val;
}

主程序代码

nop()为微秒级延时函数,printMsg()用于串口打印输出,init_platform()处理其他初始化操作,确保使用GPIO前引脚配置已完全重置。

void branch_main(){
    //测试ADC
    adc_init();

    adc_enable();

    adc_configGPIO();

    adc_setConstantGPIOValue(); //注:此函数未提供实现

    adc_setChannel();

    while(1){
        printMsg("ADC value: %d \r\n", adc_singleConversion());

        nop(1000000);
    }
}

int main() {
    init_platform();
    branch_main();
}

关键问题分析与修正方案

  1. ADC通道与引脚映射错误
    STM32L476的PC0对应ADC1的通道10,而非通道1。当前设置SQ1为1,实际采样的是PA1引脚,若PA1悬空或接高电平,会直接输出最大值4095。
    修正:将通道设置改为10:

    ADC1->SQR1 |= (10 << ADC_SQR1_SQ1_Pos); // 设置序列通道为10(对应PC0)
    

    测试内部通道时需对应正确通道号并使能:

    • VREFINT对应通道17,需先添加ADC1->CCR |= ADC_CCR_VREFEN;,再设置SQ1为17
    • VBAT对应通道18,需先添加ADC1->CCR |= ADC_CCR_VBATEN;,再设置SQ1为18
  2. GPIO配置寄存器混淆
    adc_configGPIO()中复位PUPDR和OSPEEDR时寄存器搞反,修正后:

    GPIOC->PUPDR        &= ~( GPIO_PUPDR_PUPD0 ); // 复位C0的上下拉配置
    GPIOC->OSPEEDR      &= ~( GPIO_OSPEEDR_OSPEED0 ); // 复位C0的速度配置
    
  3. 内部通道未使能
    测试VREFINT/VBAT等内部通道时,必须先在ADC1->CCR寄存器中开启对应使能位,否则无法正确采样。


内容的提问来源于stack exchange,提问作者MysticDirt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 20:00:13