Index: firmware/App/Controllers/BalancingChamber.c =================================================================== diff -u -rff8dde1e0c78936fc2db1aa496c9dce2e498e03b -rfb596540735c4cf91de9692b7274a28ce3a645f7 --- firmware/App/Controllers/BalancingChamber.c (.../BalancingChamber.c) (revision ff8dde1e0c78936fc2db1aa496c9dce2e498e03b) +++ firmware/App/Controllers/BalancingChamber.c (.../BalancingChamber.c) (revision fb596540735c4cf91de9692b7274a28ce3a645f7) @@ -8,7 +8,7 @@ * @file BalancingChamber.c * * @author (last) Jashwant Gantyada -* @date (last) 05-Mar-2026 +* @date (last) 13-Mar-2026 * * @author (original) Vinayakam Mani * @date (original) 28-Jan-2025 @@ -22,8 +22,8 @@ #include "DryBiCart.h" #include "FpgaDD.h" #include "Heaters.h" -#include "ModeStandby.h" #include "ModeGenDialysate.h" +#include "ModeStandby.h" #include "Messaging.h" #include "OperationModes.h" #include "Pressure.h" @@ -43,12 +43,18 @@ #define BAL_CHAMBER_DATA_PUBLISH_INTERVAL ( 250 / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the balancing chamber data published. #define BAL_CHAMBER_FILL_PRES_DROP_MS ( 200 / TASK_GENERAL_INTERVAL ) ///< Time (ms/tasktime) to confirm the balancing chamber filling started and corrosponding valves opened. #define BAL_CHAMBER_FILL_COMPLETE_MS ( 300 / TASK_GENERAL_INTERVAL ) ///< Time (ms/tasktime) to confirm the balancing chamber fill completed and pressure is within range -#define SPENT_FILL_COMPLETE_PRES 25.0F ///< Spent pressure that indicates fill is complete. +#define SPENT_FILL_COMPLETE_PRES 25.0F ///< Spent fill complete pressure (PSI) for Diener 2000 pump. +#define SPENT_FILL_COMPLETE_PRES_QD_LOW_PSIG 3.5F ///< Spent fill complete pressure (PSI) when Diener 1000 and Qd <= 200. +#define SPENT_FILL_COMPLETE_PRES_QD_MID_PSIG 15.0F ///< Spent fill complete pressure (PSI) when Diener 1000 and 200 < Qd <= 400. +#define SPENT_FILL_COMPLETE_PRES_QD_HIGH_PSIG 20.0F ///< Spent fill complete pressure (PSI) when Diener 1000 and Qd > 400. +#define QD_THRESHOLD_LOW_MLPM 200.0F ///< Qd threshold (mL/min) below which low pressure limit applies. +#define QD_THRESHOLD_HIGH_MLPM 400.0F ///< Qd threshold (mL/min) below which mid pressure limit applies. #define SPENT_DIFF_COUNT_ZERO 0 ///< Zero count difference for spent side fill comparing target count -#define SPENT_DIFF_COUNT_DEADBAND 1 ///< Stop changing D48 pump speed when fill difference reaches close to target count #define D48_SPEED_ADJUST_FACTOR 0.5F ///< D48 speed adjustment factor ( 50% of speed adjustment = 0.5) #define D48_SPEED_RANGE_LIMIT 0.25F ///< D48 speed adjustment range check limit ( D48 speed can vary +/-25% of initial calculated speed) #define BICARB_CHAMBER_PERIODIC_FILL_TIME ( 1 * SEC_PER_MIN * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ) ///< Periodic bicarb chamber fill request 60 sec x 20 = 1200 +#define BAL_CHAMBER_FILL_TIMEOUT_FACTOR 1.5 ///< Balancing Chamber fill timeout factor (150% of observed fill count) + /// Payload record structure for balancing chamber switch only request typedef struct { @@ -78,9 +84,8 @@ static F32 lastTdDialysateFlowrate; ///< Previous TD dialysate flow rate static F32 freshDialPressure; ///< Fresh side dialysate pressure static F32 spentDialPressure; ///< Spent side dialysate pressure -static F32 prevSpentDialPressure; ///< Previous spent side dialysate pressure -static F32 lastPrevSpentDialPressure; ///< Last previous spent side dialysate pressure static U32 currentBalChamberFillCounter; ///< Counter (in task interval) to monitor the timing spent for the spent side fill operation. +static U32 balChamberFillTimeoutCount; ///< Timeout count (in task interval) to detect BC fill timeout. static S32 diffSpentFillCompleteCount; ///< Difference between spent target fill to actual fill count static BOOL isSpentFillComplete; //TODO: remove later once level sensor working @@ -140,9 +145,8 @@ isPressureDroppedDuringFill = FALSE; freshDialPressure = 0.0F; spentDialPressure = 0.0F; - prevSpentDialPressure = 0.0F; - lastPrevSpentDialPressure = 0.0F; currentBalChamberFillCounter = 0; + balChamberFillTimeoutCount = 0; diffSpentFillCompleteCount = 0; isSpentFillComplete = FALSE; //TODO:remove once level sensor working @@ -189,6 +193,9 @@ //Update last td dialysate flow rate lastTdDialysateFlowrate = tdDialysateFlowrate; + // Update fill timeout count based on the switching period (e.g. 150% of period) + balChamberFillTimeoutCount = (U32)( (F32)balChamberSwitchingPeriod * BAL_CHAMBER_FILL_TIMEOUT_FACTOR ); + //Reset the BC switching flag for new Qd. isFirstCycleBCSwitchingCompleted = FALSE; @@ -217,7 +224,7 @@ if ( getTestConfigStatus( TEST_CONFIG_DD_ENABLE_DRY_BICARB ) == TRUE ) { - //Increment counter for dry bicarb chamber fill + // Increment counter for dry bicarb chamber fill bicarbChamberPeriodicFillCounter += 1; // Fill bicarb chamber once every 60secs. if ( bicarbChamberPeriodicFillCounter >= BICARB_CHAMBER_PERIODIC_FILL_TIME ) @@ -447,19 +454,18 @@ currentBalChamberSwitchingCounter = 0; balChamberSWState = BAL_CHAMBER_SW_STATE1; currentBalChamberFillCounter = 0; + balChamberFillTimeoutCount = 0; isBalChamberFillInProgress = FALSE; isPressureStabilizedDuringFill = FALSE; isPressureDroppedDuringFill = FALSE; isSpentFillComplete = FALSE; - lastPrevSpentDialPressure = 0.0F; - prevSpentDialPressure = 0.0F; F32 acidVolume = getF32OverrideValue( &acidDoseVolume ); F32 bicarbVolume = getF32OverrideValue( &bicarbDoseVolume ); freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); - // Check fresh and spent dialysate pressure in range or BC switch only flag set + // Check fresh dialysate pressure in range or BC switch only flag set or BC pressure alarms are disabled if ( ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) || ( TRUE == getBalChamberSwitchingOnlyStatus() ) || ( TRUE == getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) ) ) { @@ -530,9 +536,6 @@ ( TRUE == isConcentratePumpDosingCompleted( D10_PUMP ) ) ) || ( TRUE == getBalChamberSwitchingOnlyStatus() ) ) { - //Collect first sample of spent pressure - lastPrevSpentDialPressure = prevSpentDialPressure; - prevSpentDialPressure = spentDialPressure; if ( BAL_CHAMBER_SW_STATE1 == balChamberSWState ) { @@ -552,21 +555,30 @@ * The handleBalChamberState1ValvesClose function check for the balancing chamber * fill complete and close the currently opened valves. * @details \b Inputs: currentBalChamberSwitchingCounter, spent and fresh dialysate pressure - * @details \b Outputs: isPressureStabilizedDuringFill,isBalChamberFillInProgress + * @details \b Outputs: isPressureStalbilizedDuringFill,isBalChamberFillInProgress * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState1ValvesClose( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE1_VALVES_CLOSE; freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); + BOOL isBothFillsComplete = FALSE; + BOOL isFirstCycleNotDone = FALSE; + BOOL isFillCompleteOrFirstCycle = FALSE; + // If fill is taking too long, set an alarm for fill timeout + if ( ( balChamberFillTimeoutCount > 0 ) && ( currentBalChamberFillCounter > balChamberFillTimeoutCount ) ) + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_BC_FILL_TIMEOUT_FAULT, currentBalChamberFillCounter, balChamberFillTimeoutCount ); + } + // Check fresh dialysate pressure back in range to indicate fresh fill complete. if ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) { if ( ++balChamberFillCompleteStablePressureCounter >= BAL_CHAMBER_FILL_COMPLETE_MS ) { - // stabilized pressure indicating fill is complete + // stabilized pressure indicating fresh side fill is complete isPressureStabilizedDuringFill = TRUE; } } @@ -575,7 +587,11 @@ checkSpentFillComplete( spentDialPressure ); // Check both spent and fresh side fill is complete - if ( ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ) || ( FALSE == isFirstCycleBCSwitchingCompleted ) ) + isBothFillsComplete = ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ); + isFirstCycleNotDone = ( FALSE == isFirstCycleBCSwitchingCompleted ); + isFillCompleteOrFirstCycle = isBothFillsComplete || isFirstCycleNotDone; + + if ( TRUE == isFillCompleteOrFirstCycle ) { // close the state 1 opened valves valveControlForBCState1FillEnd(); @@ -665,9 +681,8 @@ isPressureDroppedDuringFill = FALSE; balChamberSWState = BAL_CHAMBER_SW_STATE2; currentBalChamberFillCounter = 0; + balChamberFillTimeoutCount = 0; isSpentFillComplete = FALSE; - lastPrevSpentDialPressure = 0.0F; - prevSpentDialPressure = 0.0F; F32 acidVolume = getF32OverrideValue( &acidDoseVolume ); F32 bicarbVolume = getF32OverrideValue( &bicarbDoseVolume ); @@ -676,7 +691,6 @@ // Check fresh dialysate pressure in range if ( ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) || - // ( ( spentDialPressure >= SPENT_DIAL_PRESSURE_MIN_PSIG ) && ( spentDialPressure <= SPENT_DIAL_PRESSURE_MAX_PSIG ) ) || ( TRUE == getBalChamberSwitchingOnlyStatus() ) || ( TRUE == getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) ) ) { // Valve control for state 2 fill @@ -714,31 +728,43 @@ * The handleBalChamberState2ValvesClose function check for the balancing chamber * fill complete and close the currently opened valves. * @details \b Inputs: currentBalChamberSwitchingCounter, spent and fresh dialysate pressure - * @details \b Outputs: isPressureStabilizedDuringFill,isBalChamberFillInProgress + * @details \b Outputs: isPressureStalbilizedDuringFill,isBalChamberFillInProgress * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState2ValvesClose( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE2_VALVES_CLOSE; + freshDialPressure = getFilteredPressure( D18_PRES ); + spentDialPressure = getFilteredPressure( D51_PRES ); + BOOL isBothFillsComplete = FALSE; + BOOL isFirstCycleNotDone = FALSE; + BOOL isFillCompleteOrFirstCycle = FALSE; - freshDialPressure = getFilteredPressure( D18_PRES ); - spentDialPressure = getFilteredPressure( D51_PRES ); - - // Check fresh and spent dialysate pressure back in range to indicate fill complete. + // Check fresh dialysate pressure back in range to indicate fill complete. if ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) { if ( ++balChamberFillCompleteStablePressureCounter >= BAL_CHAMBER_FILL_COMPLETE_MS ) { - // stabilized pressure indicating fill is complete + // stabilized pressure indicating fresh side fill is complete isPressureStabilizedDuringFill = TRUE; } } + // If fill is taking too long, set an alarm for fill timeout + if ( ( balChamberFillTimeoutCount > 0 ) && ( currentBalChamberFillCounter > balChamberFillTimeoutCount ) ) + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_BC_FILL_TIMEOUT_FAULT, currentBalChamberFillCounter, balChamberFillTimeoutCount ); + } + // Spent side of balancing chamber fill is complete or not checkSpentFillComplete( spentDialPressure ); // Check switching cycle time or pressure check for valve closure - if ( ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ) || ( FALSE == isFirstCycleBCSwitchingCompleted ) ) + isBothFillsComplete = ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ); + isFirstCycleNotDone = ( FALSE == isFirstCycleBCSwitchingCompleted ); + isFillCompleteOrFirstCycle = isBothFillsComplete || isFirstCycleNotDone; + + if ( TRUE == isFillCompleteOrFirstCycle ) { // close the valves valveControlForBCState2FillEnd(); @@ -761,7 +787,7 @@ * The handleBalChamberState2FillEnd function check for the balancing chamber * switching period complete and transition to next state. * @details \b Inputs: currentBalChamberSwitchingCounter, balChamberSwitchingPeriod - * @details \b Outputs: isPressureStabilizedDuringFill,isBalChamberFillInProgress + * @details \b Outputs: isPressureStalbilizedDuringFill,isBalChamberFillInProgress * @details \b Alarm: ALARM_ID_DD_BC_STATE2_FILL_PRESSURE_DROP_OUT_OF_RANGE * when pressure is not in range during balacing chamber state 2 fill in progress. * @details \b Alarm: ALARM_ID_DD_BC_STATE2_FILL_END_PRESSURE_OUT_OF_RANGE @@ -823,34 +849,53 @@ * fill timing close to the switching period time. * @details \b Inputs: balChamberExecState * @details \b Outputs: none + * @param spentDialPressure to check the switching time for BC and set D48 speed based on it. * @return the current state of balancing chamber states. *************************************************************************/ static void checkSpentFillComplete( F32 spentDialPressure ) { - F32 diffInSpentPressure = 0.0F; U32 absDiffSpentFillCount = 0; U32 adjustedSpeed = 0; U32 spentDialPumpSpeed = getD48PumpSpeedForBCFill(); U32 initialD48PumpSpeed = getCalculatedD48PumpSpeedForBCFill(); U32 minD48Speed = initialD48PumpSpeed - ( initialD48PumpSpeed * D48_SPEED_RANGE_LIMIT ); U32 maxD48Speed = initialD48PumpSpeed + ( initialD48PumpSpeed * D48_SPEED_RANGE_LIMIT ); U32 d48SpeedPostRangeCheck = 0; + F32 qdMlpm = getTDDialysateFlowrate(); + BOOL result = FALSE; + F32 spentFillCompletePresPsig; - // Collect the next sample of spent pressure - lastPrevSpentDialPressure = prevSpentDialPressure; - prevSpentDialPressure = spentDialPressure; - // find the pressure difference for pressure spike detection - diffInSpentPressure = fabs(spentDialPressure - lastPrevSpentDialPressure); + // Diener 2000 pump: spent fill complete pressure use legacy 25 PSI. Other pump: depends on Qd. + if ( TRUE == getTestConfigStatus( TEST_CONFIG_DD_ENABLE_DIENER_2000_PUMP ) ) + { + spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES; + } + else + { + if ( qdMlpm <= QD_THRESHOLD_LOW_MLPM ) + { + spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_LOW_PSIG; + } + else if ( qdMlpm <= QD_THRESHOLD_HIGH_MLPM ) + { + spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_MID_PSIG; + } + else + { + spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_HIGH_PSIG; + } + } - if ( ( spentDialPressure >= SPENT_FILL_COMPLETE_PRES ) && ( isSpentFillComplete != TRUE ) ) + if ( ( spentDialPressure >= spentFillCompletePresPsig ) && ( isSpentFillComplete != TRUE ) ) { // Check spent fill time against the balancing chamber closing period diffSpentFillCompleteCount = balChamberValveClosePeriod - currentBalChamberFillCounter; absDiffSpentFillCount = abs(diffSpentFillCompleteCount); adjustedSpeed = ( ( (F32)absDiffSpentFillCount / (F32)balChamberValveClosePeriod ) * spentDialPumpSpeed ) * D48_SPEED_ADJUST_FACTOR; - //Not adjust the D48 pump speed if the fill counter is just 50ms difference from the closing period. - if ( absDiffSpentFillCount > SPENT_DIFF_COUNT_DEADBAND ) + result = ( ( diffSpentFillCompleteCount <= 0 ) && ( diffSpentFillCompleteCount >= -2 ) ) ? TRUE : FALSE; + //Not adjust the D48 pump speed if the fill counter is just 50ms difference from the closing period (considering only positive DEADBAND as negative deadband might indicate under fill of BC). + if ( FALSE == result ) { if ( diffSpentFillCompleteCount < SPENT_DIFF_COUNT_ZERO ) { @@ -926,6 +971,7 @@ * switching only On/Off status. * @details \b Inputs: balanceChamberSwitchingOnly * @details \b Outputs: none + * @param OnOff to set the BC switch status * @return none *************************************************************************/ void setBalChamberSwitchingOnlyStatus( BOOL OnOff )