Files
2026-08-20 18:03:49 +08:00

252 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 气体流量控制与阀门开环标定
本项目基于艾莫迅 **MT2-AM8 模拟量采集/输出模块**,提供两个可独立运行的入口:
- [main.py](main.py):以 10 Hz 周期运行单回路气体流量增量式 PID 闭环控制;
- [open_loop.py](open_loop.py):随机扫描阀门开度,采集阀前/阀后的压力与流量,用于开环标定、动态特性观察和临界压力比分析。
两个入口都会逐周期保存 CSV,并在运行结束时为有效采样数据生成 PNG 曲线图。闭环程序还会持续进行滑动窗口稳态判定,并在首次达到稳态时输出一次控制性能报告。
## 重要安全说明
> 本项目会直接控制真实阀门。首次运行前必须核对 IP、站号、AI/AO 通道、传感器量程、阀门行程方向、压力上限和执行机构的断电/失驱位置,并准备独立的机械或电气急停手段。
闭环与开环程序当前采用不同的退出策略:
- **闭环 `main.py`**:启动时不再预先下发关闭位置,PID 内部输出由 `INITIAL_OPENING_PCT=100%` 开始;故障或退出时停止控制并断开 MT2-AM8,不主动下发关阀命令。当前系统按“执行机构失驱后回到全开”设计,现场必须确认实际硬件确实如此。
- **开环 `open_loop.py`**:连接成功后先主动设置 `0%` 开度,实验结束、异常或 `Ctrl+C` 时再次尝试设置 `0%` 开度,然后断开设备。
因此,README 中的“关闭”或“全开”均指当前代码定义的逻辑位置,不能代替现场对阀门真实动作方向的确认。
## 硬件与默认接线
- MT2-AM8Modbus TCP,默认 `192.168.1.12:502`,站号 `1`
- AI 输入寄存器:地址 `0x00~0x03`
- AO 保持寄存器:地址 `0x00~0x03`
- 模拟量原始范围:`0~4095`,传感器物理量换算使用 `819~4095` 对应有效量程;
- 默认流量量程:`300 SLM`;默认压力量程:`400 kPa`
- 电机行程范围:`0~1000`,且行程与阀门开度反向作用。
默认通道如下,通道编号均从 `0` 开始:
| 用途 | 通道 | 配置位置 |
| --- | ---: | --- |
| 闭环/阀后压力 | `AI0` | `config.PRESSURE_INPUT_CHANNEL` |
| 闭环/阀后流量 | `AI1` | `config.FLOW_INPUT_CHANNEL` |
| 开环阀前压力 | `AI2` | `open_loop.PRESSURE_BEFORE_CHANNEL` |
| 开环阀前流量 | `AI3` | `open_loop.FLOW_BEFORE_CHANNEL` |
| 电机位置指令 | `AO1` | `config.MOTOR_OUTPUT_CHANNEL` |
开度与行程的默认映射为:
```text
opening = 0% -> motor_position = 1000(关闭)
opening = 100% -> motor_position = 0(全开)
```
所有闭环通道、量程和行程端值集中在 [config.py](config.py);开环新增的阀前通道和实验参数位于 [open_loop.py](open_loop.py) 顶部。
## 环境与依赖
项目使用 Python 3,直接依赖:
- `pymodbus`Modbus TCP/RTU 通讯;
- `matplotlib`:使用 `Agg` 后端生成 PNG,无显示器环境也可运行。
示例安装:
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install pymodbus matplotlib
```
仓库当前没有锁定依赖版本。若 `PcControl.py` 导入 `pymodbus.payload` 或调用 `slave=` 参数时报错,请使用与现有 [PcControl.py](PcControl.py) API 兼容的 `pymodbus` 版本。
## 闭环控制
### 运行
先在 [config.py](config.py) 中完成现场参数核对,再执行:
```powershell
python main.py --target 50
```
`--target` 的单位为 SLM,必须位于 `TARGET_FLOW_MIN_SLM~TARGET_FLOW_MAX_SLM`(当前为 `0~300`)范围内。按 `Ctrl+C` 停止;默认 `AUTO_STOP_ON_STEADY=False`,所以达到稳态后仍会继续控制。如需首次达到稳态后自动结束,将该配置改为 `True`
启动流程为:
```text
校验配置 -> 连接 MT2-AM8 -> 读取一次初始流量
-> PID 输出初始化为 INITIAL_OPENING_PCT
-> 进入周期控制(启动阶段不主动写入关闭位置)
```
单个控制周期为:
```text
读取流量/压力
|
v
IncrementalPID.update()
error = target - measurement
|
v
开度百分比 0~100%(输出限幅 + 变化率限幅)
|
v
opening_to_motor_position()
|
v
反向行程 1000~0 -> MT2-AM8 AO1
```
增量 PID 的输出直接定义为阀门开度,因此控制器本身不包含电机行程或死区映射。目标流量不高于 `ZERO_FLOW_THRESHOLD_SLM`(当前 `0.5 SLM`)时,程序跳过常规 PID 更新并直接命令 `0%` 开度。
### 运行输出
每次闭环运行会产生:
1. **终端与日志**:约每秒输出一次状态;非 `OK` 状态立即输出。日志保存到 `logs/flow_control.log`
2. **逐周期 CSV**:默认保存为 `data/flow_run_YYYYMMDD_HHMMSS.csv`,每行写入后立即刷新到磁盘。现有 `data/日期/实验说明/` 子目录是历史数据的人工归档方式,不是程序自动创建的层级。
3. **三联曲线图**:结束时保存同名 PNG,依次显示目标/实际流量、阀门开度和压力。
4. **性能报告**:首次达到稳态时写入终端和日志;若整个运行过程未达到稳态,只输出未达到稳态的提示。
闭环 CSV 字段:
| 字段 | 含义 |
| --- | --- |
| `time_s` / `timestamp` | 相对运行时间 / Unix 时间戳 |
| `target_flow_slm` / `measured_flow_slm` | 目标流量 / 实测流量 |
| `opening_pct` / `motor_position` | 阀门开度 / 电机行程 |
| `pressure_kpa` | 压力;未启用或读取失败时为空 |
| `error_slm` | `目标流量 - 实测流量` |
| `actual_dt_s` | 实际控制周期 |
| `status` | `OK` 或传感器读取重试状态 |
### 稳态判定与性能报告
判稳只使用状态为 `OK` 的有效流量样本。滑窗至少覆盖 `STEADY_STATE_WINDOW_S - CONTROL_PERIOD_S`,以容纳真实采样时间戳抖动,并要求下列条件同时成立:
```text
|窗口均值 - 目标流量| <= STEADY_STATE_BAND_PCT / 100 * 目标流量
窗口总体标准差 <= STEADY_STATE_STD_PCT / 100 * 目标流量
```
当前默认使用 10 秒窗口、±1% 均值误差带和 1% 标准差阈值。报告内容包括 PID 参数、目标与控制范围、调节时间、窗口均值、窗口标准差、稳态误差(目标减窗口均值)、初始流量,以及按初始变化方向计算的峰值/谷值超调量。
## 开环扫点实验
[open_loop.py](open_loop.py) 不参与闭环反馈,也不会被 `main.py` 导入。默认将 `0%~100%`、间隔 `10%` 的 11 个开度按固定随机种子 `42` 打乱,然后逐点执行:
```text
写入开度 -> 每 0.1 s 采集阀前/阀后压力与流量
-> 等待阀后流量稳定,或等待满 60 s
-> 切换到下一个开度
```
运行默认实验或自定义扫描范围:
```powershell
python open_loop.py
python open_loop.py --seed 7
python open_loop.py --min 0 --max 100 --step 5
```
- `--min``--max`:扫描开度上下限,必须满足 `0 <= min < max <= 100`
- `--step`:开度间隔,必须大于 `0`;不能整除区间时会额外包含 `max` 端点;
- `--seed`:打乱顺序的随机种子;相同参数和种子会得到相同顺序。
> 当前 `open_loop.py --help` 的帮助文本存在 `%` 格式化问题,会触发 `ValueError`;上面的正常运行命令及参数解析不受影响。
开环没有目标值,其稳态条件仅依据阀后流量的滑窗总体标准差:
```text
std <= max(STEADY_STATE_STD_PCT / 100 * |窗口均值|, MIN_ABS_STD_SLM)
```
默认窗口为 10 秒,绝对标准差下限为 `0.05 SLM`。阀后流量连续 5 次读取失败会中止实验;其他通道读取失败会在对应 CSV 字段中留空。
输出保存到 `open_loop_data/open_loop_YYYYMMDD_HHMMSS.csv/.png`,日志保存到 `logs/open_loop.log`。PNG 包含三张子图:阀前/阀后压力、阀前/阀后流量、阀后绝对压力与阀前绝对压力之比。
压力比按下式计算:
```text
P_abs_ratio = (阀后表压 + 101.325 kPa) / (阀前表压 + 101.325 kPa)
```
CSV 同时记录 `is_ratio_smaller_than_0.528`:压力比小于临界值 `0.528` 时为 `Y`,否则为 `N`;任一压力缺失或分母非法时两个压力比字段留空。图中会绘制 `0.528` 参考线,并标出各开度切换时刻。
## 安全与故障处理
### 闭环
- 配置在连接硬件前由 `validate_config()` 校验;
- 流量超出 `FLOW_VALID_MIN_SLM~FLOW_VALID_MAX_SLM` 或压力高于 `MAX_PRESSURE_KPA` 时停止控制并报故障;
- 短暂的流量/压力读取失败保持上一输出,不下发新位置;连续达到各自阈值后停止控制;
- 实际控制周期超过 `MAX_CONTROL_DT_S` 时停止控制;
- PID 输出限制在 `0~100%``MAX_OPENING_RATE_PCT_S` 限制每秒最大开度变化;
- 电机命令写入异常或返回失败时停止控制;
- 所有退出路径都会尝试断开设备,但当前闭环代码不会主动下发关阀命令。
### 开环
- 启动和收尾均主动下发 `0%` 开度;
- 任一有效流量读数越界、任一有效压力读数超过上限时中止;
- 阀后流量连续读取失败达到阈值时中止;
- 写入开度失败时中止;
- 中止后仍会尽力关阀、断开连接并保留已经写入的 CSV。
## 关键配置默认值
| 配置 | 当前默认值 | 说明 |
| --- | --- | --- |
| `MT2AM8_HOST` / `MT2AM8_PORT` / `MT2AM8_SLAVE_ID` | `192.168.1.12` / `502` / `1` | 通讯参数 |
| `FLOW_INPUT_CHANNEL` / `PRESSURE_INPUT_CHANNEL` | `1` / `0` | 闭环 AI 通道;压力可设为 `None` |
| `MOTOR_OUTPUT_CHANNEL` | `1` | 电机 AO 通道 |
| `FLOW_METER_RANGE_SLM` / `PRESSURE_RANGE_KPA` | `300` / `400` | 传感器量程 |
| `TARGET_FLOW_MIN_SLM` / `TARGET_FLOW_MAX_SLM` | `0` / `300` | 允许的目标范围 |
| `CONTROL_PERIOD_S` / `MAX_CONTROL_DT_S` | `0.1` / `0.3` | 目标周期 / 超时阈值 |
| `PID_KP` / `PID_KI` / `PID_KD` | `0.5` / `0.5` / `0.0` | 当前 PI 参数,需按现场继续整定 |
| `OPENING_MIN_PCT` / `OPENING_MAX_PCT` | `0` / `100` | PID 输出开度范围 |
| `MAX_OPENING_RATE_PCT_S` | `20` | 每秒最大开度变化百分比 |
| `MOTOR_CLOSED_POSITION` / `MOTOR_OPEN_POSITION` | `1000` / `0` | 反向行程端值 |
| `INITIAL_OPENING_PCT` | `100` | 闭环 PID 内部初始输出 |
| `FLOW_VALID_MIN_SLM` / `FLOW_VALID_MAX_SLM` | `0` / `300` | 流量安全范围 |
| `MAX_PRESSURE_KPA` | `400` | 压力上限,须按实际管路额定压力确认 |
| `MAX_CONSECUTIVE_FLOW_FAILURES` / `MAX_CONSECUTIVE_PRESSURE_FAILURES` | `3` / `3` | 闭环连续读取失败阈值 |
| `STEADY_STATE_WINDOW_S` / `STEADY_STATE_BAND_PCT` / `STEADY_STATE_STD_PCT` | `10` / `1` / `1` | 闭环稳态判定参数 |
| `AUTO_STOP_ON_STEADY` | `False` | 达到稳态后是否自动结束闭环 |
## 模块职责
| 文件 | 职责 |
| --- | --- |
| [main.py](main.py) | 闭环入口:参数解析、日志、控制循环、记录器、性能报告与收尾 |
| [flow_control.py](flow_control.py) | 闭环控制层、传感器读取、开度/行程映射和故障处理 |
| [controllers.py](controllers.py) | 通用增量式 PID,含输出限幅和输出变化率限幅 |
| [config.py](config.py) | 闭环集中配置和启动前校验 |
| [data_logger.py](data_logger.py) | 闭环逐周期 CSV 与三联曲线图 |
| [performance_reporter.py](performance_reporter.py) | 闭环滑窗判稳和一次性性能报告 |
| [open_loop.py](open_loop.py) | 独立开环扫点、四传感器记录和压力比分析 |
| [PcControl.py](PcControl.py) | Modbus 硬件客户端;本项目两个入口实际使用 `MT2AM8Client` |
## 项目结构
```text
flow_control/
├── main.py
├── open_loop.py
├── flow_control.py
├── controllers.py
├── config.py
├── data_logger.py
├── performance_reporter.py
├── PcControl.py
├── README.md
├── data/ # 闭环 CSV / PNG;历史数据可能已人工分组
├── open_loop_data/ # 开环 CSV / PNG(首次运行时创建)
└── logs/ # flow_control.log / open_loop.log
```