/************************************************************************** * * Copyright (c) 2019-2020 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 DialInFlow.c * * @date 16-Dec-2019 * @author S. Nash * * @brief Monitor/Controller for dialysate inlet pump and flow sensor. * **************************************************************************/ #ifndef _VECTORCAST_ #include #endif #include "etpwm.h" #include "gio.h" #include "mibspi.h" #include "FPGA.h" #include "InternalADC.h" #include "OperationModes.h" #include "PIControllers.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "TaskPriority.h" #include "Timers.h" #include "DialInFlow.h" /** * @addtogroup DialysateInletFlow * @{ */ // ********** private definitions ********** #define DIAL_IN_FLOW_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_PRIORITY_INTERVAL ) ///> interval (ms/task time) at which the dialIn flow data is published on the CAN bus #define MAX_DIAL_IN_PUMP_PWM_STEP_CHANGE 0.01 ///> duty cycle TODO - fixed or parameterized or set in motor controller? #define MAX_DIAL_IN_PUMP_PWM_DUTY_CYCLE 0.88 ///> controller will error if PWM duty cycle > 90%, so set max to 88% #define MIN_DIAL_IN_PUMP_PWM_DUTY_CYCLE 0.12 ///> controller will error if PWM duty cycle < 10%, so set min to 12% #define DIP_CONTROL_INTERVAL ( 1000 / TASK_GENERAL_INTERVAL ) ///> interval (ms/task time) at which the dialIn pump is controlled #define DIP_P_COEFFICIENT 0.00005 ///> P term for dialIn pump control #define DIP_I_COEFFICIENT 0.00015 ///> I term for dialIn pump control #define DIP_HOME_RATE 50 ///< target pump speed (in estimate mL/min) for homing. #define DIP_HOME_TIMEOUT_MS 10000 ///< maximum time allowed for homing to complete (in ms). #define DIP_MAX_CURR_WHEN_STOPPED_MA 150.0 ///> motor controller current should not exceed this when pump should be stopped #define DIP_MIN_CURR_WHEN_RUNNING_MA 150.0 ///> motor controller current should always exceed this when pump should be running #define DIP_MAX_CURR_WHEN_RUNNING_MA 1000.0 ///> motor controller current should not exceed this when pump should be running #define DIP_MAX_CURR_ERROR_DURATION_MS 2000 ///> motor controller current errors persisting beyond this duration will trigger an alarm #define DIP_SPEED_ADC_TO_RPM_FACTOR 1.280938 ///> conversion factor from ADC counts to RPM for dialIn pump motor #define DIP_CURRENT_ADC_TO_MA_FACTOR 3.002 ///> conversion factor from ADC counts to mA for dialIn pump motor #define DIP_REV_PER_LITER 150.24 ///> rotor revolutions per liter #define DIP_ML_PER_MIN_TO_PUMP_RPM_FACTOR ( DIP_REV_PER_LITER / ML_PER_LITER ) ///> #define DIP_GEAR_RATIO 32.0 ///> dialIn pump motor to dialIn pump gear ratio #define DIP_MOTOR_RPM_TO_PWM_DC_FACTOR 0.00035 ///> ~28 BP motor RPM = 1% PWM duty cycle #define DIP_PWM_ZERO_OFFSET 0.1 ///> 10% PWM duty cycle = zero speed #define DIP_PWM_FROM_ML_PER_MIN(rate) ( (rate) * DIP_ML_PER_MIN_TO_PUMP_RPM_FACTOR * DIP_GEAR_RATIO * DIP_MOTOR_RPM_TO_PWM_DC_FACTOR + DIP_PWM_ZERO_OFFSET ) ///> #define DIAL_IN_PUMP_ADC_FULL_SCALE_V 3.0 ///> BP analog signals are 0-3V (while int. ADC ref V may be different) #define DIAL_IN_PUMP_ADC_ZERO ( (F32)( INT_ADC_ZERO ) * ( DIAL_IN_PUMP_ADC_FULL_SCALE_V / INT_ADC_REF_V ) ) ///> #define SIGN_FROM_12_BIT_VALUE(v) ( (S16)(v) - (S16)DIAL_IN_PUMP_ADC_ZERO ) ///> #define DIAL_IN_FLOW_SAMPLE_FREQ ( MS_PER_SECOND / TASK_PRIORITY_INTERVAL ) ///> #define SIZE_OF_ROLLING_AVG ( DIAL_IN_FLOW_SAMPLE_FREQ * 2 ) ///> measured dialIn flow is filtered w/ moving average #define MAX_FLOW_FILTER_INTERVAL 5 ///> slowest sample interval for filter is every 5th sample /// Enumeration of dialysate inlet pump states. typedef enum DialInPump_States { DIAL_IN_PUMP_OFF_STATE = 0, ///< Off state for the dialysate inlet pump. DIAL_IN_PUMP_RAMPING_UP_STATE, ///< Ramping up state for the dialysate inlet pump. DIAL_IN_PUMP_RAMPING_DOWN_STATE, ///< Ramping down state for the dialysate inlet pump. DIAL_IN_PUMP_CONTROL_TO_TARGET_STATE, ///< Control to target state for the dialysate inlet pump. NUM_OF_DIAL_IN_PUMP_STATES ///< Number of dialysate inlet pump states. } DIAL_IN_PUMP_STATE_T; /// Enumeration of dialysate inlet self test states. typedef enum DialInFlow_Self_Test_States { DIAL_IN_FLOW_SELF_TEST_STATE_START = 0, ///< Start state for the dialysate inlet pump self test. DIAL_IN_FLOW_TEST_STATE_IN_PROGRESS, ///< Test in progress state for the dialysate inlet pump self test. DIAL_IN_FLOW_TEST_STATE_COMPLETE, ///< Test completed state for the dialysate inlet pump self test. NUM_OF_DIAL_IN_FLOW_SELF_TEST_STATES ///< Number of dialysate inlet pump self test states. } DIAL_IN_FLOW_SELF_TEST_STATE_T; // pin assignments for pump stop and direction outputs #define STOP_DI_PUMP_GIO_PORT_PIN 2U ///< Pin # on GIO A for stopping the dialysate inlet pump. #define DIR_DI_PUMP_SPI5_PORT_MASK 0x00000100 ///< Pin on unused SPI5 peripheral (ENA) - re-purposed as output GPIO to set dialysate inlet pump direction. // dialIn pump stop and direction macros #define SET_DIP_DIR() {mibspiREG5->PC3 |= DIR_DI_PUMP_SPI5_PORT_MASK;} ///< Macro for setting the dialysate inlet pump direction pin high. #define CLR_DIP_DIR() {mibspiREG5->PC3 &= ~DIR_DI_PUMP_SPI5_PORT_MASK;} ///< Macro for setting the dialysate inlet pump direction pin low. #ifndef BREADBOARD_TARGET #define SET_DIP_STOP() gioSetBit( gioPORTA, STOP_DI_PUMP_GIO_PORT_PIN, PIN_SIGNAL_LOW ) ///< Macro for setting the dialysate inlet pump stop pin low. #define CLR_DIP_STOP() gioSetBit( gioPORTA, STOP_DI_PUMP_GIO_PORT_PIN, PIN_SIGNAL_HIGH ) ///< Macro for setting the dialysate inlet pump stop pin high. #else #define SET_DIP_STOP() gioSetBit( gioPORTA, STOP_DI_PUMP_GIO_PORT_PIN, PIN_SIGNAL_HIGH ) #define CLR_DIP_STOP() gioSetBit( gioPORTA, STOP_DI_PUMP_GIO_PORT_PIN, PIN_SIGNAL_LOW ) #endif // ********** private data ********** static DIAL_IN_PUMP_STATE_T dialInPumpState = DIAL_IN_PUMP_OFF_STATE; ///< current state of dialIn flow controller state machine static U32 dialInFlowDataPublicationTimerCounter = 5; ///< used to schedule dialIn flow data publication to CAN bus static BOOL isDialInPumpOn = FALSE; ///< dialIn pump is currently running static F32 dialInPumpPWMDutyCyclePct = 0.0; ///< initial dialIn pump PWM duty cycle static F32 dialInPumpPWMDutyCyclePctSet = 0.0; ///< currently set dialIn pump PWM duty cycle static MOTOR_DIR_T dialInPumpDirection = MOTOR_DIR_FORWARD; ///< requested dialIn flow direction static MOTOR_DIR_T dialInPumpDirectionSet = MOTOR_DIR_FORWARD; ///< currently set dialIn flow direction static PUMP_CONTROL_MODE_T dialInPumpControlMode = PUMP_CONTROL_MODE_CLOSED_LOOP; ///< requested dialIn pump control mode. static PUMP_CONTROL_MODE_T dialInPumpControlModeSet = PUMP_CONTROL_MODE_CLOSED_LOOP;///< currently set dialIn pump control mode. DATA_DECL( U32, DialInFlowDataPub, dialInFlowDataPublishInterval, DIAL_IN_FLOW_DATA_PUB_INTERVAL, DIAL_IN_FLOW_DATA_PUB_INTERVAL ); ///< interval (in ms) at which to publish dialIn flow data to CAN bus DATA_DECL( S32, TargetDialInFlowRate, targetDialInFlowRate, 0, 0 ); ///< requested dialIn flow rate DATA_DECL( F32, MeasuredDialInFlowRate, measuredDialInFlowRate, 0.0, 0.0 ); ///< measured dialIn flow rate DATA_DECL( F32, MeasuredDialInPumpRotorSpeed, dialInPumpRotorSpeedRPM, 0.0, 0.0 ); ///< measured dialIn pump rotor speed DATA_DECL( F32, MeasuredDialInPumpSpeed, dialInPumpSpeedRPM, 0.0, 0.0 ); ///< measured dialIn pump motor speed DATA_DECL( F32, MeasuredDialInPumpMCSpeed, adcDialInPumpMCSpeedRPM, 0.0, 0.0 ); ///< measured dialIn pump motor controller speed DATA_DECL( F32, MeasuredDialInPumpMCCurrent, adcDialInPumpMCCurrentmA, 0.0, 0.0 ); ///< measured dialIn pump motor controller current static U32 dipControlTimerCounter = 0; ///< determines when to perform control on dialIn flow static U32 dipRotorRevStartTime = 0; ///< dialysate inlet pump rotor rotation start time (in ms) static BOOL dipStopAtHomePosition = FALSE; ///< stop dialysate inlet pump at next home position static U32 dipHomeStartTime = 0; ///< when did dialysate inlet pump home command begin? (in ms) static F32 flowReadings[ SIZE_OF_ROLLING_AVG ]; ///< holds flow samples for a rolling average static U32 flowReadingsIdx = 0; ///< index for next sample in rolling average array static F32 flowReadingsTotal = 0.0; ///< rolling total - used to calc average static U32 flowReadingsCount = 0; ///< # of samples in flow rolling average buffer static U32 flowReadingsTmrCtr = 0; ///< determines when to add samples to filter static U32 dipCurrErrorDurationCtr = 0; ///< used for tracking persistence of dip current errors static DIAL_IN_FLOW_SELF_TEST_STATE_T dialInPumpSelfTestState = DIAL_IN_FLOW_SELF_TEST_STATE_START; ///< current dialIn pump self test state static U32 dialInPumpSelfTestTimerCount = 0; ///< timer counter for dialIn pump self test // ********** private function prototypes ********** static DIAL_IN_PUMP_STATE_T handleDialInPumpOffState( void ); static DIAL_IN_PUMP_STATE_T handleDialInPumpRampingUpState( void ); static DIAL_IN_PUMP_STATE_T handleDialInPumpRampingDownState( void ); static DIAL_IN_PUMP_STATE_T handleDialInPumpControlToTargetState( void ); static void setDialInPumpControlSignalPWM( F32 newPWM ); static void stopDialInPump( void ); static void releaseDialInPumpStop( void ); static void setDialInPumpDirection( MOTOR_DIR_T dir ); static void publishDialInFlowData( void ); static void resetDialInFlowMovingAverage( void ); static void filterDialInFlowReadings( F32 flow ); static void checkDialInPumpDirection( void ); static void checkDialInPumpMCCurrent( void ); static DATA_GET_PROTOTYPE( U32, getPublishDialInFlowDataInterval ); /*********************************************************************//** * @brief * The initDialInFlow function initializes the DialInFlow module. * @details * Inputs : none * Outputs : DialInFlow module initialized. * @return none *************************************************************************/ void initDialInFlow( void ) { stopDialInPump(); setDialInPumpDirection( MOTOR_DIR_FORWARD ); // zero rolling flow average buffer resetDialInFlowMovingAverage(); // initialize dialysate inlet flow PI controller initializePIController( PI_CONTROLLER_ID_DIALYSATE_FLOW, MIN_DIAL_IN_PUMP_PWM_DUTY_CYCLE, DIP_P_COEFFICIENT, DIP_I_COEFFICIENT, MIN_DIAL_IN_PUMP_PWM_DUTY_CYCLE, MAX_DIAL_IN_PUMP_PWM_DUTY_CYCLE ); } /*********************************************************************//** * @brief * The setDialInPumpTargetFlowRate function sets a new target flow rate and * pump direction. * @details * Inputs : isDialInPumpOn, dialInPumpDirectionSet * Outputs : targetDialInFlowRate, dialInPumpdirection, dialInPumpPWMDutyCyclePct * @param flowRate : new target dialIn flow rate * @param dir : new dialIn flow direction * @param mode : new control mode * @return TRUE if new flow rate & dir are set, FALSE if not *************************************************************************/ BOOL setDialInPumpTargetFlowRate( U32 flowRate, MOTOR_DIR_T dir, PUMP_CONTROL_MODE_T mode ) { BOOL result = FALSE; // direction change while pump is running is not allowed if ( ( FALSE == isDialInPumpOn ) || ( 0 == flowRate ) || ( dir == dialInPumpDirectionSet ) ) { // verify flow rate if ( flowRate <= MAX_DIAL_IN_FLOW_RATE ) { resetDialInFlowMovingAverage(); targetDialInFlowRate.data = ( dir == MOTOR_DIR_FORWARD ? (S32)flowRate : (S32)flowRate * -1 ); dialInPumpDirection = dir; dialInPumpControlMode = mode; // set PWM duty cycle target to an estimated initial target to ramp to based on target flow rate - then we'll control to flow when ramp completed dialInPumpPWMDutyCyclePct = DIP_PWM_FROM_ML_PER_MIN((F32)flowRate); // ~ 8% per 100 mL/min with a 10% zero offset added in (e.g. 100 mL/min = 8+10 = 18%) switch ( dialInPumpState ) { case DIAL_IN_PUMP_RAMPING_UP_STATE: // see if we need to reverse direction of ramp if ( dialInPumpPWMDutyCyclePct < dialInPumpPWMDutyCyclePctSet ) { dialInPumpState = DIAL_IN_PUMP_RAMPING_DOWN_STATE; } break; case DIAL_IN_PUMP_RAMPING_DOWN_STATE: // see if we need to reverse direction of ramp if ( dialInPumpPWMDutyCyclePct > dialInPumpPWMDutyCyclePctSet ) { dialInPumpState = DIAL_IN_PUMP_RAMPING_UP_STATE; } break; case DIAL_IN_PUMP_CONTROL_TO_TARGET_STATE: // start ramp to new target in appropriate direction if ( dialInPumpPWMDutyCyclePctSet > dialInPumpPWMDutyCyclePct ) { dialInPumpState = DIAL_IN_PUMP_RAMPING_DOWN_STATE; } else { dialInPumpState = DIAL_IN_PUMP_RAMPING_UP_STATE; } break; default: // ok - not all states need to be handled here break; } result = TRUE; } else // requested flow rate too high { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_SOFTWARE_FAULT, SW_FAULT_ID_DIAL_IN_FLOW_SET_TOO_HIGH, flowRate ) } } return result; } /*********************************************************************//** * @brief * The signalDialInPumpHardStop function stops the dialIn pump immediately. * @details * Inputs : none * Outputs : DialIn pump stopped, set point reset, state changed to off * @return none *************************************************************************/ void signalDialInPumpHardStop( void ) { targetDialInFlowRate.data = 0; stopDialInPump(); dialInPumpState = DIAL_IN_PUMP_OFF_STATE; dialInPumpPWMDutyCyclePct = 0.0; dipControlTimerCounter = 0; resetPIController( PI_CONTROLLER_ID_DIALYSATE_FLOW, MIN_DIAL_IN_PUMP_PWM_DUTY_CYCLE ); } /*********************************************************************//** * @brief * The signalDialInPumpRotorHallSensor function handles the dialysate inlet pump rotor \n * hall sensor detection. Calculates rotor speed (in RPM). Stops pump if \n * there is a pending request to home the pump. * @details * Inputs : dipRotorRevStartTime, dipStopAtHomePosition * Outputs : dipRotorRevStartTime, dialInPumpRotorSpeedRPM * @return none *************************************************************************/ void signalDialInPumpRotorHallSensor( void ) { U32 rotTime = getMSTimerCount(); U32 deltaTime = calcTimeBetween( dipRotorRevStartTime, rotTime ); // calculate rotor speed (in RPM) dialInPumpRotorSpeedRPM.data = ( 1.0 / (F32)deltaTime ) * (F32)MS_PER_SECOND * (F32)SEC_PER_MIN; dipRotorRevStartTime = rotTime; // if we're supposed to stop pump at home position, stop pump now. if ( TRUE == dipStopAtHomePosition ) { signalDialInPumpHardStop(); dipStopAtHomePosition = FALSE; } } /*********************************************************************//** * @brief * The homeDialInPump function initiates a dialysate inlet pump home operation. * @details * Inputs : dialInPumpState * Outputs : dipStopAtHomePosition, dipHomeStartTime, dialysate inlet pump started (slow) * @return none *************************************************************************/ BOOL homeDialInPump( void ) { BOOL result = FALSE; if ( DIAL_IN_PUMP_OFF_STATE == dialInPumpState ) { dipStopAtHomePosition = TRUE; dipHomeStartTime = getMSTimerCount(); result = setDialInPumpTargetFlowRate( DIP_HOME_RATE, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); } return result; } /*********************************************************************//** * @brief * The execDialInFlowMonitor function executes the dialIn flow monitor. * @details * Inputs : none * Outputs : measuredDialInFlowRate, adcDialInPumpMCSpeedRPM, adcDialInPumpMCCurrentmA * @return none *************************************************************************/ void execDialInFlowMonitor( void ) { U16 dipRPM = getIntADCReading( INT_ADC_DIAL_IN_PUMP_SPEED ); U16 dipmA = getIntADCReading( INT_ADC_DIAL_IN_PUMP_MOTOR_CURRENT ); F32 dipFlow = getFPGADialysateFlow(); adcDialInPumpMCSpeedRPM.data = (F32)(SIGN_FROM_12_BIT_VALUE(dipRPM)) * DIP_SPEED_ADC_TO_RPM_FACTOR; adcDialInPumpMCCurrentmA.data = (F32)(SIGN_FROM_12_BIT_VALUE(dipmA)) * DIP_CURRENT_ADC_TO_MA_FACTOR; filterDialInFlowReadings( dipFlow ); // don't start enforcing checks until out of init/POST mode if ( getCurrentOperationMode() != MODE_INIT ) { checkDialInPumpDirection(); checkDialInPumpMCCurrent(); } // if homing, check timeout if ( ( TRUE == dipStopAtHomePosition ) && ( TRUE == didTimeout( dipHomeStartTime, DIP_HOME_TIMEOUT_MS ) ) ) { signalDialInPumpHardStop(); dipStopAtHomePosition = FALSE; // TODO - alarm??? } // publish dialIn flow data on interval publishDialInFlowData(); } /*********************************************************************//** * @brief * The execDialInFlowController function executes the dialIn flow controller. * @details * Inputs : dialInPumpState * Outputs : dialInPumpState * @return none *************************************************************************/ void execDialInFlowController( void ) { switch ( dialInPumpState ) { case DIAL_IN_PUMP_OFF_STATE: dialInPumpState = handleDialInPumpOffState(); break; case DIAL_IN_PUMP_RAMPING_UP_STATE: dialInPumpState = handleDialInPumpRampingUpState(); break; case DIAL_IN_PUMP_RAMPING_DOWN_STATE: dialInPumpState = handleDialInPumpRampingDownState(); break; case DIAL_IN_PUMP_CONTROL_TO_TARGET_STATE: dialInPumpState = handleDialInPumpControlToTargetState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_SOFTWARE_FAULT, SW_FAULT_ID_DIAL_IN_FLOW_INVALID_DIAL_IN_PUMP_STATE, dialInPumpState ) break; } } /*********************************************************************//** * @brief * The handleDialInPumpOffState function handles the dialIn pump off state \n * of the dialIn pump controller state machine. * @details * Inputs : targetDialInFlowRate, dialInPumpDirection * Outputs : dialInPumpPWMDutyCyclePctSet, dialInPumpDirectionSet, isDialInPumpOn * @return next state *************************************************************************/ static DIAL_IN_PUMP_STATE_T handleDialInPumpOffState( void ) { DIAL_IN_PUMP_STATE_T result = DIAL_IN_PUMP_OFF_STATE; // if we've been given a flow rate, setup ramp up and transition to ramp up state if ( getTargetDialInFlowRate() != 0 ) { // set initial PWM duty cycle dialInPumpPWMDutyCyclePctSet = DIP_PWM_ZERO_OFFSET + MAX_DIAL_IN_PUMP_PWM_STEP_CHANGE; setDialInPumpControlSignalPWM( dialInPumpPWMDutyCyclePctSet ); // allow dialIn pump to run in requested direction setDialInPumpDirection( dialInPumpDirection ); releaseDialInPumpStop(); isDialInPumpOn = TRUE; result = DIAL_IN_PUMP_RAMPING_UP_STATE; } return result; } /*********************************************************************//** * @brief * The handleDialInPumpRampingUpState function handles the ramp up state \n * of the dialIn pump controller state machine. * @details * Inputs : dialInPumpPWMDutyCyclePctSet * Outputs : dialInPumpPWMDutyCyclePctSet * @return next state *************************************************************************/ static DIAL_IN_PUMP_STATE_T handleDialInPumpRampingUpState( void ) { DIAL_IN_PUMP_STATE_T result = DIAL_IN_PUMP_RAMPING_UP_STATE; // have we been asked to stop the dialIn pump? if ( 0 == getTargetDialInFlowRate() ) { // start ramp down to stop dialInPumpPWMDutyCyclePctSet -= MAX_DIAL_IN_PUMP_PWM_STEP_CHANGE; setDialInPumpControlSignalPWM( dialInPumpPWMDutyCyclePctSet ); result = DIAL_IN_PUMP_RAMPING_DOWN_STATE; } // have we reached end of ramp up? else if ( dialInPumpPWMDutyCyclePctSet >= dialInPumpPWMDutyCyclePct ) { resetDialInFlowMovingAverage(); resetPIController( PI_CONTROLLER_ID_DIALYSATE_FLOW, dialInPumpPWMDutyCyclePctSet ); dialInPumpControlModeSet = dialInPumpControlMode; // if open loop mode, set PWM to requested duty cycle where it will stay during control state if ( dialInPumpControlModeSet == PUMP_CONTROL_MODE_OPEN_LOOP ) { dialInPumpPWMDutyCyclePctSet = dialInPumpPWMDutyCyclePct; setDialInPumpControlSignalPWM( dialInPumpPWMDutyCyclePct ); } result = DIAL_IN_PUMP_CONTROL_TO_TARGET_STATE; } // continue ramp up else { dialInPumpPWMDutyCyclePctSet += MAX_DIAL_IN_PUMP_PWM_STEP_CHANGE; setDialInPumpControlSignalPWM( dialInPumpPWMDutyCyclePctSet ); } return result; } /*********************************************************************//** * @brief * The handleDialInPumpRampingDownState function handles the ramp down state \n * of the dialIn pump controller state machine. * @details * Inputs : dialInPumpPWMDutyCyclePctSet * Outputs : dialInPumpPWMDutyCyclePctSet * @return next state *************************************************************************/ static DIAL_IN_PUMP_STATE_T handleDialInPumpRampingDownState( void ) { DIAL_IN_PUMP_STATE_T result = DIAL_IN_PUMP_RAMPING_DOWN_STATE; // have we essentially reached zero speed if ( dialInPumpPWMDutyCyclePctSet < (MAX_DIAL_IN_PUMP_PWM_STEP_CHANGE + DIP_PWM_ZERO_OFFSET) ) { stopDialInPump(); result = DIAL_IN_PUMP_OFF_STATE; } // have we reached end of ramp down? else if ( dialInPumpPWMDutyCyclePctSet <= dialInPumpPWMDutyCyclePct ) { resetDialInFlowMovingAverage(); resetPIController( PI_CONTROLLER_ID_DIALYSATE_FLOW, dialInPumpPWMDutyCyclePctSet ); dialInPumpControlModeSet = dialInPumpControlMode; // if open loop mode, set PWM to requested duty cycle where it will stay during control state if ( dialInPumpControlModeSet == PUMP_CONTROL_MODE_OPEN_LOOP ) { dialInPumpPWMDutyCyclePctSet = dialInPumpPWMDutyCyclePct; setDialInPumpControlSignalPWM( dialInPumpPWMDutyCyclePct ); } result = DIAL_IN_PUMP_CONTROL_TO_TARGET_STATE; } // continue ramp down else { dialInPumpPWMDutyCyclePctSet -= MAX_DIAL_IN_PUMP_PWM_STEP_CHANGE; setDialInPumpControlSignalPWM( dialInPumpPWMDutyCyclePctSet ); } return result; } /*********************************************************************//** * @brief * The handleDialInPumpControlToTargetState function handles the "control to \n * target" state of the dialIn pump controller state machine. * @details * Inputs : none * Outputs : dialInPumpState * @return next state *************************************************************************/ static DIAL_IN_PUMP_STATE_T handleDialInPumpControlToTargetState( void ) { DIAL_IN_PUMP_STATE_T result = DIAL_IN_PUMP_CONTROL_TO_TARGET_STATE; // control at set interval if ( ++dipControlTimerCounter >= DIP_CONTROL_INTERVAL ) { if ( dialInPumpControlModeSet == PUMP_CONTROL_MODE_CLOSED_LOOP ) { F32 tgtFlow = (F32)getTargetDialInFlowRate(); F32 actFlow = getMeasuredDialInFlowRate(); F32 newPWM; newPWM = runPIController( PI_CONTROLLER_ID_DIALYSATE_FLOW, tgtFlow, actFlow ); dialInPumpPWMDutyCyclePctSet = newPWM; setDialInPumpControlSignalPWM( newPWM ); } dipControlTimerCounter = 0; } return result; } /*********************************************************************//** * @brief * The setDialInPumpControlSignalPWM function sets the PWM duty cycle for \n * the dialysate inlet pump to a given %. * @details * Inputs : none * Outputs : dialIn pump stop signal activated, PWM duty cycle zeroed * @param newPWM : new duty cycle % to apply to PWM * @return none *************************************************************************/ static void setDialInPumpControlSignalPWM( F32 newPWM ) { etpwmSetCmpA( etpwmREG2, (U32)( (S32)( ( newPWM * (F32)(etpwmREG2->TBPRD) ) + FLOAT_TO_INT_ROUNDUP_OFFSET ) ) ); } /*********************************************************************//** * @brief * The stopDialInPump function sets the dialIn pump stop signal. * @details * Inputs : none * Outputs : dialIn pump stop signal activated, PWM duty cycle zeroed * @return none *************************************************************************/ static void stopDialInPump( void ) { isDialInPumpOn = FALSE; dialInPumpPWMDutyCyclePctSet = 0.0; etpwmSetCmpA( etpwmREG2, 0 ); SET_DIP_STOP(); } /*********************************************************************//** * @brief * The releaseDialInPumpStop function clears the dialIn pump stop signal. * @details * Inputs : none * Outputs : dialIn pump stop signal * @return none *************************************************************************/ static void releaseDialInPumpStop( void ) { CLR_DIP_STOP(); } /*********************************************************************//** * @brief * The setDialInPumpDirection function sets the set dialIn pump direction to \n * the given direction. * @details * Inputs : dialInPumpState * Outputs : dialInPumpState * @param dir : dialIn pump direction to set * @return none *************************************************************************/ static void setDialInPumpDirection( MOTOR_DIR_T dir ) { switch ( dir ) { case MOTOR_DIR_FORWARD: dialInPumpDirectionSet = dir; SET_DIP_DIR(); break; case MOTOR_DIR_REVERSE: dialInPumpDirectionSet = dir; CLR_DIP_DIR(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_SOFTWARE_FAULT, SW_FAULT_ID_DIAL_IN_FLOW_INVALID_DIAL_IN_PUMP_DIRECTION, dir ) break; } } /*********************************************************************//** * @brief * The getPublishDialInFlowDataInterval function gets the dialIn flow data \n * publication interval. * @details * Inputs : dialInFlowDataPublishInterval * Outputs : none * @return the current dialIn flow data publication interval (in ms). *************************************************************************/ DATA_GET( U32, getPublishDialInFlowDataInterval, dialInFlowDataPublishInterval ) /*********************************************************************//** * @brief * The getTargetDialInFlowRate function gets the current target dialIn flow \n * rate. * @details * Inputs : targetDialInFlowRate * Outputs : none * @return the current target dialIn flow rate (in mL/min). *************************************************************************/ DATA_GET( S32, getTargetDialInFlowRate, targetDialInFlowRate ) /*********************************************************************//** * @brief * The getMeasuredDialInFlowRate function gets the measured dialIn flow \n * rate. * @details * Inputs : measuredDialInFlowRate * Outputs : none / * @return the current dialIn flow rate (in mL/min). *************************************************************************/ DATA_GET( F32, getMeasuredDialInFlowRate, measuredDialInFlowRate ) /*********************************************************************//** * @brief * The getMeasuredDialInPumpRotorSpeed function gets the measured dialIn flow \n * rate. * @details * Inputs : dialInPumpRotorSpeedRPM * Outputs : none * @return the current dialIn flow rate (in mL/min). *************************************************************************/ DATA_GET( F32, getMeasuredDialInPumpRotorSpeed, dialInPumpRotorSpeedRPM ) /*********************************************************************//** * @brief * The getMeasuredDialInPumpSpeed function gets the measured dialIn flow \n * rate. * @details * Inputs : dialInPumpSpeedRPM * Outputs : none * @return the current dialIn flow rate (in mL/min). *************************************************************************/ DATA_GET( F32, getMeasuredDialInPumpSpeed, dialInPumpSpeedRPM ) /*********************************************************************//** * @brief * The getMeasuredDialInPumpMCSpeed function gets the measured dialIn pump \n * speed. * @details * Inputs : adcDialInPumpMCSpeedRPM * Outputs : none * @return the current dialIn pump speed (in RPM). *************************************************************************/ DATA_GET( F32, getMeasuredDialInPumpMCSpeed, adcDialInPumpMCSpeedRPM ) /*********************************************************************//** * @brief * The getMeasuredDialInPumpMCCurrent function gets the measured dialIn pump \n * current. * @details * Inputs : adcDialInPumpMCCurrentmA * Outputs : none / * @return the current dialIn pump current (in mA). *************************************************************************/ DATA_GET( F32, getMeasuredDialInPumpMCCurrent, adcDialInPumpMCCurrentmA ) /*********************************************************************//** * @brief * The publishDialInFlowData function publishes dialIn flow data at the set \n * interval. * @details * Inputs : target flow rate, measured flow rate, measured MC speed, \n * measured MC current * Outputs : DialIn flow data is published to CAN bus. * @return none *************************************************************************/ static void publishDialInFlowData( void ) { // publish dialIn flow data on interval if ( ++dialInFlowDataPublicationTimerCounter >= getPublishDialInFlowDataInterval() ) { S32 flowStPt = (S32)getTargetDialInFlowRate(); #ifndef SHOW_RAW_FLOW_VALUES F32 measFlow = getMeasuredDialInFlowRate(); #else F32 measFlow = getFPGADialysateFlow(); #endif F32 measRotSpd = getMeasuredDialInPumpRotorSpeed(); F32 measSpd = getMeasuredDialInPumpSpeed(); F32 measMCSpd = getMeasuredDialInPumpMCSpeed(); F32 measMCCurr = getMeasuredDialInPumpMCCurrent(); F32 pumpPWMPctDutyCycle = dialInPumpPWMDutyCyclePctSet * FRACTION_TO_PERCENT_FACTOR; broadcastDialInFlowData( flowStPt, measFlow, measRotSpd, measSpd, measMCSpd, measMCCurr, pumpPWMPctDutyCycle ); dialInFlowDataPublicationTimerCounter = 0; } } /*********************************************************************//** * @brief * The resetDialInFlowMovingAverage function resets the properties of the \n * dialIn flow moving average sample buffer. * @details * Inputs : none * Outputs : flowReadingsTotal, flowReadingsIdx, flowReadingsCount all set to zero. *************************************************************************/ static void resetDialInFlowMovingAverage( void ) { flowReadingsTotal = 0.0; flowReadingsIdx = 0; flowReadingsCount = 0; flowReadingsTmrCtr = 0; dipControlTimerCounter = 0; } /*********************************************************************//** * @brief * The filterDialInFlowReadings function adds a new flow sample to the filter \n * if decimation rate for current set point calls for it. * @details * Inputs : none * Outputs : flowReadings[], flowReadingsIdx, flowReadingsCount * @param flow : newest dialIn flow sample * @return none *************************************************************************/ static void filterDialInFlowReadings( F32 flow ) { BOOL addSampleToFilter = FALSE; if ( ( targetDialInFlowRate.data < MIN_DIAL_IN_FLOW_RATE ) || ( targetDialInFlowRate.data >= MAX_DIAL_IN_FLOW_RATE ) ) { addSampleToFilter = TRUE; } else { switch ( flowReadingsTmrCtr ) { case 0: addSampleToFilter = TRUE; break; case 1: addSampleToFilter = FALSE; break; case 2: if ( targetDialInFlowRate.data >= 400 ) { addSampleToFilter = TRUE; } break; case 3: if ( targetDialInFlowRate.data >= 200 ) { addSampleToFilter = TRUE; } break; case 4: if ( targetDialInFlowRate.data >= 300 ) { addSampleToFilter = TRUE; } break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_SOFTWARE_FAULT, SW_FAULT_ID_DIAL_IN_FLOW_INVALID_FILTER_STATE, flowReadingsTmrCtr ) break; } } // if it's time to add sample to filter, add it. if ( TRUE == addSampleToFilter ) { if ( flowReadingsCount >= SIZE_OF_ROLLING_AVG ) { flowReadingsTotal -= flowReadings[ flowReadingsIdx ]; } flowReadings[ flowReadingsIdx ] = flow; flowReadingsTotal += flow; flowReadingsIdx = INC_WRAP( flowReadingsIdx, 0, SIZE_OF_ROLLING_AVG - 1 ); flowReadingsCount = INC_CAP( flowReadingsCount, SIZE_OF_ROLLING_AVG ); measuredDialInFlowRate.data = flowReadingsTotal / (F32)flowReadingsCount; } flowReadingsTmrCtr = INC_WRAP( flowReadingsTmrCtr, 0, MAX_FLOW_FILTER_INTERVAL - 1 ); } /*********************************************************************//** * @brief * The checkDialInPumpDirection function checks the set direction vs. \n * the direction implied by the sign of the measured MC speed. * @details * Inputs : adcDialInPumpMCSpeedRPM, dialInPumpDirectionSet, dialInPumpState * Outputs : none * @return none *************************************************************************/ static void checkDialInPumpDirection( void ) { MOTOR_DIR_T dipMCDir; if ( DIAL_IN_PUMP_CONTROL_TO_TARGET_STATE == dialInPumpState ) { // check set direction vs. direction from sign of motor controller speed dipMCDir = ( getMeasuredDialInPumpMCSpeed() >= 0.0 ? MOTOR_DIR_FORWARD : MOTOR_DIR_REVERSE ); if ( dialInPumpDirectionSet != dipMCDir ) { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DIAL_IN_PUMP_MC_DIRECTION_CHECK, (U32)dialInPumpDirectionSet, (U32)dipMCDir ) } } } /*********************************************************************//** * @brief * The checkDialInPumpMCCurrent function checks the measured MC current vs. \n * the set state of the dialIn pump (stopped or running). * @details * Inputs : dialInPumpState, dipCurrErrorDurationCtr, adcDialInPumpMCCurrentmA * Outputs : none * @return none *************************************************************************/ static void checkDialInPumpMCCurrent( void ) { F32 dipCurr; // dialIn pump should be off if ( DIAL_IN_PUMP_OFF_STATE == dialInPumpState ) { dipCurr = fabs( getMeasuredDialInPumpMCCurrent() ); if ( dipCurr > DIP_MAX_CURR_WHEN_STOPPED_MA ) { dipCurrErrorDurationCtr += TASK_PRIORITY_INTERVAL; if ( dipCurrErrorDurationCtr > DIP_MAX_CURR_ERROR_DURATION_MS ) { #ifndef DISABLE_MOTOR_CURRENT_ERRORS SET_ALARM_WITH_1_F32_DATA( ALARM_ID_DIAL_IN_PUMP_MC_CURRENT_CHECK, getMeasuredDialInPumpMCCurrent() ); #endif } } else { dipCurrErrorDurationCtr = 0; } } // dialIn pump should be running else { dipCurr = fabs( getMeasuredDialInPumpMCCurrent() ); if ( ( dipCurr < DIP_MIN_CURR_WHEN_RUNNING_MA ) || ( dipCurr > DIP_MAX_CURR_WHEN_RUNNING_MA ) ) { dipCurrErrorDurationCtr += TASK_PRIORITY_INTERVAL; if ( dipCurrErrorDurationCtr > DIP_MAX_CURR_ERROR_DURATION_MS ) { #ifndef DISABLE_MOTOR_CURRENT_ERRORS SET_ALARM_WITH_1_F32_DATA( ALARM_ID_DIAL_IN_PUMP_MC_CURRENT_CHECK, getMeasuredDialInPumpMCCurrent() ); #endif } } else { dipCurrErrorDurationCtr = 0; } } } /*********************************************************************//** * @brief * The execDialInFlowTest function executes the state machine for the \n * DialInFlow self test. * @details * Inputs : none * Outputs : none * @return the current state of the DialInFlow self test. *************************************************************************/ SELF_TEST_STATUS_T execDialInFlowTest( void ) { SELF_TEST_STATUS_T result = SELF_TEST_STATUS_FAILED; // TODO - implement self test(s) return result; } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSetDialInFlowDataPublishIntervalOverride function overrides the \n * dialIn flow data publish interval. * @details * Inputs : none * Outputs : dialInFlowDataPublishInterval * @param value : override dialIn flow data publish interval with (in ms) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetDialInFlowDataPublishIntervalOverride( U32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { U32 intvl = value / TASK_PRIORITY_INTERVAL; result = TRUE; dialInFlowDataPublishInterval.ovData = intvl; dialInFlowDataPublishInterval.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetDialInFlowDataPublishIntervalOverride function resets the override \n * of the dialIn flow data publish interval. * @details * Inputs : none * Outputs : dialInFlowDataPublishInterval * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetDialInFlowDataPublishIntervalOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; dialInFlowDataPublishInterval.override = OVERRIDE_RESET; dialInFlowDataPublishInterval.ovData = dialInFlowDataPublishInterval.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetTargetDialInFlowRateOverride function overrides the target \n * dialysate inlet flow rate. \n * @details * Inputs : none * Outputs : targetDialInFlowRate * @param value : override target dialysate inlet flow rate (in mL/min) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetTargetDialInFlowRateOverride( S32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { MOTOR_DIR_T dir; if ( value < 0 ) { dir = MOTOR_DIR_REVERSE; } else { dir = MOTOR_DIR_FORWARD; } targetDialInFlowRate.ovInitData = targetDialInFlowRate.data; // backup current target flow rate targetDialInFlowRate.ovData = value; targetDialInFlowRate.override = OVERRIDE_KEY; result = setDialInPumpTargetFlowRate( ABS(value), dir, dialInPumpControlMode ); } return result; } /*********************************************************************//** * @brief * The testResetTargetDialInFlowRateOverride function resets the override of the \n * target dialysate inlet flow rate. * @details * Inputs : none * Outputs : targetDialInFlowRate * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetTargetDialInFlowRateOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { targetDialInFlowRate.data = targetDialInFlowRate.ovInitData; // restore pre-override target flow rate targetDialInFlowRate.override = OVERRIDE_RESET; targetDialInFlowRate.ovInitData = 0; targetDialInFlowRate.ovData = 0; result = setDialInPumpTargetFlowRate( targetDialInFlowRate.data, dialInPumpDirection, dialInPumpControlMode ); } return result; } /*********************************************************************//** * @brief * The testResetMeasuredDialInFlowRateOverride function overrides the measured \n * dialIn flow rate. * @details * Inputs : none * Outputs : measuredDialInFlowRate * @param value : override measured dialIn flow rate (in mL/min) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetMeasuredDialInFlowRateOverride( F32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; measuredDialInFlowRate.ovData = value; measuredDialInFlowRate.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetMeasuredDialInFlowRateOverride function resets the override of the \n * measured dialIn flow rate. * @details * Inputs : none * Outputs : measuredDialInFlowRate * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetMeasuredDialInFlowRateOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; measuredDialInFlowRate.override = OVERRIDE_RESET; measuredDialInFlowRate.ovData = measuredDialInFlowRate.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetMeasuredDialInPumpRotorSpeedOverride function overrides the measured \n * dialIn pump rotor speed. * @details * Inputs : none * Outputs : dialInPumpRotorSpeedRPM * @param value : override measured dialIn pump rotor speed (in RPM) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetMeasuredDialInPumpRotorSpeedOverride( F32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; dialInPumpRotorSpeedRPM.ovData = value; dialInPumpRotorSpeedRPM.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetMeasuredDialInPumpRotorSpeedOverride function resets the override of the \n * measured dialIn pump rotor speed. * @details * Inputs : none * Outputs : dialInPumpRotorSpeedRPM * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetMeasuredDialInPumpRotorSpeedOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; dialInPumpRotorSpeedRPM.override = OVERRIDE_RESET; dialInPumpRotorSpeedRPM.ovData = dialInPumpRotorSpeedRPM.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetMeasuredDialInPumpSpeedOverride function overrides the measured \n * dialIn pump motor speed. * @details * Inputs : none * Outputs : dialInPumpSpeedRPM * @param value : override measured dialIn pump motor speed (in RPM) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetMeasuredDialInPumpSpeedOverride( F32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; dialInPumpSpeedRPM.ovData = value; dialInPumpSpeedRPM.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetMeasuredDialInPumpSpeedOverride function resets the override of the \n * measured dialIn pump motor speed. * @details * Inputs : none * Outputs : dialInPumpSpeedRPM * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetMeasuredDialInPumpSpeedOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; dialInPumpSpeedRPM.override = OVERRIDE_RESET; dialInPumpSpeedRPM.ovData = dialInPumpSpeedRPM.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetMeasuredDialInPumpMCSpeedOverride function overrides the measured \n * dialIn pump motor speed. * @details * Inputs : none * Outputs : adcDialInPumpMCSpeedRPM * @param value : override measured dialIn pump speed (in RPM) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetMeasuredDialInPumpMCSpeedOverride( F32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; adcDialInPumpMCSpeedRPM.ovData = value; adcDialInPumpMCSpeedRPM.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetMeasuredDialInPumpMCSpeedOverride function resets the override of the \n * measured dialIn pump motor speed. * @details * Inputs : none * Outputs : adcDialInPumpMCSpeedRPM * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetMeasuredDialInPumpMCSpeedOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; adcDialInPumpMCSpeedRPM.override = OVERRIDE_RESET; adcDialInPumpMCSpeedRPM.ovData = adcDialInPumpMCSpeedRPM.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetMeasuredDialInPumpMCCurrentOverride function overrides the measured \n * dialIn pump motor current. * @details * Inputs : none * Outputs : adcDialInPumpMCCurrentmA * @param value : override measured dialIn pump current (in mA) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetMeasuredDialInPumpMCCurrentOverride( F32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; adcDialInPumpMCCurrentmA.ovData = value; adcDialInPumpMCCurrentmA.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetMeasuredDialInPumpMCCurrentOverride function resets the override of the \n * measured dialIn pump motor current. * @details * Inputs : none * Outputs : adcDialInPumpMCCurrentmA * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetMeasuredDialInPumpMCCurrentOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; adcDialInPumpMCCurrentmA.override = OVERRIDE_RESET; adcDialInPumpMCCurrentmA.ovData = adcDialInPumpMCCurrentmA.ovInitData; } return result; } /**@}*/