ZZZ-22-STM32内部参考电压通道

本篇继 ZZZ-21-串口通讯+VOFA

为什么需要参考电压通道

想象你在用一把塑料尺子量桌子的长度。

  • 正常情况: 尺子是 1 米长,量出桌子是 1.2 米。
  • 意外情况: 天气太热,尺子受热膨胀变成了 1.1 米,但尺子上的刻度没变。你再去量,发现桌子变成“1.1 米”了。
  • 结论: 你的“基准”(尺子)变了,量出来的数就不准了。

在 STM32 里,那个 $3.3V$ 电源就是你的尺子。如果 USB 供电不稳定,或者接了电机导致电压掉到了 $3.1V$,“尺子”就缩水了,ADC 算出来的电压就会偏大。

参考电压通道自带“标准样棒”

STM32 芯片内部藏着一个非常稳定的电压源(通常是 $1.2V$ 左右),这个电压不会随着外部 $3.3V$ 的波动而改变。

操作逻辑很简单:

  1. 你用“缩水的尺子”(不稳的 $3.3V$)去量这根“标准样棒”(内部 $1.2V$)。
  2. 如果你量出来是 $1.25V$,你就知道尺子缩水了。
  3. 通过一个简单的比例换算,你就能把桌子的真实长度找回来。$$真实的电压 = 测量出的电压 \times \frac{内部参考电压额定值}{测量出的内部参考电压}$$

第一步:CubeMX 配置(给尺子加刻度)

你需要在 CubeMX 里做三处微调:

  1. 开启通道:在 ADC1 的配置界面,找到 Vrefint Channel,把它勾选上。
  2. 调整转换数量:将 Number of Conversions2 改为 3【[[ZZZ-21-串口通讯+VOFA]] 中使用了两个传感器,所以之前这里一只是 2,现在需要再新增一个】
  3. 配置 Rank
    • Rank 1 和 Rank 2 保持你原来的通道。
    • Rank 3 选择 Internal Reference Voltage。采样时间(Sampling Time)建议选长一点(比如 55.5 或 239.5 Cycles),因为这个内部基准电压需要充放电稳定。
  4. DMA 检查:确保 DMA 依然是 Circular 模式。
  5. 保存生成代码(Generate Code)。

第二步:代码实现(数学的魔术)

回到你的 main.c。因为多了一个通道,我们要改一下定义和缓冲区。

1. 修改宏定义

1
2
#define SAMPLES 10  
#define CHANNELS 3  // 从 2 改成 3

2. 在循环中计算真实电压

STM32F1 系列的内部参考电压额定值大约是 1.2V。我们的逻辑是:利用这 1.2V 反向推导出当前的电源电压到底是多少。

注意,这里也用了两种滤波: 算术平均滤波:一次性搬运 10 个数据并取平均,可以有效消除这种随机的“毛刺”,得到一个相对扎实的“原始均值” 一阶滞后滤波:它通过比例分配,让“本次测量值”只占一小部分比重(15%),而大部分比重保留“上次的结果”(85%)【靠 alpha 来调节】。它能让波形变得非常“丝滑”,像绸缎一样,几乎看不到锯齿

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  /* 在 while(1) 之前定义 */
  static float filter_volt_A = 0.0f;
  const float alpha = 0.15f; 
  const float VREFINT_NOMINAL = 1.2f; // 内部参考电压额定值

  while (1)
  {
      uint32_t sum_A = 0;
      uint32_t sum_Vref = 0; // 新增:用来存内部基准的累加值

      // 1. 获取累加值
      for(int i = 0; i < SAMPLES; i++) {
          sum_A += adc_buffer[i * CHANNELS];
          // 假设 Rank 3 是内部基准,索引就是 i*3 + 2
          sum_Vref += adc_buffer[i * CHANNELS + 2]; 
      }

      // 2. 计算平均原始数值
      float raw_avg_A = (float)sum_A / SAMPLES;
      float raw_avg_Vref = (float)sum_Vref / SAMPLES;

      // 3. 【核心数学换算】
      // 原理:V_A / raw_avg_A = VREFINT_NOMINAL / raw_avg_Vref
      // 推导出 V_A = VREFINT_NOMINAL * (raw_avg_A / raw_avg_Vref)
      // 你会发现,3.3 这个不准的数字被彻底踢出了公式!
      float real_volt_A = VREFINT_NOMINAL * (raw_avg_A / raw_avg_Vref);

      // 4. 同样进行一阶滤波
      filter_volt_A = alpha * real_volt_A + (1.0f - alpha) * filter_volt_A;

      // 5. 发送给 VOFA+
      printf("%.3f,%.3f\n", real_volt_A, filter_volt_A);

      HAL_Delay(20);
  }

为什么这个公式很牛?

请看这个神奇的推导:

  • 传统的算式:$V_A = \frac{ADC_{data}}{4095} \times V_{DDA}$ ($V_{DDA}$ 通常假设是 3.3)
  • 现在的算式:$V_A = 1.2 \times \frac{ADC_{data_A}}{ADC_{data_Vref}}$

发现了吗?$4095$ 消失了,$3.3$ 也消失了!这意味着:

  1. 就算你的 $3.3V$ 电源因为接了太多负载掉到了 $3.0V$,公式依然成立。
  2. 就算你的 ADC 有微小的增益误差,也会因为分子分母同时缩放而抵消。
Licensed under CC BY-NC-SA 4.0