Index: firmware/App/Controllers/Heaters.c =================================================================== diff -u --- firmware/App/Controllers/Heaters.c (revision 0) +++ firmware/App/Controllers/Heaters.c (revision 3b3833a0b1aed89f1ff66104519f658f5a41fa99) @@ -0,0 +1,765 @@ +/************************************************************************** +* +* Copyright (c) 2024-2024 Diality Inc. - All Rights Reserved. +* +* THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +* WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +* +* @file Heaters.c +* +* @author (last) Vinayakam Mani +* @date (last) 07-Oct-2024 +* +* @author (original) Vinayakam Mani +* @date (original) 07-Oct-2024 +* +***************************************************************************/ + +#include // Used for mathematical operations + +//#include "DDDefs.h" +#include "FpgaDD.h" +#include "Heaters.h" +#include "MessageSupport.h" +#include "Messaging.h" +//#include "NVDataMgmt.h" +#include "OperationModes.h" +#include "PersistentAlarm.h" +#include "PIControllers.h" +#include "SafetyShutdown.h" +#include "TaskGeneral.h" +#include "TaskPriority.h" +#include "TemperatureSensors.h" +#include "Timers.h" +#include "Utilities.h" + +/** + * @addtogroup Heaters + * @{ + */ + +// ********** private definitions ********** + +#define HEATERS_MAX_DUTY_CYCLE 1.00F ///< Heaters max duty cycle (100%). +#define HEATERS_MIN_DUTY_CYCLE 0.00F ///< Heaters minimum duty cycle (0.00%). +#define HEATERS_DISINFECT_DUTY_CYCLE 0.80F ///< Heaters disinfect cycle. +#define HEATERS_DISINFECT_TRANSFER_DUTY_CYCLE 0.60F ///< Heaters disinfect transfer duty cycle. +#define HEATERS_DISINFECT_TEMPERATURE_DRIFT_C 3.0F ///< Heaters disinfect temperature drift in C. +#define HEATERS_ZERO_DELTA_TEMP_C 0.0F ///< Heaters zero delta temperature in C. +#define HEATERS_DUTY_CYCLE_CONVERSION_FACTOR 127.0F ///< Heaters duty cycle 0: OFF, 127: 100% duty cycle. + +#define PRIMARY_HEATER_P_COEFFICIENT 1.0F ///< P Term for primary heater control. +#define PRIMARY_HEATER_I_COEFFICIENT 1.0F ///< I Term for primary heater control. +#define TRIMMER_HEATER_P_COEFFICIENT 1.0F ///< P Term for trimmer heater control. +#define TRIMMER_HEATER_I_COEFFICIENT 1.0F ///< I Term for trimmer heater control. + +#define HEATERS_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_PRIORITY_INTERVAL ) ///< Heaters data publish interval. + +#define HEATER_TARGET_TEMPERATURE_MIN 10.0F ///< Minimum allowed target temperature for the heaters. +#define HEATER_TARGET_TEMPERATURE_MAX 90.0F ///< Maximum allowed target temperature for the heaters. + +#define PRIMARY_HEATER_ON_NO_FLUID_TIMEOUT_MS ( 10 * MS_PER_SECOND ) ///< Primary heater on with no flow time out in milliseconds. +#define TRIMMER_HEATER_ON_NO_FLUID_TIMEOUT_MS ( 12 * MS_PER_SECOND ) ///< Trimmer heater on with no flow time out in milliseconds. +#define HEATERS_MAX_OPERATING_VOLTAGE_V 24.0F ///< Heaters max operating voltage in volts. +#define HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ( 2 * MS_PER_SECOND ) ///< Heaters voltage out of range time out in milliseconds. +#define HEATERS_MAX_VOLTAGE_OUT_OF_RANGE_TOL 0.2F ///< Heaters max voltage out of range tolerance. +#define TRIMMER_HEATER_INITIAL_CONTROL_INTERVAL_COUNT ( ( 5 * MS_PER_SECOND ) / TASK_GENERAL_INTERVAL ) ///< Trimmer heater initial control interval count. +#define TRIMMER_HEATER_CONTROL_INTERVAL_COUNT ( ( 10 * MS_PER_SECOND ) / TASK_GENERAL_INTERVAL ) ///< Trimmer heater control interval count. +#define PRIMARY_HEATER_CONTROL_INTERVAL_COUNT ( ( 1 * MS_PER_SECOND ) / TASK_GENERAL_INTERVAL ) ///< Primary heater control interval count. + +#define DATA_PUBLISH_COUNTER_START_COUNT 70 ///< Data publish counter start count. + +//static const F32 HEATERS_VOLTAGE_TOLERANCE_V = HEATERS_MAX_OPERATING_VOLTAGE_V * HEATERS_MAX_VOLTAGE_OUT_OF_RANGE_TOL; ///< Heaters voltage tolerance in volts. + +/// Heaters data structure +typedef struct +{ + F32 targetTempC; ///< Heater target temperature. + HEATERS_STATE_T state; ///< Heater state. + BOOL startHeaterSignal; ///< Heater start indication flag. + BOOL isHeaterOn; ///< Heater on/off status flag. + OVERRIDE_F32_T dutyCycle; ///< Heater duty cycle. + F32 targetFlowLPM; ///< Heater target flow in L/min to calculate the duty cycle. + F32 nomTargetFlowLPM; ///< Heater nominal target flow in L/min. + BOOL hasTargetTempChanged; ///< Heater target temperature change flag indicator. + F32 calculatedTemperatureC; ///< Heater calculated temperature. + U32 controlIntervalCounter; ///< Heater control interval counter. + BOOL isThisFirstControl; ///< Heater is this first control interval. + F32 prevDiaTargetFlowLPM; ///< Heater previous target dialysate flow in L/min. +} HEATER_STATUS_T; + +static HEATER_STATUS_T heatersStatus[ NUM_OF_DD_HEATERS ]; ///< Heaters status. +static U32 dataPublicationTimerCounter; ///< Data publication timer counter. +static OVERRIDE_U32_T heatersDataPublishInterval = { HEATERS_DATA_PUBLISH_INTERVAL, HEATERS_DATA_PUBLISH_INTERVAL, 0, 0 }; ///< Heaters data publish time interval. + +// ********** private function prototypes ********** + +static HEATERS_STATE_T handleHeaterStateOff( DD_HEATERS_T heater ); +static HEATERS_STATE_T handleHeaterStatePrimaryRampToTarget( void ); +static HEATERS_STATE_T handleHeaterStatePrimaryControlToTarget( void ); +static HEATERS_STATE_T handleHeaterStateControlToDisinfectTarget( DD_HEATERS_T heater ); +static HEATERS_STATE_T handleHeaterStateTrimmerRampToTarget( void ); +static HEATERS_STATE_T handleHeaterStateTrimmerControlToTarget( void ); + +static void setHeaterDutyCycle( DD_HEATERS_T heater ); +static F32 getHeaterDutyCycle( DD_HEATERS_T heater ); + +static void publishHeatersData( void ); +//static void monitorHeatersVoltage( void ); + +/*********************************************************************//** + * @brief + * The initHeaters initializes the heaters controller unit. + * @details \b Inputs: none + * @details \b Outputs: Heaters unit variables initialized. + * @return none + *************************************************************************/ +void initHeaters( void ) +{ + DD_HEATERS_T heater; + dataPublicationTimerCounter = DATA_PUBLISH_COUNTER_START_COUNT; + + for ( heater = DD_PRIMARY_HEATER; heater < NUM_OF_DD_HEATERS; heater++ ) + { + heatersStatus[ heater ].targetTempC = 0.0F; + heatersStatus[ heater ].state = HEATER_EXEC_STATE_OFF; + heatersStatus[ heater ].startHeaterSignal = FALSE; + heatersStatus[ heater ].isHeaterOn = FALSE; + heatersStatus[ heater ].dutyCycle.data = HEATERS_MIN_DUTY_CYCLE; + heatersStatus[ heater ].dutyCycle.ovData = HEATERS_MIN_DUTY_CYCLE; + heatersStatus[ heater ].dutyCycle.ovInitData = HEATERS_MIN_DUTY_CYCLE; + heatersStatus[ heater ].dutyCycle.override = 0; + heatersStatus[ heater ].targetFlowLPM = 0.0F; + heatersStatus[ heater ].nomTargetFlowLPM = 0.0F; + heatersStatus[ heater ].hasTargetTempChanged = FALSE; + heatersStatus[ heater ].controlIntervalCounter = 0; + heatersStatus[ heater ].isThisFirstControl = TRUE; + heatersStatus[ heater ].prevDiaTargetFlowLPM = 0.0F; + } + + // Initialize the primary controller PI controller + initializePIController( PI_CONTROLLER_ID_PRIMARY_HEATER, HEATERS_MIN_DUTY_CYCLE, PRIMARY_HEATER_P_COEFFICIENT, PRIMARY_HEATER_I_COEFFICIENT, + HEATERS_MIN_DUTY_CYCLE, HEATERS_MAX_DUTY_CYCLE ); + + // Initialize the trimmer heater PI controller + 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 ); + + // Initialize the persistent alarms + //initPersistentAlarm( ALARM_ID_DD_PRIMARY_HEATER_VOLTAGE_OUT_OF_RANGE, 0, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); + //initPersistentAlarm( ALARM_ID_DD_TRIMMER_HEATER_VOLTAGE_OUT_OF_RANGE, 0, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); + initPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON, 0, PRIMARY_HEATER_ON_NO_FLUID_TIMEOUT_MS ); + initPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON, 0, TRIMMER_HEATER_ON_NO_FLUID_TIMEOUT_MS ); +} + +/*********************************************************************//** + * @brief + * The setHeaterTargetTemperature function sets the target temperature of + * a given heater. + * @details \b Inputs: none + * @details \b Outputs: heaterStatus + * @details \b Alarms: ALARM_ID_DD_SOFTWARE_FAULT when invalid heater ID + * passed. + * @param heater: heater ID that its target temperature is set + * @param targetTemperature: target temperature of that the heater has to + * heat the fluid + * @return TRUE if the temperature was set otherwise, FALSE + *************************************************************************/ +BOOL setHeaterTargetTemperature( DD_HEATERS_T heater, F32 targetTemperature ) +{ + BOOL result = FALSE; + + if( heater < NUM_OF_DD_HEATERS ) + { + // Assume the target temperature has not changed + heatersStatus[ heater ].hasTargetTempChanged = FALSE; + + // Check if the requested temperature is within the allowed range + if ( ( targetTemperature >= HEATER_TARGET_TEMPERATURE_MIN ) && ( targetTemperature <= HEATER_TARGET_TEMPERATURE_MAX ) ) + { + heatersStatus[ heater ].targetTempC = targetTemperature; + heatersStatus[ heater ].hasTargetTempChanged = TRUE; + result = TRUE; + } + } + else + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED, heater ) + } + + return result; +} + +/*********************************************************************//** + * @brief + * The getHeaterTargetTemperature function returns the given heater target + * temperature. + * @details \b Inputs: none + * @details \b Outputs: heaterStatus + * @param heater: heater ID to get heater target temperature. + * @return the given heater target temperature + *************************************************************************/ +F32 getHeaterTargetTemperature( DD_HEATERS_T heater ) +{ + return heatersStatus[ heater ].targetTempC; +} + +/*********************************************************************//** + * @brief + * The isHeaterOn function returns the given heater status whether + * it is on or off + * @details \b Inputs: heaterStatus + * @details \b Outputs: none + * @param heater: heater ID to get the heater status(On/Off). + * @return heater on/off status + *************************************************************************/ +BOOL isHeaterOn( DD_HEATERS_T heater ) +{ + return heatersStatus[ heater ].isHeaterOn; +} + +/*********************************************************************//** + * @brief + * The startHeater function starts the given heater by setting the flag. + * @details \b Inputs: heatersStatus + * @details \b Outputs: startHeaterSignal + * @details \b Alarms: ALARM_ID_DD_SOFTWARE_FAULT when invalid heater ID passed + * @param heater: heater ID to set the heater start signal. + * @return status TRUE when heater start flag set otherwise, FALSE + *************************************************************************/ +BOOL startHeater( DD_HEATERS_T heater ) +{ + BOOL status = FALSE; + + if( heater < NUM_OF_DD_HEATERS ) + { + if ( HEATER_EXEC_STATE_OFF == heatersStatus[ heater ].state ) + { + heatersStatus[ heater ].startHeaterSignal = TRUE; + status = TRUE; + } + } + else + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED, heater ) + } + + return status; +} + +/*********************************************************************//** + * @brief + * The stopHeater stops the specified heater. + * @details \b Inputs: none + * @details \b Outputs: heaterStatus + * @details \b Alarms: ALARM_ID_DD_SOFTWARE_FAULT when invalid heater ID + * passed. + * @param heater: heater ID that is requested to turn on + * @return none + *************************************************************************/ +void stopHeater( DD_HEATERS_T heater ) +{ + if( heater < NUM_OF_DD_HEATERS ) + { + heatersStatus[ heater ].startHeaterSignal = FALSE; + heatersStatus[ heater ].isHeaterOn = FALSE; + } + else + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED, heater ) + } +} + +/*********************************************************************//** + * @brief + * The execHeaters function executes the heaters state machine. + * @details \b Inputs: heaterStatus + * @details \b Outputs: heaterStatus + * @details \b Alarms: ALARM_ID_DD_SOFTWARE_FAULT when invalid heater + * executive state found. + * @return none + *************************************************************************/ +void execHeaters( void ) +{ + DD_HEATERS_T heater; + HEATERS_STATE_T state; + + for ( heater = DD_PRIMARY_HEATER; heater < NUM_OF_DD_HEATERS; heater++ ) + { + state = heatersStatus[ heater ].state; + + switch( state ) + { + case HEATER_EXEC_STATE_OFF: + heatersStatus[ heater ].state = handleHeaterStateOff( heater ); + break; + + case HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET: + heatersStatus[ heater ].state = handleHeaterStatePrimaryRampToTarget(); + break; + + 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 ); + heatersStatus[ heater ].state = HEATER_EXEC_STATE_OFF; + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_EXEC_STATE, heater ); + break; + } + + // Check if the heater is requested to be off + if ( FALSE == heatersStatus[ heater ].isHeaterOn ) + { + heatersStatus[ heater ].dutyCycle.data = HEATERS_MIN_DUTY_CYCLE; + setHeaterDutyCycle( heater ); + heatersStatus[ heater ].state = HEATER_EXEC_STATE_OFF; + } + } +} + +/*********************************************************************//** + * @brief + * The execHeatersMonitor function monitors the status of the heaters. + * The internal temperature sensors and the voltages of the heaters are + * monitored. The hydraulics chamber and balance air separation level is + * continuously checked and if there is no fluid at defined level, + * the heaters are turned off. + * @details \b Inputs: heaterStatus + * @details \b Outputs: heaterStatus + * @return none + *************************************************************************/ +void execHeatersMonitor( void ) +{ + DD_HEATERS_T heater; + + for ( heater = DD_PRIMARY_HEATER; heater < NUM_OF_DD_HEATERS; heater++ ) + { + // Check if the heater is on and if it is, check the level sensor status + if ( TRUE == heatersStatus[ heater ].isHeaterOn ) + { + ALARM_ID_T alarm; + BOOL isLevelLow = FALSE; + + if ( DD_PRIMARY_HEATER == heater ) + { + alarm = ALARM_ID_DD_FLUID_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON; + //isLevelLow = getFloaterLevelStatus(); + } + else + { + alarm = ALARM_ID_DD_FLUID_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON; + //isLevelLow = getBalanceAirSeparationLevelStatus(); + } + + checkPersistentAlarm( alarm, isLevelLow, 0.0F, 0.0F ); + } + else + { + if ( DD_PRIMARY_HEATER == heater ) + { + checkPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_PRIMARY_HEATER_IS_ON, FALSE, 0.0F, 0.0F ); + } + else + { + checkPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_TRIMMER_HEATER_IS_ON, FALSE, 0.0F, 0.0F ); + } + } + } + + // Monitor heater voltage + //monitorHeatersVoltage(); + + // Check for data publication + publishHeatersData(); +} + +/*********************************************************************//** + * @brief + * The handleHeaterStateOff function handles the heater not running state + * and transition to heater On condition when the heater start flag is set to true. + * @details \b Inputs: heaterStatus + * @details \b Outputs: heaterStatus + * @param heater: The heater Id that its not running state is handled + * @return next state of the state machine + *************************************************************************/ +static HEATERS_STATE_T handleHeaterStateOff( DD_HEATERS_T heater ) +{ + HEATERS_STATE_T state = HEATER_EXEC_STATE_OFF; + + if ( TRUE == heatersStatus[ heater ].startHeaterSignal ) + { + heatersStatus[ heater ].isHeaterOn = TRUE; + heatersStatus[ heater ].startHeaterSignal = FALSE; + + // Depending on which heater is called, go to different states + state = ( DD_PRIMARY_HEATER == heater ? HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET : HEATER_EXEC_STATE_TRIMMER_RAMP_TO_TARGET ); + } + + return state; +} + +/*********************************************************************//** + * @brief + * The handleHeaterStatePrimaryRampToTarget function handles the primary heaters' + * control while they are ramping to target temperature. + * @details \b Inputs: heaterStatus + * @details \b Outputs: heaterStatus + * @return next state of the state machine + *************************************************************************/ +static HEATERS_STATE_T handleHeaterStatePrimaryRampToTarget( void ) +{ + HEATERS_STATE_T state = HEATER_EXEC_STATE_PRIMARY_RAMP_TO_TARGET; + DD_HEATERS_T heater = DD_PRIMARY_HEATER; + F32 dutyCycle = 0.0F; + DD_OP_MODE_T opMode = getCurrentOperationMode(); + + if ( DD_MODE_HEAT != opMode ) + { + // TODO : Calculate the initial duty cycle + dutyCycle = ( ( heatersStatus[ heater ].targetTempC / HEATER_TARGET_TEMPERATURE_MAX ) * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR ) + FLOAT_TO_INT_ROUNDUP_OFFSET; + } + else + { + // TODO : Calculate required duty cycle + state = HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET; + } + + // Update the calculated target temperature and duty cycle + heatersStatus[ DD_PRIMARY_HEATER ].calculatedTemperatureC = heatersStatus[ heater ].targetTempC; + heatersStatus[ DD_PRIMARY_HEATER ].dutyCycle.data = dutyCycle; + setHeaterDutyCycle( heater ); + + return state; +} + +/*********************************************************************//** + * @brief + * The handleHeaterStatePrimaryControlToTarget function handles the primary + * heaters' control to target while the heater is targeting to reach to temperature. + * @details \b Inputs: heaterStatus + * @details \b Outputs: heaterStatus + * @return next state of the state machine + *************************************************************************/ +static HEATERS_STATE_T handleHeaterStatePrimaryControlToTarget( void ) +{ + HEATERS_STATE_T state = HEATER_EXEC_STATE_PRIMARY_CONTROL_TO_TARGET; + DD_HEATERS_T heater = DD_PRIMARY_HEATER; + F32 measuredTemperature = getTemperatureValue( (U32)TEMPSENSORS_HYDRAULICS_PRIMARY_HEATER); + F32 targetTemperature = heatersStatus[ heater ].targetTempC; + F32 dutyCycle = 0.0F; + + if( ++heatersStatus[ heater ].controlIntervalCounter > PRIMARY_HEATER_CONTROL_INTERVAL_COUNT ) + { + dutyCycle = runPIController( PI_CONTROLLER_ID_PRIMARY_HEATER, targetTemperature, measuredTemperature ); + + heatersStatus[ heater ].calculatedTemperatureC = targetTemperature; + heatersStatus[ heater ].hasTargetTempChanged = FALSE; + heatersStatus[ heater ].dutyCycle.data = ( dutyCycle * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR ) + FLOAT_TO_INT_ROUNDUP_OFFSET; + heatersStatus[ heater ].controlIntervalCounter = 0; + + setHeaterDutyCycle( heater ); + } + + return state; +} + +/*********************************************************************//** + * @brief + * The handleHeaterStateControlToDisinfectTarget function handles the + * heaters' control to target while the operation mode is heat or chemical + * disinfects. + * @details \b Inputs: heaterStatus + * @details \b Outputs: heaterStatus + * @param heater: The heater Id that its on state is handled + * @return next state of the state machine + *************************************************************************/ +static HEATERS_STATE_T handleHeaterStateControlToDisinfectTarget( DD_HEATERS_T heater ) +{ + HEATERS_STATE_T state = HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET; + + //TODO : update dutycycle for the heat disinfect state + + setHeaterDutyCycle( DD_TRIMMER_HEATER ); + setHeaterDutyCycle( DD_PRIMARY_HEATER ); + + return state; +} + +/*********************************************************************//** + * @brief + * The handleHeaterStateTrimmerRampToTarget function handles the trimmer + * heater's ramp to target. + * @details \b Inputs: heaterStatus + * @details \b 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; + DD_HEATERS_T heater = DD_TRIMMER_HEATER; + F32 dutyCycle = 0.0F; + DD_OP_MODE_T opMode = getCurrentOperationMode(); + + if ( DD_MODE_HEAT != opMode ) + { + // TODO : Calculate the initial duty cycle + dutyCycle = ( ( heatersStatus[ heater ].targetTempC / HEATER_TARGET_TEMPERATURE_MAX ) * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR ) + FLOAT_TO_INT_ROUNDUP_OFFSET; + } + else + { + // TODO : Calculate required duty cycle + state = HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET; + } + + // Update the calculated target temperature and duty cycle + heatersStatus[ DD_TRIMMER_HEATER ].calculatedTemperatureC = heatersStatus[ heater ].targetTempC; + heatersStatus[ DD_TRIMMER_HEATER ].dutyCycle.data = dutyCycle; + setHeaterDutyCycle( heater ); + + return state; +} + +/*********************************************************************//** + * @brief + * The handleHeaterStateTrimmerControlToTarget function handles the trimmer + * heater's control to target state. + * @details \b Inputs: heaterStatus, trimmerHeaterControlCounter + * @details \b Outputs: heaterStatus, trimmerHeaterControlCounter + * @return next state of the state machine + *************************************************************************/ +static HEATERS_STATE_T handleHeaterStateTrimmerControlToTarget( void ) +{ + HEATERS_STATE_T state = HEATER_EXEC_STATE_TRIMMER_CONTROL_TO_TARGET; + DD_HEATERS_T heater = DD_TRIMMER_HEATER; + F32 measuredTemperature = getTemperatureValue( (U32)TEMPSENSORS_TRIMMER_HEATER ); + F32 targetTemperature = heatersStatus[ heater ].targetTempC; + F32 dutyCycle = 0.0F; + + if( ++heatersStatus[ heater ].controlIntervalCounter > TRIMMER_HEATER_CONTROL_INTERVAL_COUNT ) + { + dutyCycle = runPIController( PI_CONTROLLER_ID_PRIMARY_HEATER, targetTemperature, measuredTemperature ); + + heatersStatus[ heater ].calculatedTemperatureC = targetTemperature; + heatersStatus[ heater ].hasTargetTempChanged = FALSE; + heatersStatus[ heater ].dutyCycle.data = ( dutyCycle * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR ) + FLOAT_TO_INT_ROUNDUP_OFFSET; + heatersStatus[ heater ].controlIntervalCounter = 0; + + setHeaterDutyCycle( heater ); + } + + return state; +} + +/*********************************************************************//** + * @brief + * The setHeaterDutyCycle function sets the duty cycle of a heater. + * @details \b Inputs: dutyCycle + * @details \b Outputs: FPGA heater control + * @details \b Alarms: ALARM_ID_DD_SOFTWARE_FAULT when invalid heater ID + * is selected. + * @param heater: The heater Id that its duty cycle is set + * @return none + *************************************************************************/ +static void setHeaterDutyCycle( DD_HEATERS_T heater ) +{ + if ( heater < NUM_OF_DD_HEATERS ) + { + F32 duty; + + duty = getHeaterDutyCycle( heater ); + + if ( DD_PRIMARY_HEATER == heater ) + { + setFPGAPrimaryHeaterPWMControl( (U08)duty ); + } + else + { + setFPGATrimmerHeaterPWMControl( (U08)duty ); + } + } + else + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED, heater ); + } +} + +/*********************************************************************//** + * @brief + * The getHeaterDutyCycle function returns the heater's duty cycle. + * @details \b Inputs: heaterStatus + * @details \b Outputs: none + * @param heater: The heater Id to get the duty cycle. + * @return PWM duty cycle for the given heater + *************************************************************************/ +static F32 getHeaterDutyCycle( DD_HEATERS_T heater ) +{ + F32 duty = getF32OverrideValue( &heatersStatus[ heater ].dutyCycle ); + + return duty; +} + +/*********************************************************************//** + * @brief + * The monitorHeatersVoltage function monitors the heaters' voltages + * @details \b Inputs: Voltage range + * @details \b Outputs: none + * @details \b Alarms: ALARM_ID_DD_MAIN_PRIMARY_HEATER_VOLTAGE_OUT_OF_RANGE when + * primary heater voltage found out of range. + * @details \b Alarms: ALARM_ID_DD_TRIMMER_HEATER_VOLTAGE_OUT_OF_RANGE when + * trimmer heater voltage found out of range. + * @return none + *************************************************************************/ +//static void monitorHeatersVoltage( void ) +//{ +// F32 mainPriVoltage = getMonitoredLineLevel( MONITORED_LINE_24V_GND_MAIN_PRIM_HTR_V ); +// F32 trimmerVoltage = getMonitoredLineLevel( MONITORED_LINE_24V_GND_TRIM_HTR_V ); +// +// // Voltage to PWM is reverse. If PWM = 0 -> V = 24V +// F32 mainPriDC = getHeaterDutyCycle( DD_PRIMARY_HEATER ); +// F32 trimmerDC = getHeaterDutyCycle( DD_TRIMMER_HEATER ); +// +// // The expected voltage is the inverse of the duty cycle +// F32 mainPriExpectedVoltage = HEATERS_MAX_OPERATING_VOLTAGE_V * ( 1.0F - mainPriDC ); +// F32 trimmerExpectedVoltage = HEATERS_MAX_OPERATING_VOLTAGE_V * ( 1.0F - trimmerDC ); +// +// BOOL isMainPriOut = FALSE; +// BOOL isTrimmerOut = FALSE; +// +// // 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 ); +// isTrimmerOut = ( fabs( trimmerExpectedVoltage - trimmerVoltage ) > HEATERS_VOLTAGE_TOLERANCE_V ? TRUE : FALSE ); +// +// if ( getCurrentOperationMode() != DD_MODE_INIT ) +// { +// checkPersistentAlarm( ALARM_ID_DD_MAIN_PRIMARY_HEATER_VOLTAGE_OUT_OF_RANGE, isMainPriOut, mainPriDC, HEATERS_VOLTAGE_TOLERANCE_V ); +// checkPersistentAlarm( ALARM_ID_DD_TRIMMER_HEATER_VOLTAGE_OUT_OF_RANGE, isTrimmerOut, trimmerDC, HEATERS_VOLTAGE_TOLERANCE_V ); +// } +//} + +/*********************************************************************//** + * @brief + * The publishHeatersData function publishes the heaters data info + * at the defined time interval. + * @details \b Inputs: dataPublicationTimerCounter + * @details \b Outputs: dataPublicationTimerCounter + * @details \b Message \b Sent: MSG_ID_DD_HEATERS_DATA to publish + * heaters data. + * @return none + *************************************************************************/ +static void publishHeatersData( void ) +{ + if ( ++dataPublicationTimerCounter >= getU32OverrideValue( &heatersDataPublishInterval ) ) + { + HEATERS_DATA_T data; + + data.mainPrimayHeaterDC = getHeaterDutyCycle( DD_PRIMARY_HEATER ) * FRACTION_TO_PERCENT_FACTOR; + data.trimmerHeaterDC = getHeaterDutyCycle( DD_TRIMMER_HEATER ) * FRACTION_TO_PERCENT_FACTOR; + data.primaryTargetTemp = heatersStatus[ DD_PRIMARY_HEATER ].targetTempC; + data.trimmerTargetTemp = heatersStatus[ DD_TRIMMER_HEATER ].targetTempC; + data.primaryHeaterState = heatersStatus[ DD_PRIMARY_HEATER ].state; + data.trimmerHeaterState = heatersStatus[ DD_TRIMMER_HEATER ].state; + data.primaryCalcTargetTemp = heatersStatus[ DD_PRIMARY_HEATER ].calculatedTemperatureC; + data.trimmerCalcCurrentTemp = heatersStatus[ DD_TRIMMER_HEATER ].calculatedTemperatureC; + data.primaryControlCounter = heatersStatus[ DD_PRIMARY_HEATER ].controlIntervalCounter; + data.trimmerControlCounter = heatersStatus[ DD_TRIMMER_HEATER ].controlIntervalCounter; + + dataPublicationTimerCounter = 0; + + broadcastData( MSG_ID_DD_HEATERS_DATA, COMM_BUFFER_OUT_CAN_DD_BROADCAST, (U08*)&data, sizeof( HEATERS_DATA_T ) ); + } +} + + +/************************************************************************* + * TEST SUPPORT FUNCTIONS + *************************************************************************/ + + +/*********************************************************************//** + * @brief + * The testHeatersDataPublishIntervalOverride function overrides the + * heaters data publish interval. + * @details \b Inputs: heatersDataPublishInterval + * @details \b Outputs: heatersDataPublishInterval + * @param Override message from Dialin which includes the interval + * (in ms) to override the heaters data broadcast interval to. + * @return TRUE if override successful, FALSE if not + *************************************************************************/ +BOOL testHeatersDataPublishIntervalOverride( MESSAGE_T *message ) +{ + BOOL result = u32BroadcastIntervalOverride( message, &heatersDataPublishInterval, TASK_PRIORITY_INTERVAL ); + + return result; +} + +/*********************************************************************//** + * @brief + * The testSetHeaterDutyCycleOverride function overrides the specified heater's + * duty cycle. + * @details \b Inputs: heatersStatus + * @details \b Outputs: heatersStatus + * @return TRUE if the override was successful otherwise FALSE + *************************************************************************/ +BOOL testSetHeaterDutyCycleOverride( U32 heater, F32 value ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + if ( ( value >= HEATERS_MIN_DUTY_CYCLE ) && ( value <= HEATERS_MAX_DUTY_CYCLE ) ) + { + result = TRUE; + heatersStatus[ (DD_HEATERS_T)heater ].dutyCycle.ovData = ( value * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR ) + FLOAT_TO_INT_ROUNDUP_OFFSET; + heatersStatus[ (DD_HEATERS_T)heater ].dutyCycle.override = OVERRIDE_KEY; + + if ( TRUE == heatersStatus[ (DD_HEATERS_T)heater ].isHeaterOn ) + { + setHeaterDutyCycle( (DD_HEATERS_T)heater ); + } + } + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testResetHeaterDutyCycleOverride function resets the heater's + * duty cycle overridden value. + * @details \b Inputs: heatersStatus + * @details \b Outputs: heatersStatus + * @return TRUE if the reset was successful otherwise, FALSE + *************************************************************************/ +BOOL testResetHeaterDutyCycleOverride( U32 heater ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + heatersStatus[ (DD_HEATERS_T)heater ].dutyCycle.override = OVERRIDE_RESET; + heatersStatus[ (DD_HEATERS_T)heater ].dutyCycle.ovData = heatersStatus[ (DD_HEATERS_T)heater ].dutyCycle.ovInitData; + setHeaterDutyCycle( (DD_HEATERS_T)heater ); + } + + return result; +} + +/**@}*/ Index: firmware/App/Controllers/Heaters.h =================================================================== diff -u --- firmware/App/Controllers/Heaters.h (revision 0) +++ firmware/App/Controllers/Heaters.h (revision 3b3833a0b1aed89f1ff66104519f658f5a41fa99) @@ -0,0 +1,77 @@ +/************************************************************************** +* +* Copyright (c) 2024-2024 Diality Inc. - All Rights Reserved. +* +* THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN +* WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. +* +* @file Heaters.h +* +* @author (last) Vinayakam Mani +* @date (last) 07-Oct-2024 +* +* @author (original) Vinayakam Mani +* @date (original) 07-Oct-2024 +* +***************************************************************************/ + +#ifndef _HEATERS_H_ +#define _HEATERS_H_ + +#include "DDCommon.h" + +/** + * @defgroup Heaters Heaters + * @brief Heaters driver modules. Controls the primary and trimmer heaters. + * The primary heaters are manufactured by Watlow with + * fluid operating temperature in between 5 degrees C and 95 degrees C. + * The trimmer heater is manufactured by Watlow with + * fluid operating temperature in between 5 degrees C and 95 degrees C. + * + * @addtogroup Heaters + * @{ + */ + +// ********** Public definitions ********** + +/// Name of DD heaters +typedef enum Heaters +{ + DD_PRIMARY_HEATER = 0, ///< DD primary heater + DD_TRIMMER_HEATER, ///< DD trimmer heater + NUM_OF_DD_HEATERS, ///< Number of the heaters +} DD_HEATERS_T; + +/// Heaters data structure. +typedef struct +{ + F32 mainPrimayHeaterDC; ///< Main primary heater DC + F32 trimmerHeaterDC; ///< Trimmer heater DC + F32 primaryTargetTemp; ///< Primary heater target temperature + F32 trimmerTargetTemp; ///< Trimmer heater target temperature + U32 primaryHeaterState; ///< Primary heater state + U32 trimmerHeaterState; ///< Trimmer heater state + F32 primaryCalcTargetTemp; ///< Primary heater calculated target temperature + F32 trimmerCalcCurrentTemp; ///< Trimmer heater calculated current temperature + U32 primaryControlCounter; ///< Primary heater control count + U32 trimmerControlCounter; ///< Trimmer heater control count +} HEATERS_DATA_T; + +// ********** Public function prototypes ********** + +void initHeaters( void ); +BOOL setHeaterTargetTemperature( DD_HEATERS_T heater, F32 targetTemperature ); +BOOL startHeater( DD_HEATERS_T heater ); +F32 getHeaterTargetTemperature( DD_HEATERS_T heater ); +BOOL isHeaterOn( DD_HEATERS_T heater ); +void stopHeater( DD_HEATERS_T heater ); +void execHeaters( void ); +void execHeatersMonitor( void ); + +BOOL testHeatersDataPublishIntervalOverride( MESSAGE_T *message ); +BOOL testSetHeaterDutyCycleOverride( U32 heater, F32 value ); +BOOL testResetHeaterDutyCycleOverride( U32 heater ); + +/**@}*/ + +#endif Index: firmware/App/Services/AlarmMgmtSWFaults.h =================================================================== diff -u -rbbcdba8850dd6f6a53044eca6a5ece611654653a -r3b3833a0b1aed89f1ff66104519f658f5a41fa99 --- firmware/App/Services/AlarmMgmtSWFaults.h (.../AlarmMgmtSWFaults.h) (revision bbcdba8850dd6f6a53044eca6a5ece611654653a) +++ firmware/App/Services/AlarmMgmtSWFaults.h (.../AlarmMgmtSWFaults.h) (revision 3b3833a0b1aed89f1ff66104519f658f5a41fa99) @@ -124,6 +124,8 @@ SW_FAULT_ID_DIALYSATE_PUMP_INVALID_RPM_SELECTED = 93, SW_FAULT_ID_DIALYSATE_PUMP_EXEC_INVALID_STATE = 94, SW_FAULT_ID_DIALYSATE_PUMP_INVALID_PUMP_ID = 95, + SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED = 96, + SW_FAULT_ID_HEATERS_INVALID_EXEC_STATE = 97, NUM_OF_SW_FAULT_IDS } SW_FAULT_ID_T; Index: firmware/App/Services/FpgaDD.c =================================================================== diff -u -rbbcdba8850dd6f6a53044eca6a5ece611654653a -r3b3833a0b1aed89f1ff66104519f658f5a41fa99 --- firmware/App/Services/FpgaDD.c (.../FpgaDD.c) (revision bbcdba8850dd6f6a53044eca6a5ece611654653a) +++ firmware/App/Services/FpgaDD.c (.../FpgaDD.c) (revision 3b3833a0b1aed89f1ff66104519f658f5a41fa99) @@ -214,7 +214,7 @@ U16 fpgaDGPCurrentFeedback; ///< Reg 409. Fresh dialysate pump current feedback U16 fpgaSDPCurrentFeedback; ///< Reg 411. Spent dialysate pump current feedback U08 fpgaDGPHallStatus; ///< Reg 413. Fresh dialysate pump hall sensor direction status - U08 fpgaSDPHallStatus; ///< Reg 413. Spent dialysate pump hall sensor direction status + U08 fpgaSDPHallStatus; ///< Reg 414. Spent dialysate pump hall sensor direction status } DD_FPGA_SENSORS_T; typedef struct @@ -290,6 +290,8 @@ U08 fpgaBloodLeakSensorTest; ///< Reg 118. Blood leak sensor test U08 fpgaBloodLeakUARTControl; ///< Reg 119. Blood leak sensor UART control U08 fpgaBloodLeakFIFOTx; ///< Reg 120. Blood leak sensor FIFO transmit control + U08 fpgaPrimaryHeaterPWMControl; ///< Reg 121. Primary heater PWM control + U08 fpgaTrimmerHeaterPWMControl; ///< Reg 122. Trimmer heater PWM control } FPGA_ACTUATORS_T; #pragma pack(pop) @@ -1227,6 +1229,34 @@ /*********************************************************************//** * @brief + * The setFPGAPrimaryHeaterPWMControl function sets the primary heater + * PWM input. + * @details \b Inputs: none + * @details \b Outputs: fpgaPrimaryHeaterPWMControl + * @param control the PWM dutycycle to control the heater + * @return none + *************************************************************************/ +void setFPGAPrimaryHeaterPWMControl( U08 control ) +{ + fpgaActuatorSetPoints.fpgaPrimaryHeaterPWMControl = control; +} + +/*********************************************************************//** + * @brief + * The setFPGATrimmerHeaterPWMControl function sets the trimmer heater + * PWM input. + * @details \b Inputs: none + * @details \b Outputs: fpgaTrimmerHeaterPWMControl + * @param control the PWM dutycycle to control the heater + * @return none + *************************************************************************/ +void setFPGATrimmerHeaterPWMControl( U08 control ) +{ + fpgaActuatorSetPoints.fpgaTrimmerHeaterPWMControl = control; +} + +/*********************************************************************//** + * @brief * The getFPGAVersions function gets the FPGA version numbers. * @details \b Inputs: fpgaHeader * @details \b Outputs: none Index: firmware/App/Services/FpgaDD.h =================================================================== diff -u -rbbcdba8850dd6f6a53044eca6a5ece611654653a -r3b3833a0b1aed89f1ff66104519f658f5a41fa99 --- firmware/App/Services/FpgaDD.h (.../FpgaDD.h) (revision bbcdba8850dd6f6a53044eca6a5ece611654653a) +++ firmware/App/Services/FpgaDD.h (.../FpgaDD.h) (revision 3b3833a0b1aed89f1ff66104519f658f5a41fa99) @@ -204,6 +204,10 @@ U32 getFPGABaroPressure( void ); U32 getFPGABaroTemperature( void ); +//Primary and Trimmer heater +void setFPGAPrimaryHeaterPWMControl( U08 control ); +void setFPGATrimmerHeaterPWMControl( U08 control ); + /**@}*/ #endif