STM32入门笔记(03): ADC(SPL库函数版)(2)

A/D转换的常用技术有逐次逼近式双积分式并行式跟踪比较式等。目前用的较多的是前3种。

A/D转换器的主要技术指标

转换时间
分辨率

例如,8位A/D转换器的数字输出量的变化范围为0~255,当输入电压的满刻度为5V时,数字量每变化一个数字所对应输入模拟电压的值为5V/255=19.6mV,其分辨能力为19.6mV

转换精度

转换精度指的是转换后所得的结果相对于实际值的准确度,可以用满量程的百分比这一相对误差来表示,如±0.05%

ADC应用设计深入讨论尽管STM32内部集成了12位ADC,但在实际应用中,要想真正实现12位精度且比较稳定的ADC并不简单,需要进一步从硬件、软件方面进行综合、细致地考虑。下面介绍一些在ADC应用设计中应该考虑的几个要点:

工作电压的稳定性

AVCC是提供给ADC工作的电源,如果AVCC不稳定,就会影响ADC的转换精度

参考电压的确定

ADC的参考电压应稍大于输入电压的最高值。ADC的参考电压VREF可以选择为AVCC,或外接参考电压源,外接的参考电压源应该稳定。

采样时钟的选择

ADC时钟频率最大为14MHz。如果STM32系统时钟频率为56MHz时,一般为4分频,ADC时钟频率为14MHz,如果系统时钟频率为72MHz时,一般为6分频,ADC时钟频率为12MHz。

ADC通道的输入信号频率带宽取决于ADC时钟频率。把ADC通道配置为55.5个周期,若ADCCLK的时钟频率配置为12MHz,则ADC采样的时间计算公式如下。Tcovn=采样时间+12.5个周期

其中:Tcovn为总转换时间,采样时间是根据每个通道的ADC_SampleTime的值来决定的。后面的12.5个周期是ADC转换时量化所需要的

固定的周期,ADC的一次转换所需要的时间是Tcovn=(55.5+12.5)×(1/12),大约是5.67μs。STM32的ADC输入阻抗典型值为50kΩ,为了保证测量准确,被测信号源的输出阻抗要尽可能低。

模拟噪声的抑制

器件外部和内部的数字电路会产生电磁干扰,并会影响模拟测量的精度。如果对ADC的转换精度要求很高,则可以采用以下的技术来降低噪声的影响。

使模拟信号的通路尽可能地短,模拟信号连线应从模拟地的布线盘上通过,并使它们尽可能远离高速开关数字信号线。

STM32的AVCC引脚应该通过LC网络与数字端电源VCC相连。

如果某些ADC引脚作为通用数字输出口,那么在ADC转换过程中,不要改变这些引脚的状态。

校准


ADC - 电压

要将 ADC (模数转换器) 的原始数值转换为电压值,通常需要进行以下步骤:

  1. 确定 ADC 的分辨率
    例如,对于 STM32F103C8T6,ADC 的分辨率是 12 位,因此其数值范围是 0 到 4095。

  2. 确定参考电压
    确定 ADC 的参考电压 Vref。在 STM32F103C8T6 中,默认情况下Vref 通常是 3.3V,但这取决于具体的硬件配置。

  3. 读取 ADC 值
    读取 ADC 转换后的数值,例如 ADC_value 。

  4. 使用公式转换为电压值
    使用公式计算实际的电压值:
    在这里插入图片描述

    其中 ( N ) 是 ADC 的分辨率。例如,对于 12 位 ADC,( N = 12 )。

具体步骤如下:

  1. 读取 ADC 值:

    uint16_t ADC_value = HAL_ADC_GetValue(&hadc1);
    
  2. 计算电压值:

    float V_REF = 3.3; // 假设参考电压为 3.3V
    float voltage = (ADC_value / 4095.0) * V_REF;
    

以下是一个完整的例子,假设你使用 STM32 HAL 库:

#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)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_ADC1_Init();

  HAL_ADC_Start(&hadc1);

  while (1)
  {
    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
    uint16_t ADC_value = HAL_ADC_GetValue(&hadc1);
    float V_REF = 3.3;
    float voltage = (ADC_value / 4095.0) * V_REF;
    
    // 在这里使用计算出的电压值
  }
}

static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
}

void SystemClock_Config(void)
{
  // 时钟配置代码
}

这个例子展示了如何初始化 ADC1 并读取通道 1 (PA1) 的 ADC 值,然后将其转换为电压值。


如果将100V电压经过串联电阻分压,电阻R1为66K,R2为3K,这样如何通过ADC采样计算出电压?

将 100V 电压经过分压电路并通过 ADC 进行采样,计算出实际电压的方法如下:

  1. 分压电路公式
    分压电路的输出电压 Vout 可以用以下公式计算:
    在这里插入图片描述

    其中 Vin 是输入电压, R1 和 R2 是分压电阻。

  2. 具体分压电路参数

    在这里插入图片描述

    注意:在实际应用中,必须确保 ( V_{out} ) 不超过 ADC 的参考电压(例如 3.3V)。在这种情况下,分压比使得 Vout 超过了 ADC 允许的最大电压,需要调整分压电阻以确保 Vout 在安全范围内

  3. 假设安全分压电阻

在这里插入图片描述

  1. 通过 ADC 读取分压后的电压
    设定 ADC 的参考电压 ( V_{REF} = 3.3V )。
假设 ADC 读取的数值为 ( ADC_value ),分辨率为 12 位 (0 到 4095)。

  2. 计算实际电压

在这里插入图片描述

  1. 反向计算原始输入电压

在这里插入图片描述

假设使用 STM32 和 HAL 库,代码示例如下:

#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)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_ADC1_Init();

  HAL_ADC_Start(&hadc1);

  while (1)
  {
    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
    uint16_t ADC_value = HAL_ADC_GetValue(&hadc1);

    float V_REF = 3.3;
    float V_out = (ADC_value / 4095.0) * V_REF;

    float R1 = 96000.0; // 96KΩ
    float R2 = 3000.0;  // 3KΩ

    float V_in = V_out * (R1 + R2) / R2;

    // 使用计算出的 V_in
  }
}

static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
}

void SystemClock_Config(void)
{
  // 时钟配置代码
}

这个代码展示了如何读取 ADC 值并将其转换为实际电压,再通过分压公式计算出原始输入电压。注意在实际电路中,请确保所有电压和电阻值在安全范围内,以防止损坏设备。


ADC 软件滤波

ADC 读取到的值有抖动是常见现象,可以通过以下几种方法来进行滤波:

  1. 平均值滤波:读取多次 ADC 值,取平均值,可以平滑抖动。
  2. 中值滤波:读取多次 ADC 值,取中间值,可以去除突发噪声。
  3. 低通滤波器:通过数字滤波算法,平滑高频噪声。

以下是每种方法的具体实现:

1. 平均值滤波

#define NUM_SAMPLES 10

uint16_t Read_ADC_Averaged(ADC_HandleTypeDef* hadc)
{
    uint32_t sum = 0;
    for (int i = 0; i < NUM_SAMPLES; i++)
    {
        HAL_ADC_Start(hadc);
        HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
        sum += HAL_ADC_GetValue(hadc);
    }
    return sum / NUM_SAMPLES;
}

2. 中值滤波

#define NUM_SAMPLES 9

uint16_t Read_ADC_Median(ADC_HandleTypeDef* hadc)
{
    uint16_t samples[NUM_SAMPLES];
    for (int i = 0; i < NUM_SAMPLES; i++)
    {
        HAL_ADC_Start(hadc);
        HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
        samples[i] = HAL_ADC_GetValue(hadc);
    }
    // 排序
    for (int i = 0; i < NUM_SAMPLES - 1; i++)
    {
        for (int j = 0; j < NUM_SAMPLES - i - 1; j++)
        {
            if (samples[j] > samples[j + 1])
            {
                uint16_t temp = samples[j];
                samples[j] = samples[j + 1];
                samples[j + 1] = temp;
            }
        }
    }
    return samples[NUM_SAMPLES / 2];
}

3. 低通滤波器 (IIR 滤波器)

#define ALPHA 0.1 // 滤波系数,取值范围为 0 到 1

float Read_ADC_LowPass(ADC_HandleTypeDef* hadc, float previous_filtered_value)
{
    HAL_ADC_Start(hadc);
    HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
    uint16_t raw_value = HAL_ADC_GetValue(hadc);
    float current_filtered_value = ALPHA * raw_value + (1 - ALPHA) * previous_filtered_value;
    return current_filtered_value;
}

综合代码示例

将这些滤波方法整合到你的代码中,可以如下:

#include "main.h"

ADC_HandleTypeDef hadc1;

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

#define NUM_SAMPLES 10
#define ALPHA 0.1

float previous_filtered_value = 0;

uint16_t Read_ADC_Averaged(ADC_HandleTypeDef* hadc)
{
    uint32_t sum = 0;
    for (int i = 0; i < NUM_SAMPLES; i++)
    {
        HAL_ADC_Start(hadc);
        HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
        sum += HAL_ADC_GetValue(hadc);
    }
    return sum / NUM_SAMPLES;
}

uint16_t Read_ADC_Median(ADC_HandleTypeDef* hadc)
{
    uint16_t samples[NUM_SAMPLES];
    for (int i = 0; i < NUM_SAMPLES; i++)
    {
        HAL_ADC_Start(hadc);
        HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
        samples[i] = HAL_ADC_GetValue(hadc);
    }
    for (int i = 0; i < NUM_SAMPLES - 1; i++)
    {
        for (int j = 0; j < NUM_SAMPLES - i - 1; j++)
        {
            if (samples[j] > samples[j + 1])
            {
                uint16_t temp = samples[j];
                samples[j] = samples[j + 1];
                samples[j + 1] = temp;
            }
        }
    }
    return samples[NUM_SAMPLES / 2];
}

float Read_ADC_LowPass(ADC_HandleTypeDef* hadc, float previous_filtered_value)
{
    HAL_ADC_Start(hadc);
    HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
    uint16_t raw_value = HAL_ADC_GetValue(hadc);
    float current_filtered_value = ALPHA * raw_value + (1 - ALPHA) * previous_filtered_value;
    return current_filtered_value;
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    HAL_ADC_Start(&hadc1);

    while (1)
    {
        uint16_t ADC_value_averaged = Read_ADC_Averaged(&hadc1);
        uint16_t ADC_value_median = Read_ADC_Median(&hadc1);
        previous_filtered_value = Read_ADC_LowPass(&hadc1, previous_filtered_value);

        float V_REF = 3.3;
        float V_out_averaged = (ADC_value_averaged / 4095.0) * V_REF;
        float V_out_median = (ADC_value_median / 4095.0) * V_REF;
        float V_out_lowpass = (previous_filtered_value / 4095.0) * V_REF;

        float R1 = 96000.0;
        float R2 = 3000.0;

        float V_in_averaged = V_out_averaged * (R1 + R2) / R2;
        float V_in_median = V_out_median * (R1 + R2) / R2;
        float V_in_lowpass = V_out_lowpass * (R1 + R2) / R2;

        // 使用计算出的 V_in
    }
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
}

void SystemClock_Config(void)
{
    // 时钟配置代码
}

这个代码示例展示了如何分别使用平均值滤波、中值滤波和低通滤波来处理 ADC 抖动。根据实际需求选择合适的滤波方法,可以有效减小 ADC 读数的抖动。


低通滤波器(IIR)

低通滤波器是一种常用的信号处理技术,用于平滑信号并去除高频噪声。最常见的低通滤波器之一是指数加权移动平均滤波器(IIR 低通滤波器)。

原理

指数加权移动平均滤波器的基本原理是对当前采样值和先前的滤波值进行加权平均。公式如下:

在这里插入图片描述

具体实现步骤

  1. 定义变量

    • 滤波系数 (\alpha)
    • 上一次的滤波输出值(初始为 0)
  2. 读取 ADC 值

    • 使用 HAL 库读取 ADC 值
  3. 计算滤波值

    • 应用低通滤波公式
  4. 重复进行

    • 在循环中持续读取 ADC 值并计算滤波值

代码实现

以下是一个详细的 C 语言代码示例,使用 STM32 HAL 库实现低通滤波器:

#include "main.h"

ADC_HandleTypeDef hadc1;

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

#define ALPHA 0.1  // 滤波系数,范围在 0 到 1 之间

float previous_filtered_value = 0;  // 上一次的滤波输出值

float Read_ADC_LowPass(ADC_HandleTypeDef* hadc, float previous_filtered_value)
{
    HAL_ADC_Start(hadc);
    HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
    uint16_t raw_value = HAL_ADC_GetValue(hadc);
    float current_filtered_value = ALPHA * raw_value + (1 - ALPHA) * previous_filtered_value;
    return current_filtered_value;
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    HAL_ADC_Start(&hadc1);

    while (1)
    {
        previous_filtered_value = Read_ADC_LowPass(&hadc1, previous_filtered_value);

        float V_REF = 3.3;
        float V_out = (previous_filtered_value / 4095.0) * V_REF;

        float R1 = 96000.0; // 96KΩ
        float R2 = 3000.0;  // 3KΩ

        float V_in = V_out * (R1 + R2) / R2;

        // 在这里使用计算出的 V_in
    }
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
}

void SystemClock_Config(void)
{
    // 时钟配置代码
}

详细说明

  1. 定义滤波系数和变量

    • #define ALPHA 0.1 定义滤波系数,决定新输入值的权重。
    • float previous_filtered_value = 0; 初始化上一次的滤波输出值。
  2. 读取 ADC 值并应用低通滤波公式

    • HAL_ADC_Start(hadc); 启动 ADC。
    • HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY); 等待 ADC 转换完成。
    • uint16_t raw_value = HAL_ADC_GetValue(hadc); 获取 ADC 读取值。
    • float current_filtered_value = ALPHA * raw_value + (1 - ALPHA) * previous_filtered_value; 应用低通滤波公式计算当前滤波值。
  3. 在主循环中持续滤波

    • previous_filtered_value = Read_ADC_LowPass(&hadc1, previous_filtered_value); 调用滤波函数,更新滤波值。
  4. 计算和使用滤波后的电压值

    • float V_out = (previous_filtered_value / 4095.0) * V_REF; 计算分压电路的输出电压。
    • float V_in = V_out * (R1 + R2) / R2; 反向计算原始输入电压。

通过这个实现,可以有效地平滑 ADC 读取值的抖动,提高测量的稳定性。


ADC - 电流

为了通过 ADC 采集电流,并根据 ADC 值计算出电流值,需要以下几个步骤:

  1. 选择电流传感器:通常使用分流电阻 (shunt resistor) 或霍尔效应电流传感器。
  2. 采样电压:通过分流电阻或传感器将电流转换为电压,然后用 ADC 采样该电压。
  3. 计算电流:根据电压值和已知的分流电阻或传感器的特性,计算出实际电流。

以下是详细步骤:

1. 分流电阻测量电流

使用分流电阻测量电流的方法如下:

在这里插入图片描述

2. ADC 采样电压

使用 ADC 采样分流电阻上的电压。假设 ADC 的参考电压 ( V_{REF} ) 为 3.3V,分辨率为 12 位 (0 到 4095)。

3. 计算电流

根据 ADC 采样到的电压值,计算实际电流:
[
I = \frac{V_{shunt}}{R_{shunt}} = \frac{ADC_{value} \times V_{REF}}{4095 \times R_{shunt}}
]

代码实现

以下是一个详细的 C 语言代码示例,使用 STM32 HAL 库通过分流电阻测量电流:

#include "main.h"

ADC_HandleTypeDef hadc1;

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

#define R_SHUNT 0.01  // 分流电阻值 (单位: 欧姆)
#define V_REF 3.3     // ADC 参考电压 (单位: 伏特)
#define ADC_RESOLUTION 4095.0 // ADC 分辨率 (12 位)

float Read_Current(ADC_HandleTypeDef* hadc, float R_shunt)
{
    HAL_ADC_Start(hadc);
    HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
    uint16_t ADC_value = HAL_ADC_GetValue(hadc);
    float V_shunt = (ADC_value / ADC_RESOLUTION) * V_REF;
    float current = V_shunt / R_shunt;
    return current;
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    HAL_ADC_Start(&hadc1);

    while (1)
    {
        float current = Read_Current(&hadc1, R_SHUNT);

        // 在这里使用计算出的电流值 current
    }
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
}

void SystemClock_Config(void)
{
    // 时钟配置代码
}

详细说明

  1. 定义分流电阻和 ADC 参数

    • #define R_SHUNT 0.01 定义分流电阻值,单位为欧姆。
    • #define V_REF 3.3 定义 ADC 参考电压,单位为伏特。
    • #define ADC_RESOLUTION 4095.0 定义 ADC 的分辨率。
  2. 读取 ADC 值并计算电流

    • HAL_ADC_Start(hadc); 启动 ADC。
    • HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY); 等待 ADC 转换完成。
    • uint16_t ADC_value = HAL_ADC_GetValue(hadc); 获取 ADC 读取值。
    • float V_shunt = (ADC_value / ADC_RESOLUTION) * V_REF; 计算分流电阻上的电压。
    • float current = V_shunt / R_shunt; 计算实际电流。
  3. 在主循环中持续测量电流

    • float current = Read_Current(&hadc1, R_SHUNT); 调用测量电流的函数,获取当前电流值。

通过这个实现,可以有效地使用 ADC 采集电流,并根据 ADC 值计算出实际电流值。


ADC - 温度

使用查表法计算温度是一种常见且简单的方法,特别适合在微控制器上实现,因为它不需要复杂的数学运算。以下是具体步骤和实现方法:

步骤概述

  1. 建立查表:根据 NTC 热敏电阻的特性曲线,创建一个电阻值与温度对应的查表(数组)。
  2. 读取 ADC 值:使用 ADC 读取分压电路的电压。
  3. 计算 NTC 电阻值:根据分压公式计算 NTC 热敏电阻的电阻值。
  4. 查找温度:在查表中找到对应的温度值。

具体步骤

1. 建立查表

NTC 热敏电阻的电阻值随温度变化。可以根据热敏电阻的数据手册或特性曲线建立一个电阻值与温度对应的查表。例如:

const uint32_t resistance_table[] = {3380, 3300, 3220, /* ... */ 100}; // 阻值 (单位: 欧姆)
const float temperature_table[] = {0, 1, 2, /* ... */ 100}; // 温度值 (单位: 摄氏度)
const int table_size = sizeof(resistance_table) / sizeof(resistance_table[0]);
2. 使用 ADC 读取电压

使用 STM32 的 ADC 采样 Vout。

3. 计算 NTC 电阻值

在这里插入图片描述

4. 查找温度

在查表中找到对应的温度值,可以使用线性插值法提高精度。

代码实现

以下是一个详细的 C 语言代码示例,使用 STM32 HAL 库实现以上步骤:

#include "main.h"
#include <stdio.h>

ADC_HandleTypeDef hadc1;

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

#define R1 10000  // 固定电阻值 (单位: 欧姆)
#define V_REF 3.3 // ADC 参考电压 (单位: 伏特)
#define ADC_RESOLUTION 4095.0 // ADC 分辨率 (12 位)

// 查表 (电阻值单位: 欧姆, 温度单位: 摄氏度)
const uint32_t resistance_table[] = {3380, 3300, 3220, /* ... */ 100};
const float temperature_table[] = {0, 1, 2, /* ... */ 100};
const int table_size = sizeof(resistance_table) / sizeof(resistance_table[0]);

float Read_NTC_Temperature(ADC_HandleTypeDef* hadc)
{
    HAL_ADC_Start(hadc);
    HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY);
    uint16_t ADC_value = HAL_ADC_GetValue(hadc);
    
    float Vout = (ADC_value / ADC_RESOLUTION) * V_REF;
    float R_ntc = R1 * (V_REF / Vout - 1);

    // 查表法找到温度值
    for (int i = 0; i < table_size - 1; i++)
    {
        if (R_ntc >= resistance_table[i+1] && R_ntc <= resistance_table[i])
        {
            // 线性插值计算温度
            float R1 = resistance_table[i];
            float R2 = resistance_table[i+1];
            float T1 = temperature_table[i];
            float T2 = temperature_table[i+1];
            float temperature = T1 + (R_ntc - R1) * (T2 - T1) / (R2 - R1);
            return temperature;
        }
    }

    // 如果未找到匹配的电阻值,返回一个默认值
    return -273.15; // 表示错误
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    HAL_ADC_Start(&hadc1);

    while (1)
    {
        float temperature = Read_NTC_Temperature(&hadc1);

        // 使用计算出的温度值 temperature
    }
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
}

void SystemClock_Config(void)
{
    // 时钟配置代码
}

详细说明

  1. 定义常量和查表

    • #define R1 10000 定义固定电阻 ( R1 ) 的值,单位为欧姆。
    • #define V_REF 3.3 定义 ADC 参考电压,单位为伏特。
    • #define ADC_RESOLUTION 4095.0 定义 ADC 的分辨率。
    • const uint32_t resistance_table[]const float temperature_table[] 分别定义电阻值和温度值的查表。
    • const int table_size 定义查表的大小。
  2. 读取 ADC 值并计算电阻和温度

    • HAL_ADC_Start(hadc); 启动 ADC。
    • HAL_ADC_PollForConversion(hadc, HAL_MAX_DELAY); 等待 ADC 转换完成。
    • uint16_t ADC_value = HAL_ADC_GetValue(hadc); 获取 ADC 读取值。
    • float Vout = (ADC_value / ADC_RESOLUTION) * V_REF; 计算分压电路的输出电压。
    • float R_ntc = R1 * (V_REF / Vout - 1); 计算 NTC 热敏电阻的电阻值。
  3. 查找温度值

    • 遍历查表,通过线性插值计算电阻值与温度值之间的关系,找到对应的温度。

通过这个实现,可以有效地使用 ADC 采集 NTC 热敏电阻的电阻值,并通过查表法将其转换为温度。线性插值可以提高温度计算的精度。查表数据可以根据具体的 NTC 热敏电阻特性曲线来建立。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/768882.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Google Play上架防关联,打包环境是关联因素之一还是无足轻重?

在Google Play上架应用&#xff0c;对于矩阵式上架或马甲包的开发者来说&#xff0c;防关联的处理技能是必须要精通的。想象一下&#xff0c;你辛辛苦苦开发的应用&#xff0c;因为一些看似微不足道的细节&#xff0c;比如打包环境的问题&#xff0c;就可能被谷歌无情下架或封号…

力扣双指针算法题目:移动零

1.题目 . - 力扣&#xff08;LeetCode&#xff09; 2.思路解析 这个题目的思路和“使用递归排序快速排序解决数组的排序问题”相同 class solution { public:void QuickSort(vector<int>& nums, int left, int right){if (left > right) return;int key left…

信息收集---端口服务信息收集

1. 什么是端口 是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口&#xff0c;其中虚拟端口指计算机内部或交换机路由器内的端口&#xff0c;不可见。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口&#xff0c;是可见端口&#xff0c;计算机背板的RJ45…

我在高职教STM32——时钟系统与延时控制(2)

大家好&#xff0c;我是老耿&#xff0c;高职青椒一枚&#xff0c;一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次&#xff0c;同行应该都懂的&#xff0c;老师在课堂上教学几乎是没什么成就感的。正因如此&#xff0c;才有了借助 CSDN 平台寻求认同感和成就…

Http 实现请求body体和响应body体的双向压缩方案

目录 一、前言 二、方案一(和http header不进行关联) 二、方案二(和http header进行关联) 三、 客户端支持Accept-Encoding压缩方式,服务器就一定会进行压缩吗? 四、参考 一、前言 有时请求和响应的body体比较大,需要进行压缩,以减少传输的带宽。 二、方案一(和…

助力构建新型电力系统自主可控实时底座

近日&#xff0c;2024亚洲新型电力系统及储能展览会&#xff06;亚洲新型电力及储能论坛会在广州广交会展馆圆满落下帷幕&#xff01;科东软件携多款电力产品亮相展会,并在2024亚洲新型电力及储能论坛发表《“鸿道Intewell操作系统助力构建新型电力系统自主可控实时底座”》的主…

Unity之创建与导出PDF

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之创建与导出PDF TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; 助力快速…

2024-2025年本田维修电路图线路图接线图资料更新

此次更新了2024-2025年本田车系电路图资料&#xff0c;覆盖市面上99%车型&#xff0c;包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对照表位置等等&#xff01; 汽修帮手汽…

日期和时区

日期 时区 修改时区可分为两步 删除系统自带的 localtime 文件 rm -f /etc/localtime 将系统中内置的 Shanghai 文件软连接到 /etc/localtime中 sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

Unity3D中,AI角色Rigidbody旋转导致的动画问题

在制作一些AI角色的时候&#xff0c;可能会运用到Rigidbody组件来使AI角色拥有一些相关的物理属性&#xff0c;但是AI角色在受到一些物理碰撞或者惯性等原因&#xff0c;会发生旋转导致动画出现意料外的错误&#xff0c;比如在由动转静的时候&#xff0c;可能会发生向前翻转等一…

TP8/6 子域名绑定应用

原www.xxx.com/admin改为admincms.xxx.com config/app.php

【Linux】线程——线程的概念、线程的特点、线程的优点和缺点、线程和进程、线程函数的使用

文章目录 Linux线程1. 线程的概念1.1 什么是线程 2. 线程的特点2.1 线程的优点2.2 线程的缺点2.4 线程和进程 3. 线程函数的使用pthread_create() 创建线程pthread_self() 获取线程IDpthread_exit() 线程终止pthread_cancel() 线程取消pthread_join() 线程等待pthread_detach()…

Element-UI - el-table中自定义图片悬浮弹框 - 位置优化

该篇为前一篇“Element-UI - 解决el-table中图片悬浮被遮挡问题”的优化升级部分&#xff0c;解决当图片位于页面底部时&#xff0c;显示不全问题优化。 Vue.directive钩子函数已在上一篇中详细介绍&#xff0c;不清楚的朋友可以翻看上一篇&#xff0c; “Element-UI - 解决el-…

3.js - 色调映射(renderer.toneMapping)

// ts-nocheck// 引入three.js import * as THREE from three// 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 导入lil.gui import { GUI } from three/examples/jsm/libs/lil-gui.module.min.js// 导入tween import * as TWEEN…

不同操作系统下的换行符

1. 关键字2. 换行符的比较3. ASCII码4. 修改换行符 4.1. VSCode 5. 参考文档 1. 关键字 CR LF CRLF 换行符 2. 换行符的比较 英文全称英文缩写中文含义转义字符ASCII码值操作系统Carriage ReturnCR回车\r13MacIntosh&#xff08;早期的Mac&#xff09;LinefeedLF换行/新行\…

Windows下载安装配置并使用Redis(保姆级教程)

文章目录 1、Redis的下载与安装 2、Redis的使用 3、Redis的图形界面客户端 4、Redis开机自启动 1、Redis的下载与安装 下载Redis&#xff1a;https://pan.baidu.com/s/1zBonkO2y6AZeqCdRe0W5ow?pwd9999 提取码: 9999 下载后直接解压就可以使用了 2、Redis的使用 我们…

全网最佳硕士研究生复试简历模板

硕士研究生复试简历模板 ✨ 简介 提供了一个适用于国内硕士研究生复试的个人简历模板。该模板通过统一的“样式”形成规范的Word格式&#xff0c;是目前研究生复试的最佳简历模板之一。模板使用“华文中宋”字体&#xff0c;如您的电脑中未安装此字体&#xff0c;请提前安装。…

软件测试与质量保证 | 云班课选择题库

目录 第1章课后习题 第2章课后习题 第3章课后习题 第4章课后习题 第5章课后习题 第6章课后习题 第7章课后习题 第8章课后习题 第9章课后习题 第10章课后习题 第11章课后习题 第12章课后习题 第13章 测试相关未分类习题 第1章课后习题 1. 与质量相关的概念包括 &a…

去中心化革命:探索区块链技术的前沿

随着信息技术的飞速发展&#xff0c;区块链技术作为一种新兴的去中心化解决方案&#xff0c;正逐渐改变着我们的经济、社会和技术格局。本文将从区块链的基本原理、当前的应用实例以及未来的发展趋势三个方面&#xff0c;深入探讨区块链技术在革命性变革中的角色和影响。 1. 区…

springboot 自定义的全局捕获异常失效

背景&#xff1a;springbootspringcloud 分布式微服务。 问题&#xff1a;公共模块在使用RestControllerAdvice全局捕获异常时&#xff0c;捕获不到子服务抛出的相应异常 首先看一下全局异常组件有么有被扫描到 如何查看&#xff0c;很简单只需要写一段类加载打印代码&#x…