ZZZ-23-零点校准

在 CEMS 维护中,技术人员经常需要进行“零点校准”,以消除变送器老化或环境变化带来的微小偏差。 我们要实现的逻辑是:当你按下开发板上的按键(或外接按键)时,系统记录当前的电压值为“零点”,之后所有的压力计算都以这个新零点为基准。

1. 开发准备

如果开发板上没有按键,需要外接按键的话,推荐使用 “内部上拉 + 外部接地” 的方案,因为这样只需要一根导线和一个按键,不需要额外的电阻。

  • 轻触按键:一端接单片机的 GPIO 引脚(例如 PB0),另一端接 GND
  • 在 CubeMX 里做如下设置:
    • Pin Selection:选择一个空闲引脚( PB0),设为 GPIO_Input
    • GPIO Pull-up/Pull-down:必须选择 Pull-up(上拉)。这样引脚平时才是稳定的高电平。

2. 代码

代码主体结构、单片机配置和 [[ZZZ-21-串口通讯+VOFA]]、[[ZZZ-22-制作简易变送器]] 大体相同

1
2
3
4
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
1
2
3
4
5
6
7
8
/* USER CODE BEGIN PV */
#define SAMPLES 10  // 每个通道采样 10 次
#define CHANNELS 2 // 共 2 个通道

uint16_t adc_buffer[SAMPLES * CHANNELS]; // 长度为 20 的数组
static float zero_offset_volt = 0.6f;  // 默认零点是 0.6V (4mA)
uint8_t calibration_flag = 0;         // 标定触发标志
/* USER CODE END PV */
1
2
3
4
5
6
7
  /* USER CODE BEGIN 1 */
  static float filter_volt_A = 0.0f;
  const float alpha = 0.15f; // 滤波系数,你可以尝试修改这个值观察 VOFA+ 上的变化
  const float R_SAMPLE = 150.0f;    // 采样电阻 150 欧
  const float P_MAX = 1.6f;         // 假设压力传感器量程 0-1.6 MPa

  /* USER CODE END 1 */
1
2
3
4
5
6
7
8
  /* USER CODE BEGIN 2 */
  // 为了精准,建议先进行 ADC 校准(F1系列特有)
  HAL_ADCEx_Calibration_Start(&hadc1);

  // 核心命令:启动 ADC + DMA
  // 参数:ADC句柄,存储的目标地址,要搬运的数据个数
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, SAMPLES * CHANNELS);
  /* USER CODE END 2 */
 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  while (1)
  {
		uint32_t sum_A = 0;
		uint32_t sum_B = 0;

		// 1. 计算平均值(作为原始输入值值)
		for(int i = 0; i < SAMPLES; i++) {
			sum_A += adc_buffer[i * CHANNELS];
			sum_B += adc_buffer[i * CHANNELS + 1];
		}

		// 计算当前这一刻的原始电压
		float raw_volt_A = (float)sum_A / SAMPLES * 3.3f / 4095.0f;
		//这里只用到了一个传感器,传感器B的数据都没有要
		//float raw_volt_B = (float)sum_B / SAMPLES * 3.3f / 4095.0f;

		// 2. 核心算法:一阶滞后滤波(只对 A 进行处理作为演示)
		// 公式:本次输出 = alpha * 本次采样 + (1 - alpha) * 上次输出
		filter_volt_A = alpha * raw_volt_A + (1.0f - alpha) * filter_volt_A;

      // --- 1. 获取滤波后的电压 ---
      float volt = filter_volt_A;

      // --- 2. 检测按键(假设按键按下为低电平) ---
      if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
      {
          HAL_Delay(20); // 去抖动
          if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
          {
              // 【核心动作】:把当前稳定的滤波电压记录为新零点
              zero_offset_volt = volt;
              calibration_flag = 1; // 触发一个标志位,可以在 VOFA+ 上看到

              // 亮灯提示标定完成
              HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
              HAL_Delay(500);
              HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);

              while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET); // 等待按键松开
          }
      }

      // --- 3. 换算电流 (mA) ---
      float current_mA = (volt / R_SAMPLE) * 1000.0f;

      // --- 4. 【标定后的压力计算】 ---
      // 关键逻辑:用当前电压 减去 标定零点电压,再映射到量程
      // 这样无论零点怎么飘,标定后的输出在零点处一定是 0 MPa
      float calibrated_volt_diff = volt - zero_offset_volt;
      float pressure = (calibrated_volt_diff) * (P_MAX / (3.0f - 0.6f)); // 基于 4-20mA 跨度 (2.4V) 换算

      if (pressure < 0) pressure = 0; // 忽略微小的负向波动

      // --- 5. VOFA+ 输出 ---
      // 通道:电流, 标定前压力(参考), 标定后压力, 零点电压值
      printf("%.2f,%.3f,%.3f,%.3f\n", current_mA, (volt-0.6f)*(P_MAX/2.4f), pressure, zero_offset_volt);

      HAL_Delay(20);
  }

标定前

假设在该位置处进行零点压力校准。也就是按一下按键:

  • 标定前后:压力显示 0.026MPa—>0;压力量程:0-1.6 MPa
  • 标定前后:零点电压 0.6—>0.639;电压量程:0.6~3V
  • 也就是说,电压量程比上压力量程(也就是斜率) = 2.4/1.6=1.5。压力从 0.026 变到 0,对应的电压变化就是 0.026X1.5=0.039

标定逻辑

graph TD A[开始: While 循环] --> B[获取双重滤波后的电压 volt] B --> C{是否按下标定键?} %% 标定分支 C -- 是 --> D[消抖延时 20ms] D --> E{再次确认按下?} E -- 是 --> F[更新 zero_offset_volt = volt] F --> G[指示灯反馈/提示标定完成] G --> H[等待按键松开] H --> I[进入物理量换算] %% 正常运行分支 C -- 否 --> I E -- 否 --> I %% 计算逻辑 I --> J[换算电流: current_mA = volt / 150 * 1000] J --> K[计算标定后压差: diff = volt - zero_offset_volt] K --> L[换算压力: pressure = diff * 比例系数] %% 报警逻辑 L --> M{current_mA < 3.6mA?} M -- 是 --> N[输出: 传感器断线故障] M -- 否 --> O{pressure > 1.2MPa?} O -- 是 --> P[输出: 压力超限报警] O -- 否 --> Q[输出: 状态正常] %% 结束一轮 N --> R[VOFA+ 数据打包发送] P --> R Q --> R R --> S[HAL_Delay 20ms] S --> A

第一步:为什么要“减法”?(建立参考点)

假设一个理想的 $0 \sim 1.6,MPa$ 压力传感器:

  • 理想状态:$4mA$ 时电压应为 $0.6V$。此时压力显示应为 $0$。
  • 实际状态:由于各种原因,现在没压力时,传感器输出变成了 $0.65V$。
  • 如果不标定:直接计算会显示出一个微小的压力值(误差)。

标定动作:当你按下标定键,代码执行 zero_offset_volt = 0.65f

从此以后,所有的测量值都要先减去这 $0.65V$【这里有一个容易混乱的点:压力输出为 0,压力传感器零点对应的零点电压不一定为 0.并且校准前和校准后,只是零点电压这个点位变了而已】:

  • 如果现在电压是 $0.65V$:$0.65 - 0.65 = 0V$(对应 $0$ 压力,完美对齐!)。
  • 如果现在电压是 $1.0V$:$1.0 - 0.65 = 0.35V$(只计算超出零点的部分)。

第二步:为什么要“映射”?(比例缩放)

减完之后,我们得到了一个“纯净的电压差”,但我们最终要的是“MPa(压力单位)”。这时需要用到线性插值公式。我们需要知道一个关键值:有效电压跨度(Span)

  • 工业标准中,$20mA$ ($3.0V$) 到 $4mA$ ($0.6V$) 之间的跨度是固定不变的:$$Span = 3.0V - 0.6V = 2.4V$$
  • 这意味着:每增加 $2.4V$ 电压,就代表压力增加了 $1.6,MPa$。 最终公式:$$Pressure = (Volt_{current} - Volt_{zero_offset}) \times \frac{P_{max}}{Span}$$

一个常见误区

例如一个传感器,输出 0~X 量程,之前总是认为传感器输出 0 时,其电压也是 0,实际上不是这样。例如上面的案例中,压力传感器输出 0 时,电压为 0.6V。

这里的设计同【020mA 输出比较好还是 420mA 输出比较好】,想要解决的是一类问题

重新理解你的校准逻辑

现在你就能明白为什么我们的标定公式是这样设计的了:$$Pressure = (V_{measured} - 0.6) \times \frac{P_{max}}{2.4}$$

  • 减去 0.6:是把那个“活零点”的基础电压减掉,提取出纯粹的信号增量。
  • 除以 2.4:是因为你的有效信号范围其实只有 $3.0V - 0.6V = 2.4V$。

工业界常见的电压标准

在学习 STM32 和硬件电路时,你会发现工业仪表通常有这几套“方言”:

信号类型 零点 (0%) 满量程 (100%) 特点
4-20mA (电流) 4mA 20mA 工业标准,抗干扰最强。
1-5V (电压) 1V 5V 对应 4-20mA 在 250$\Omega$ 电阻上的电压。
0.6-3V (电压) 0.6V 3.0V 上面案例的配置,适合 150$\Omega$ 电阻和 3.3V 的 STM32。
Licensed under CC BY-NC-SA 4.0