This blog documents explains how persistent calibration data is managed in the electronic load that peteroakes, jc2048 and Jan Cumps are designing. This post will again be firmware heavy. It explains how calibration data is structured and managed. |
Calibration data has to be kept, also when the instrument is powered down. The MSP432 we're using has user Flash for such purposes.
The MSP43x drivers have functions to program it.
Calibration Structure
All calibration data is held in a structure. We'll do a read/write of the flash for that complete structure every time.
typedef struct CalibrationData { char code[6]; uint32_t version; float temperature_threshold; // todo convert to the ADC 16 bit value in stead of float float sense_voltage_read_multiplier; float sense_voltage_read_offset; float current_read_multiplier; float current_read_offset; float current_write_multiplier; float current_write_offset; } CalibrationData; #define CALIBRATION_DATA_SIZE (sizeof(CalibrationData))
The version is used for compatibility with later extensions. It will be set - or overwritten - when callibration data is saved.
The read function can check the version and see if it has to do compatibility functions if the data in the Flash is created by an older firmware version.
Writing and reading the data is done in a helper function. They are in a demo state now, to check if I can get this working.
Check the MSP432 datasheet for the relevant Flash memory area.
void calibrationRead() { calibrationInit(); EEPROMRead((uint32_t *)&_CalibrationData, (uint32_t)(CALIBRATION_START), (uint32_t)CALIBRATION_DATA_SIZE); if(strcmp(_CalibrationData.code, "eload")) { // check if code == "eload"s calibrationErase(); } // compatibility with older versions of calibration structure _calibrationStructureBackwardCompatibility(); }
Here's RIGOL's calibration guide for a power supply. We could use the same command structure:
:CALibration:Start ... :CALibration:Clear ... :CALibration:Set ... :CALibration:MEAS ... :CALibration:End ....
This post focuses on the firmware development of the storage mechanism. The actual SCPI commands and calibration/configuration processes are documented in this post: Programmable Electronic Load - Calibration Steps.
Manage the Calibration Record Version
Detection a previous version is needed when updating firmware. We want to retain any calibration settings stored in the past. Because the new firmware my introduce new fields, there are actions to do.
All existing fields from previous versions are already initialised, or set by the user with calls to the CALIbrate: functions. We don't interfere with them.
But any new field has random data in it: whatever the bits happens to be in the part of FLASH beyond where the existing persistent calibration values are stored.
When a new version of the firmware extends the calibration record, it could (Murphy: will) contain garbage.
Every new version of firmware should take care to initialise any field that is added with a reasonable value, if it detects that the stored calibration record did not have this field.
The code has two mechanisms to do that.
First, it detects if there is a valid calibration record. It does that by checking if the signature of the persistent calibration area is valid. It checks if that area starts with "eload".
If that is not the case, the firmware decides that you never stored any calibration data, and fully initialises the record. All fields get a default value.
EEPROMRead((uint32_t *)&_CalibrationData, (uint32_t)(CALIBRATION_START), (uint32_t)CALIBRATION_DATA_SIZE); if(strcmp(_CalibrationData.code, "eload")) { // check if code == "eload"s calibrationErase(); }
bool calibrationErase() { bool bRetVal = false; if (_bCalibrationActive) { strcpy(_CalibrationData.code, "eload"); _CalibrationData.version = CALIBRATION_DATA_VERSION; _CalibrationData.temperature_threshold = 0.246244535; _CalibrationData.sense_voltage_read_multiplier = 0.33; _CalibrationData.sense_voltage_read_offset = 0.0; _CalibrationData.current_read_multiplier = 6.8; _CalibrationData.current_read_offset = 0.0; _CalibrationData.current_write_multiplier = 0.000116; _CalibrationData.current_write_offset = 0.008; _CalibrationData.sense_resistance = 0.05; // Ohm bRetVal = true; } return (bRetVal); }
If it detects that there is a valid calibration record in the persistent storage, it checks for the version stamp (an unsigned integer, starting with 1U for the initial firmware version when the project started) of that record.
If that stamp is not lower than the stamp stored in the firmware, nothing happens. We know that each field in the record that we will use has been initialised or set.
If the stamp is lower, we know that the new firmware introduces additional fields to he calibration record. In that case, action is required.
For each field added to the record, a reasonable value is defaulted (but not yet saved) at the startup of the device.
It allows the device to run with reasonable, uncalibrated, values.
void _calibrationStructureBackwardCompatibility() { // compatibility with older versions of calibration structure if(_CalibrationData.version < CALIBRATION_DATA_VERSION) { if(_CalibrationData.version < 2U) { // initialise fields added in version 2 of the calibration structure _CalibrationData.sense_resistance = 0.05; } } }
The user can then enter calibration mode and replace the suggested, reasonable, values with effective values for her instrument.
When the user completely runs the calibration cycle (including calling CALI:END), the data will be stored into persistent storage.That will also stamp the record with the latest calibration data version number.
void calibrationWrite() { _CalibrationData.version = CALIBRATION_DATA_VERSION; // writing calibration data will always set the version to the latest strcpy(_CalibrationData.code, "eload"); // ..
On the next run, the code will detect that the calibration data version matches the one stored in firmware. It will then use the stored values instead of providing reasonable defaults.
Top Comments