/************************************************************************** * * 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 "FpgaDD.h" #include "Heaters.h" #include "Level.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 "TDInterface.h" #include "Temperature.h" #include "Timers.h" #include "Utilities.h" /** * @addtogroup Heaters * @{ */ // ********** private definitions ********** #define AC_HEATER_TX_MAX_DUTY_CYCLE 0.50F ///< AC Heater treatment mode max duty cycle (50% of 1400W : 700W ) #define AC_HEATER_HEAT_MAX_DUTY_CYCLE 0.70F ///< AC Heater heat disinfect mode max duty cycle (70% of 1400W : 980W ) #define AC_HEATER_MAX_DUTY_CYCLE 1.0F ///< AC Heater max duty cycle (100.0%) or ON state #define DC_HEATER_MAX_DUTY_CYCLE 1.0F ///< DC Heater max duty cycle (100%) or ON state #define HEATERS_MIN_DUTY_CYCLE 0.0F ///< Heaters minimum duty cycle (0.00%) or OFF state #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 100.0F ///< Heaters duty cycle 0: OFF, 100: 100% duty cycle. #define HEATERS_ZERO_EFFICIENCY 0.0F ///< Zero heater efficiency #define HEATER_CNTL_TRANSFER_DELTA_TEMP_C 0.50F ///< AC heater delta temperature to transfer control from open to close loop #define D5_HEAT_TX_INIT_FEED_FORWARD 0.0F ///< Initial Feed forward term for heater control //#define D5_HEAT_TX_P_COEFFICIENT 0.050F ///< P Term for AC primary heater control during treatment mode. //#define D5_HEAT_TX_I_COEFFICIENT 0.015F ///< I Term for AC primary heater control during treatment mode. #define D5_HEAT_TX_P_COEFFICIENT 0.035F ///< P Term for AC primary heater control during treatment mode. #define D5_HEAT_TX_I_COEFFICIENT 0.004F ///< I Term for AC primary heater control during treatment mode. #define D45_HEAT_P_COEFFICIENT 0.20F ///< P Term for trimmer heater control. #define D45_HEAT_I_COEFFICIENT 0.05F ///< I Term for trimmer heater control. #define D45_HEAT_TX_INIT_FEED_FORWARD 0.0F ///< Initial Feed forward term for heater control #define HEATERS_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_PRIORITY_INTERVAL ) ///< Heaters data publish interval. #define HEATER_TEMP_CONTROL_TRANSFER 1.0F ///< Primary Heater temperature difference to switch to control function #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 D5_HEAT_ON_NO_FLUID_TIMEOUT_MS ( 10 * MS_PER_SECOND ) ///< Primary heater on with no flow time out in milliseconds. #define D45_HEAT_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 D5_HEATER_DEADBAND_CONTROL 0.1F ///< Heater deadband range for conrtol. //#define D5_HEAT_CONTROL_INTERVAL_MS 30000 /// Primary heater control interval in milli seconds #define D5_HEAT_CONTROL_INTERVAL_MS 3000 /// Primary heater control interval in milli seconds #define D5_HEAT_CONTROL_INTERVAL_COUNT ( D5_HEAT_CONTROL_INTERVAL_MS / TASK_GENERAL_INTERVAL ) ///< Primary heater control interval count. #define D45_HEAT_CONTROL_INTERVAL_MS ( 1 * MS_PER_SECOND ) ///< Trimmer heater control interval in milli seconds #define D45_HEAT_CONTROL_INTERVAL_COUNT ( D45_HEAT_CONTROL_INTERVAL_MS / TASK_GENERAL_INTERVAL ) ///< Trimmer heater control interval count. #define PRIMARY_HEATER_MAX_PWR_WATTS 1400.0F ///< AC Primary Heater Max Power consumeption in Watts #define TX_PRIMARY_HEATER_MAX_PWR_WATTS 700.0F ///< Estimated power to be supplied to the primary heater during treatement mode #define HEAT_PRIMARY_HEATER_MAX_PWR_WATTS 980.0F ///< Estimated power to be supplied to the primary heater during heat disinfect mode #define MAX_INLET_FLOW_LPM ( 600.0F / 1000.0F ) ///< Maximum inlet flow to hydraulics chamber from FP #define LITER_IN_ML 1000.0F ///< Liter in milliliter units #define TRIMMER_HEATER_MAX_PWR_WATTS 120.0F ///< Maximum power supplied to trimmer heater #define AC_HEATER_PWM_PERIOD 10000 ///< PWM period 100 ms( in 10us resoultion), 1/10Hz = 1000000us/10us = 10000. #define AC_HEATER_EFFICIENCY 0.90F ///< Approximated AC heater efficiency to be used in energy calcualtions. #define DC_HEATER_EFFICIENCY 1.0F ///< DC heater efficiency #define D5_HEAT_CONTROL_INTERVAL_START_COUNT ( D5_HEAT_CONTROL_INTERVAL_COUNT - 10 ) ///< AC heater control interval start count to jump feedforward control from open loop. #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 { HEATERS_STATE_T state; ///< Heater state. BOOL startHeaterSignal; ///< Heater start indication flag. BOOL heaterOnState; ///< Heater on/off status flag. 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. 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; /// Payload record structure for heaters start request typedef struct { U32 heaterID; ///< Heater ID (0: Primary, 1: Trimmer) U32 startStop; ///< Heater Start:1 and Stop: 0 F32 temperature; ///< Temperature range ( 10.0 to 90.0 deg ) } HEATER_START_CMD_PAYLOAD_T; static HEATER_STATUS_T heatersStatus[ NUM_OF_DD_HEATERS ]; ///< Heaters status. static OVERRIDE_F32_T targetTempC[ NUM_OF_DD_HEATERS ]; ///< Heater target temperature. static OVERRIDE_F32_T control[ NUM_OF_DD_HEATERS ]; ///< Heater control ( Primary : On/Off, Trimmer : Dutycycle). static OVERRIDE_F32_T pwmPeriod[ NUM_OF_DD_HEATERS ]; ///< Total PWM period ( ON state + Off State of PWM) static U32 controlInterval[ NUM_OF_DD_HEATERS ]; ///< Heater control interval time. static U32 dataPublicationTimerCounter; ///< Data publication timer counter. static const F32 WATER_SPECIFIC_HEAT_DIVIDED_BY_MINUTES = 4184.0F / (F32)SEC_PER_MIN; ///< Water specific heat in J/KgC / 60. static OVERRIDE_U32_T heatersDataPublishInterval = { HEATERS_DATA_PUBLISH_INTERVAL, HEATERS_DATA_PUBLISH_INTERVAL, 0, 0 }; ///< Heaters data publish time interval. static F32 convertDC; ///< AC Heater converted duty cycle static F32 lastDialTargetTemperatureSet; ///< last dialysate target temperature set for heater control static BOOL startupHeaterControl; ///< First time control with the energy equation. //For testing #ifdef __HEATERS_DEBUG__ static F32 pIControlSignal[ NUM_OF_CONTROLLER_SIGNAL ]; #endif // ********** private function prototypes ********** static HEATERS_STATE_T handleHeaterStateOff( DD_HEATERS_T heater ); static HEATERS_STATE_T handleHeaterStateRampToTarget( DD_HEATERS_T heater ); static HEATERS_STATE_T handleHeaterStateControlToTarget( DD_HEATERS_T heater ); static HEATERS_STATE_T handleHeaterStateControlToDisinfectTarget( DD_HEATERS_T heater ); static F32 calculateDutyCycle( F32 flowrate, F32 deltaTemp, F32 power, F32 efficiency, F32 min, F32 max ); static void setHeaterControl( DD_HEATERS_T heater ); static F32 getHeaterControl( 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; #ifdef __HEATERS_DEBUG__ U32 i; #endif // Assign start count dataPublicationTimerCounter = DATA_PUBLISH_COUNTER_START_COUNT; //Enable PWM based heater control setFPGAD5HeaterPWMEnableControl( TRUE ); // Define PWM period for AC heater pwmPeriod[ D5_HEAT ].data = AC_HEATER_PWM_PERIOD; pwmPeriod[ D5_HEAT ].ovData = AC_HEATER_PWM_PERIOD; pwmPeriod[ D5_HEAT ].ovInitData = AC_HEATER_PWM_PERIOD; pwmPeriod[ D5_HEAT ].override = OVERRIDE_RESET; // TODO : Trimmer heater PWM period pwmPeriod[ D45_HEAT ].data = 0.0F; pwmPeriod[ D45_HEAT ].ovData = 0.0F; pwmPeriod[ D45_HEAT ].ovInitData = 0.0F; pwmPeriod[ D45_HEAT ].override = OVERRIDE_RESET; //Initialize Converted Duty cycle convertDC = 0.0F; controlInterval[ D5_HEAT ] = D5_HEAT_CONTROL_INTERVAL_COUNT; controlInterval[ D45_HEAT ] = D45_HEAT_CONTROL_INTERVAL_COUNT; // Assign counter close to the target period heatersStatus[ D5_HEAT ].controlIntervalCounter = D5_HEAT_CONTROL_INTERVAL_START_COUNT; heatersStatus[ D45_HEAT ].controlIntervalCounter = 0; startupHeaterControl = TRUE; lastDialTargetTemperatureSet = 0.0F; for ( heater = DD_HEATERS_FIRST; heater < NUM_OF_DD_HEATERS; heater++ ) { targetTempC[ heater ].data = 0.0F; targetTempC[ heater ].ovData = 0.0F; targetTempC[ heater ].ovInitData = 0.0F; targetTempC[ heater ].override = OVERRIDE_RESET; heatersStatus[ heater ].state = HEATER_EXEC_STATE_OFF; heatersStatus[ heater ].startHeaterSignal = FALSE; heatersStatus[ heater ].heaterOnState = FALSE; control[ heater ].data = HEATERS_MIN_DUTY_CYCLE; control[ heater ].ovData = HEATERS_MIN_DUTY_CYCLE; control[ heater ].ovInitData = HEATERS_MIN_DUTY_CYCLE; control[ heater ].override = OVERRIDE_RESET; heatersStatus[ heater ].targetFlowLPM = 0.0F; heatersStatus[ heater ].nomTargetFlowLPM = 0.0F; heatersStatus[ heater ].hasTargetTempChanged = FALSE; heatersStatus[ heater ].isThisFirstControl = TRUE; heatersStatus[ heater ].prevDiaTargetFlowLPM = 0.0F; setHeaterControl( heater ); } // Initialize the primary heater PI controller initializePIController( PI_CONTROLLER_ID_D5_HEAT, HEATERS_MIN_DUTY_CYCLE, D5_HEAT_TX_P_COEFFICIENT, D5_HEAT_TX_I_COEFFICIENT, HEATERS_MIN_DUTY_CYCLE, AC_HEATER_TX_MAX_DUTY_CYCLE, TRUE, D5_HEAT_TX_INIT_FEED_FORWARD ); // Initialize the trimmer heater PI controller initializePIController( PI_CONTROLLER_ID_D45_HEAT, HEATERS_MIN_DUTY_CYCLE, D45_HEAT_P_COEFFICIENT, D45_HEAT_I_COEFFICIENT, HEATERS_MIN_DUTY_CYCLE, DC_HEATER_MAX_DUTY_CYCLE, FALSE, D45_HEAT_TX_INIT_FEED_FORWARD ); #ifdef __HEATERS_DEBUG__ for ( i = 0; i < NUM_OF_CONTROLLER_SIGNAL; i++ ) { pIControlSignal[ i ] = 0.0F; } #endif // Initialize the persistent alarms //initPersistentAlarm( ALARM_ID_DD_D5_HEAT_VOLTAGE_OUT_OF_RANGE, 0, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); //initPersistentAlarm( ALARM_ID_DD_D45_HEAT_VOLTAGE_OUT_OF_RANGE, 0, HEATERS_VOLTAGE_OUT_OF_RANGE_TIMEOUT_MS ); initPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_D5_HEAT_IS_ON, 0, D5_HEAT_ON_NO_FLUID_TIMEOUT_MS ); initPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_D45_HEAT_IS_ON, 0, D45_HEAT_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 ) ) && ( lastDialTargetTemperatureSet != targetTemperature ) ) { targetTempC[ heater ].data = targetTemperature; heatersStatus[ heater ].hasTargetTempChanged = TRUE; result = TRUE; lastDialTargetTemperatureSet = targetTemperature; if ( D5_HEAT == heater ) { startupHeaterControl = 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: targetTempC * @param heater: heater ID to get heater target temperature. * @return the given heater target temperature *************************************************************************/ F32 getHeaterTargetTemperature( DD_HEATERS_T heater ) { F32 targetTemp = getF32OverrideValue( &targetTempC[ heater ] ); return targetTemp; } /*********************************************************************//** * @brief * The getHeaterPWMPeriod function returns the given heater PWM * period. * @details \b Inputs: none * @details \b Outputs: pwmPeriod * @param heater: heater ID to get heater PWM period. * @return the given heater PWM period. *************************************************************************/ F32 getHeaterPWMPeriod( DD_HEATERS_T heater ) { F32 pwmPrd = getF32OverrideValue( &pwmPeriod[ heater ] ); return pwmPrd; } /*********************************************************************//** * @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 ].heaterOnState; } /*********************************************************************//** * @brief * The signalHeaterControlOnQDUpdate function updates heater control based on * the latest treatment paratmeter (Dialysate Flow rate) change * @details \b Inputs: heatersStatus * @details \b Outputs: none * @param heater: heater ID to update the heater control. * @return none *************************************************************************/ void signalHeaterControlOnQDUpdate( DD_HEATERS_T heater ) { if ( D5_HEAT == heater ) { // check heater state if ( HEATER_EXEC_STATE_CONTROL_TO_TARGET == heatersStatus[ heater ].state ) { // Set flag to recalculate the feedforward signals startupHeaterControl = TRUE; } } } /*********************************************************************//** * @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 None *************************************************************************/ void startHeater( DD_HEATERS_T heater ) { if( heater < NUM_OF_DD_HEATERS ) { if ( HEATER_EXEC_STATE_OFF == heatersStatus[ heater ].state ) { heatersStatus[ heater ].startHeaterSignal = TRUE; } } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED, heater ) } } /*********************************************************************//** * @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 ].heaterOnState = FALSE; control[ heater ].data = HEATERS_MIN_DUTY_CYCLE; heatersStatus[ heater ].state = HEATER_EXEC_STATE_OFF; //Update control interval counter if ( D5_HEAT == heater ) { heatersStatus[ D5_HEAT ].controlIntervalCounter = D5_HEAT_CONTROL_INTERVAL_START_COUNT; startupHeaterControl = TRUE; } else { heatersStatus[ D45_HEAT ].controlIntervalCounter = 0; } // update duty cycle setHeaterControl( heater ); } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED, heater ) } } /*********************************************************************//** * @brief * The execHeatersControl 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 execHeatersControl( void ) { DD_HEATERS_T heater; for ( heater = DD_HEATERS_FIRST; heater < NUM_OF_DD_HEATERS; heater++ ) { switch( heatersStatus[ heater ].state ) { case HEATER_EXEC_STATE_OFF: heatersStatus[ heater ].state = handleHeaterStateOff( heater ); break; case HEATER_EXEC_STATE_RAMP_TO_TARGET: heatersStatus[ heater ].state = handleHeaterStateRampToTarget( heater ); break; case HEATER_EXEC_STATE_CONTROL_TO_TARGET: heatersStatus[ heater ].state = handleHeaterStateControlToTarget( heater ); break; case HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET: heatersStatus[ heater ].state = handleHeaterStateControlToDisinfectTarget( heater ); break; default: // The heater is in an unknown state. Turn it off and switch to not running state stopHeater( heater ); 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 ].heaterOnState ) { // stop the heater stopHeater( heater ); } } } /*********************************************************************//** * @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_HEATERS_FIRST; 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 ].heaterOnState ) { ALARM_ID_T alarm; BOOL isLevelLow = FALSE; if ( D5_HEAT == heater ) { alarm = ALARM_ID_DD_FLUID_TOO_LOW_WHILE_D5_HEAT_IS_ON; isLevelLow = ( ( getLevelStatus( D6_LEVL ) != 0 )? FALSE : TRUE ); } else { alarm = ALARM_ID_DD_FLUID_TOO_LOW_WHILE_D45_HEAT_IS_ON; isLevelLow = ( ( getLevelStatus( D46_LEVL ) != 0 )? FALSE : TRUE ); } checkPersistentAlarm( alarm, isLevelLow, 0.0F, 0.0F ); } else { if ( D5_HEAT == heater ) { checkPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_D5_HEAT_IS_ON, FALSE, 0.0F, 0.0F ); } else { checkPersistentAlarm( ALARM_ID_DD_FLUID_TOO_LOW_WHILE_D45_HEAT_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 ].heaterOnState = TRUE; heatersStatus[ heater ].startHeaterSignal = FALSE; // proceed to setup state state = HEATER_EXEC_STATE_RAMP_TO_TARGET; } return state; } /*********************************************************************//** * @brief * The handleHeaterStateRampToTarget function handles the given heaters' * control while they are ramping to target temperature. * @details \b Inputs: heaterStatus * @details \b Outputs: heaterStatus * @param heater: The heater Id to handle the initial setup control for heater. * @return next state of the state machine *************************************************************************/ static HEATERS_STATE_T handleHeaterStateRampToTarget( DD_HEATERS_T heater ) { HEATERS_STATE_T state = HEATER_EXEC_STATE_RAMP_TO_TARGET; F32 ctrl = 0.0F; DD_OP_MODE_T opMode = getCurrentOperationMode(); F32 measuredTemperature = 0.0F; F32 targetTemperature = getHeaterTargetTemperature( heater ); if ( D5_HEAT == heater ) { measuredTemperature = getD4AverageTemperature(); if ( DD_MODE_HEAT != opMode ) { F32 deltaTempC = measuredTemperature - targetTemperature; F32 capDeltaTempC = MAX( deltaTempC, HEATERS_ZERO_DELTA_TEMP_C ); if ( capDeltaTempC >= HEATER_CNTL_TRANSFER_DELTA_TEMP_C ) { // Transfer Control to target when delta temp is minimal. state = HEATER_EXEC_STATE_CONTROL_TO_TARGET; } else { // Start Open loop control with max power control[ heater ].data = AC_HEATER_TX_MAX_DUTY_CYCLE; } } else { // TODO : Calculate required duty cycle state = HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET; } } else { measuredTemperature = getD50AverageTemperature(); if ( DD_MODE_HEAT != opMode ) { F32 deltaTempC = targetTemperature - measuredTemperature; F32 capDeltaTempC = MAX( deltaTempC, HEATERS_ZERO_DELTA_TEMP_C ); F32 flowrate = getTDDialysateFlowrate() / LITER_IN_ML ; F32 dutyCycle = calculateDutyCycle( flowrate, capDeltaTempC, TRIMMER_HEATER_MAX_PWR_WATTS, DC_HEATER_EFFICIENCY, HEATERS_MIN_DUTY_CYCLE, DC_HEATER_MAX_DUTY_CYCLE ); control[ heater ].data = dutyCycle; resetPIController( PI_CONTROLLER_ID_D45_HEAT, dutyCycle, D45_HEAT_TX_INIT_FEED_FORWARD ); state = HEATER_EXEC_STATE_CONTROL_TO_TARGET; } else { // TODO : Calculate required duty cycle state = HEATER_EXEC_STATE_CONTROL_TO_DISINFECT_TARGET; } // Update the duty cycle control[ heater ].data = ctrl; } setHeaterControl( heater ); return state; } /*********************************************************************//** * @brief * The handleHeaterStateControlToTarget function handles the given * heaters' control to target while the heater is targeting to reach to temperature. * @details \b Inputs: heaterStatus * @details \b Outputs: heaterStatus * @details \b Alarms: ALARM_ID_DD_SOFTWARE_FAULT when invalid heater ID passed * @param heater: The heater Id to handle the control to target state * @return next state of the state machine *************************************************************************/ static HEATERS_STATE_T handleHeaterStateControlToTarget( DD_HEATERS_T heater ) { HEATERS_STATE_T state = HEATER_EXEC_STATE_CONTROL_TO_TARGET; F32 targetTemperature = getHeaterTargetTemperature( heater ); F32 inletTemperature = 0.0F; F32 measuredTemperature = 0.0F; F32 ctrl = 0.0F; if ( ++heatersStatus[ heater ].controlIntervalCounter >= controlInterval[ heater ] ) { if ( D5_HEAT == heater ) { measuredTemperature = getD4AverageTemperature(); // Inlet temperature post heat exchanger inletTemperature = getTemperatureValue( X6_TEMP ); if ( TRUE == startupHeaterControl ) { startupHeaterControl = FALSE; F32 deltaTempC = targetTemperature - inletTemperature; F32 capDeltaTempC = MAX( deltaTempC, HEATERS_ZERO_DELTA_TEMP_C ); F32 flowrate = getTDDialysateFlowrate() / LITER_IN_ML ; F32 feedforward = calculateDutyCycle( flowrate, capDeltaTempC, PRIMARY_HEATER_MAX_PWR_WATTS, AC_HEATER_EFFICIENCY, HEATERS_MIN_DUTY_CYCLE, AC_HEATER_TX_MAX_DUTY_CYCLE ); control[ heater ].data = feedforward; resetPIController( PI_CONTROLLER_ID_D5_HEAT, HEATERS_MIN_DUTY_CYCLE, feedforward ); } else { F32 deltaTempC = fabs( targetTemperature - measuredTemperature ); if ( deltaTempC >= D5_HEATER_DEADBAND_CONTROL ) { ctrl = runPIController( PI_CONTROLLER_ID_D5_HEAT, targetTemperature, measuredTemperature ); control[ heater ].data = ctrl; } } #ifdef __HEATERS_DEBUG__ U32 i; for ( i = 0; i < NUM_OF_CONTROLLER_SIGNAL; i++ ) { pIControlSignal[ i ] = getPIControllerSignals( PI_CONTROLLER_ID_D5_HEAT, (PI_CONTROLLER_SIGNALS_ID)i ); } #endif } else { measuredTemperature = getD50AverageTemperature(); ctrl = runPIController( PI_CONTROLLER_ID_D45_HEAT, targetTemperature, measuredTemperature ); control[ heater ].data = ctrl; //#ifdef __HEATERS_DEBUG__ // U32 i; // // for ( i = 0; i < NUM_OF_CONTROLLER_SIGNAL; i++ ) // { // pIControlSignal[ i ] = getPIControllerSignals( PI_CONTROLLER_ID_D45_HEAT, (PI_CONTROLLER_SIGNALS_ID)i ); // } //#endif } heatersStatus[ heater ].hasTargetTempChanged = FALSE; heatersStatus[ heater ].controlIntervalCounter = 0; setHeaterControl( 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 setHeaterControl( D45_HEAT ); setHeaterControl( D5_HEAT ); return state; } /*********************************************************************//** * @brief * The setHeaterControl function sets the duty cycle of a heater. * @details \b Inputs: control * @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 setHeaterControl( DD_HEATERS_T heater ) { if ( heater < NUM_OF_DD_HEATERS ) { F32 control = getHeaterControl( heater ); F32 period = getHeaterPWMPeriod ( heater ); if ( D5_HEAT == heater ) { //Convert duty cycle into LowState and multiply by period F32 duty = AC_HEATER_MAX_DUTY_CYCLE - control; convertDC = duty * period; setFPGAD5HeaterPWMPeriod( (U16)period ); setFPGAD5HeaterPWMLowState( (U16)convertDC ); } else { U08 ctrl = ( control * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR ) + FLOAT_TO_INT_ROUNDUP_OFFSET; setFPGAD45HeaterPWMControl( ctrl ); } } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEATERS_INVALID_HEATER_ID_SELECTED, heater ); } } /*********************************************************************//** * @brief * The getHeaterControl 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 getHeaterControl( DD_HEATERS_T heater ) { F32 duty = getF32OverrideValue( &control[ heater ] ); return duty; } /*********************************************************************//** * @brief * The calculateDutyCycle function calculates the heater's duty cycle * based on the delta temperature, flowrate and power applied to the heater. * @details \b Inputs: none * @details \b Outputs: duty cycle * @param flowrate: dialysate flowrate. * @param deltaTemp: Temperature difference between actual and target. * @param power: Power supplied to the heater * @param efficiency: Heater efficeincy to compensate heatloss * @param min: heaters minimum duty cycle * @param max: heaters maximum duty cycle * @return calcualted PWM duty cycle *************************************************************************/ static F32 calculateDutyCycle( F32 flowrate, F32 deltaTemp, F32 power, F32 efficiency, F32 min, F32 max ) { F32 duty = ( WATER_SPECIFIC_HEAT_DIVIDED_BY_MINUTES * deltaTemp * flowrate ) / power; // Updated duty with efficiency if ( HEATERS_ZERO_EFFICIENCY != efficiency ) { duty = duty / efficiency; } // Apply min/max limits duty = MIN( duty, max ); duty = MAX( duty, min ); 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_D5_HEAT_VOLTAGE_OUT_OF_RANGE when * primary heater voltage found out of range. * @details \b Alarms: ALARM_ID_D45_HEAT_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 = getHeaterControl( D5_HEAT ); // F32 trimmerDC = getHeaterControl( D45_HEAT ); // // // 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_D5_HEAT_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_D5_HEAT_VOLTAGE_OUT_OF_RANGE, isMainPriOut, mainPriDC, HEATERS_VOLTAGE_TOLERANCE_V ); // checkPersistentAlarm( ALARM_ID_D45_HEAT_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.d5_HeaterDC = getHeaterControl( D5_HEAT ) * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR; data.d45_HeaterDC = getHeaterControl( D45_HEAT ) * HEATERS_DUTY_CYCLE_CONVERSION_FACTOR; data.d5_HeaterTargetTemp = getHeaterTargetTemperature( D5_HEAT ); data.d45_HeaterTargetTemp = getHeaterTargetTemperature( D45_HEAT ); data.d5_HeaterState = heatersStatus[ D5_HEAT ].state; data.d45_HeaterState = heatersStatus[ D45_HEAT ].state; #ifndef __HEATERS_DEBUG__ data.d5_HeaterControlCounter = heatersStatus[ D5_HEAT ].controlIntervalCounter; data.d45_HeaterControlCounter = heatersStatus[ D45_HEAT ].controlIntervalCounter; #else data.d5_HeaterControlCounter = (U32)convertDC; data.d45_HeaterControlCounter = (U32)getHeaterPWMPeriod( D5_HEAT ); #endif #ifdef __HEATERS_DEBUG__ data.dbg1 = pIControlSignal[ 0 ]; data.dbg2 = pIControlSignal[ 1 ]; data.dbg3 = pIControlSignal[ 2 ]; data.dbg4 = pIControlSignal[ 3 ]; data.dbg5 = pIControlSignal[ 4 ]; data.dbg6 = pIControlSignal[ 5 ]; data.dbg7 = pIControlSignal[ 6 ]; data.dbg8 = pIControlSignal[ 7 ]; data.dbg9 = pIControlSignal[ 8 ]; #endif 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 testHeaterDutyCycleOverride function overrides the specified heater's * duty cycle. * @details \b Inputs: heatersStatus * @details \b Outputs: heatersStatus * @param message Override message from Dialin which includes an ID of * the heater to override and the duty cyle of the heater. * @return TRUE if the override was successful otherwise FALSE *************************************************************************/ BOOL testHeaterDutyCycleOverride( MESSAGE_T *message ) { BOOL result = f32ArrayOverride( message, &control[ 0 ], NUM_OF_DD_HEATERS - 1 ); return result; } /*********************************************************************//** * @brief * The testHeaterTargetTemperatureOverride function overrides the specified heater's * target temperature. * @details \b Inputs: targetTempC * @details \b Outputs: targetTempC * @param message Override message from Dialin which includes an ID of * the heater to override and the target temperature of the heater. * @return TRUE if the override was successful otherwise FALSE *************************************************************************/ BOOL testHeaterTargetTemperatureOverride( MESSAGE_T *message ) { BOOL result = f32ArrayOverride( message, &targetTempC[ 0 ], NUM_OF_DD_HEATERS - 1 ); return result; } /*********************************************************************//** * @brief * The testHeaterPWMPeriodOverride function overrides the specified heater's * PWM period. * @details \b Inputs: pwmPeriod * @details \b Outputs: pwmPeriod * @param message Override message from Dialin which includes an ID of * the heater to override and the PWM period of the heater. * @return TRUE if the override was successful otherwise FALSE *************************************************************************/ BOOL testHeaterPWMPeriodOverride( MESSAGE_T *message ) { BOOL result = f32ArrayOverride( message, &pwmPeriod[ 0 ], NUM_OF_DD_HEATERS - 1 ); return result; } /*********************************************************************//** * @brief * The testHeaterStartStopOverride function starts/stops a given heater * at mentioned temperature. * @details \b Inputs: tester logged in * @details \b Outputs: heatersStatus[] * @param message set message from Dialin which includes the heater to set * and the state to set the heater to. * @return TRUE if set request is successful, FALSE if not *************************************************************************/ BOOL testHeaterStartStopOverride( MESSAGE_T *message ) { BOOL result = FALSE; // Verify tester has logged in with DD if ( TRUE == isTestingActivated() ) { // Verify payload length is valid if ( sizeof( HEATER_START_CMD_PAYLOAD_T ) == message->hdr.payloadLen ) { HEATER_START_CMD_PAYLOAD_T payload; memcpy( &payload, message->payload, sizeof(HEATER_START_CMD_PAYLOAD_T) ); if ( (DD_HEATERS_T)payload.heaterID < NUM_OF_DD_HEATERS ) { // Handle start command if ( ( TRUE == payload.startStop ) && ( ( payload.temperature >= HEATER_TARGET_TEMPERATURE_MIN ) && ( payload.temperature <= HEATER_TARGET_TEMPERATURE_MAX ) ) ) { setHeaterTargetTemperature( (DD_HEATERS_T)payload.heaterID, payload.temperature ); startHeater( (DD_HEATERS_T)payload.heaterID ); result = TRUE; } //Handle stop command if ( FALSE == payload.startStop ) { stopHeater( (DD_HEATERS_T)payload.heaterID ); result = TRUE; } } } } return result; } /**@}*/