From 0d197038cc9561b507be620192168739400cae26 Mon Sep 17 00:00:00 2001 From: Edwin Chiu Date: Thu, 30 Jul 2020 02:31:04 -0700 Subject: [PATCH 1/5] WIP add simple fio2 controller via PID controlled coupling value between blower valve and o2 solenoid From 2e42d9afc1609c787e70e563d4262cdfcd70675c Mon Sep 17 00:00:00 2001 From: Edwin Chiu Date: Sat, 1 Aug 2020 23:11:37 -0700 Subject: [PATCH 2/5] working demo of experimental air flow controller --- software/controller/lib/core/controller.cpp | 44 +++++++++++++++++---- software/controller/lib/core/controller.h | 20 ++++++++-- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/software/controller/lib/core/controller.cpp b/software/controller/lib/core/controller.cpp index 658fff38c..f86847801 100644 --- a/software/controller/lib/core/controller.cpp +++ b/software/controller/lib/core/controller.cpp @@ -62,6 +62,7 @@ std::pair Controller::Run(Time now, const VentP blower_valve_pid_.reset(); psol_pid_.reset(); fio2_pid_.reset(); + air_flow_pid_.reset(); actuators_state = { .fio2_valve = 0, @@ -79,8 +80,8 @@ std::pair Controller::Run(Time now, const VentP // At the moment we don't support oxygen mixing -- we deliver either pure // air or pure oxygen. For any fio2 < 1, deliver air. - if (params.fio2 < 1) { - // Delivering pure air. + if (params.fio2 < 0.6) { + // Delivering air + oxygen mixes from 21 to 59%. psol_pid_.reset(); // Calculate blower valve command using calculated gains @@ -89,7 +90,7 @@ std::pair Controller::Run(Time now, const VentP float fio2_coupling_value = fio2_pid_.compute(now, sensor_readings.fio2, params.fio2); actuators_state = { - .fio2_valve = blower_valve * fio2_coupling_value, + .fio2_valve = sensor_readings.air_inflow.liters_per_sec() * fio2_coupling_value, // In normal mode, blower is always full power; pid controls pressure // by actuating the blower pinch valve. .blower_power = 1, @@ -98,21 +99,48 @@ std::pair Controller::Run(Time now, const VentP .exhale_valve = 1.0f - 0.55f * blower_valve - 0.4f, }; } else { - // Delivering pure oxygen. + // Delivering air + oxygen mixes from 60 to 100% blower_valve_pid_.reset(); - float psol_valve = psol_pid_.compute(now, sensor_readings.patient_pressure.kPa(), - desired_state.pressure_setpoint->kPa()); + float blower_valve = air_flow_pid_.compute(now, sensor_readings.air_inflow.liters_per_sec(), + dbg_air_flow_setpoint_.get()); + + /*float blower_valve = + air_flow_pid_.Compute(now, sensor_readings.inflow.liters_per_sec(), + psol_valve * (1-fio2_coupling_value)); + + float psol_valve = + psol_pid_.Compute(now, sensor_readings.patient_pressure.kPa(), + desired_state.pressure_setpoint->kPa()); + + float fio2_coupling_value = fio2_pid_.Compute(now, sensor_readings.fio2, + params.fio2); + + */ + + // experimental shit actuators_state = { + // Force psol to stay very slightly open to avoid the discontinuity + // caused by valve hysteresis at very low command. The exhale valve + // compensates for this intentional leakage by staying open when the + // psol valve is closed. + .fio2_valve = 0, + .blower_power = 1, + .blower_valve = blower_valve, + .exhale_valve = 1.0f, // - 0.6f * psol_valve - 0.4f, + }; + + /*actuators_state = { // Force psol to stay very slightly open to avoid the discontinuity // caused by valve hysteresis at very low command. The exhale valve // compensates for this intentional leakage by staying open when the // psol valve is closed. .fio2_valve = std::clamp(psol_valve + 0.05f, 0.0f, 1.0f), .blower_power = 0, - .blower_valve = 0, + .blower_valve = blower_valve, .exhale_valve = 1.0f - 0.6f * psol_valve - 0.4f, - }; + }; + */ } // Start controlling pressure. diff --git a/software/controller/lib/core/controller.h b/software/controller/lib/core/controller.h index ede490d99..fca3b39cf 100644 --- a/software/controller/lib/core/controller.h +++ b/software/controller/lib/core/controller.h @@ -89,14 +89,24 @@ class Controller { PID fio2_pid_{"fio2_", " for FIO2 PID", - /*initial_kp=*/0.001f, - /*initial_ki=*/0.1f, - /*initial_kd=*/0.0f, + /*kp=*/0.001f, + /*ki=*/0.1f, + /*kd=*/0.0f, /*p_term=*/PID::TermApplication::OnError, /*d_term=*/PID::TermApplication::OnMeasurement, /*output_min=*/0.f, /*output_max=*/1.0f}; + PID air_flow_pid_{"air_flow_", + " for air flow PID", + /*kp=*/0.1f, + /*ki=*/20.0f, + /*kd=*/0.0f, + /*p_term=*/PID::TermApplication::OnError, + /*d_term=*/PID::TermApplication::OnMeasurement, + /*output_min=*/0.f, + /*output_max=*/1.0f}; + // These objects accumulate flow to calculate volume. // // For debugging, we accumulate flow with and without error correction. See @@ -125,10 +135,14 @@ class Controller { // Outputs - read from external debug program, modified by the controller. DbgFloat dbg_pc_setpoint_{"pc_setpoint", DbgAccess::ReadOnly, 0.0f, "cmH2O", "Pressure control set-point"}; + // \todo: need a forced_fio2 variable DbgFloat dbg_fio2_setpoint_{"fio2_setpoint", DbgAccess::ReadOnly, 0.21f, "ratio", "FiO2 setpoint [0.0, 1.0] as commanded by GUI"}; + DbgFloat dbg_air_flow_setpoint_{"air_flow_setpoint", DbgAccess::ReadOnly, 0.0f, "L/s", + "Setpoint for air flow PID"}; + DbgFloat dbg_net_flow_{"net_flow", DbgAccess::ReadOnly, 0.0f, "mL/s", "Net flow rate"}; DbgFloat dbg_net_flow_uncorrected_{"net_flow_uncorrected", DbgAccess::ReadOnly, 0.0f, "mL/s", "Net flow rate w/o correction"}; From bea2028c6ab77b9ea2386d920242150a5607468c Mon Sep 17 00:00:00 2001 From: Edwin Chiu Date: Wed, 5 Aug 2020 14:19:29 -0700 Subject: [PATCH 3/5] created nested flow control for pinch valve during fio2 60-100%. Working demo of continuous fio2 control from 21 to 100% --- software/controller/lib/core/controller.cpp | 61 +++++++++++---------- software/controller/lib/core/controller.h | 2 +- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/software/controller/lib/core/controller.cpp b/software/controller/lib/core/controller.cpp index f86847801..e264443ac 100644 --- a/software/controller/lib/core/controller.cpp +++ b/software/controller/lib/core/controller.cpp @@ -87,10 +87,13 @@ std::pair Controller::Run(Time now, const VentP // Calculate blower valve command using calculated gains float blower_valve = blower_valve_pid_.compute(now, sensor_readings.patient_pressure.kPa(), desired_state.pressure_setpoint->kPa()); - float fio2_coupling_value = fio2_pid_.compute(now, sensor_readings.fio2, params.fio2); + float fio2_coupling_value = + std::clamp(params.fio2 + fio2_pid_.compute(now, sensor_readings.fio2, params.fio2), 0.0f, + 1.0f); // just a little bit of feed-forward actuators_state = { - .fio2_valve = sensor_readings.air_inflow.liters_per_sec() * fio2_coupling_value, + .fio2_valve = std::clamp( + sensor_readings.air_inflow.liters_per_sec() * fio2_coupling_value, 0.0f, 1.0f), // In normal mode, blower is always full power; pid controls pressure // by actuating the blower pinch valve. .blower_power = 1, @@ -102,45 +105,47 @@ std::pair Controller::Run(Time now, const VentP // Delivering air + oxygen mixes from 60 to 100% blower_valve_pid_.reset(); - float blower_valve = air_flow_pid_.compute(now, sensor_readings.air_inflow.liters_per_sec(), - dbg_air_flow_setpoint_.get()); - - /*float blower_valve = - air_flow_pid_.Compute(now, sensor_readings.inflow.liters_per_sec(), - psol_valve * (1-fio2_coupling_value)); + // experimental shit + /* + float blower_valve = + air_flow_pid_.compute(now, sensor_readings.air_inflow.liters_per_sec(), + dbg_air_flow_setpoint_.get()); + */ - float psol_valve = - psol_pid_.Compute(now, sensor_readings.patient_pressure.kPa(), - desired_state.pressure_setpoint->kPa()); + float psol_valve = psol_pid_.compute(now, sensor_readings.patient_pressure.kPa(), + desired_state.pressure_setpoint->kPa()); - float fio2_coupling_value = fio2_pid_.Compute(now, sensor_readings.fio2, - params.fio2); + float fio2_coupling_value = + std::clamp(params.fio2 + fio2_pid_.compute(now, sensor_readings.fio2, params.fio2), 0.0f, + 1.0f); // just a little bit of feed-forward - */ + float blower_valve = air_flow_pid_.compute(now, sensor_readings.air_inflow.liters_per_sec(), + psol_valve * (1 - fio2_coupling_value)); // experimental shit + /* actuators_state = { - // Force psol to stay very slightly open to avoid the discontinuity - // caused by valve hysteresis at very low command. The exhale valve - // compensates for this intentional leakage by staying open when the - // psol valve is closed. - .fio2_valve = 0, - .blower_power = 1, - .blower_valve = blower_valve, - .exhale_valve = 1.0f, // - 0.6f * psol_valve - 0.4f, - }; +// Force psol to stay very slightly open to avoid the discontinuity +// caused by valve hysteresis at very low command. The exhale valve +// compensates for this intentional leakage by staying open when the +// psol valve is closed. +.fio2_valve = 0, +.blower_power = 1, +.blower_valve = blower_valve, +.exhale_valve = 1.0f, // - 0.6f * psol_valve - 0.4f, + }; + */ - /*actuators_state = { + actuators_state = { // Force psol to stay very slightly open to avoid the discontinuity // caused by valve hysteresis at very low command. The exhale valve // compensates for this intentional leakage by staying open when the // psol valve is closed. .fio2_valve = std::clamp(psol_valve + 0.05f, 0.0f, 1.0f), - .blower_power = 0, - .blower_valve = blower_valve, + .blower_power = 1.0f, + .blower_valve = std::clamp(blower_valve, 0.0f, 1.0f), .exhale_valve = 1.0f - 0.6f * psol_valve - 0.4f, - }; - */ + }; } // Start controlling pressure. diff --git a/software/controller/lib/core/controller.h b/software/controller/lib/core/controller.h index fca3b39cf..e3cb44082 100644 --- a/software/controller/lib/core/controller.h +++ b/software/controller/lib/core/controller.h @@ -94,7 +94,7 @@ class Controller { /*kd=*/0.0f, /*p_term=*/PID::TermApplication::OnError, /*d_term=*/PID::TermApplication::OnMeasurement, - /*output_min=*/0.f, + /*output_min=*/-1.0f, /*output_max=*/1.0f}; PID air_flow_pid_{"air_flow_", From 0a2b9ca0fbbf8a3c6bef3668f538ec408591771e Mon Sep 17 00:00:00 2001 From: Edwin Chiu Date: Thu, 13 Aug 2020 23:57:30 -0700 Subject: [PATCH 4/5] bringing nested flow-pressure control to fio2 <0.6 --- software/controller/lib/core/controller.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/software/controller/lib/core/controller.cpp b/software/controller/lib/core/controller.cpp index e264443ac..41ce0e410 100644 --- a/software/controller/lib/core/controller.cpp +++ b/software/controller/lib/core/controller.cpp @@ -85,8 +85,15 @@ std::pair Controller::Run(Time now, const VentP psol_pid_.reset(); // Calculate blower valve command using calculated gains - float blower_valve = blower_valve_pid_.compute(now, sensor_readings.patient_pressure.kPa(), - desired_state.pressure_setpoint->kPa()); + /*float blower_valve = + blower_valve_pid_.Compute(now, sensor_readings.patient_pressure.kPa(), + desired_state.pressure_setpoint->kPa());*/ + + float flow_cmd = blower_valve_pid_.compute(now, sensor_readings.patient_pressure.kPa(), + desired_state.pressure_setpoint->kPa()); + float blower_valve = + air_flow_pid_.compute(now, sensor_readings.air_inflow.liters_per_sec(), flow_cmd); + float fio2_coupling_value = std::clamp(params.fio2 + fio2_pid_.compute(now, sensor_readings.fio2, params.fio2), 0.0f, 1.0f); // just a little bit of feed-forward @@ -108,8 +115,8 @@ std::pair Controller::Run(Time now, const VentP // experimental shit /* float blower_valve = - air_flow_pid_.compute(now, sensor_readings.air_inflow.liters_per_sec(), - dbg_air_flow_setpoint_.get()); + air_flow_pid_.Compute(now, + sensor_readings.inflow.liters_per_sec(), dbg_air_flow_setpoint.Get()); */ float psol_valve = psol_pid_.compute(now, sensor_readings.patient_pressure.kPa(), From b9cd33fd83d6477426f36e9be153b6872ca3fd7a Mon Sep 17 00:00:00 2001 From: Martin Shetty <1972005+martukas@users.noreply.github.com> Date: Mon, 4 Jul 2022 01:20:27 -0700 Subject: [PATCH 5/5] a bit of cleanup --- software/controller/lib/core/controller.cpp | 32 +-------------------- software/controller/lib/core/controller.h | 12 ++++---- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/software/controller/lib/core/controller.cpp b/software/controller/lib/core/controller.cpp index 41ce0e410..b96188e0f 100644 --- a/software/controller/lib/core/controller.cpp +++ b/software/controller/lib/core/controller.cpp @@ -25,9 +25,7 @@ Duration Controller::GetLoopPeriod() { return MainLoopPeriod; } std::pair Controller::Run(Time now, const VentParams ¶ms, const SensorReadings &sensor_readings) { VolumetricFlow uncorrected_net_flow = - sensor_readings.air_inflow - // + sensor_readings.oxygen_inflow \todo add this once it is well tested - - sensor_readings.outflow; + sensor_readings.air_inflow + sensor_readings.oxygen_inflow - sensor_readings.outflow; flow_integrator_->AddFlow(now, uncorrected_net_flow); uncorrected_flow_integrator_->AddFlow(now, uncorrected_net_flow); @@ -78,17 +76,10 @@ std::pair Controller::Run(Time now, const VentP uncorrected_flow_integrator_.emplace(); } - // At the moment we don't support oxygen mixing -- we deliver either pure - // air or pure oxygen. For any fio2 < 1, deliver air. if (params.fio2 < 0.6) { // Delivering air + oxygen mixes from 21 to 59%. psol_pid_.reset(); - // Calculate blower valve command using calculated gains - /*float blower_valve = - blower_valve_pid_.Compute(now, sensor_readings.patient_pressure.kPa(), - desired_state.pressure_setpoint->kPa());*/ - float flow_cmd = blower_valve_pid_.compute(now, sensor_readings.patient_pressure.kPa(), desired_state.pressure_setpoint->kPa()); float blower_valve = @@ -112,13 +103,6 @@ std::pair Controller::Run(Time now, const VentP // Delivering air + oxygen mixes from 60 to 100% blower_valve_pid_.reset(); - // experimental shit - /* - float blower_valve = - air_flow_pid_.Compute(now, - sensor_readings.inflow.liters_per_sec(), dbg_air_flow_setpoint.Get()); - */ - float psol_valve = psol_pid_.compute(now, sensor_readings.patient_pressure.kPa(), desired_state.pressure_setpoint->kPa()); @@ -129,20 +113,6 @@ std::pair Controller::Run(Time now, const VentP float blower_valve = air_flow_pid_.compute(now, sensor_readings.air_inflow.liters_per_sec(), psol_valve * (1 - fio2_coupling_value)); - // experimental shit - /* - actuators_state = { -// Force psol to stay very slightly open to avoid the discontinuity -// caused by valve hysteresis at very low command. The exhale valve -// compensates for this intentional leakage by staying open when the -// psol valve is closed. -.fio2_valve = 0, -.blower_power = 1, -.blower_valve = blower_valve, -.exhale_valve = 1.0f, // - 0.6f * psol_valve - 0.4f, - }; - */ - actuators_state = { // Force psol to stay very slightly open to avoid the discontinuity // caused by valve hysteresis at very low command. The exhale valve diff --git a/software/controller/lib/core/controller.h b/software/controller/lib/core/controller.h index e3cb44082..71651a0e6 100644 --- a/software/controller/lib/core/controller.h +++ b/software/controller/lib/core/controller.h @@ -89,9 +89,9 @@ class Controller { PID fio2_pid_{"fio2_", " for FIO2 PID", - /*kp=*/0.001f, - /*ki=*/0.1f, - /*kd=*/0.0f, + /*initial_kp=*/0.001f, + /*initial_ki=*/0.1f, + /*initial_kd=*/0.0f, /*p_term=*/PID::TermApplication::OnError, /*d_term=*/PID::TermApplication::OnMeasurement, /*output_min=*/-1.0f, @@ -99,9 +99,9 @@ class Controller { PID air_flow_pid_{"air_flow_", " for air flow PID", - /*kp=*/0.1f, - /*ki=*/20.0f, - /*kd=*/0.0f, + /*initial_kp=*/0.1f, + /*initial_ki=*/20.0f, + /*initial_kd=*/0.0f, /*p_term=*/PID::TermApplication::OnError, /*d_term=*/PID::TermApplication::OnMeasurement, /*output_min=*/0.f,