使用STM32 HAL库配置ADC单次转换模式详解

前言

在嵌入式开发中,ADC(模数转换器)是连接模拟世界与数字世界的重要桥梁。STM32微控制器内置了高性能的ADC模块,而HAL库则为我们提供了简洁高效的配置方式。今天,我将详细介绍如何使用STM32 HAL库配置ADC的单次转换模式。

什么是单次转换模式?

单次转换模式下,ADC只执行一次转换,完成后自动停止并等待下次触发。这种模式适用于不要求连续采样、需要节能或由特定事件触发的应用场景。

硬件准备

  • STM32开发板(本文以STM32F4系列为例)

  • 模拟信号源(如电位器、传感器等)

  • STM32CubeIDE或Keil MDK开发环境

配置步骤详解

1. 引脚与ADC外设初始化

首先,我们需要初始化ADC使用的GPIO引脚和ADC外设本身:

cs 复制代码
// ADC句柄声明
ADC_HandleTypeDef hadc1;

void ADC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 1. 使能时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_ADC1_CLK_ENABLE();
    
    // 2. 配置GPIO引脚为模拟输入模式
    GPIO_InitStruct.Pin = GPIO_PIN_0;  // 假设使用PA0(ADC1_IN0)
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 3. 配置ADC参数
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.ScanConvMode = DISABLE;           // 单通道,禁用扫描模式
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; // 每次转换后产生EOC
    hadc1.Init.ContinuousConvMode = DISABLE;     // 禁用连续转换
    hadc1.Init.NbrOfConversion = 1;              // 1个转换序列
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.NbrOfDiscConversion = 0;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    
    HAL_ADC_Init(&hadc1);
}

2. 配置ADC通道

接下来配置具体的ADC通道参数:

cs 复制代码
void ADC_ChannelConfig(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};
    
    sConfig.Channel = ADC_CHANNEL_0;      // 通道0(对应PA0)
    sConfig.Rank = 1;                     // 转换序列中的第一个
    sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; // 采样时间
    
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

3. 启动转换与读取结果

编写ADC转换函数:

cs 复制代码
uint16_t ADC_ReadSingleConversion(void)
{
    uint16_t adc_value = 0;
    
    // 启动ADC转换
    HAL_ADC_Start(&hadc1);
    
    // 等待转换完成,超时时间10ms
    if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
    {
        // 读取ADC值
        adc_value = HAL_ADC_GetValue(&hadc1);
    }
    
    // 停止ADC(单次转换模式会自动停止,这里确保状态正确)
    HAL_ADC_Stop(&hadc1);
    
    return adc_value;
}

4. 电压计算函数

将ADC原始值转换为实际电压:

cs 复制代码
float ADC_ConvertToVoltage(uint16_t adc_value, float vref)
{
    // 12位ADC,最大值为4095(2^12 - 1)
    return (adc_value * vref) / 4095.0f;
}

完整示例代码

cs 复制代码
#include "main.h"

ADC_HandleTypeDef hadc1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);

int main(void)
{
    uint16_t raw_adc;
    float voltage;
    const float VREF = 3.3f;  // 参考电压
    
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();
    
    while (1)
    {
        // 读取ADC值
        raw_adc = ADC_ReadSingleConversion();
        
        // 转换为电压
        voltage = ADC_ConvertToVoltage(raw_adc, VREF);
        
        // 输出结果(可通过串口或调试器查看)
        printf("ADC Raw: %d, Voltage: %.2f V\r\n", raw_adc, voltage);
        
        // 延迟1秒
        HAL_Delay(1000);
    }
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};
    
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.NbrOfDiscConversion = 0;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    
    HAL_ADC_Init(&hadc1);
    
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

关键参数解析

采样时间选择

采样时间决定了ADC对输入信号采样的时长,需要根据信号源阻抗来设置:

  • 高阻抗信号源 → 需要更长的采样时间

  • 低阻抗信号源 → 可以使用较短的采样时间

触发方式

除了软件触发,还可以配置为硬件触发:

cs 复制代码
// 例如,使用定时器2的TRGO事件触发
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;

调试技巧

  1. 使用断点调试 :在HAL_ADC_GetValue()后设置断点,查看ADC原始值

  2. 验证参考电压:使用万用表测量VREF的实际值

  3. 检查时钟配置:确保ADC时钟不超过规格书规定的最大值

  4. 使用内部参考:部分STM32有内部参考电压,可用于校准

常见问题与解决方案

Q1: ADC读数跳动较大

  • ✅ 检查电源稳定性

  • ✅ 增加软件滤波(如移动平均)

  • ✅ 调整采样时间

  • ✅ 添加硬件滤波电路

Q2: 转换速度慢

  • ✅ 减少采样时间

  • ✅ 提高ADC时钟频率

  • ✅ 考虑使用连续转换+DMA模式

Q3: 精度不足

  • ✅ 确保VREF稳定

  • ✅ 避免高噪声环境

  • ✅ 使用过采样技术提高分辨率

性能优化建议

  1. 使用DMA:即使单次转换,配合DMA也能减少CPU开销

  2. 校准ADC :部分STM32支持内部校准,调用HAL_ADCEx_Calibration_Start()

  3. 温度补偿:注意ADC性能随温度变化,高温下可能需重新校准

总结

配置STM32的ADC单次转换模式并不复杂,关键是要理解各个参数的含义,并根据实际应用需求进行优化。单次转换模式在需要节能或事件触发的场景中非常有用。掌握这种基础配置后,你可以进一步探索扫描模式、注入通道、差分输入等高级功能。

希望这篇博客能帮助你更好地理解和使用STM32的ADC功能。如果有任何问题或建议,欢迎在评论区留言讨论!

相关推荐
wrj的博客32 分钟前
python环境安装
python·学习·环境配置
优雅的潮叭37 分钟前
c++ 学习笔记之 chrono库
c++·笔记·学习
星火开发设计1 小时前
C++ 数组:一维数组的定义、遍历与常见操作
java·开发语言·数据结构·c++·学习·数组·知识
Y1rong1 小时前
STM32之中断(二)
stm32·单片机·嵌入式硬件
Y1rong1 小时前
STM32之中断(一)
stm32·单片机·嵌入式硬件
星幻元宇VR1 小时前
走进公共安全教育展厅|了解安全防范知识
学习·安全·虚拟现实
知识分享小能手1 小时前
Oracle 19c入门学习教程,从入门到精通, Oracle 表空间与数据文件管理详解(9)
数据库·学习·oracle
不大姐姐AI智能体2 小时前
搭了个小红书笔记自动生产线,一句话生成图文,一键发布,支持手机端、电脑端发布
人工智能·经验分享·笔记·矩阵·aigc
LaoZhangGong1232 小时前
学习TCP/IP的第3步:和SYN相关的数据包
stm32·单片机·网络协议·tcp/ip·以太网
小郭团队2 小时前
2_1_七段式SVPWM (经典算法)算法理论与 MATLAB 实现详解
嵌入式硬件·算法·硬件架构·arm·dsp开发