software:firmware
MakAir Firmware
pc_vsai_controller.cpp
Go to the documentation of this file.
1 
8 // INCLUDES ==================================================================
9 
10 // Associated header
11 #include "../includes/pc_vsai_controller.h"
12 
13 // External
14 #include "Arduino.h"
15 #include <algorithm>
16 
17 // Internal
18 #include "../includes/main_controller.h"
19 #include "../includes/pressure_valve.h"
20 
21 // INITIALISATION =============================================================
22 
24 
25 // FUNCTIONS ==================================================================
26 
27 // cppcheck-suppress misra-c2012-5.2 ; false positive
34  + (1000u / MAIN_CONTROLLER_COMPUTE_PERIOD_MS); // Possible to trigger 1s before end
35 
38 
39  for (uint8_t i = 0u; i < PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN; i++) {
42  }
43 
55 }
56 
58 
63  + (1400u / MAIN_CONTROLLER_COMPUTE_PERIOD_MS); // Possible to trigger 1.4s before end
66  // Reset PID values
75  for (uint8_t i = 0; i < PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN; i++) {
79  }
80 
81  // Apply blower ramp-up
82  if (m_blowerIncrement >= 0) {
83  blower.runSpeedWithRampUp(m_blowerSpeed + static_cast<uint16_t>(abs(m_blowerIncrement)));
84  } else {
85  // When blower increment is negative, we need to check that it is less than current speed
86  // If not, it would result in an overflow
87  if (static_cast<uint16_t>(abs(m_blowerIncrement)) < blower.getSpeed()) {
89  - static_cast<uint16_t>(abs(m_blowerIncrement)));
90  } else {
92  }
93  }
96 
98 
100 }
101 
103  m_expiratoryPidFastMode = false;
104 
105  // Keep the inspiratory valve open using a PID
106  int32_t inspiratoryValveOpenningValue = PCinspiratoryPID(
108 
109  // Normally inspiratory valve is open, but at the end of the cycle it could be closed and
110  // expiratory valve will open
111  (void)inspiratoryValve.openLinear(inspiratoryValveOpenningValue);
113 
115 
116  // m_inspiratorySlope is used for blower regulations, -20 corresponds to open loop openning
120  / static_cast<int32_t>(mainController.tick()); // in mmH2O/s
122  }
123 
124  int32_t tiMinInTick =
126 
129  } else if ((mainController.inspiratoryFlow()
131  && (static_cast<int64_t>(mainController.tick())
132  > static_cast<int64_t>(tiMinInTick))) {
134  } else {
135  // Do nothing
136  }
137 }
138 
140  // Open the expiration valve so the patient can exhale outside
141  int32_t expiratoryValveOpenningValue = PCexpiratoryPID(
143 
144  (void)expiratoryValve.openLinear(expiratoryValveOpenningValue);
145 
147 
148  // Calculate max pressure for the last samples
150  for (uint8_t i = 0; i < MAX_PRESSURE_SAMPLES; i++) {
153  }
154  }
155 
156  // In case the pressure trigger mode is enabled, check if inspiratory trigger is raised
157  if ((mainController.tick()
159  // m_peakPressure > CONST_MIN_PEAK_PRESSURE ensures that the patient is plugged on the
160  // machine
161  if (((mainController.pressure())
166  }
167  }
168 }
169 
171 
173  int16_t peakDelta =
175  int16_t rebouncePeakDelta =
177  DBG_DO(Serial.print("peakPressure:");)
178  DBG_DO(Serial.println(mainController.peakPressureMeasure());)
179  DBG_DO(Serial.print("plateauPressureCommand:");)
180  DBG_DO(Serial.println(mainController.plateauPressureCommand());)
181 
182  // Check that pressure didn't rebounced
183  // High rebounces will decrease the blower
184  // Low rebounce will prevent increase (but not decrease)
185  bool veryHighRebounce = (peakDelta > 60) || ((rebouncePeakDelta < -60) && (peakDelta >= 0));
186  bool highRebounce = (peakDelta > 40) || ((rebouncePeakDelta < -40) && (peakDelta >= 0));
187  bool lowRebounce = (peakDelta > 20) || ((rebouncePeakDelta < -15) && (peakDelta >= 0));
188  bool veryLowRebounce = (peakDelta > 10) || ((rebouncePeakDelta < -10) && (peakDelta >= 0));
189 
190  // Update blower only if patient is plugged on the machine
191  if (mainController.peakPressureMeasure() > 20) {
192  // Safety condition: a too high peak (4 cmH2O) should decrease the blower
193  if (veryHighRebounce) {
194  m_blowerIncrement = -100;
195  } else if (highRebounce) {
196  m_blowerIncrement = -10;
197  } else if (m_inspiratorySlope > 650) { // We want the m_inspiratorySlope = 600 mmH2O/s
198  // Only case for decreasing the blower: ramping is too fast or overshooting is too high
199  if ((m_inspiratorySlope > 1000)
200  || ((lowRebounce && (m_inspiratorySlope > 800)) || (peakDelta > 25))) {
201  m_blowerIncrement = -100;
202  } else {
203  m_blowerIncrement = 0;
204  }
205  } else if (m_inspiratorySlope > 550) {
206  m_blowerIncrement = 0;
207  } else if ((m_inspiratorySlope > 450) && !veryLowRebounce) {
208  m_blowerIncrement = +25;
209  } else if ((m_inspiratorySlope > 350) && !veryLowRebounce) {
210  m_blowerIncrement = +50;
211  } else if ((m_inspiratorySlope > 250) && !veryLowRebounce) {
212  m_blowerIncrement = +75;
213  } else if (!lowRebounce) {
214  m_blowerIncrement = +100;
215  } else {
216  // Do nothing
217  }
218  }
219 
220  DBG_DO(Serial.print("BLOWER"));
221  DBG_DO(Serial.println(m_blowerIncrement));
222  DBG_DO(Serial.print("m_inspiratorySlope:");)
223  DBG_DO(Serial.println(m_inspiratorySlope);)
224 }
225 
226 int32_t
227 PC_VSAI_Controller::PCinspiratoryPID(int32_t targetPressure, int32_t currentPressure, int32_t dt) {
228  int32_t minAperture = inspiratoryValve.minAperture();
229  int32_t maxAperture = inspiratoryValve.maxAperture();
230  int32_t inspiratoryValveAperture;
231  int32_t derivative = 0;
232  int32_t smoothError = 0;
233  int32_t totalValues = 0;
234  int32_t proportionnalWeight;
235  int32_t derivativeWeight;
236 
237  int32_t coefficientP;
238  int32_t coefficientI;
239  int32_t coefficientD;
240 
241  int32_t temporarym_inspiratoryPidIntegral = 0;
242 
243  // Compute error
244  int32_t error = targetPressure - currentPressure;
245 
246  // Windowing (it overrides the parameter.h coefficients)
247  coefficientP = 2500;
248 
249  coefficientD = 0;
250  if (error < 0) {
251  coefficientI = 200;
252 
253  } else {
254  coefficientI = 50;
255  }
256 
257  // Calculate derivative part
258  // Include a moving average on error for smoothing purpose
262  >= static_cast<int32_t>(PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN)) {
264  }
265  for (uint8_t i = 0u; i < PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN; i++) {
266  totalValues += m_inspiratoryPidLastErrors[i];
267  }
268  smoothError = totalValues / static_cast<int32_t>(PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN);
269 
270  // Fast mode ends at 20 mmH20 from target
271  // When changing from fast mode to PID, set the integral to the previous value
272  if (error < 20) {
274  proportionnalWeight = (coefficientP * error) / 1000;
275  derivativeWeight = (coefficientD * derivative / 1000);
277  * ((int32_t)m_inspiratoryValveLastAperture - maxAperture)
278  / (minAperture - maxAperture)
279  - (proportionnalWeight + derivativeWeight);
280  }
281  m_inspiratoryPidFastMode = false;
282  }
283 
284  // In fast mode: everything is openned (open loop)
286  // Ramp from 125 to 0 angle during 250 ms
287  int32_t increment =
288  (5 * static_cast<int32_t>(MAIN_CONTROLLER_COMPUTE_PERIOD_MICROSECONDS)) / 10000;
289  if (m_inspiratoryValveLastAperture >= abs(increment)) {
290  inspiratoryValveAperture =
291  max(minAperture,
292  min(maxAperture,
293  (static_cast<int32_t>(m_inspiratoryValveLastAperture) - increment)));
294  } else {
295  inspiratoryValveAperture = 0;
296  }
297  } else { // If not in fast mode, the PID is used
298  derivative = ((dt == 0)) ? 0 : ((1000000 * (m_inspiratoryPidLastError - smoothError)) / dt);
299 
300  temporarym_inspiratoryPidIntegral =
301  m_inspiratoryPidIntegral + ((coefficientI * error * dt) / 1000000);
302  temporarym_inspiratoryPidIntegral =
304  min(PID_BLOWER_INTEGRAL_MAX, temporarym_inspiratoryPidIntegral));
305 
306  proportionnalWeight = ((coefficientP * error) / 1000);
307  int32_t integralWeight = temporarym_inspiratoryPidIntegral;
308  derivativeWeight = coefficientD * derivative / 1000;
309 
310  int32_t blowerCommand = proportionnalWeight + integralWeight + derivativeWeight;
311  inspiratoryValveAperture =
312  max(minAperture,
313  min(maxAperture, maxAperture + (minAperture - maxAperture) * blowerCommand / 1000));
314  }
315 
316  // If the valve is completely open or completely closed, don't update integral part
317  if ((inspiratoryValveAperture != minAperture) && (inspiratoryValveAperture != maxAperture)) {
318  m_inspiratoryPidIntegral = temporarym_inspiratoryPidIntegral;
319  }
320 
321  m_inspiratoryValveLastAperture = inspiratoryValveAperture;
322  m_inspiratoryPidLastError = smoothError;
323 
324  return inspiratoryValveAperture;
325 }
326 
327 int32_t
328 PC_VSAI_Controller::PCexpiratoryPID(int32_t targetPressure, int32_t currentPressure, int32_t dt) {
329  int32_t minAperture = expiratoryValve.minAperture();
330  int32_t maxAperture = expiratoryValve.maxAperture();
331  int32_t expiratoryValveAperture;
332  int32_t derivative = 0;
333  int32_t smoothError = 0;
334  int32_t totalValues = 0;
335  int32_t temporarym_expiratoryPidIntegral = 0;
336  int32_t proportionnalWeight;
337  int32_t derivativeWeight;
338 
339  int32_t coefficientP;
340  int32_t coefficientI;
341  int32_t coefficientD;
342 
343  // Compute error
344  int32_t error = targetPressure + PID_PATIENT_SAFETY_PEEP_OFFSET - currentPressure;
345 
346  // Calculate derivative part
347  // Include a moving average on error for smoothing purpose
351  >= static_cast<int32_t>(PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN)) {
353  }
354  for (uint8_t i = 0u; i < PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN; i++) {
355  totalValues += m_expiratoryPidLastErrors[i];
356  }
357  smoothError = totalValues / static_cast<int32_t>(PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN);
358  derivative = (dt == 0) ? 0 : (1000000 * (smoothError - m_expiratoryPidLastError)) / dt;
359 
360  // Windowing (it overrides the parameter.h coefficients)
361  if (error < 0) {
362  coefficientI = 50;
363  coefficientP = 2500;
364  coefficientD = 0;
365  } else {
366  // For a high PEEP, a lower KI is required
367  // For PEEP = 100 mmH2O, KI = 120
368  // For PEEP = 50 mmH2O, KI = 250
369  if (mainController.peepCommand() > 100) {
370  coefficientI = 120;
371  } else {
372  coefficientI = ((-130 * ((int32_t)mainController.peepCommand())) / 50) + 380;
373  }
374  coefficientP = 2500;
375  coefficientD = 0;
376  }
377 
378  // Fast mode ends at 30 mmH20 from target
379  // When changing from fast mode to PID, set the integral to the previous value
380  if (error > -30) {
382  proportionnalWeight = (coefficientP * error) / 1000;
383  derivativeWeight = (coefficientD * derivative / 1000);
384  m_expiratoryPidIntegral = 1000 * ((int32_t)m_expiratoryValveLastAperture - maxAperture)
385  / (maxAperture - minAperture)
386  - (proportionnalWeight + derivativeWeight);
387  }
388  m_expiratoryPidFastMode = false;
389  }
390 
391  // Fast mode: open loop with ramp
393  expiratoryValveAperture = 0;
394  } else { // If not in fast mode, the PID is used
395  temporarym_expiratoryPidIntegral =
396  m_expiratoryPidIntegral + ((coefficientI * error * dt) / 1000000);
397  temporarym_expiratoryPidIntegral =
399  min(PID_PATIENT_INTEGRAL_MAX, temporarym_expiratoryPidIntegral));
400 
401  proportionnalWeight = ((coefficientP * error) / 1000);
402  int32_t integralWeight = temporarym_expiratoryPidIntegral;
403  derivativeWeight = coefficientD * derivative / 1000;
404 
405  int32_t patientCommand = proportionnalWeight + integralWeight + derivativeWeight;
406 
407  expiratoryValveAperture = max(
408  minAperture,
409  min(maxAperture, maxAperture + (maxAperture - minAperture) * patientCommand / 1000));
410  }
411 
412  // If the valve is completely open or completely closed, don't update integral part
413  if ((expiratoryValveAperture != minAperture) && (expiratoryValveAperture != maxAperture)) {
414  m_expiratoryPidIntegral = temporarym_expiratoryPidIntegral;
415  }
416 
417  m_expiratoryPidLastError = smoothError;
418  m_expiratoryValveLastAperture = expiratoryValveAperture;
419 
420  return expiratoryValveAperture;
421 }
Blower blower
Definition: blower.cpp:20
uint16_t getSpeed() const
Get speed value.
Definition: blower.cpp:101
uint16_t getTargetSpeed() const
Get target speed value.
Definition: blower.cpp:103
void runSpeedWithRampUp(uint16_t p_targetSpeed)
Run the blower to a given speed applying a ramp-up to prevent high current drain.
Definition: blower.cpp:44
int32_t dt() const
Get the delta of time since the last cycle (in ms)
const int16_t tiMinCommand() const
Get the value of the minimum duration of inspiration in ms.
int16_t * lastPressureValues()
Get last pressure values.
uint32_t tick() const
Get the tick number of the current cycle.
int16_t pressure() const
Get the current measured pressure.
int16_t rebouncePeakPressureMeasure() const
Get the measured rebounce peak pressure.
const int16_t expiratoryTriggerFlowCommand() const
Get the value of the expiratory trigger flow command.
int16_t peakPressureMeasure() const
Get the measured peak pressure.
const int16_t pressureTriggerOffsetCommand() const
Get the value of the inspiratory trigger pressure command.
int16_t peepCommand() const
Get the desired PEEP.
int16_t peepMeasure() const
Get the measured PEEP.
int16_t plateauPressureCommand() const
Get the desired plateau pressure.
int32_t inspiratoryFlow() const
Get the current inspiratoryFlow.
void ticksPerInhalationSet(uint32_t p_ticksPerInhalation)
Get the duration of an inhalation in ticks.
uint32_t ticksPerInhalation() const
Get the duration of an inhalation in ticks.
int32_t pressureCommand() const
Get the pressure command.
const void setTrigger(bool triggerValue)
Reset the trigger to false.
Controller for the VSAI mode.
int32_t m_expiratoryPidLastErrors[PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN]
Last errors in expiratory PID.
bool m_plateauPressureReached
True if plateau pressure has been reached (but not necessarily converged)
void setup() override
Initialize controller.
bool m_reOpenInspiratoryValve
True if we want to reopen the inspiratory valve to create a circulation flow able to detect inspirato...
int32_t m_maxInspiratoryFlow
Max flow during inspiration.
int32_t m_inspiratoryPidLastError
Error of the last computation of the blower PID.
int32_t PCexpiratoryPID(int32_t targetPressure, int32_t currentPressure, int32_t dt)
PID to control the patient valve during some specific steps of the cycle.
uint16_t m_blowerSpeed
Current blower speed.
PC_VSAI_Controller()
Default constructor.
void endCycle() override
End the current breathing cycle.
uint16_t m_triggerWindow
Number of ticks from which it is possible to trigger a new inspiration.
void inhale() override
Control the inhalation.
int32_t m_expiratoryPidIntegral
Integral gain of the patient PID.
int32_t m_expiratoryPidLastErrorsIndex
Last error index in expiratory PID.
int32_t m_blowerIncrement
Current blower speed increment (to apply at the beginning of the next cycle)
int32_t m_inspiratoryPidIntegral
Error of the last computation of the blower PID.
int32_t m_inspiratoryPidLastErrorsIndex
Last error index in inspiratory PID.
int32_t m_inspiratoryPidLastErrors[PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN]
Last errors in inspiratory PID.
void calculateBlowerIncrement()
Determine the blower speed to adopt for next cycle.
bool m_inspiratoryPidFastMode
Fast mode at start of inspiration.
bool m_expiratoryPidFastMode
Fast mode at start of expiration.
void initCycle() override
Begin a new breathing cycle.
int32_t m_inspiratorySlope
Slope of the inspiration open loop (in mmH2O/s)
int32_t m_inspiratoryValveLastAperture
Last aperture of the blower valve.
int32_t m_expiratoryValveLastAperture
Last aperture of the blower valve.
int32_t m_expiratoryPidLastError
Error of the last computation of the PID.
void exhale() override
Control the exhalation.
int32_t PCinspiratoryPID(int32_t targetPressure, int32_t currentPressure, int32_t dt)
PID to control the blower valve during some specific steps of the cycle.
uint16_t minAperture() const
Minimum valve aperture angle in degrees.
uint16_t maxAperture() const
Maximum valve aperture angle in degrees.
void close()
Request closing of the Pressure Valve.
uint16_t openLinear(uint16_t p_command)
Request opening of the Pressure Valve with a given angle with linearization.
#define DBG_DO(statement)
Expand arbitrary code only when in debug mode.
Definition: debug.h:24
int32_t maxPressureValue
MainController mainController
#define MAX_PRESSURE_SAMPLES
Number of values to aggregate when computing plateau pressure.
uint16_t i
static const int32_t PID_PATIENT_INTEGRAL_MIN
Definition: parameters.h:146
#define PC_NUMBER_OF_SAMPLE_DERIVATIVE_MOVING_MEAN
Definition: parameters.h:152
static const int32_t PID_BLOWER_INTEGRAL_MAX
Definition: parameters.h:139
#define DEFAULT_BLOWER_SPEED
Definition: parameters.h:194
#define MAIN_CONTROLLER_COMPUTE_PERIOD_MS
Definition: parameters.h:28
#define CONST_MIN_PEAK_PRESSURE
Definition: parameters.h:34
static const int32_t PID_PATIENT_INTEGRAL_MAX
Definition: parameters.h:145
#define MAIN_CONTROLLER_COMPUTE_PERIOD_MICROSECONDS
Definition: parameters.h:30
static const int32_t PID_BLOWER_INTEGRAL_MIN
Definition: parameters.h:140
#define MIN_BLOWER_SPEED
Definition: parameters.h:192
static const int32_t PID_PATIENT_SAFETY_PEEP_OFFSET
Increase target pressure by an offset (in mmH2O) for safety, to avoid going below the target pressure...
Definition: parameters.h:150
PC_VSAI_Controller pcVsaiController
PressureValve inspiratoryValve
PressureValve expiratoryValve