Index: firmware/App/Controllers/Heaters.c =================================================================== diff -u -r3efd50bef6d3618b9fed1727aa2bc234ededee2a -r59357d3831aa60f17ccdfbe0eef1a005935b9a58 --- firmware/App/Controllers/Heaters.c (.../Heaters.c) (revision 3efd50bef6d3618b9fed1727aa2bc234ededee2a) +++ firmware/App/Controllers/Heaters.c (.../Heaters.c) (revision 59357d3831aa60f17ccdfbe0eef1a005935b9a58) @@ -7,8 +7,8 @@ * * @file Heaters.c * -* @author (last) Michael Garthwaite -* @date (last) 01-Mar-2023 +* @author (last) Dara Navaei +* @date (last) 20-May-2023 * * @author (original) Dara Navaei * @date (original) 23-Apr-2020 @@ -81,7 +81,6 @@ static const F32 WATER_SPECIFIC_HEAT_DIVIDED_BY_MINUTES = 4184 / SEC_PER_MIN; ///< Water specific heat in J/KgC / 60. static const F32 PRIMARY_HEATERS_MAXIMUM_POWER_WATTS = 475 + 237.5F; ///< Primary heaters maximum power (main primary = 475W and small primary = 237.5W). static const F32 HEATERS_VOLTAGE_TOLERANCE_V = HEATERS_MAX_OPERATING_VOLTAGE_V * HEATERS_MAX_VOLTAGE_OUT_OF_RANGE_TOL; ///< Heaters voltage tolerance in volts. -static const F32 PRIMARY_HEATER_VOLTAGE_TOLERANCE_V = HEATERS_MAX_OPERATING_VOLTAGE_V * PRIMARY_HEATER_POWER_TOL; ///< Primary heater power voltage tolerance in volts. /// Heaters data structure typedef struct @@ -100,6 +99,7 @@ U32 controlIntervalCounter; ///< Heater control interval counter. BOOL isThisFirstControl; ///< Heater is this first control interval. BOOL useLastDutyCycle; ///< Heater has use previous duty cycle been requested flag. + F32 prevDiaTargetFlowLPM; ///< Heater previous target dialysate flow in L/min. } HEATER_STATUS_T; static HEATER_STATUS_T heatersStatus[ NUM_OF_DG_HEATERS ]; ///< Heaters status. @@ -115,7 +115,7 @@ static HEATERS_STATE_T handleHeaterStateTrimmerRampToTarget( void ); static HEATERS_STATE_T handleHeaterStateTrimmerControlToTarget( void ); -static void setHeaterDutyCycle( DG_HEATERS_T heater, F32 pwm ); +static void setHeaterDutyCycle( DG_HEATERS_T heater ); static F32 calculatePrimaryHeaterDutyCycle( F32 targetTemperature, F32 currentTemperature, F32 flow, BOOL checkEfficiency ); static F32 calculateTrimmerHeaterDutyCycle( F32 targetTemperature, F32 currentTemperature, F32 flow, BOOL checkEfficiency ); static BOOL haveHeaterControlConditionsChanged( DG_HEATERS_T heater ); @@ -157,15 +157,15 @@ heatersStatus[ heater ].controlIntervalCounter = 0; heatersStatus[ heater ].isThisFirstControl = TRUE; heatersStatus[ heater ].useLastDutyCycle = FALSE; + heatersStatus[ heater ].prevDiaTargetFlowLPM = 0.0F; } // Initialize the persistent alarms initPersistentAlarm( ALARM_ID_DG_MAIN_PRIMARY_HEATER_VOLTAGE_OUT_OF_RANGE, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); initPersistentAlarm( ALARM_ID_DG_SMALL_PRIMARY_HEATER_VOLTAGE_OUT_OF_RANGE, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); initPersistentAlarm( ALARM_ID_DG_TRIMMER_HEATER_VOLTAGE_OUT_OF_RANGE, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); - initPersistentAlarm( ALARM_ID_DG_PRIMARY_HEATER_POWER_VOLTAGE_OUT_OF_RANGE, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); - initPersistentAlarm( ALARM_ID_RO_FLOW_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON, HEATERS_ON_NO_FLOW_TIMEOUT_MS, HEATERS_ON_NO_FLOW_TIMEOUT_MS ); - initPersistentAlarm( ALARM_ID_DIALYSATE_FLOW_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON, HEATERS_ON_NO_FLOW_TIMEOUT_MS, HEATERS_ON_NO_FLOW_TIMEOUT_MS ); + initPersistentAlarm( ALARM_ID_DG_RO_FLOW_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON, HEATERS_ON_NO_FLOW_TIMEOUT_MS, HEATERS_ON_NO_FLOW_TIMEOUT_MS ); + initPersistentAlarm( ALARM_ID_DG_DIALYSATE_FLOW_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON, HEATERS_ON_NO_FLOW_TIMEOUT_MS, HEATERS_ON_NO_FLOW_TIMEOUT_MS ); // Initialize the FPGA persistent alarm initFPGAPersistentAlarm( FPGA_PERS_ERROR_MAIN_PRIMARY_HEATER_VOLTAGE_ADC, ALARM_ID_DG_MAIN_PRIMARY_HEATER_FPGA_FAULT, @@ -341,7 +341,8 @@ // Check if the heater is requested to be off if ( FALSE == heatersStatus[ heater ].isHeaterOn ) { - setHeaterDutyCycle( heater, HEATERS_MIN_DUTY_CYCLE ); + heatersStatus[ heater ].dutyCycle.data = HEATERS_MIN_DUTY_CYCLE; + setHeaterDutyCycle( heater ); heatersStatus[ heater ].state = HEATER_EXEC_STATE_OFF; } } @@ -373,14 +374,14 @@ if ( DG_PRIMARY_HEATER == heater ) { - alarm = ALARM_ID_RO_FLOW_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON; + alarm = ALARM_ID_DG_RO_FLOW_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON; measFlow = getMeasuredFlowRateLPM( RO_FLOW_SENSOR ); minFlow = MIN_RO_HEATER_FLOWRATE_LPM; isFlowLow = ( measFlow < minFlow ? TRUE : FALSE ); } else { - alarm = ALARM_ID_DIALYSATE_FLOW_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON; + alarm = ALARM_ID_DG_DIALYSATE_FLOW_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON; measFlow = getMeasuredRawFlowRateLPM( DIALYSATE_FLOW_SENSOR ); minFlow = TRIMMER_HEATER_MIN_DIALYSATE_FLOWRATE_LPM; isFlowLow = ( measFlow > minFlow ? FALSE : TRUE ); @@ -392,11 +393,11 @@ { if ( DG_PRIMARY_HEATER == heater ) { - checkPersistentAlarm( ALARM_ID_RO_FLOW_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON, FALSE, 0.0F, 0.0F ); + checkPersistentAlarm( ALARM_ID_DG_RO_FLOW_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON, FALSE, 0.0F, 0.0F ); } else { - checkPersistentAlarm( ALARM_ID_DIALYSATE_FLOW_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON, FALSE, 0.0F, 0.0F ); + checkPersistentAlarm( ALARM_ID_DG_DIALYSATE_FLOW_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON, FALSE, 0.0F, 0.0F ); } } } @@ -560,7 +561,8 @@ heatersStatus[ DG_PRIMARY_HEATER ].calculatedTemperatureC = targetTemperature; heatersStatus[ DG_PRIMARY_HEATER ].targetFlowLPM = targetFlowLPM; heatersStatus[ DG_PRIMARY_HEATER ].nomTargetFlowLPM = getTargetROPumpFlowRateLPM(); - setHeaterDutyCycle( heater, dutyCycle ); + heatersStatus[ DG_PRIMARY_HEATER ].dutyCycle.data = dutyCycle; + setHeaterDutyCycle( heater ); return state; } @@ -584,15 +586,16 @@ } else if ( TRUE == heatersStatus[ heater ].hasTargetTempChanged ) { - F32 inletTemperature = getTemperatureValue( (U32)TEMPSENSORS_HEAT_DISINFECT ); + F32 inletTemperature = getTemperatureValue( TEMPSENSORS_HEAT_DISINFECT ); F32 targetTemperature = heatersStatus[ heater ].targetTempC; F32 targetFlow = getTargetROPumpFlowRateLPM(); F32 dutyCycle = calculatePrimaryHeaterDutyCycle( targetTemperature, inletTemperature, targetFlow, TRUE ); heatersStatus[ heater ].calculatedTemperatureC = targetTemperature; heatersStatus[ heater ].targetFlowLPM = targetFlow; heatersStatus[ heater ].hasTargetTempChanged = FALSE; - setHeaterDutyCycle( heater, dutyCycle ); + heatersStatus[ heater ].dutyCycle.data = dutyCycle; + setHeaterDutyCycle( heater ); } return state; @@ -616,18 +619,24 @@ if ( currentTemperature < targetTemperature ) { - setHeaterDutyCycle( DG_TRIMMER_HEATER, HEATERS_MAX_DUTY_CYCLE ); - setHeaterDutyCycle( DG_PRIMARY_HEATER, HEATERS_MAX_DUTY_CYCLE ); + heatersStatus[ DG_TRIMMER_HEATER ].dutyCycle.data = HEATERS_MAX_DUTY_CYCLE; + heatersStatus[ DG_PRIMARY_HEATER ].dutyCycle.data = HEATERS_MAX_DUTY_CYCLE; + setHeaterDutyCycle( DG_TRIMMER_HEATER ); + setHeaterDutyCycle( DG_PRIMARY_HEATER ); } else if ( currentTemperature - targetTemperature < HEATERS_DISINFECT_TEMPERATURE_DRIFT_C ) { - setHeaterDutyCycle( DG_TRIMMER_HEATER, HEATERS_DISINFECT_DUTY_CYCLE ); - setHeaterDutyCycle( DG_PRIMARY_HEATER, HEATERS_DISINFECT_DUTY_CYCLE ); + heatersStatus[ DG_TRIMMER_HEATER ].dutyCycle.data = HEATERS_DISINFECT_DUTY_CYCLE; + heatersStatus[ DG_PRIMARY_HEATER ].dutyCycle.data = HEATERS_DISINFECT_DUTY_CYCLE; + setHeaterDutyCycle( DG_TRIMMER_HEATER ); + setHeaterDutyCycle( DG_PRIMARY_HEATER ); } else { - setHeaterDutyCycle( DG_TRIMMER_HEATER, HEATERS_MIN_DUTY_CYCLE ); - setHeaterDutyCycle( DG_PRIMARY_HEATER, HEATERS_MIN_DUTY_CYCLE ); + heatersStatus[ DG_TRIMMER_HEATER ].dutyCycle.data = HEATERS_MIN_DUTY_CYCLE; + heatersStatus[ DG_PRIMARY_HEATER ].dutyCycle.data = HEATERS_MIN_DUTY_CYCLE; + setHeaterDutyCycle( DG_TRIMMER_HEATER ); + setHeaterDutyCycle( DG_PRIMARY_HEATER ); } return state; @@ -670,30 +679,29 @@ // If the mode is heat disinfect, use the target flow rate instead of the avg. flow // Most of the times the heater should be running at 100% duty cycle since the target temperature is 81 C and // it is far from the inlet temperature. - currentTemperature = getTemperatureValue( (U32)TEMPSENSORS_HEAT_DISINFECT ); + currentTemperature = getTemperatureValue( TEMPSENSORS_HEAT_DISINFECT ); targetFlowLPM = getTargetROPumpFlowRateLPM(); dutyCycle = calculateTrimmerHeaterDutyCycle( targetTemperature, currentTemperature, targetFlowLPM, FALSE ); state = HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET; } else { // If not any of the above modes, just calculate the energy equation based on TRo - currentTemperature = getTemperatureValue( (U32)TEMPSENSORS_OUTLET_REDUNDANT ); + currentTemperature = getTemperatureValue( TEMPSENSORS_OUTLET_REDUNDANT ); dutyCycle = calculateTrimmerHeaterDutyCycle( targetTemperature, currentTemperature, targetFlowLPM, TRUE ); state = HEATER_EXEC_STATE_TRIMMER_CONTROL_TO_TARGET; } // Update the calculated target temperature // Reset the duty cycle since the reservoir has been switched + // Cap the minimum duty cycle. So if it is calculated to negative, set it to 0 heatersStatus[ heater ].calculatedTemperatureC = currentTemperature; heatersStatus[ heater ].inactiveRsrvr = getInactiveReservoir(); heatersStatus[ heater ].targetFlowLPM = targetFlowLPM; heatersStatus[ heater ].isThisFirstControl = TRUE; + heatersStatus[ heater ].dutyCycle.data = MAX( dutyCycle, HEATERS_MIN_DUTY_CYCLE ); + setHeaterDutyCycle( heater ); - // Cap the minimum duty cycle. So if it is calculated to negative, set it to 0 - dutyCycle = MAX( dutyCycle, HEATERS_MIN_DUTY_CYCLE ); - setHeaterDutyCycle( heater, dutyCycle ); - return state; } @@ -710,35 +718,50 @@ HEATERS_STATE_T state = HEATER_EXEC_STATE_TRIMMER_CONTROL_TO_TARGET; F32 tempDutyCycle = 0.0F; DG_HEATERS_T heater = DG_TRIMMER_HEATER; + F32 targetFlowLPM = getTargetDialysateFlowLPM(); U32 controlInterval = ( TRUE == heatersStatus[ heater ].isThisFirstControl ? TRIMMER_HEATER_INITIAL_CONTROL_INTERVAL_COUNT : TRIMMER_HEATER_CONTROL_INTERVAL_COUNT ); - // If the inactive reservoir has changed from the last run transition to ramp state to recalculate the + // If the inactive reservoir has changed from the last run, transition to ramp state to recalculate the // duty cycle for the next delivery if ( heatersStatus[ heater ].inactiveRsrvr != getInactiveReservoir() ) { state = HEATER_EXEC_STATE_TRIMMER_RAMP_TO_TARGET; } - else if ( ++heatersStatus[ heater ].controlIntervalCounter > controlInterval ) + else if ( ( ++heatersStatus[ heater ].controlIntervalCounter > controlInterval ) || + ( fabs( heatersStatus[ heater ].prevDiaTargetFlowLPM - targetFlowLPM ) > NEARLY_ZERO ) ) { - // Reset the control counter + // Check if it is time for another control interval or the current target flow is different from the previous flow and + // we need to reset the control counter heatersStatus[ heater ].controlIntervalCounter = 0; heatersStatus[ heater ].isThisFirstControl = FALSE; // When the trimmer heater is on, its duty cycle is adjusted at the control interval. For this control check, // dialysate inlet temperature sensor is used rather than the theoretical calculations. - F32 outletRedundantTemperature = getTemperatureValue( TEMPSENSORS_OUTLET_REDUNDANT ); - F32 targetTemperature = heatersStatus[ heater ].targetTempC; - F32 targetFlowLPM = heatersStatus[ heater ].targetFlowLPM; - F32 dutyCycle = calculateTrimmerHeaterDutyCycle( targetTemperature, outletRedundantTemperature, targetFlowLPM, TRUE ); + F32 dialysateInletTemperature = getTemperatureValue( TEMPSENSORS_OUTLET_REDUNDANT ); + F32 targetTemperature = heatersStatus[ heater ].targetTempC; + F32 dutyCycle = calculateTrimmerHeaterDutyCycle( targetTemperature, dialysateInletTemperature, targetFlowLPM, TRUE ); - tempDutyCycle = heatersStatus[ heater ].dutyCycle.data + dutyCycle; + if ( fabs( heatersStatus[ heater ].prevDiaTargetFlowLPM - targetFlowLPM ) > NEARLY_ZERO ) + { + // If the current flow is different from the previous flow, restart the duty cycle as the controller is restarting itself + // Set it as it is the first control so the control interval is shorter since it is the first guess with the new dialysate flow + tempDutyCycle = dutyCycle; + heatersStatus[ heater ].isThisFirstControl = TRUE; + } + else + { + tempDutyCycle = heatersStatus[ heater ].dutyCycle.data + dutyCycle; + } + tempDutyCycle = MIN( tempDutyCycle, HEATERS_MAX_DUTY_CYCLE ); tempDutyCycle = MAX( tempDutyCycle, HEATERS_MIN_DUTY_CYCLE ); - - setHeaterDutyCycle( heater, tempDutyCycle ); + heatersStatus[ heater ].dutyCycle.data = tempDutyCycle; + setHeaterDutyCycle( heater ); } + heatersStatus[ heater ].prevDiaTargetFlowLPM = targetFlowLPM; + return state; } @@ -751,14 +774,13 @@ * @param pwm: The PWM that is set * @return none *************************************************************************/ -static void setHeaterDutyCycle( DG_HEATERS_T heater, F32 pwm ) +static void setHeaterDutyCycle( DG_HEATERS_T heater ) { if ( heater < NUM_OF_DG_HEATERS ) { F32 duty; - heatersStatus[ heater ].dutyCycle.data = pwm; - duty = getHeaterDutyCycle( heater ); + duty = getHeaterDutyCycle( heater ); if ( DG_PRIMARY_HEATER == heater ) { @@ -968,6 +990,8 @@ data.primaryCalcTargetTemp = heatersStatus[ DG_PRIMARY_HEATER ].calculatedTemperatureC; data.trimmerCalcCurrentTemp = heatersStatus[ DG_TRIMMER_HEATER ].calculatedTemperatureC; data.trimmerUseLastDC = (U32)heatersStatus[ DG_TRIMMER_HEATER ].useLastDutyCycle; + data.previsouFlow = heatersStatus[ DG_TRIMMER_HEATER ].prevDiaTargetFlowLPM; + data.controlCounter = heatersStatus[ DG_TRIMMER_HEATER ].controlIntervalCounter; dataPublicationTimerCounter = 0; broadcastData( MSG_ID_DG_HEATERS_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( HEATERS_DATA_T ) ); @@ -1010,9 +1034,8 @@ else #endif { - // TODO this is not working on DVT-002 - // If the system is DVT, check the FPGA persistent alarm of the main primary heater's voltage ADC - //checkFPGAPersistentAlarms( FPGA_PERS_ERROR_MAIN_PRIMARY_HEATER_VOLTAGE_ADC, getFPGAHeaterGateADCErrorCount(), getFPGAHeaterGateADCReadCount() ); + // If the system is DVT, check the FPGA persistent alarm of the main primary heater's voltage ADC + checkFPGAPersistentAlarms( FPGA_PERS_ERROR_MAIN_PRIMARY_HEATER_VOLTAGE_ADC, getFPGAHeaterGateADCReadCount() ); } isMainPriOut = ( fabs( mainPriExpectedVoltage - mainPriVoltage ) > HEATERS_VOLTAGE_TOLERANCE_V ? TRUE : FALSE ); @@ -1024,17 +1047,6 @@ checkPersistentAlarm( ALARM_ID_DG_MAIN_PRIMARY_HEATER_VOLTAGE_OUT_OF_RANGE, isMainPriOut, mainPriDC, HEATERS_VOLTAGE_TOLERANCE_V ); checkPersistentAlarm( ALARM_ID_DG_SMALL_PRIMARY_HEATER_VOLTAGE_OUT_OF_RANGE, isSmallPriOut, smallPriDC, HEATERS_VOLTAGE_TOLERANCE_V ); checkPersistentAlarm( ALARM_ID_DG_TRIMMER_HEATER_VOLTAGE_OUT_OF_RANGE, isTrimmerOut, trimmerDC, HEATERS_VOLTAGE_TOLERANCE_V ); - -#ifndef _RELEASE_ - if ( getHardwareConfigStatus() != HW_CONFIG_BETA ) -#endif - { - // If the system is DVT, check the primary heater's power line voltage - F32 powerMainPriVoltage = getMonitoredLineLevel( MONITORED_LINE_24V_POWER_PRIM_HTR_V ); - BOOL isPriPowerOut = ( fabs( powerMainPriVoltage - HEATERS_MAX_OPERATING_VOLTAGE_V ) > PRIMARY_HEATER_VOLTAGE_TOLERANCE_V ? TRUE : FALSE ); - - checkPersistentAlarm( ALARM_ID_DG_PRIMARY_HEATER_POWER_VOLTAGE_OUT_OF_RANGE, isPriPowerOut, powerMainPriVoltage, PRIMARY_HEATER_VOLTAGE_TOLERANCE_V ); - } } } @@ -1066,7 +1078,7 @@ if ( TRUE == heatersStatus[ (DG_HEATERS_T)heater ].isHeaterOn ) { - setHeaterDutyCycle( (DG_HEATERS_T)heater, value ); + setHeaterDutyCycle( (DG_HEATERS_T)heater ); } } } @@ -1091,6 +1103,8 @@ result = TRUE; heatersStatus[ (DG_HEATERS_T)heater ].dutyCycle.override = OVERRIDE_RESET; heatersStatus[ (DG_HEATERS_T)heater ].dutyCycle.ovData = heatersStatus[ (DG_HEATERS_T)heater ].dutyCycle.ovInitData; + setHeaterDutyCycle( (DG_HEATERS_T)heater ); + } return result;