前言:最近项目中又使用了一次ADC用于检测温度与电流,配置驱动的时候也比较简单,基本上一气呵成,由于前面还没有介绍ADC相关的文章,所以这里就记录一下配置的过程,ADC全称模数转换器,顾名思义就是将一个模拟信号转换成一个数字信号,使用的场景也比较广泛,估计搞嵌入式的小伙伴都知道ADC的使用频率之高,几乎每一个嵌入式的产品都或多或少的使用到ADC。所以,话不多说,直接上正文吧。
目录
1,ADC使用场景
STM32微控制器中的ADC(模数转换器)模块广泛应用于各种场景,主要用于将模拟信号转换为数字信号,主要应用于电压的采集,有了最原始的电压数据就可以根据欧姆定律扩展计算得到电流与电阻,然后一些使用到阻值或者电流的传感器就可以接到这个外设上面,最后换算得到需要检测的参数数据,比如温湿度传感器、烟雾传感器、霍尔传感器、热敏电阻等等。
本篇文章是基于STM32G474芯片主控用于检测一个系统的温度数据与电流数据和一个供电电压的参数数据,使用到了6个ADC的通道。
另外上面也提到每一个嵌入式的产品都会使用ADC的原因是因为市场上面的智能嵌入式产品基本都用到内部供电电池,而检测电量的其中一种方式就是检测电压,这就会用到ADC。
总结来看呢就是下面的几个方面,使用AI生成的,虽然就是冷冰冰的知识,但还是比较全的,大家简单认识了解一下就可以了。。。
1. 传感器数据采集
温度传感器:如NTC、PTC、热电偶等,ADC将模拟温度信号转换为数字信号。
光强传感器:如光敏电阻、光电二极管等,ADC用于测量环境光强度。
压力传感器:如气压传感器、压力变送器等,ADC用于测量压力变化。
湿度传感器:如电容式湿度传感器,ADC用于测量环境湿度。
2. 电池管理
电池电压监测:ADC用于实时监测电池电压,确保电池在安全范围内工作。
电流检测:通过测量电流检测电阻上的电压,ADC可以计算电池的充放电电流。
3. 音频处理
音频信号采集:ADC用于采集麦克风或其他音频源的模拟信号,转换为数字信号进行后续处理。
音频信号处理:在音频处理系统中,ADC用于将模拟音频信号转换为数字信号,便于数字信号处理(DSP)。
4. 工业控制
模拟信号监控:在工业自动化中,ADC用于监控各种模拟信号,如电压、电流、压力、流量等。
闭环控制:ADC用于反馈控制系统中,实时采集传感器数据,调整控制参数。
5. 医疗设备
生物信号采集:如心电图(ECG)、脑电图(EEG)等,ADC用于采集生物电信号。
血氧监测:通过ADC采集光电传感器的信号,计算血氧饱和度。
6. 通信系统
信号调制解调:在无线通信系统中,ADC用于将接收到的模拟信号转换为数字信号,便于解调和处理。
信号强度检测:ADC用于测量接收信号的强度,调整发射功率或天线方向。
7. 汽车电子
发动机控制:ADC用于采集发动机温度、压力、氧气浓度等信号,优化发动机性能。
车载传感器:如加速度传感器、陀螺仪等,ADC用于采集车辆运动状态数据。
8. 消费电子
触摸屏控制:ADC用于检测触摸屏上的触摸位置,将模拟触摸信号转换为数字坐标。
电源管理:ADC用于监测电源电压和电流,确保设备稳定运行。
9. 环境监测
空气质量检测:ADC用于采集气体传感器的信号,监测空气中的有害气体浓度。
水质监测:ADC用于采集水质传感器的信号,监测水中的pH值、溶解氧等参数。
10. 科学研究
实验数据采集:在科学实验中,ADC用于采集各种模拟信号,如电压、电流、温度等,进行数据分析和处理。
2,ADC外设资源简介
由于本篇文章使用到的主控芯片是STM32G474,所以第一件事情就需要找到官网上面的参考手册进行查看
当然了,正所谓举一反三,触类旁通,基于其他主控MCU的ADC配置也是需要先找到对应的官网上面的参考手册,先查看其基本信息在进行合理的配置使用。
由于官网上面的下载的都是英文的,一定会有些小伙伴读不下去,我下面就充当一下翻译进行简单介绍一下吧。
ST官方系列的主控芯片每一个型号都有其不同的外设资源配置,并且每一种外设也都有不同的通道或者对应的管脚的索引关系。
此款MCU所有ADC的资源简介:
1,ADC1和ADC2紧密耦合,可以在双模式(ADC1为主)下运行。ADC3 和 ADC4 紧密耦合,可以以双模式运行(ADC3是主设备)。ADC5是独立控制的。,
2,每个ADC由一个12位的逐次逼近式模数转换器组成。
3,每个ADC最多有19个多路复用通道。各种通道的A/D转换可以以单通道、连续、扫描或非连续模式进行。
4,ADC的结果存储在左对齐或右对齐的16位数据寄存器中。
5,ADC被映射到AHB总线上,以允许快速数据处理。
6,模拟看门狗功能允许应用程序检测输入电压是否超出用户定义的高或低值。
7,内置的硬件过采样器可以改善模拟性能,同时从CPU卸载相关的计算负担。实现了一种高效的低功耗模式,以便在低频下实现非常低的功耗。
ADC主要功能:
1,最多5个ADC,其中4个(成对)可以以双模式运行,ADC1连接到14个外部通道+4个内部通道,ADC2连接到16个外部通道+2个内部通道,ADC3连接到15个外部通道+3个内部通道,ADC4连接到16个外部通道+2个内部通道,ADC5连接到13个外部通道+5个内部通道
2,12、10、8或6位 可配置分辨率
3,ADC转换时间与AHB总线时钟频率无关,可以通过降低分辨率加快转换时间
4,管理单端或差分输入AHB从机总线接口,允许快速数据处理,
5,自校准
6,通道可编程采样时间灵活的采样时间控制最多四个注入通道(将模拟输入分配给常规或注入通道是完全可配置的)
7,硬件助手准备注入通道的上下文,以允许快速上下文切换
8,数据与内置数据一致性对齐,数据可以通过DMA管理以进行常规通道转换4个专用数据寄存器,用于注入通道
9,过采样器:16位数据寄存器;超采样比率可调节从2到256;可编程数据移位可达8位,
10,数据预处理:获得补偿;偏移补偿
11,低功耗特性:速度自适应低功耗模式,可在低频下降低ADC功耗,允许在低速总线频率下应用,同时保持最佳的ADC性能-提供自动控制,以避免ADC在低AHB总线时钟频率下溢出
12,每个 ADC 的外部模拟输入通道数量:从GPI0引脚最多5个快速通道;从GPIO引脚最多可达13个慢通道。
13,此外,还有几个内部专用频道:内部参考电压(VREFINT),连接到ADC1、3、4和5
;内部温度传感器(VTS),连接到ADC1和5;VBAT监测通道(VBAT/3),连接到ADC1、3(仅适用于第3类设备)和5;OPAMP1的内部输出连接到ADC1;OPAMP2和OPAMP3的内部输出连接到ADC2
;OPAMP3的内部输出连接到ADC3;OPAMP6的内部输出连接到ADC4(用于第3类设备)或ADC3(用于第4类设备);OPAMP4和OPAMP5的内部输出连接到ADC5
14,转换的开始可以由以下方式启动:通过软件进行常规和注入转换;通过可配置极性的硬件触发器(内部定时器事件或GPI0输入事件)进行常规和注入转换。
15,转换模式:每个ADC可以转换单个通道或扫描一系列通道;单模式在触发一次时转换选定的输入;连续模式连续转换选定的输入;不连续模式,
16,ADC1、ADC2、ADC3和ADC4的双ADC模式
17,ADC就绪时中断生成,采样结束,转换结束(常规或注入),序列转换结束(常规或注入),模拟看门狗1、2或3或超时事件
18,每个ADC有3个模拟看门狗-看门狗可以进行过滤,忽略超出范围的数据
19,ADC输入范围:VREF-≤ VIN≤VREF+(这个电压一般是3.3V)
下面显示了一个ADC的框图可以了解下其内部的基本实现原理:嘿嘿,其实这个东西就是在使用非常熟练的情况下在进行研究的,对于初级工程师而言会配置实现就可以了,有兴趣的小伙伴可以下载一下这个参考手册进行查看学习。
ADC的管脚与内部信号描述:
就简单介绍上面的那些吧,当然限于篇幅没有完全介绍完成,还有诸如ADC时钟特征、ADC低功耗模式、ADC中断、ADC寄存器的配置的介绍有兴趣的小伙伴就去参考手册那里学习吧。
3,ADC的CUBEMX配置
这里就不过多介绍了,在我的前面的文章也非常详细的介绍,包括且不限于ADC外设资源
【STM32项目实战系列】基于CUBEMX创建一个ST项目工程(详细版)_smbus-alert-mode-优快云博客
4,ADC的代码API编写
使用了6个通道进行数据采集,都是ADC1,没有使用中断,就是简单的数据循环读取,配置如下,代码仅供参考学习使用。。。
1,3路电流检测
PC0 ------> ADC1_IN6 ----> ISENDA
PC1 ------> ADC1_IN7 ----> ISENDC
PC2 ------> ADC1_IN8 ----> ISENDB
2,两路热敏电阻检测
PC3 ------> ADC1_IN9 ----> ADC T [NTC]
PA0 ------> ADC1_IN1 ----> ADC M [MF51]
3,一路直接电压的检测
PA1 ------> ADC1_IN2 ----> 24~48V
adc.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file adc.c
* @brief This file provides code for the configuration
* of the ADC instances.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "adc.h"
/* USER CODE BEGIN 0 */
// 电压计算常量
#define VREF 3.3f // 假设 VREF 为 3.3V
#define ADC_RESOLUTION 4095 // 12-bit ADC 分辨率 (0-4095)
/* USER CODE END 0 */
ADC_HandleTypeDef hadc1;
ADC_ChannelConfTypeDef sConfig;
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_MultiModeTypeDef multimode = {0};
// ADC_ChannelConfTypeDef sConfig = {0};
// 配置 ADC1 的基本设置
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; // 设置 ADC 时钟为 2 分频
hadc1.Init.Resolution = ADC_RESOLUTION_12B; // 12 位分辨率
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; // 启用扫描模式(支持多个通道)
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; // 每次转换完成后触发中断
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE; // 连续转换模式
hadc1.Init.NbrOfConversion = 1; // 设置转换通道数为1个
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发启动
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE; // 禁用 DMA 请求
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.OversamplingMode = DISABLE;
// 初始化 ADC1
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler(); // 如果初始化失败,则调用错误处理函数
}
// 配置 ADC 多模模式
multimode.Mode = ADC_MODE_INDEPENDENT; // 独立模式
if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
{
Error_Handler(); // 配置失败,调用错误处理函数
}
// 配置 ADC 通道 1 (ADC_CHANNEL_1)
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1; // 第1个通道
sConfig.SamplingTime = ADC_SAMPLETIME_47CYCLES_5; // 设置采样时间
sConfig.SingleDiff = ADC_SINGLE_ENDED; // 单端输入,另一个可选的是差分模式
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler(); // 配置失败,调用错误处理函数
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* ADC1 clock enable */
__HAL_RCC_ADC12_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC1 GPIO Configuration
PC0 ------> ADC1_IN6
PC1 ------> ADC1_IN7
PC2 ------> ADC1_IN8
PC3 ------> ADC1_IN9
PA0 ------> ADC1_IN1
PA1 ------> ADC1_IN2
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* ADC1 interrupt Init */
HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if (adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspDeInit 0 */
/* USER CODE END ADC1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_ADC12_CLK_DISABLE();
/**ADC1 GPIO Configuration
PC0 ------> ADC1_IN6
PC1 ------> ADC1_IN7
PC2 ------> ADC1_IN8
PC3 ------> ADC1_IN9
PA0 ------> ADC1_IN1
PA1 ------> ADC1_IN2
*/
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1);
/* ADC1 interrupt Deinit */
HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
/* USER CODE BEGIN ADC1_MspDeInit 1 */
/* USER CODE END ADC1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/**************************************************** */
// 输入通道号,输出计算的电压
// PC0 ------> ADC1_IN6 ----> ISENDA
// PC1 ------> ADC1_IN7 ----> ISENDC
// PC2 ------> ADC1_IN8 ----> ISENDB
// PC3 ------> ADC1_IN9 ----> ADC T [NTC]
// PA0 ------> ADC1_IN1 ----> ADC M [MF51]
// PA1 ------> ADC1_IN2 ----> 24~48V
/**************************************************** */
float get_voltage(uint32_t channel)
{
// ADC_ChannelConfTypeDef sConfig = {0};
float voltage = 0;
// 配置 ADC通道
sConfig.Channel = channel; // 输入指定的通道号
sConfig.Rank = ADC_REGULAR_RANK_1; // 设置通道排名
sConfig.SamplingTime = ADC_SAMPLETIME_47CYCLES_5; // 设置采样时间
HAL_ADC_ConfigChannel(&hadc1, &sConfig); // 配置 ADC 通道
// 启动 ADC 转换并等待完成
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY) == HAL_OK) {
// 获取 ADC 转换结果
uint32_t adc_value = HAL_ADC_GetValue(&hadc1);
// printf("HAL_ADC_GetValue:%d\r\n", HAL_ADC_GetValue(&hadc1));
// 根据 ADC 值计算电压
voltage = adc_value * VREF / ADC_RESOLUTION;
HAL_ADC_Stop(&hadc1);
}
else {
HAL_ADC_Stop(&hadc1);
// 错误处理,如果 ADC 转换失败
return -1.0f; // 返回错误值
}
return voltage;
}
//PA1 检测连接的输入电源的电压值
float get_power_voltage(void)
{
return get_voltage(ADC_24_48V) * 16;
}
/* USER CODE END 1 */
adc.h
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file adc.h
* @brief This file contains all the function prototypes for
* the adc.c file
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __ADC_H__
#define __ADC_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern ADC_HandleTypeDef hadc1;
/* USER CODE BEGIN Private defines */
#define ISENDA ADC_CHANNEL_6
#define ISENDC ADC_CHANNEL_7
#define ISENDB ADC_CHANNEL_8
#define ADC_M ADC_CHANNEL_1
#define ADC_T ADC_CHANNEL_9
#define ADC_24_48V ADC_CHANNEL_2
/* USER CODE END Private defines */
void MX_ADC1_Init(void);
/* USER CODE BEGIN Prototypes */
float get_voltage(uint32_t channel);
float get_power_voltage(void);
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __ADC_H__ */
具体的效果就不再展示了,如果哪位小伙伴的ADC配置无法达到效果的可以评论区留言。。。完结。。。