Index: firmware/App/Controllers/BalancingChamber.c =================================================================== diff -u -r91685a2616bc603aaa3d01206e27ac0b0fd02b45 -r8348015aa77e1ee2c4c20e9f8e2c45b59bcf3f17 --- firmware/App/Controllers/BalancingChamber.c (.../BalancingChamber.c) (revision 91685a2616bc603aaa3d01206e27ac0b0fd02b45) +++ firmware/App/Controllers/BalancingChamber.c (.../BalancingChamber.c) (revision 8348015aa77e1ee2c4c20e9f8e2c45b59bcf3f17) @@ -104,6 +104,9 @@ static BOOL isBalChamberSwitchingActive; ///< Flag indicating balancing chamber switching is active or not. static BOOL isBalChamberSwitchingOnRequested; ///< Flag indicating that a request was made to activate balancing chamber switching. static BOOL isBalChamberSwitchingOffRequested; ///< Flag indicating that a request was made to deactivate balancing chamber switching. +static F32 pendingTdDialysateFlowrate; ///< Pending TD dialysate flow rate to apply after dosing completes for the active half-cycle. +static BOOL isBalChamberSwitchingPeriodUpdatePending; ///< Flag indicating a BC switching period update is waiting for apply after dosing completes. +static U32 firstCycleRelaxHalfSwitchesRemaining; ///< Valve-close halves remaining before first-cycle relaxations end after a Qd timing apply. //TODO: remove later once level sensor working static U32 bicarbChamberPeriodicFillCounter; @@ -122,6 +125,10 @@ static U32 getBalChamberDataPublishInterval( void ); static void checkSpentFillComplete( F32 spentDialPressure ); static BOOL isSpentFillCompleteByPressure( F32 spentDialPressure, F32 qdMlpm, F32 spentFillCompletePresPsig ); +static void applyBalChamberSwitchingPeriod( F32 tdDialysateFlowrate ); +static void applyPendingBalChamberSwitchingPeriod( void ); +static BOOL isBalChamberTimeBasedSwitching( void ); +static void scheduleFirstCycleRelaxAfterQdApply( void ); /*********************************************************************//** * @brief @@ -177,6 +184,9 @@ isBalChamberSwitchingActive = FALSE; isBalChamberSwitchingOnRequested = FALSE; isBalChamberSwitchingOffRequested = FALSE; + pendingTdDialysateFlowrate = 0.0F; + isBalChamberSwitchingPeriodUpdatePending = FALSE; + firstCycleRelaxHalfSwitchesRemaining = 0U; //TODO:remove once level sensor working bicarbChamberPeriodicFillCounter = 0; } @@ -209,44 +219,130 @@ if ( lastTdDialysateFlowrate != tdDialysateFlowrate ) { - // update the balancing chamber switching frequency - balChamberSwitchingFreq.data = tdDialysateFlowrate / BAL_CHAMBER_FILL_VOLUME_ML; + if ( ( TRUE == isBalChamberSwitchingActive ) && ( BAL_CHAMBER_STATE_IDLE != balChamberExecState ) ) + { + pendingTdDialysateFlowrate = tdDialysateFlowrate; + isBalChamberSwitchingPeriodUpdatePending = TRUE; + } + else + { + applyBalChamberSwitchingPeriod( tdDialysateFlowrate ); + scheduleFirstCycleRelaxAfterQdApply(); + } + } + else + { + pendingTdDialysateFlowrate = 0.0F; + isBalChamberSwitchingPeriodUpdatePending = FALSE; + } +} - //update the switching period in task interval for balancing chamber fill timeout check - balChamberSwitchingPeriod = (U32)( (F32)SEC_PER_MIN / getBalChamberSwitchingFreq() * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ); +/*********************************************************************//** + * @brief + * The applyBalChamberSwitchingPeriod function applies the active balancing + * chamber switching time based on the provided dialysis flow rate. + * @details \b Inputs: Dialysis flow rate. + * @details \b Outputs: balChamberSwitchingFreq,balChamberSwitchingPeriod + * @return none + *************************************************************************/ +static void applyBalChamberSwitchingPeriod( F32 tdDialysateFlowrate ) +{ + U32 initialPumpSpeed; - // finish the balancing chamber fill 50 ms prior completing the regular cycle time. - balChamberSwitchingPeriod -= 1; + // update the balancing chamber switching frequency + balChamberSwitchingFreq.data = tdDialysateFlowrate / BAL_CHAMBER_FILL_VOLUME_ML; - //Update last td dialysate flow rate - lastTdDialysateFlowrate = tdDialysateFlowrate; + //update the switching period in task interval for balancing chamber fill timeout check + balChamberSwitchingPeriod = (U32)( (F32)SEC_PER_MIN / getBalChamberSwitchingFreq() * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ); - // Update fill timeout count based on the switching period (e.g. 250% of period) - balChamberFillTimeoutCount = (U32)( (F32)balChamberSwitchingPeriod * BAL_CHAMBER_FILL_TIMEOUT_FACTOR ); + // finish the balancing chamber fill 50 ms prior completing the regular cycle time. + balChamberSwitchingPeriod -= 1; - //Reset the BC switching flag for new Qd. - isFirstCycleBCSwitchingCompleted = FALSE; + //Update last td dialysate flow rate + lastTdDialysateFlowrate = tdDialysateFlowrate; - //Update heater control on dialysate flow change - signalHeaterControlOnQDUpdate( D5_HEAT ); + // Update fill timeout count based on the switching period (e.g. 250% of period) + balChamberFillTimeoutCount = (U32)( (F32)balChamberSwitchingPeriod * BAL_CHAMBER_FILL_TIMEOUT_FACTOR ); - //Testing - balChamberValveClosePeriod = balChamberSwitchingPeriod; - balChamberValveClosePeriod -= 1; // Close valves prior 50 msecond for testing + //Reset the BC switching flag for new Qd. + isFirstCycleBCSwitchingCompleted = FALSE; - currentBalChamberFillCounter = 0; - currentBalChamberSwitchingCounter = 0; - spentFillRiseHitCount = 0; - spentFillRiseMissCounter = 0; - isSpentFillComplete = FALSE; - spentDialPressure = getFilteredPressure( D51_PRES ); - lastPrevSpentDialPressure = spentDialPressure; - prevSpentDialPressure = spentDialPressure; + //Update heater control on dialysate flow change + signalHeaterControlOnQDUpdate( D5_HEAT ); + + //Testing + balChamberValveClosePeriod = balChamberSwitchingPeriod; + balChamberValveClosePeriod -= 1; // Close valves prior 50 msecond for testing + + currentBalChamberFillCounter = 0; + currentBalChamberSwitchingCounter = 0; + spentFillRiseHitCount = 0; + spentFillRiseMissCounter = 0; + isSpentFillComplete = FALSE; + spentDialPressure = getFilteredPressure( D51_PRES ); + lastPrevSpentDialPressure = spentDialPressure; + prevSpentDialPressure = spentDialPressure; + + initialPumpSpeed = getCalculatedD48PumpSpeedForBCFill(); + setD48PumpSpeedForBCFill( initialPumpSpeed ); + + if ( FALSE == getBalChamberSwitchingOnlyStatus() ) + { + setDialysatePumpTargetRPM( D48_PUMP, initialPumpSpeed, TRUE ); } + + pendingTdDialysateFlowrate = 0.0F; + isBalChamberSwitchingPeriodUpdatePending = FALSE; } /*********************************************************************//** * @brief + * The applyPendingBalChamberSwitchingPeriod function applies a pending Qd + * update after concentrate dosing completes (transition into VALVES_CLOSE). + * @details \b Inputs: pendingTdDialysateFlowrate + * @details \b Outputs: balChamberSwitchingFreq,balChamberSwitchingPeriod + * @return none + *************************************************************************/ +static void applyPendingBalChamberSwitchingPeriod( void ) +{ + if ( TRUE == isBalChamberSwitchingPeriodUpdatePending ) + { + applyBalChamberSwitchingPeriod( pendingTdDialysateFlowrate ); + } +} + +/*********************************************************************//** + * @brief + * The scheduleFirstCycleRelaxAfterQdApply function configures how many BC + * half-switches still use first-cycle relaxations after a Qd timing update. + * @details Require two valve-close halves under the new timing before clearing + * first-cycle relaxations (one full switching cycle from a dosing boundary). + * @return none + *************************************************************************/ +static void scheduleFirstCycleRelaxAfterQdApply( void ) +{ + firstCycleRelaxHalfSwitchesRemaining = 2U; +} + +/*********************************************************************//** + * @brief + * The isBalChamberTimeBasedSwitching function checks whether the nominal + * (pre-clamp) D48 starting speed should use fixed-speed, time-based balancing + * chamber switching. + * @details \b Inputs: nominal ceiled D48 speed + * @details \b Outputs: none + * @return TRUE if time-based switching should be used; otherwise FALSE. + *************************************************************************/ +static BOOL isBalChamberTimeBasedSwitching( void ) +{ + BOOL state = FALSE; + state = ( getNominalD48PumpSpeedCeiledForBCFill() <= BAL_CHAMBER_TIME_BASED_D48_SPEED_RPM ) ? TRUE : FALSE; + + return state; +} + +/*********************************************************************//** + * @brief * The execBalancingChamberControl function executes the balancing chamber state machine. * @details \b Inputs: balChamberExecState * @details \b Outputs: balChamberExecState @@ -611,6 +707,7 @@ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberConcentrateControl( void ) { BAL_CHAMBER_EXEC_STATE_T state; + BOOL hadPendingQdUpdate; freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); @@ -640,7 +737,15 @@ ( TRUE == isConcentratePumpDosingCompleted( D10_PUMP ) ) ) || ( TRUE == getBalChamberSwitchingOnlyStatus() ) ) { + hadPendingQdUpdate = isBalChamberSwitchingPeriodUpdatePending; + applyPendingBalChamberSwitchingPeriod(); + + if ( TRUE == hadPendingQdUpdate ) + { + scheduleFirstCycleRelaxAfterQdApply(); + } + if ( BAL_CHAMBER_SW_STATE1 == balChamberSWState ) { // Low-Qd spent slope: baseline = spent pressure on last dosing tick (this task period) before VALVES_CLOSE. @@ -671,6 +776,7 @@ BOOL isBothFillsComplete = FALSE; BOOL isFirstCycleNotDone = FALSE; BOOL isFillCompleteOrFirstCycle = FALSE; + BOOL isTimeBasedSwitching = isBalChamberTimeBasedSwitching(); BOOL isFillTimeoutEnabled = ( ( TRUE == isFirstCycleBCSwitchingCompleted ) && ( FALSE == getBalChamberSwitchingOnlyStatus() ) ) ? TRUE : FALSE; @@ -695,9 +801,22 @@ isPressureStabilizedDuringFill = TRUE; } } + else if ( ( balChamberValveClosePeriod > 0 ) && ( currentBalChamberSwitchingCounter >= balChamberValveClosePeriod ) ) + { + // If pressure does not stabilize by the expected close time, allow the cycle to complete. + isPressureStabilizedDuringFill = TRUE; + } - // Spent side of balancing chamber fill is complete or not - checkSpentFillComplete( spentDialPressure ); + if ( ( TRUE == isTimeBasedSwitching ) && ( currentBalChamberSwitchingCounter >= balChamberValveClosePeriod ) ) + { + isPressureStabilizedDuringFill = TRUE; + isSpentFillComplete = TRUE; + } + else + { + // Spent side of balancing chamber fill is complete or not + checkSpentFillComplete( spentDialPressure ); + } // Check both spent and fresh side fill is complete isBothFillsComplete = ( ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ) ) ? TRUE : FALSE; @@ -719,6 +838,16 @@ setDialysatePumpTargetRPM( D48_PUMP, getD48PumpSpeedForBCFill(), TRUE ); } + if ( firstCycleRelaxHalfSwitchesRemaining > 0U ) + { + firstCycleRelaxHalfSwitchesRemaining -= 1U; + } + + if ( ( FALSE == isFirstCycleBCSwitchingCompleted ) && ( 0U == firstCycleRelaxHalfSwitchesRemaining ) ) + { + isFirstCycleBCSwitchingCompleted = TRUE; + } + // Transition to next state state = BAL_CHAMBER_STATE1_FILL_END; } @@ -750,8 +879,12 @@ if ( TRUE != getBalChamberSwitchingOnlyStatus() ) { - if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) + if ( TRUE == isBalChamberTimeBasedSwitching() ) { + state = BAL_CHAMBER_STATE2_FILL_START; + } + else if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) + { if ( ( TRUE != isPressureDroppedDuringFill ) && ( TRUE == isFirstCycleBCSwitchingCompleted ) ) { // When fill initiated, pressure is not dropped to the expected range, possible valve failures. @@ -923,6 +1056,7 @@ BOOL isBothFillsComplete = FALSE; BOOL isFirstCycleNotDone = FALSE; BOOL isFillCompleteOrFirstCycle = FALSE; + BOOL isTimeBasedSwitching = isBalChamberTimeBasedSwitching(); BOOL isFillTimeoutEnabled = ( ( TRUE == isFirstCycleBCSwitchingCompleted ) && ( FALSE == getBalChamberSwitchingOnlyStatus() ) ) ? TRUE : FALSE; @@ -938,6 +1072,11 @@ isPressureStabilizedDuringFill = TRUE; } } + else if ( ( balChamberValveClosePeriod > 0 ) && ( currentBalChamberSwitchingCounter >= balChamberValveClosePeriod ) ) + { + // If pressure does not stabilize by the expected close time, allow the cycle to complete. + isPressureStabilizedDuringFill = TRUE; + } // After the first BC cycle, fault if fill never completes at the Qd-based timeout. if ( ( TRUE == isFillTimeoutEnabled ) && ( balChamberFillTimeoutCount > 0 ) && ( currentBalChamberFillCounter > balChamberFillTimeoutCount ) ) @@ -948,8 +1087,16 @@ state = BAL_CHAMBER_STATE_IDLE; } - // Spent side of balancing chamber fill is complete or not - checkSpentFillComplete( spentDialPressure ); + if ( ( TRUE == isTimeBasedSwitching ) && ( currentBalChamberSwitchingCounter >= balChamberValveClosePeriod ) ) + { + isPressureStabilizedDuringFill = TRUE; + isSpentFillComplete = TRUE; + } + else + { + // Spent side of balancing chamber fill is complete or not + checkSpentFillComplete( spentDialPressure ); + } // Check switching cycle time or pressure check for valve closure isBothFillsComplete = ( ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ) ) ? TRUE : FALSE; @@ -971,6 +1118,16 @@ setDialysatePumpTargetRPM( D48_PUMP, getD48PumpSpeedForBCFill(), TRUE ); } + if ( firstCycleRelaxHalfSwitchesRemaining > 0U ) + { + firstCycleRelaxHalfSwitchesRemaining -= 1U; + } + + if ( ( FALSE == isFirstCycleBCSwitchingCompleted ) && ( 0U == firstCycleRelaxHalfSwitchesRemaining ) ) + { + isFirstCycleBCSwitchingCompleted = TRUE; + } + //Transition to next state state = BAL_CHAMBER_STATE2_FILL_END; } @@ -1003,8 +1160,12 @@ // Pressure alarm check if ( TRUE != getBalChamberSwitchingOnlyStatus() ) { - if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) + if ( TRUE == isBalChamberTimeBasedSwitching() ) { + state = BAL_CHAMBER_STATE1_FILL_START; + } + else if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) + { if ( ( TRUE != isPressureDroppedDuringFill ) && ( TRUE == isFirstCycleBCSwitchingCompleted ) ) { // When fill initiated, pressure is not dropped to the expected range, possible valve failures. @@ -1042,12 +1203,6 @@ } } - // Trigger pressure drop alarm after first cycle of balancing chamber switching complete - if ( FALSE == isFirstCycleBCSwitchingCompleted ) - { - isFirstCycleBCSwitchingCompleted = TRUE; - } - // Clear the switching only on request flag. balanceChamberSwitchingOnlyOnRequested = FALSE; @@ -1230,68 +1385,79 @@ U32 minD48Speed = initialD48PumpSpeed - ( initialD48PumpSpeed * D48_SPEED_RANGE_LIMIT ); U32 maxD48Speed = initialD48PumpSpeed + ( initialD48PumpSpeed * D48_SPEED_RANGE_LIMIT ); U32 d48SpeedPostRangeCheck = 0; - F32 qdMlpm = getTDDialysateFlowrate(); + F32 qdMlpm = getBalChamberActiveDialysateFlowrate(); BOOL result = FALSE; F32 spentFillCompletePresPsig; BOOL isSpentFillCompleteDetected = FALSE; - // 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 ) ) + if ( TRUE == isBalChamberTimeBasedSwitching() ) { - spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES; + if ( currentBalChamberSwitchingCounter >= balChamberValveClosePeriod ) + { + isSpentFillComplete = TRUE; + } } else { - if ( qdMlpm <= QD_THRESHOLD_HIGH_MLPM ) + // 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_QD_MID_PSIG; + spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES; } else { - spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_HIGH_PSIG; + if ( qdMlpm <= QD_THRESHOLD_HIGH_MLPM ) + { + spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_MID_PSIG; + } + else + { + spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_HIGH_PSIG; + } } - } - isSpentFillCompleteDetected = isSpentFillCompleteByPressure( spentDialPressure, qdMlpm, spentFillCompletePresPsig ); + isSpentFillCompleteDetected = isSpentFillCompleteByPressure( spentDialPressure, qdMlpm, spentFillCompletePresPsig ); - if ( ( TRUE == isSpentFillCompleteDetected ) && ( isSpentFillComplete != TRUE ) ) - { - // Check spent fill time against the balancing chamber closing period - if ( balChamberValveClosePeriod > 0 ) + if ( ( TRUE == isSpentFillCompleteDetected ) && ( isSpentFillComplete != TRUE ) ) { - diffSpentFillCompleteCount = balChamberValveClosePeriod - currentBalChamberFillCounter; - absDiffSpentFillCount = abs(diffSpentFillCompleteCount); - adjustedSpeed = ( ( (F32)absDiffSpentFillCount / (F32)balChamberValveClosePeriod ) * spentDialPumpSpeed ) * D48_SPEED_ADJUST_FACTOR; + // Check spent fill time against the balancing chamber closing period + if ( balChamberValveClosePeriod > 0 ) + { + diffSpentFillCompleteCount = balChamberValveClosePeriod - currentBalChamberFillCounter; + absDiffSpentFillCount = abs(diffSpentFillCompleteCount); + adjustedSpeed = ( ( (F32)absDiffSpentFillCount / (F32)balChamberValveClosePeriod ) * spentDialPumpSpeed ) * D48_SPEED_ADJUST_FACTOR; - // Skip D48 trim in deadband: diff 0..-2 counts (0..100 ms at 50 ms/task); positive band only (larger negative may mean under-fill). - result = ( ( diffSpentFillCompleteCount <= 0 ) && ( diffSpentFillCompleteCount >= -2 ) ) ? TRUE : FALSE; + // Skip D48 trim in deadband: diff 0..-2 counts (0..100 ms at 50 ms/task); positive band only (larger negative may mean under-fill). + result = ( ( diffSpentFillCompleteCount <= 0 ) && ( diffSpentFillCompleteCount >= -2 ) ) ? TRUE : FALSE; - if ( FALSE == result ) - { - if ( diffSpentFillCompleteCount < SPENT_DIFF_COUNT_ZERO ) + if ( FALSE == result ) { - // Increase the D48 pump speed - spentDialPumpSpeed += adjustedSpeed; - } - else - { - //Decrease the D48 pump speed - spentDialPumpSpeed -= adjustedSpeed; - } + if ( diffSpentFillCompleteCount < SPENT_DIFF_COUNT_ZERO ) + { + // Increase the D48 pump speed + spentDialPumpSpeed += adjustedSpeed; + } + else + { + //Decrease the D48 pump speed + spentDialPumpSpeed -= adjustedSpeed; + } - d48SpeedPostRangeCheck = RANGE( spentDialPumpSpeed, minD48Speed, maxD48Speed ); + d48SpeedPostRangeCheck = RANGE( spentDialPumpSpeed, minD48Speed, maxD48Speed ); - // Do not turn on the pump if the switching only is enabled in the standby mode. - if ( FALSE == getBalChamberSwitchingOnlyStatus() ) - { - // Update the D48 pump speed - setD48PumpSpeedForBCFill( d48SpeedPostRangeCheck ); + + // Do not turn on the pump if the switching only is enabled in the standby mode. + if ( FALSE == getBalChamberSwitchingOnlyStatus() ) + { + // Update the D48 pump speed + setD48PumpSpeedForBCFill( d48SpeedPostRangeCheck ); + } } } - } - //Update spent fill is complete - isSpentFillComplete = TRUE; + //Update spent fill is complete + isSpentFillComplete = TRUE; + } } } @@ -1343,6 +1509,19 @@ /*********************************************************************//** * @brief + * The getBalChamberActiveDialysateFlowrate function gets the Qd currently + * applied to balancing chamber timing. + * @details \b Inputs: lastTdDialysateFlowrate + * @details \b Outputs: none + * @return active balancing chamber dialysate flow rate + *************************************************************************/ +F32 getBalChamberActiveDialysateFlowrate( void ) +{ + return lastTdDialysateFlowrate; +} + +/*********************************************************************//** + * @brief * The setBalChamberSwitchingOnlyStatus function sets the balancing chamber * switching only On/Off status. * @details \b Inputs: balanceChamberSwitchingOnly