Index: firmware/App/Controllers/Heaters.c =================================================================== diff -u -rf215311725c002c9b9f2915e3502b89ae6a93c91 -r1b95ae077de1d01a314be8cc011662ab508fba10 --- firmware/App/Controllers/Heaters.c (.../Heaters.c) (revision f215311725c002c9b9f2915e3502b89ae6a93c91) +++ firmware/App/Controllers/Heaters.c (.../Heaters.c) (revision 1b95ae077de1d01a314be8cc011662ab508fba10) @@ -43,14 +43,10 @@ // ********** private definitions ********** -#define HEATERS_MAX_DUTY_CYCLE 1.00 ///< Main primary heater (heater A) max duty cycle (100%). -#define HEATERS_MIN_DUTY_CYCLE 0.00 ///< Primary and trimmer heaters minimum duty cycle (0.00%). +#define HEATERS_MAX_DUTY_CYCLE 1.00 ///< Heaters max duty cycle (100%). +#define HEATERS_MIN_DUTY_CYCLE 0.00 ///< Heaters minimum duty cycle (0.00%). +#define HEATERS_MIN_HEAT_DISINFECT_DUTY_CYCLE 0.6 ///< Heaters minimum duty cycle during heat disinfect. -#define PRIMARY_HEATER_P_COEFFICIENT 0.05 ///< Primary heaters proportional coefficient. - -#define TRIMMER_HEATER_P_COEFFICIENT 0.02 // TODO remove ///< Trimmer heater proportional coefficient. -#define TRIMMER_HEATER_I_COEFFICIENT 0.001 // TODO remove ///< Trimmer heater integral coefficient. - #define HEATERS_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_PRIORITY_INTERVAL ) ///< Heaters data publish interval. #define MINIMUM_TARGET_TEMPERATURE 10.0 ///< Minimum allowed target temperature for the heaters. @@ -70,27 +66,29 @@ #define TEMPERATURES_MOVING_AVG_SIZE 3U ///< Heaters ramp state temperatures moving average size. #define DELTA_TEMPERATURE_TIME_COSNTANT_C 8.6 ///< Delta temperature calculated from time constant. +#define MAXIMUM_ALLOWED_TARGET_TEMPERATURE_DEVIATION_C 0.25 ///< Maximum allowed temperature deviation from target temperature in C. +#define PRIMARY_HEATER_DUTY_CYCLE_PER_TEMPERATURE_C 0.03 ///< Primary heaters duty cycle per temperature in C. 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.5; ///< Primary heaters maximum power (main primary = 475W and small primary = 237.5W). +/// Heaters exec states typedef enum Heaters_Exec_States { HEATER_EXEC_STATE_OFF = 0, ///< Heater exec state off. - HEATER_EXEC_STATE_RAMP_TO_TARGET, ///< Heater exec state ramp to target. - HEATER_EXEC_STATE_CONTROL_TO_TARGET, ///< Heater exec state control to target. + HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET, ///< Heater exec state primary ramp to target. + HEATER_EXEC_STATE_PRIMARY_CONTROL_TO_TARGET, ///< Heater exec state primary control to target. HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET, ///< Heater exec state control to disinfect (heat or chemical) target. + HEATER_EXEC_STATE_TRIMMER_RAMP_TO_TARGET, ///< Heater exec state trimmer ramp to target. + HEATER_EXEC_STATE_TRIMMER_CONTROL_TO_TARGET, ///< Heater exec state trimmer control to target. NUM_OF_HEATERS_STATE, ///< Number of heaters state. } HEATERS_STATE_T; /// Heaters data structure typedef struct { - F32 targetTemp; // TODO do we need this anymore? ///< Heater target temperature. - F32 originalTargetTemp; ///< Heater original target temperature set by user. + F32 targetTemp; ///< Heater target temperature. HEATERS_STATE_T state; ///< Heater state. - TEMPERATURE_SENSORS_T feedbackSensor; // TODO do we need this? ///< Heater feedback sensor for controlling. - U32 controlTimerCounter; // TODO remove? Maybe use in heat disinfect ///< Heater control timer counter. BOOL startHeaterSignal; ///< Heater start indication flag. BOOL isHeaterOn; ///< Heater on/off status flag. F32 dutycycle; ///< Heater duty cycle. @@ -101,7 +99,6 @@ F32 heaterEfficiency; ///< Heater efficiency during the run. BOOL hasTargetBeenReached; ///< Heater flag to indicate whether the target temperature has been reached. - PI_CONTROLLER_ID_T controllerID; ///< Heater PI controller ID TODO remove this? U32 tempOutOfRangeTimer; ///< Heater temperature out of range timer TODO remove once the mechanical thermal cutoff was implemented BOOL isHeaterTempOutOfRange; ///< Heater temperature out of range flag indicator TODO remove once the mechanical thermal cutoff was implemented } HEATER_STATUS_T; @@ -115,12 +112,14 @@ // ********** private function prototypes ********** static HEATERS_STATE_T handleHeaterStateOff( DG_HEATERS_T heater ); -static HEATERS_STATE_T handleHeaterStateRampToTarget( DG_HEATERS_T heater ); -static HEATERS_STATE_T handleHeaterStateControlToTarget( DG_HEATERS_T heater ); +static HEATERS_STATE_T handleHeaterStatePrimaryRampToTarget( void ); +static HEATERS_STATE_T handleHeaterStatePrimaryControlToTarget( void ); static HEATERS_STATE_T handleHeaterStateControlToDisinfectTarget( DG_HEATERS_T heater ); +static HEATERS_STATE_T handleHeaterStateTrimmerRampToTarget( void ); +static HEATERS_STATE_T handleHeaterStateTrimmerControlToTarget( void ); static void setHeaterDutyCycle( DG_HEATERS_T heater, F32 pwm ); -static F32 calculatePrimaryHeaterDutyCycle( F32 targetTemperature, F32 currentTemperature, F32 flow ); +static F32 calculatePrimaryHeaterDutyCycle( F32 targetTemperature, F32 currentTemperature, F32 flow, BOOL checkEfficiency ); static BOOL haveHeaterControlConditionsChanged( DG_HEATERS_T heater ); static void setMainPrimaryHeaterPWM( F32 pwm ); @@ -130,8 +129,6 @@ static void checkPrimaryHeaterTempSensors( void ); static void checkTrimmerHeaterTempSensors( void ); static void monitorHeatersVoltage( void ); -static void checkHeaterOnStatus( DG_HEATERS_T heater ); -static void getHeatersEfficiencyFromRTCRAM( void ); /*********************************************************************//** * @brief @@ -146,31 +143,21 @@ DG_HEATERS_T heater; voltageMonitorTimeCounter = 0; - operationMode = 0; + operationMode = 0; for ( heater = DG_PRIMARY_HEATER; heater < NUM_OF_DG_HEATERS; heater++ ) { - heatersStatus[ heater ].controlTimerCounter = 0; - // The default feedback sensor of the primary heater is TPo but it changes to THd in heat disinfect - // The default feedback sensor of the trimmer heater is TDi all the time - heatersStatus[ heater ].feedbackSensor = ( DG_PRIMARY_HEATER == heater ? TEMPSENSORS_OUTLET_PRIMARY_HEATER : TEMPSENSORS_INLET_DIALYSATE ); heatersStatus[ heater ].startHeaterSignal = FALSE; heatersStatus[ heater ].tempOutOfRangeTimer = 0; heatersStatus[ heater ].isHeaterTempOutOfRange = FALSE; heatersStatus[ heater ].state = HEATER_EXEC_STATE_OFF; heatersStatus[ heater ].targetTemp = 0.0; - heatersStatus[ heater ].originalTargetTemp = 0.0; heatersStatus[ heater ].dutycycle = 0.0; heatersStatus[ heater ].targetROFlow = 0.0; - heatersStatus[ heater ].controllerID = ( DG_PRIMARY_HEATER == heater ? 4 : PI_CONTROLLER_ID_TRIMMER_HEATER ); // TODO remove or refactor? heatersStatus[ heater ].hasTargetTempChanged = FALSE; heatersStatus[ heater ].heaterEfficiency = 1.0; // Assuming 100% efficiency during initialization until it is updated heatersStatus[ heater ].hasTargetBeenReached = FALSE; } - - // Initialize the PI controller for the trimmer heater - initializePIController( PI_CONTROLLER_ID_TRIMMER_HEATER, HEATERS_MIN_DUTY_CYCLE, TRIMMER_HEATER_P_COEFFICIENT, TRIMMER_HEATER_I_COEFFICIENT, - HEATERS_MIN_DUTY_CYCLE, HEATERS_MAX_DUTY_CYCLE ); // TODO remove? } /*********************************************************************//** @@ -194,7 +181,7 @@ // Check if the requested temperature is within the allowed range if ( ( targetTemperature >= MINIMUM_TARGET_TEMPERATURE ) && ( targetTemperature <= MAXIMUM_TARGET_TEMPERATURE ) ) { - heatersStatus[ heater ].originalTargetTemp = targetTemperature; + heatersStatus[ heater ].targetTemp = targetTemperature; heatersStatus[ heater ].hasTargetTempChanged = TRUE; // TODO alarm if temperature if out of range or just reject? } @@ -284,18 +271,26 @@ heatersStatus[ heater ].state = handleHeaterStateOff( heater ); break; - case HEATER_EXEC_STATE_RAMP_TO_TARGET: - heatersStatus[ heater ].state = handleHeaterStateRampToTarget( heater ); + case HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET: + heatersStatus[ heater ].state = handleHeaterStatePrimaryRampToTarget(); break; - case HEATER_EXEC_STATE_CONTROL_TO_TARGET: - heatersStatus[ heater ].state = handleHeaterStateControlToTarget( heater ); + case HEATER_EXEC_STATE_PRIMARY_CONTROL_TO_TARGET: + heatersStatus[ heater ].state = handleHeaterStatePrimaryControlToTarget(); break; case HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET: heatersStatus[ heater ].state = handleHeaterStateControlToDisinfectTarget( heater ); break; + case HEATER_EXEC_STATE_TRIMMER_RAMP_TO_TARGET: + heatersStatus[ heater ].state = handleHeaterStateTrimmerRampToTarget(); + break; + + case HEATER_EXEC_STATE_TRIMMER_CONTROL_TO_TARGET: + heatersStatus[ heater ].state = handleHeaterStateTrimmerControlToTarget(); + break; + default: // The heater is in an unknown state. Turn it off and switch to not running state stopHeater( heater ); @@ -421,8 +416,23 @@ publishHeatersData(); } + /*********************************************************************//** * @brief + * The resetHeatersEfficiency function resets the heaters efficiency upon + * the start of a treatment. + * @details Inputs: none + * @details Outputs: heaterStatus + * @return none + *************************************************************************/ +void resetHeatersEfficiency( void ) +{ + heatersStatus[ DG_PRIMARY_HEATER ].heaterEfficiency = 1.0; + heatersStatus[ DG_TRIMMER_HEATER ].heaterEfficiency = 1.0; +} + +/*********************************************************************//** + * @brief * The handleHeaterStateOff function handles the heater not running state. * @details Inputs: heaterStatus * @details Outputs: heaterStatus @@ -438,54 +448,51 @@ heatersStatus[ heater ].isHeaterOn = TRUE; heatersStatus[ heater ].startHeaterSignal = FALSE; - // Turn on the heater - state = HEATER_EXEC_STATE_RAMP_TO_TARGET; + // Depending on which heater is called, go to different states + state = ( heater == DG_PRIMARY_HEATER ? HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET : HEATER_EXEC_STATE_TRIMMER_RAMP_TO_TARGET ); } return state; } /*********************************************************************//** * @brief - * The handleHeaterStateRampToTarget function handles the heaters' control - * while they are ramping to target temperature. + * The handleHeaterStatePrimaryRampToTarget function handles the primary heaters' + * control while they are ramping to target temperature. * @details Inputs: heaterStatus * @details Outputs: heaterStatus - * @param heater: The heater Id that its on state is handled * @return next state of the state machine *************************************************************************/ -static HEATERS_STATE_T handleHeaterStateRampToTarget( DG_HEATERS_T heater ) +static HEATERS_STATE_T handleHeaterStatePrimaryRampToTarget( void ) { - HEATERS_STATE_T state = HEATER_EXEC_STATE_RAMP_TO_TARGET; + HEATERS_STATE_T state = HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET; + DG_HEATERS_T heater = DG_PRIMARY_HEATER; F32 inletTemperature = getTemperatureValue( (U32)TEMPSENSORS_HEAT_DISINFECT ); F32 targetFlow = 0.0; F32 dutyCycle = 0.0; - F32 targetTemperature = heatersStatus[ heater ].originalTargetTemp; - F32 heaterEfficiency = heatersStatus[ heater ].heaterEfficiency; + F32 targetTemperature = heatersStatus[ heater ].targetTemp; if ( DG_MODE_FILL == getCurrentOperationMode() ) { - // Get the previous fill's average flow rate - targetFlow = getAvgFillFlowRate(); - dutyCycle = calculatePrimaryHeaterDutyCycle( targetTemperature, inletTemperature, targetFlow ); - // Multiply the duty cycle to the heater efficiency - dutyCycle *= heaterEfficiency; - state = HEATER_EXEC_STATE_CONTROL_TO_TARGET; + // If the previous average fill flow rate is 0, use the nominal target RO flow from the RO pump + targetFlow = ( getAvgFillFlowRate() - 0.0 > NEARLY_ZERO ? getAvgFillFlowRate() : getTargetROPumpFlowRate() ); + dutyCycle = calculatePrimaryHeaterDutyCycle( targetTemperature, inletTemperature, targetFlow, TRUE ); + state = HEATER_EXEC_STATE_PRIMARY_CONTROL_TO_TARGET; } else if ( ( DG_MODE_GENE == getCurrentOperationMode() ) || ( DG_MODE_DRAI == getCurrentOperationMode() ) ) { targetTemperature += DELTA_TEMPERATURE_TIME_COSNTANT_C; // Use target flow rate during Idle and drain targetFlow = getTargetROPumpFlowRate(); - dutyCycle = calculatePrimaryHeaterDutyCycle( targetTemperature, inletTemperature, targetFlow ); - state = HEATER_EXEC_STATE_CONTROL_TO_TARGET; + dutyCycle = calculatePrimaryHeaterDutyCycle( targetTemperature, inletTemperature, targetFlow, FALSE ); + state = HEATER_EXEC_STATE_PRIMARY_CONTROL_TO_TARGET; } else if ( ( DG_MODE_HEAT == getCurrentOperationMode() ) || ( DG_MODE_CHEM == getCurrentOperationMode() ) ) { // If the mode is any of the disinfects, especially heat, 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 targetFlow = getTargetROPumpFlowRate(); - dutyCycle = calculatePrimaryHeaterDutyCycle( targetTemperature, inletTemperature, targetFlow ); + dutyCycle = calculatePrimaryHeaterDutyCycle( targetTemperature, inletTemperature, targetFlow, FALSE ); state = HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET; } @@ -496,22 +503,22 @@ /*********************************************************************//** * @brief - * The handleHeaterStateControlToTarget function handles the heaters' control - * to target while the heater is targeting to reach to temperature. + * The handleHeaterStatePrimaryControlToTarget function handles the primary + * heaters' control to target while the heater is targeting to reach to temperature. * @details Inputs: heaterStatus * @details Outputs: heaterStatus - * @param heater: The heater Id that its on state is handled * @return next state of the state machine *************************************************************************/ -static HEATERS_STATE_T handleHeaterStateControlToTarget( DG_HEATERS_T heater ) +static HEATERS_STATE_T handleHeaterStatePrimaryControlToTarget( void ) { - HEATERS_STATE_T state = HEATER_EXEC_STATE_CONTROL_TO_TARGET; + HEATERS_STATE_T state = HEATER_EXEC_STATE_PRIMARY_CONTROL_TO_TARGET; + DG_HEATERS_T heater = DG_PRIMARY_HEATER; // TODO do we need any control to maintain the temperature? if ( TRUE == haveHeaterControlConditionsChanged( heater ) ) { - state = HEATER_EXEC_STATE_RAMP_TO_TARGET; + state = HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET; } return state; @@ -536,30 +543,63 @@ // Check if the heaters control conditions have changed, if yes, switch back to ramp to target if ( TRUE == haveHeaterControlConditionsChanged( heater ) ) { - state = HEATER_EXEC_STATE_RAMP_TO_TARGET; + state = HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET; } // If the heat disifect sensor indicates that the temperature is below target temperature but the target temperature had been reached // before turn on the heaters with 100% power - if ( ( heatDisinfectSensorTemp <= heatersStatus[ heater ].originalTargetTemp ) && ( TRUE == heatersStatus[ heater ].hasTargetBeenReached ) ) + if ( ( heatDisinfectSensorTemp <= heatersStatus[ heater ].targetTemp ) && ( TRUE == heatersStatus[ heater ].hasTargetBeenReached ) ) { heatersStatus[ heater ].hasTargetBeenReached = FALSE; setHeaterDutyCycle( heater, HEATERS_MAX_DUTY_CYCLE ); } // If we have reached to target temperature, turn off the heaters - if ( heatDisinfectSensorTemp > heatersStatus[ heater ].originalTargetTemp ) + if ( heatDisinfectSensorTemp > heatersStatus[ heater ].targetTemp ) { // Set the flag to true for the next run heatersStatus[ heater ].hasTargetBeenReached = TRUE; - setHeaterDutyCycle( heater, HEATERS_MIN_DUTY_CYCLE ); + // The primary heater are not turned off but it is set to a minimum duty cycle so the temperature is kept + // above the target + setHeaterDutyCycle( heater, HEATERS_MIN_HEAT_DISINFECT_DUTY_CYCLE ); } return state; } /*********************************************************************//** * @brief + * The handleHeaterStateTrimmerRampToTarget function handles the trimmer + * heater's ramp to target. + * @details Inputs: heaterStatus + * @details Outputs: heaterStatus + * @return next state of the state machine + *************************************************************************/ +static HEATERS_STATE_T handleHeaterStateTrimmerRampToTarget( void ) +{ + HEATERS_STATE_T state = HEATER_EXEC_STATE_TRIMMER_RAMP_TO_TARGET; + + return state; +} + +/*********************************************************************//** + * @brief + * The handleHeaterStateTrimmerControlToTarget function handles the trimmer + * heater's control to target. + * @details Inputs: heaterStatus + * @details Outputs: heaterStatus + * @return next state of the state machine + *************************************************************************/ +static HEATERS_STATE_T handleHeaterStateTrimmerControlToTarget( void ) +{ + HEATERS_STATE_T state = HEATER_EXEC_STATE_TRIMMER_CONTROL_TO_TARGET; + + + return state; +} + +/*********************************************************************//** + * @brief * The setHeaterDutyCycle function sets the duty cycle of a heater. * @details Inputs: none * @details Outputs: none @@ -593,11 +633,35 @@ * @param flow current flow * @return calculated duty cycle *************************************************************************/ -static F32 calculatePrimaryHeaterDutyCycle( F32 targetTemperature, F32 currentTemperature, F32 flow ) +static F32 calculatePrimaryHeaterDutyCycle( F32 targetTemperature, F32 currentTemperature, F32 flow, BOOL checkEfficiency ) { + // Get the primary heater's efficiency and the last fill temperature from the ModeFill + F32 heaterEfficiency = heatersStatus[ DG_PRIMARY_HEATER ].heaterEfficiency; + + if ( TRUE == checkEfficiency ) + { + F32 lastFillTemperature = getLastFillTemperature(); + + // If the last fill temperature > target temperature, it means the primary heater overshot the duty cycle + // so with its efficiency is toned down for the next fill cycle + // If the heater undershoots the duty cycle, the efficiency increases the duty cycle for the next fill cycle + if ( lastFillTemperature - targetTemperature > MAXIMUM_ALLOWED_TARGET_TEMPERATURE_DEVIATION_C ) + { + heaterEfficiency -= ( lastFillTemperature - targetTemperature ) * PRIMARY_HEATER_DUTY_CYCLE_PER_TEMPERATURE_C; + } + else if ( lastFillTemperature - targetTemperature <= MAXIMUM_ALLOWED_TARGET_TEMPERATURE_DEVIATION_C ) + { + heaterEfficiency += ( lastFillTemperature - targetTemperature ) * PRIMARY_HEATER_DUTY_CYCLE_PER_TEMPERATURE_C; + } + + // Update the heaters efficiency + heatersStatus[ DG_PRIMARY_HEATER ].heaterEfficiency = heaterEfficiency; + } + // Duty cycle = ( 69.73 * flow rate * deltaT / primary heater maximum power ) ^ 1/2 + // Multiply the duty cycle to the heater efficiency F32 dutyCycle = sqrt( ( WATER_SPECIFIC_HEAT_DIVIDED_BY_MINUTES * - fabs( targetTemperature - currentTemperature ) * flow ) / PRIMARY_HEATERS_MAXIMUM_POWER_WATTS ); + fabs( targetTemperature - currentTemperature ) * flow ) / PRIMARY_HEATERS_MAXIMUM_POWER_WATTS ) * heaterEfficiency; // Check the boundaries of the calculated duty cycle dutyCycle = ( dutyCycle > HEATERS_MAX_DUTY_CYCLE ? HEATERS_MAX_DUTY_CYCLE : dutyCycle ); @@ -697,6 +761,7 @@ data.trimmerTargetTemp = heatersStatus[ DG_TRIMMER_HEATER ].targetTemp; data.primaryHeaterState = heatersStatus[ DG_PRIMARY_HEATER ].state; data.trimmerHeaterState = heatersStatus[ DG_TRIMMER_HEATER ].state; + data.primaryEfficiency = heatersStatus[ DG_PRIMARY_HEATER ].heaterEfficiency * 100; broadcastData( MSG_ID_DG_HEATERS_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( HEATERS_DATA_T ) ); @@ -832,16 +897,7 @@ } } -static void checkHeaterOnStatus( DG_HEATERS_T heater ) -{ - if ( FALSE == heatersStatus[ heater ].isHeaterOn ) - { - setHeaterDutyCycle( heater, HEATERS_MIN_DUTY_CYCLE ); - //state = HEATER_EXEC_STATE_NOT_RUNNING; - } -} - /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/