Index: firmware/App/Controllers/LoadCell.c =================================================================== diff -u -r696e732c9742535a58b9c65f243df7cd797d1423 -r011aa9c2df785a0fdcb69e30f7145b959084f472 --- firmware/App/Controllers/LoadCell.c (.../LoadCell.c) (revision 696e732c9742535a58b9c65f243df7cd797d1423) +++ firmware/App/Controllers/LoadCell.c (.../LoadCell.c) (revision 011aa9c2df785a0fdcb69e30f7145b959084f472) @@ -1,14 +1,14 @@ /************************************************************************** * -* Copyright (c) 2020-2022 Diality Inc. - All Rights Reserved. +* Copyright (c) 2020-2023 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 LoadCell.c * -* @author (last) Dong Nguyen -* @date (last) 27-Sep-2022 +* @author (last) Michael Garthwaite +* @date (last) 01-Mar-2023 * * @author (original) Saeed Nejatali * @date (original) 25-Feb-2020 @@ -21,6 +21,7 @@ #include "LoadCell.h" #include "MessageSupport.h" #include "NVDataMgmt.h" +#include "OperationModes.h" #include "PersistentAlarm.h" #include "SystemCommMessages.h" #include "TaskPriority.h" @@ -51,14 +52,17 @@ #define EMPTY_RESERVOIR_WEIGHT_GRAMS 1600.0F ///< Reservoirs empty weight in grams. #define MAX_ALLOWED_EXTRA_WEIGHT_BEFORE_FIRST_TARE_GRAMS 300.0F ///< Max allowed extra weight before first tare in grams. #define MAX_ALLOWED_EXTRA_WEIGHT_BEFORE_TARE_GRAMS 60.0F ///< Max allowed extra weight before tare in grams. -#define LOAD_CELL_PRIMARY_BACKUP_MAX_ALLOWED_DRIFT_GRAMS 60.0F ///< Load cell primary and backup maximum allowed weight drift in grams. +#define LOAD_CELL_PRIMARY_BACKUP_MAX_ALLOWED_DRIFT_GRAMS 80.0F ///< Load cell primary and backup maximum allowed weight drift in grams. +#define LOAD_CELL_PRIMARY_BACKUP_MAX_HEAT_DRIFT_GRAMS 200.0F ///< Load cell primary and backup maximum allowed weight drift in grams for heat disinfect mode. #define DATA_PUBLISH_COUNTER_START_COUNT 0 ///< Data publish counter start count. #define LOAD_CELL_FPGA_ERROR_TIMEOUT_MS ( 2 * MS_PER_SECOND ) ///< Load cell FPGA error timeout in milliseconds. +#define LOAD_CELL_FPGA_SIGN_BIT 0x800000 ///< Load cell FPGA sign bit. +#define LOAD_CELL_SIGN_EXTENSION 0xFF800000 ///< Load cell FPGA sign bit extension. /// Load cell data structure. typedef struct { - U32 rawReading; ///< Latest raw load cell reading. + S32 rawReading; ///< Latest raw load cell reading. OVERRIDE_F32_T weight; ///< Latest load cell weight. F32 autoCalOffset; ///< Load cell auto-calibration offset. F32 loadCellVelocity_g_min; ///< Velocity (in g/min) of load cell. @@ -87,7 +91,6 @@ // ********** private function prototypes ********** -static void monitorLoadCellsWeightOutOfRange( LOAD_CELL_ID_T loadCell ); static void monitorLoadCellsPrimaryBackupDriftOutOfRange( void ); /*********************************************************************//** @@ -161,23 +164,37 @@ *************************************************************************/ void execLoadCell( void ) { - U32 ii; + LOAD_CELL_ID_T sensorId; + LOAD_CELL_ID_T sensorInAlarm = LOAD_CELL_FIRST; U32 a1 = getFPGALoadCellA1(); U32 a2 = getFPGALoadCellA2(); U32 b1 = getFPGALoadCellB1(); U32 b2 = getFPGALoadCellB2(); + F32 weight = 0.0F; + BOOL isLoadCellOutOfRange = FALSE; + F32 alarmLoadCell = 0.0F; // Check error bits from new readings - U32 a1Err = ( a1 >> SHIFT_BITS_BY_31 ) << SHIFT_24_BITS; - U32 a2Err = ( a2 >> SHIFT_BITS_BY_31 ) << SHIFT_16_BITS_FOR_WORD_SHIFT; - U32 b1Err = ( b1 >> SHIFT_BITS_BY_31 ) << SHIFT_8_BITS_FOR_BYTE_SHIFT; + U32 a1Err = ( a1 >> SHIFT_BITS_BY_31 ); + U32 a2Err = ( a2 >> SHIFT_BITS_BY_31 ); + U32 b1Err = ( b1 >> SHIFT_BITS_BY_31 ); U32 b2Err = ( b2 >> SHIFT_BITS_BY_31 ); - loadcells[ LOAD_CELL_RESERVOIR_1_PRIMARY ].rawReading = ( 0 == a1Err ? a1 & MASK_OFF_U32_MSB : loadcells[ LOAD_CELL_RESERVOIR_1_PRIMARY ].rawReading ); - loadcells[ LOAD_CELL_RESERVOIR_1_BACKUP ].rawReading = ( 0 == a2Err ? a2 & MASK_OFF_U32_MSB : loadcells[ LOAD_CELL_RESERVOIR_1_BACKUP ].rawReading ); - loadcells[ LOAD_CELL_RESERVOIR_2_PRIMARY ].rawReading = ( 0 == b1Err ? b1 & MASK_OFF_U32_MSB : loadcells[ LOAD_CELL_RESERVOIR_2_PRIMARY ].rawReading ); - loadcells[ LOAD_CELL_RESERVOIR_2_BACKUP ].rawReading = ( 0 == b2Err ? b2 & MASK_OFF_U32_MSB : loadcells[ LOAD_CELL_RESERVOIR_2_BACKUP ].rawReading ); + U32 a1Sign = ( ( a1 & MASK_OFF_U32_MSB ) & LOAD_CELL_FPGA_SIGN_BIT ) >> SHIFT_BITS_BY_23; + U32 a2Sign = ( ( a2 & MASK_OFF_U32_MSB ) & LOAD_CELL_FPGA_SIGN_BIT ) >> SHIFT_BITS_BY_23; + U32 b1Sign = ( ( b1 & MASK_OFF_U32_MSB ) & LOAD_CELL_FPGA_SIGN_BIT ) >> SHIFT_BITS_BY_23; + U32 b2Sign = ( ( b2 & MASK_OFF_U32_MSB ) & LOAD_CELL_FPGA_SIGN_BIT ) >> SHIFT_BITS_BY_23; + S32 a1Signed = (S32)( 1 == a1Sign ? ( a1 & MASK_OFF_U32_MSB ) | LOAD_CELL_SIGN_EXTENSION : ( a1 & MASK_OFF_U32_MSB ) ); + S32 a2Signed = (S32)( 1 == a2Sign ? ( a2 & MASK_OFF_U32_MSB ) | LOAD_CELL_SIGN_EXTENSION : ( a2 & MASK_OFF_U32_MSB ) ); + S32 b1Signed = (S32)( 1 == b1Sign ? ( b1 & MASK_OFF_U32_MSB ) | LOAD_CELL_SIGN_EXTENSION : ( b1 & MASK_OFF_U32_MSB ) ); + S32 b2Signed = (S32)( 1 == b2Sign ? ( b2 & MASK_OFF_U32_MSB ) | LOAD_CELL_SIGN_EXTENSION : ( b2 & MASK_OFF_U32_MSB ) ); + + loadcells[ LOAD_CELL_RESERVOIR_1_PRIMARY ].rawReading = ( 0 == a1Err ? a1Signed : loadcells[ LOAD_CELL_RESERVOIR_1_PRIMARY ].rawReading ); + loadcells[ LOAD_CELL_RESERVOIR_1_BACKUP ].rawReading = ( 0 == a2Err ? a2Signed : loadcells[ LOAD_CELL_RESERVOIR_1_BACKUP ].rawReading ); + loadcells[ LOAD_CELL_RESERVOIR_2_PRIMARY ].rawReading = ( 0 == b1Err ? b1Signed : loadcells[ LOAD_CELL_RESERVOIR_2_PRIMARY ].rawReading ); + loadcells[ LOAD_CELL_RESERVOIR_2_BACKUP ].rawReading = ( 0 == b2Err ? b2Signed : loadcells[ LOAD_CELL_RESERVOIR_2_BACKUP ].rawReading ); + checkFPGAPersistentAlarms( FPGA_PERS_ERROR_LOAD_CELL_A1_B1_SENSORS, getFPGAADC1ErrorCount(), getFPGAADC1ReadCount() ); checkFPGAPersistentAlarms( FPGA_PERS_ERROR_LOAD_CELL_A2_B2_SENSORS, getFPGAADC2ErrorCount(), getFPGAADC2ReadCount() ); @@ -195,48 +212,58 @@ } // Rolling average of last 100 raw samples in small filter - for ( ii = 0; ii < NUM_OF_LOAD_CELLS; ++ii ) + for ( sensorId = LOAD_CELL_FIRST; sensorId < NUM_OF_LOAD_CELLS; ++sensorId ) { - F32 loadCell = (F32)loadcells[ ii ].rawReading * ADC2GRAM; + F32 loadCell = (F32)loadcells[ sensorId ].rawReading * ADC2GRAM; // Apply the calibration factors to the data. // load_cell_weight = fourth_order_coeff * (load_cell^4) + third_order_coeff * (load_cell^3) + second_order_coeff * (load_cell^2) + gain * load_cell + offset - loadcells[ ii ].weight.data = pow( loadCell, 4 ) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].fourthOrderCoeff + - pow( loadCell, 3 ) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].thirdOrderCoeff + - pow( loadCell, 2 ) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].secondOrderCoeff + - loadCell * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].gain + - loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].offset; + loadcells[ sensorId ].weight.data = pow( loadCell, 4 ) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)sensorId ].fourthOrderCoeff + + pow( loadCell, 3 ) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)sensorId ].thirdOrderCoeff + + pow( loadCell, 2 ) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)sensorId ].secondOrderCoeff + + loadCell * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)sensorId ].gain + + loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)sensorId ].offset; // Monitor the load cells weight - monitorLoadCellsWeightOutOfRange( (LOAD_CELL_ID_T)ii ); + // Since there is a single alarm value for all 4 load cells the persistence check must be + // executed if ANY of the load cell values are out of range. Only when all of the load + // cells are in range should the persistence for the alarm be cleared. + weight = getLoadCellWeight( ( LOAD_CELL_ID_T) sensorId ); + if ( ( weight < LOAD_CELL_MIN_ALLOWED_WEIGHT_GRAMS ) || ( weight > LOAD_CELL_MAX_ALLOWED_WEIGHT_GRAMS ) ) + { + isLoadCellOutOfRange = TRUE; + sensorInAlarm = sensorId; + alarmLoadCell = weight; + } - loadcells[ ii ].loadCellVelocity_g_min = ( getLoadCellWeight( (LOAD_CELL_ID_T)ii ) - - loadcells[ ii ].smallFilterReadings[ smallReadingsIdx ] ) * (F32)SEC_PER_MIN; + loadcells[ sensorId ].loadCellVelocity_g_min = ( getLoadCellWeight( (LOAD_CELL_ID_T)sensorId ) - + loadcells[ sensorId ].smallFilterReadings[ smallReadingsIdx ] ) * (F32)SEC_PER_MIN; // Update small filter with new weight sample - loadcells[ ii ].smallFilterTotal -= loadcells[ ii ].smallFilterReadings[ smallReadingsIdx ]; - loadcells[ ii ].smallFilterReadings[ smallReadingsIdx ] = getLoadCellWeight( (LOAD_CELL_ID_T)ii ); - loadcells[ ii ].smallFilterTotal += getLoadCellWeight( (LOAD_CELL_ID_T)ii ); + loadcells[ sensorId ].smallFilterTotal -= loadcells[ sensorId ].smallFilterReadings[ smallReadingsIdx ]; + loadcells[ sensorId ].smallFilterReadings[ smallReadingsIdx ] = getLoadCellWeight( (LOAD_CELL_ID_T)sensorId ); + loadcells[ sensorId ].smallFilterTotal += getLoadCellWeight( (LOAD_CELL_ID_T)sensorId ); // Calculate the load cell value before applying calibration to it - loadcells[ ii ].smallFilteredWeight.data = (F32)( loadcells[ ii ].smallFilterTotal / (F64)SIZE_OF_SMALL_LOAD_CELL_AVG ); + loadcells[ sensorId ].smallFilteredWeight.data = (F32)( loadcells[ sensorId ].smallFilterTotal / (F64)SIZE_OF_SMALL_LOAD_CELL_AVG ); // Apply the tare offset. NOTE: tare must be applied after checking the weight out of range. - loadcells[ ii ].smallFilteredWeight.data -= loadcells[ ii ].autoCalOffset; + loadcells[ sensorId ].smallFilteredWeight.data -= loadcells[ sensorId ].autoCalOffset; } + checkPersistentAlarm( ALARM_ID_DG_LOAD_CELL_WEIGHT_OUT_OF_RANGE, isLoadCellOutOfRange, sensorInAlarm, alarmLoadCell ); smallReadingsIdx = INC_WRAP( smallReadingsIdx, 0, SIZE_OF_SMALL_LOAD_CELL_AVG - 1 ); // filter every 100ms if ( ++loadCellFilterTimerCount >= LOAD_CELL_REPORT_PERIOD ) { - for ( ii = 0; ii < NUM_OF_LOAD_CELLS; ++ii ) + for ( sensorId = LOAD_CELL_FIRST; sensorId < NUM_OF_LOAD_CELLS; ++sensorId ) { // Update large filter with new small filter weight sample - loadcells[ ii ].largeFilterTotal -= loadcells[ ii ].largeFilterReadings[ largeReadingsIdx ]; - loadcells[ ii ].largeFilterReadings[ largeReadingsIdx ] = getLoadCellSmallFilteredWeight((LOAD_CELL_ID_T) ii); - loadcells[ ii ].largeFilterTotal += getLoadCellSmallFilteredWeight((LOAD_CELL_ID_T) ii); - loadcells[ ii ].largeFilteredWeight = (F32)( loadcells[ ii ].largeFilterTotal / (F64)SIZE_OF_LARGE_LOAD_CELL_AVG ); + loadcells[ sensorId ].largeFilterTotal -= loadcells[ sensorId ].largeFilterReadings[ largeReadingsIdx ]; + loadcells[ sensorId ].largeFilterReadings[ largeReadingsIdx ] = getLoadCellSmallFilteredWeight((LOAD_CELL_ID_T) sensorId); + loadcells[ sensorId ].largeFilterTotal += getLoadCellSmallFilteredWeight((LOAD_CELL_ID_T) sensorId); + loadcells[ sensorId ].largeFilteredWeight = (F32)( loadcells[ sensorId ].largeFilterTotal / (F64)SIZE_OF_LARGE_LOAD_CELL_AVG ); } loadCellFilterTimerCount = 0; @@ -254,7 +281,7 @@ loadCellData.loadCellB2inGram = getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_2_BACKUP ); // Broadcast small filtered load cell data - broadcastData( MSG_ID_LOAD_CELL_READINGS, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&loadCellData, sizeof( LOAD_CELL_DATA_T ) ); + broadcastData( MSG_ID_LOAD_CELL_READINGS_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&loadCellData, sizeof( LOAD_CELL_DATA_T ) ); loadCellDataPublicationTimerCounter = 0; } @@ -344,7 +371,7 @@ { // Add old auto calibration offset to get back to actual weight value loadcells[ loadCellID ].autoCalOffset = ( loadcells[ loadCellID ].smallFilteredWeight.data + loadcells[ loadCellID ].autoCalOffset ); - hasLoadCellBeenTared[ loadCellID ] = TRUE; + hasLoadCellBeenTared[ loadCellID ] = TRUE; } else { @@ -478,25 +505,6 @@ /*********************************************************************//** * @brief - * The monitorLoadCellsWeightOutOfRange function monitors the weight of the - * load cells and if they are not in range, it raises an alarm. - * Function must be called prior to applying tare so that un-tared weight - * is checked for out of range. - * @details Inputs: none - * @details Outputs: none - * @param loadCell which is the load cell ID that its range is checked - * @return none - *************************************************************************/ -static void monitorLoadCellsWeightOutOfRange( LOAD_CELL_ID_T loadCell ) -{ - F32 weight = getLoadCellWeight( loadCell ); - BOOL isWeightOutOfRange = ( ( weight < LOAD_CELL_MIN_ALLOWED_WEIGHT_GRAMS ) || ( weight > LOAD_CELL_MAX_ALLOWED_WEIGHT_GRAMS ) ? TRUE : FALSE ); - - checkPersistentAlarm( ALARM_ID_DG_LOAD_CELL_WEIGHT_OUT_OF_RANGE, isWeightOutOfRange, weight, LOAD_CELL_MAX_ALLOWED_WEIGHT_GRAMS ); -} - -/*********************************************************************//** - * @brief * The monitorLoadCellsPrimaryBackupDriftOutOfRange function monitors the * load cells' primary and backup drift. * @details Inputs: none @@ -508,8 +516,15 @@ F32 drift = 0.0; F32 loadCellADrift = 0.0; F32 loadCellBDrift = 0.0; + F32 maxDrift = LOAD_CELL_PRIMARY_BACKUP_MAX_ALLOWED_DRIFT_GRAMS; BOOL isDriftOutOfRange = FALSE; + // Increase max drift limit if in heat disinfect mode + if ( DG_MODE_HEAT == getCurrentOperationMode() ) + { + maxDrift = LOAD_CELL_PRIMARY_BACKUP_MAX_HEAT_DRIFT_GRAMS; + } + // Test is valid after load cells are tared if ( isLoadCellTared( LOAD_CELL_RESERVOIR_1_PRIMARY ) && isLoadCellTared( LOAD_CELL_RESERVOIR_1_BACKUP ) ) { @@ -522,8 +537,7 @@ getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_2_BACKUP ) ); } - if ( ( loadCellADrift > LOAD_CELL_PRIMARY_BACKUP_MAX_ALLOWED_DRIFT_GRAMS ) || - ( loadCellBDrift > LOAD_CELL_PRIMARY_BACKUP_MAX_ALLOWED_DRIFT_GRAMS ) ) + if ( ( loadCellADrift > maxDrift ) || ( loadCellBDrift > maxDrift ) ) { isDriftOutOfRange = TRUE; } @@ -534,8 +548,7 @@ // Pick the biggest drift to log w/ alarm if triggered drift = ( loadCellADrift > loadCellBDrift ? loadCellADrift : loadCellBDrift ); - checkPersistentAlarm( ALARM_ID_DG_LOAD_CELL_PRIMARY_BACKUP_DRIFT_OUT_OF_RANGE, isDriftOutOfRange, drift, - LOAD_CELL_PRIMARY_BACKUP_MAX_ALLOWED_DRIFT_GRAMS ); + checkPersistentAlarm( ALARM_ID_DG_LOAD_CELL_PRIMARY_BACKUP_DRIFT_OUT_OF_RANGE, isDriftOutOfRange, drift, maxDrift ); }