325 lines
17 KiB
Markdown
325 lines
17 KiB
Markdown
# 气体流量控制与阀门开环标定
|
||
|
||
本项目基于艾莫迅 **MT2-AM8 模拟量采集/输出模块**,提供三个可独立运行的入口:
|
||
|
||
- [main.py](main.py):以 10 Hz 周期运行单回路气体流量增量式 PID 闭环控制;
|
||
- [open_loop.py](open_loop.py):随机扫描阀门开度,采集阀前/阀后的压力与流量,用于开环标定、动态特性观察和临界压力比分析。
|
||
- [test_valve.py](test_valve.py):在 Windows 终端中手动切换阀门开度,同时观察并记录流量、压力和电机行程。
|
||
|
||
三个入口都会持续保存 CSV,并在运行结束时为有效采样数据生成 PNG 曲线图。闭环程序还会持续进行滑动窗口稳态判定,并在首次达到稳态时输出一次控制性能报告。
|
||
|
||
## 重要安全说明
|
||
|
||
> 本项目会直接控制真实阀门。首次运行前必须核对 IP、站号、AI/AO 通道、传感器量程、阀门行程方向、压力上限和执行机构的断电/失驱位置,并准备独立的机械或电气急停手段。
|
||
|
||
三个入口当前采用不同的阀门初始化与退出策略:
|
||
|
||
- **闭环 `main.py`**:启动时不再预先下发关闭位置,PID 内部输出由 `INITIAL_OPENING_PCT=100%` 开始;故障或退出时停止控制并断开 MT2-AM8,不主动下发关阀命令。当前系统按“执行机构失驱后回到全开”设计,现场必须确认实际硬件确实如此。
|
||
- **开环 `open_loop.py`**:连接成功后先主动设置 `0%` 开度,实验结束、异常或 `Ctrl+C` 时再次尝试设置 `0%` 开度,然后断开设备。
|
||
- **手动测试 `test_valve.py`**:连接时仅按已知状态记录 `100%` 开度,不向电机写入初始位置;退出时不主动下发新开度,仅断开设备。该脚本不执行流量/压力越限和连续读取失败保护,只适合在具备独立安全措施的受控测试中使用。
|
||
|
||
因此,README 中的“关闭”或“全开”均指当前代码定义的逻辑位置,不能代替现场对阀门真实动作方向的确认。
|
||
|
||
## 硬件与默认接线
|
||
|
||
- MT2-AM8:Modbus TCP,默认 `192.168.1.12:502`,站号 `1`;
|
||
- AI 输入寄存器:地址 `0x00~0x03`;
|
||
- AO 保持寄存器:地址 `0x00~0x03`;
|
||
- 模拟量原始范围:`0~4095`,传感器物理量换算使用 `819~4095` 对应有效量程;
|
||
- 默认流量量程:`300 SLM`;默认压力量程:`1600 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 = 800(当前打开端/机械死区边界)
|
||
```
|
||
|
||
虽然 `MT2AM8Client` 接受的物理行程仍为 `0~1000`,当前控制映射只使用 `800~1000`。这是近期根据阀门有效调节区间设置的打开端边界;逻辑 `100%` 不再对应物理行程 `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`。
|
||
|
||
### 运行中切换目标流量
|
||
|
||
闭环运行过程中无需重启即可切换目标流量:在终端按 `S`(`TARGET_SWITCH_KEY`)进入输入模式,屏幕会提示当前目标并等待输入,输入数字后按回车生效,按 `Esc` 取消。输入模式期间:
|
||
|
||
- 控制循环照常运行,原目标流量继续生效,直到回车确认新值;
|
||
- 终端输出整体静默(状态行、性能报告均不打印),避免与输入回显混在一起;
|
||
- 逐周期 CSV 与 `logs/flow_control.log` 照常写入,不丢数据。
|
||
|
||
新目标生效后会重置稳态判定(`PerformanceReporter.reset()`),因此切换后的目标仍会重新判稳并输出一次性能报告。目标值需位于 `TARGET_FLOW_MIN_SLM~TARGET_FLOW_MAX_SLM` 范围内,非法或超范围输入会被拒绝并提示,不影响控制。
|
||
|
||
> 该热键仅在真实控制台(Windows 的 `msvcrt`)下可用;`Ctrl+C` 无论是否处于输入模式都会停止控制。
|
||
|
||
启动流程为:
|
||
|
||
```text
|
||
校验配置 -> 连接 MT2-AM8 -> 读取一次初始流量
|
||
-> PID 输出初始化为 INITIAL_OPENING_PCT
|
||
-> 进入周期控制(启动阶段不主动写入关闭位置)
|
||
```
|
||
|
||
单个控制周期为:
|
||
|
||
```text
|
||
读取流量/压力
|
||
|
|
||
v
|
||
IncrementalPID.update()
|
||
error = target - measurement
|
||
|
|
||
v
|
||
开度百分比 0~100%(输出限幅 + 变化率限幅)
|
||
|
|
||
v
|
||
opening_to_motor_position()
|
||
|
|
||
v
|
||
反向行程 1000~800 -> MT2-AM8 AO1
|
||
```
|
||
|
||
增量 PID 的输出直接定义为阀门开度,因此控制器本身不包含电机行程或死区映射。目标流量不高于 `ZERO_FLOW_THRESHOLD_SLM`(当前 `0.5 SLM`)时,程序跳过常规 PID 更新并直接命令 `0%` 开度。
|
||
|
||
### FAR/NEAR 两档 PID
|
||
|
||
闭环启动时进入 `FAR` 模式。两档模式分别使用不同的 PID 参数和开度变化速度:
|
||
|
||
| 模式 | 用途 | Kp / Ki / Kd | 最大开度速度 |
|
||
| --- | --- | --- | ---: |
|
||
| `FAR` | 大误差时快速接近目标 | `0.5 / 0.5 / 0.0` | `20%/s` |
|
||
| `NEAR` | 小误差时抑制振荡和稳态误差 | `0.05 / 0.01 / 0.0` | `2%/s` |
|
||
|
||
切换阈值同时考虑绝对误差下限和目标流量比例:
|
||
|
||
```text
|
||
near_threshold = max(3 SLM, 5% * target)
|
||
far_threshold = max(12 SLM, 20% * target)
|
||
|
||
FAR -> NEAR:|error| <= near_threshold,连续满足 10 个采样周期
|
||
NEAR -> FAR :|error| >= far_threshold,连续满足 10 个采样周期
|
||
```
|
||
|
||
两个阈值构成滞回区间,可避免误差位于边界附近时逐周期反复切换。按当前 `0.1 s` 控制周期,连续 10 个采样周期约为 1 秒;期间只要切换条件有一个周期不成立,确认计数便清零。只有模式真正改变时才更新 PID 参数和输出变化率,切换原因会写入日志。
|
||
|
||
运行中修改目标后,模式确认计数会清零。如果尚无有效流量,或新目标相对最后有效流量的误差大于 `near_threshold`,控制器立即回到 `FAR`;若误差仍处于 NEAR 阈值内,则保持当前模式。PID 的累计输出和误差历史不会因修改目标而整体重置,性能报告器则会重置并从新目标重新计时、判稳和统计超调。
|
||
|
||
### 运行输出
|
||
|
||
每次闭环运行会产生:
|
||
|
||
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`:打乱顺序的随机种子;相同参数和种子会得到相同顺序。
|
||
|
||
可通过 `python open_loop.py --help` 查看完整参数帮助。
|
||
|
||
开环没有目标值,其稳态条件仅依据阀后流量的滑窗总体标准差:
|
||
|
||
```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` 参考线,并标出各开度切换时刻。
|
||
|
||
## 手动阀门测试
|
||
|
||
[test_valve.py](test_valve.py) 用于绕过 PID,人工设置单个阀门开度并观察传感器响应。该交互依赖 Windows 的 `msvcrt`,请在真实 Windows 终端中运行:
|
||
|
||
```powershell
|
||
python test_valve.py
|
||
```
|
||
|
||
连接成功后,脚本把当前已知状态记为 `100%` 开度、行程 `800`,但不会立即向电机写入该位置。运行期间:
|
||
|
||
- 每秒读取并显示一次流量、压力、逻辑开度和映射后的电机行程;
|
||
- 按 `S` 进入输入模式,输入 `0~100` 的新开度后按回车下发,按 `Esc` 取消;
|
||
- 输入期间暂停终端状态显示,但传感器采样和 CSV 写入继续;
|
||
- 按 `Ctrl+C` 保存数据并退出,不主动下发新的阀门开度。
|
||
|
||
数据保存为 `test_valve_data/test_valve_YYYYMMDD_HHMMSS.csv`,字段包含相对时间、ISO 时间戳、流量、开度、压力和电机行程;结束时生成同名三联图,依次显示流量、开度和压力。
|
||
|
||
> 此脚本不会调用 `config.validate_config()`,也没有闭环/开环程序中的流量越限、压力越限或连续读取失败保护。运行前应人工完成配置核对,并确保测试人员能够立即切断气源或执行急停。
|
||
|
||
## 安全与故障处理
|
||
|
||
### 闭环
|
||
|
||
- 配置在连接硬件前由 `validate_config()` 校验;
|
||
- 流量超出 `FLOW_VALID_MIN_SLM~FLOW_VALID_MAX_SLM` 或压力高于 `MAX_PRESSURE_KPA` 时停止控制并报故障;
|
||
- 短暂的流量/压力读取失败保持上一输出,不下发新位置;连续达到各自阈值后停止控制;
|
||
- 实际控制周期超过 `MAX_CONTROL_DT_S` 时停止控制;
|
||
- PID 输出限制在 `0~100%`;FAR/NEAR 两档分别限制每秒最大开度变化;
|
||
- 电机命令写入异常或返回失败时停止控制;
|
||
- 所有退出路径都会尝试断开设备,但当前闭环代码不会主动下发关阀命令。
|
||
|
||
### 开环
|
||
|
||
- 启动和收尾均主动下发 `0%` 开度;
|
||
- 任一有效流量读数越界、任一有效压力读数超过上限时中止;
|
||
- 阀后流量连续读取失败达到阈值时中止;
|
||
- 写入开度失败时中止;
|
||
- 中止后仍会尽力关阀、断开连接并保留已经写入的 CSV。
|
||
|
||
### 手动测试
|
||
|
||
- 只校验输入开度是否为 `0~100` 的有限数值以及电机写入是否成功;
|
||
- 传感器读取失败时 CSV 对应字段留空,终端显示 `--`,不会自动中止;
|
||
- 不检查流量和压力安全阈值;
|
||
- `Ctrl+C`、异常和正常收尾均不会主动关阀,只会断开连接并保存已有数据。
|
||
|
||
## 关键配置默认值
|
||
|
||
| 配置 | 当前默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `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` / `1600` | 传感器量程 |
|
||
| `TARGET_FLOW_MIN_SLM` / `TARGET_FLOW_MAX_SLM` | `0` / `300` | 允许的目标范围 |
|
||
| `TARGET_SWITCH_KEY` | `s` | 运行中按此键进入"输入新目标流量"模式 |
|
||
| `CONTROL_PERIOD_S` / `MAX_CONTROL_DT_S` | `0.1` / `0.3` | 目标周期 / 超时阈值 |
|
||
| `PID_FAR_KP` / `PID_FAR_KI` / `PID_FAR_KD` | `0.5` / `0.5` / `0.0` | 大误差快速模式 PID 参数 |
|
||
| `PID_NEAR_KP` / `PID_NEAR_KI` / `PID_NEAR_KD` | `0.05` / `0.01` / `0.0` | 小误差稳定模式 PID 参数 |
|
||
| `OPENING_MIN_PCT` / `OPENING_MAX_PCT` | `0` / `100` | PID 输出开度范围 |
|
||
| `MAX_OPENING_RATE_FAR_PCT_S` / `MAX_OPENING_RATE_NEAR_PCT_S` | `20` / `2` | FAR/NEAR 每秒最大开度变化百分比 |
|
||
| `PID_NEAR_ERROR_MIN_SLM` / `PID_NEAR_ERROR_TARGET_RATIO` | `3` / `0.05` | FAR 进入 NEAR 的绝对/相对误差阈值 |
|
||
| `PID_FAR_ERROR_MIN_SLM` / `PID_FAR_ERROR_TARGET_RATIO` | `12` / `0.20` | NEAR 返回 FAR 的绝对/相对误差阈值 |
|
||
| `PID_MODE_SWITCH_CONFIRM_CYCLES` | `10` | 模式切换条件需连续满足的采样周期数(当前约 1 秒) |
|
||
| `MOTOR_CLOSED_POSITION` / `MOTOR_OPEN_POSITION` | `1000` / `800` | 当前反向控制区间;打开端为机械死区边界 |
|
||
| `INITIAL_OPENING_PCT` | `100` | 闭环 PID 内部初始输出 |
|
||
| `FLOW_VALID_MIN_SLM` / `FLOW_VALID_MAX_SLM` | `0` / `300` | 流量安全范围 |
|
||
| `MAX_PRESSURE_KPA` | `500` | 压力安全上限,须按实际管路额定压力确认 |
|
||
| `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) | 独立开环扫点、四传感器记录和压力比分析 |
|
||
| [test_valve.py](test_valve.py) | Windows 终端手动开度测试、每秒采样、CSV 与三联图 |
|
||
| [PcControl.py](PcControl.py) | Modbus 硬件客户端;本项目三个入口实际使用 `MT2AM8Client` |
|
||
|
||
## 项目结构
|
||
|
||
```text
|
||
flow_control/
|
||
├── main.py
|
||
├── open_loop.py
|
||
├── test_valve.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(首次运行时创建)
|
||
├── test_valve_data/ # 手动阀门测试 CSV / PNG
|
||
└── logs/ # flow_control.log / open_loop.log
|
||
```
|