Index: firmware/App/Controllers/LoadCell.c =================================================================== diff -u -rebbb1f85550a1f9b8f946655f7b2b63f76fbf67d -rc0700a4503f28288f16070634bb87f4eccb2568c --- firmware/App/Controllers/LoadCell.c (.../LoadCell.c) (revision ebbb1f85550a1f9b8f946655f7b2b63f76fbf67d) +++ firmware/App/Controllers/LoadCell.c (.../LoadCell.c) (revision c0700a4503f28288f16070634bb87f4eccb2568c) @@ -20,6 +20,7 @@ #include "FPGA.h" #include "LoadCell.h" #include "NVDataMgmt.h" +#include "PersistentAlarm.h" #include "SystemCommMessages.h" #include "TaskPriority.h" @@ -33,29 +34,33 @@ // TODO check the maximum weight on the load cells in tare. There was 1500 grams limit // but it has been removed. Check the load cells data sheet. -#define LOAD_CELL_REPORT_PERIOD (100 / TASK_PRIORITY_INTERVAL) ///< Broadcast load cell values message every 100 ms. +#define LOAD_CELL_REPORT_PERIOD (100 / TASK_PRIORITY_INTERVAL) ///< Broadcast load cell values message every 100 ms. /// Conversion factor from ADC counts to grams. -static const F32 ADC2GRAM = (0.0894 * 1.1338); -#define LOAD_CELL_ZERO_OFFSET -1500.0 ///< Zero offset (in grams). TODO - right now, this is empty reservoir weight. -#define LOAD_CELL_FILTER_ALPHA 0.05 ///< Alpha factor for the alpha filter used on load cell readings. +static const F32 ADC2GRAM = (0.0894 * 1.1338); +#define LOAD_CELL_FILTER_ALPHA 0.05 ///< Alpha factor for the alpha filter used on load cell readings. -#define SIZE_OF_SMALL_LOAD_CELL_AVG 100 ///< Small load cell moving average has 100 raw samples @ 10ms intervals (1-second). -#define SIZE_OF_LARGE_LOAD_CELL_AVG 40 ///< Large load cell moving average has 40 samples from small filter @ 100ms intervals (4-second). +#define SIZE_OF_SMALL_LOAD_CELL_AVG 100 ///< Small load cell moving average has 100 raw samples @ 10ms intervals (1-second). +#define SIZE_OF_LARGE_LOAD_CELL_AVG 40 ///< Large load cell moving average has 40 samples from small filter @ 100ms intervals (4-second). +#define LOAD_CELL_ADC_ERROR_PERSISTENCE 500 ///< Alarm persistence period (in ms) for load cell ADC errors. +#define EMPTY_RESERVOIR_WEIGHT_GRAMS 1600 ///< Reservoirs empty weight in grams. +#define MAX_ALLOWED_EXTRA_WEIGHT_BEFORE_TARE_GRAMS 300 ///< Max allowed extra weight before tare in grams. + /// Load cell data structure. typedef struct { U32 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. - F32 smallFilterReadings[ SIZE_OF_SMALL_LOAD_CELL_AVG ]; ///< Load cell samples for small load cell moving average - F32 smallFilterTotal; ///< Small filter rolling total - used to calc small load cell moving average - F32 smallFilteredWeight; ///< Load cell small filtered (100 100Hz raw sample) weight + F32 smallFilterReadings[ SIZE_OF_SMALL_LOAD_CELL_AVG ]; ///< Load cell samples for small load cell moving average. + F32 smallFilterTotal; ///< Small filter rolling total - used to calc small load cell moving average. + F32 smallFilteredWeight; ///< Load cell small filtered (100 100Hz raw sample) weight. - F32 largeFilterReadings[ SIZE_OF_LARGE_LOAD_CELL_AVG ]; ///< Load cell samples for large load cell moving average - F32 largeFilterTotal; ///< Large filter rolling total - used to calc small load cell moving average - F32 largeFilteredWeight; ///< Load cell large filtered (40 10Hz filtered sample) weight + F32 largeFilterReadings[ SIZE_OF_LARGE_LOAD_CELL_AVG ]; ///< Load cell samples for large load cell moving average. + F32 largeFilterTotal; ///< Large filter rolling total - used to calc small load cell moving average. + F32 largeFilteredWeight; ///< Load cell large filtered (40 10Hz filtered sample) weight. } LOADCELL_T; // ********** private data ********** @@ -70,8 +75,6 @@ static U32 largeReadingsIdx; ///< Index for next sample in load cell large rolling average sample array. static DG_LOAD_CELLS_CAL_RECORD_T loadCellsCalRecord; ///< Load cells calibration record. -// TODO - gain and offset for load cells should be read from NV Data calibration record. - // ********** private function prototypes ********** static U32 getLoadCellDataPublishInterval( void ); @@ -119,6 +122,8 @@ { loadcells[ i ].largeFilterReadings[ j ] = 0.0; } + + loadcells[ i ].loadCellVelocity_g_min = 0.0; } // Set all the load cells' calibration values to benign values @@ -131,6 +136,9 @@ loadCellsCalRecord.loadCells[ cell ].gain = 1.0; loadCellsCalRecord.loadCells[ cell ].offset = 0.0; } + + // Initialize persistent alarm(s) + initPersistentAlarm( ALARM_ID_DG_LOAD_CELL_ADC_ERROR, 0, LOAD_CELL_ADC_ERROR_PERSISTENCE ); } /*********************************************************************//** @@ -144,17 +152,53 @@ void execLoadCell( void ) { U32 ii; + U32 a1 = getFPGALoadCellA1(); + U32 a2 = getFPGALoadCellA2(); + U32 b1 = getFPGALoadCellB1(); + U32 b2 = getFPGALoadCellB2(); // update sums for load cell average calculations - loadcells[ LOAD_CELL_RESERVOIR_1_PRIMARY ].rawReading = getFPGALoadCellA1(); - loadcells[ LOAD_CELL_RESERVOIR_1_BACKUP ].rawReading = getFPGALoadCellA2(); - loadcells[ LOAD_CELL_RESERVOIR_2_PRIMARY ].rawReading = getFPGALoadCellB1(); - loadcells[ LOAD_CELL_RESERVOIR_2_BACKUP ].rawReading = getFPGALoadCellB2(); + loadcells[ LOAD_CELL_RESERVOIR_1_PRIMARY ].rawReading = a1 & MASK_OFF_U32_MSB; + loadcells[ LOAD_CELL_RESERVOIR_1_BACKUP ].rawReading = a2 & MASK_OFF_U32_MSB; + loadcells[ LOAD_CELL_RESERVOIR_2_PRIMARY ].rawReading = b1 & MASK_OFF_U32_MSB; + loadcells[ LOAD_CELL_RESERVOIR_2_BACKUP ].rawReading = b2 & MASK_OFF_U32_MSB; + // Check error bits from new readings + a1 = ( a1 >> 31 ) << SHIFT_24_BITS; + a2 = ( a2 >> 31 ) << SHIFT_16_BITS_FOR_WORD_SHIFT; + b1 = ( b1 >> 31 ) << SHIFT_8_BITS_FOR_BYTE_SHIFT; + b2 = ( b2 >> 31 ); + if ( TRUE == isPersistentAlarmTriggered( ALARM_ID_DG_LOAD_CELL_ADC_ERROR, ( ( a1 > 0 ) || ( a2 > 0 ) || ( b1 > 0 ) || ( b2 > 0 ) ) ) ) + { + SET_ALARM_WITH_1_U32_DATA( ALARM_ID_DG_LOAD_CELL_ADC_ERROR, ( a1 | a2 | b1 | b2 ) ) + } + + // Check if a new calibration is available + if ( isNewCalibrationRecordAvailable() == TRUE ) + { + // Get the new calibration data and check its validity + processCalibrationData(); + + // Zero the current tare values when new calibration data is available + loadcells[ LOAD_CELL_RESERVOIR_1_PRIMARY ].autoCalOffset = 0.0; + loadcells[ LOAD_CELL_RESERVOIR_1_BACKUP ].autoCalOffset = 0.0; + loadcells[ LOAD_CELL_RESERVOIR_2_PRIMARY ].autoCalOffset = 0.0; + loadcells[ LOAD_CELL_RESERVOIR_2_BACKUP ].autoCalOffset = 0.0; + } + // Rolling average of last 100 raw samples in small filter for ( ii = 0; ii < NUM_OF_LOAD_CELLS; ++ii ) { - loadcells[ ii ].weight.data = (F32)loadcells[ ii ].rawReading * ADC2GRAM + LOAD_CELL_ZERO_OFFSET - loadcells[ ii ].autoCalOffset; + loadcells[ ii ].weight.data = (F32)loadcells[ ii ].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(loadcells[ ii ].weight.data, 4) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].fourthOrderCoeff + + pow(loadcells[ ii ].weight.data, 3) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].thirdOrderCoeff + + pow(loadcells[ ii ].weight.data, 2) * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].secondOrderCoeff + + loadcells[ ii ].weight.data * loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].gain + + loadCellsCalRecord.loadCells[ (CAL_DATA_DG_LOAD_CELLS_T)ii ].offset; + loadcells[ ii ].weight.data = loadcells[ ii ].weight.data - loadcells[ ii ].autoCalOffset; + loadcells[ ii ].loadCellVelocity_g_min = ( getLoadCellWeight( (LOAD_CELL_ID_T)ii ) - loadcells[ ii ].smallFilterReadings[ smallReadingsIdx ] ) * (F32)SEC_PER_MIN; // Update small filter with new weight sample loadcells[ ii ].smallFilterTotal -= loadcells[ ii ].smallFilterReadings[ smallReadingsIdx ]; @@ -231,8 +275,18 @@ *************************************************************************/ void tareLoadCell( LOAD_CELL_ID_T loadCellID ) { - // Add old auto calibration offset to get back to actual weight value - loadcells[ loadCellID ].autoCalOffset = ( loadcells[ loadCellID ].smallFilteredWeight + loadcells[ loadCellID ].autoCalOffset ); + F32 weight = getLoadCellSmallFilteredWeight( loadCellID ); + BOOL isWeightOutOfRange = ( weight > ( EMPTY_RESERVOIR_WEIGHT_GRAMS + MAX_ALLOWED_EXTRA_WEIGHT_BEFORE_TARE_GRAMS ) ? TRUE : FALSE ); + + if ( FALSE == isWeightOutOfRange ) + { + // Add old auto calibration offset to get back to actual weight value + loadcells[ loadCellID ].autoCalOffset = ( loadcells[ loadCellID ].smallFilteredWeight + loadcells[ loadCellID ].autoCalOffset ); + } + else + { + SET_ALARM_WITH_1_F32_DATA( ALARM_ID_DG_LOAD_CELLS_TARE_WEIGHT_OUT_OF_RANGE, weight ) + } } /*********************************************************************//** @@ -274,7 +328,7 @@ } else { - activateAlarmNoData( ALARM_ID_DG_SOFTWARE_FAULT ); + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_LOAD_CELL_ID, (U32)loadCellID ) } return result; @@ -299,7 +353,7 @@ } else { - activateAlarmNoData( ALARM_ID_DG_SOFTWARE_FAULT ); + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_LOAD_CELL_ID, (U32)loadCellID ) } return result; @@ -316,22 +370,47 @@ *************************************************************************/ F32 getLoadCellLargeFilteredWeight( LOAD_CELL_ID_T loadCellID ) { - F32 result = 0; + F32 result = 0.0; if ( loadCellID < NUM_OF_LOAD_CELLS ) { result = loadcells[ loadCellID ].largeFilteredWeight; } else { - activateAlarmNoData( ALARM_ID_DG_SOFTWARE_FAULT ); + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_LOAD_CELL_ID, (U32)loadCellID ) } return result; } /*********************************************************************//** * @brief + * The getLoadCellVelocity function gets the current velocity (in g/min) + * for the given load cell. + * @details Inputs: loadcells[] + * @details Outputs: none + * @param loadCellID ID of load cell to get velocity + * @return the velocity (in g/min) for the given load cell ID. + *************************************************************************/ +F32 getLoadCellVelocity( LOAD_CELL_ID_T loadCellID ) +{ + F32 result = 0.0; + + if ( loadCellID < NUM_OF_LOAD_CELLS ) + { + result = loadcells[ loadCellID ].loadCellVelocity_g_min; + } + else + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_LOAD_CELL_ID, (U32)loadCellID ) + } + + return result; +} + +/*********************************************************************//** + * @brief * The getLoadCellDataPublishInterval function gets the load cell data publish interval. * @details Inputs: loadCellDataPublishInterval * @details Outputs: none