#include #include #include "Communication.h" #include "HW_HAL.hpp" #include "Hardware.hpp" #define LOG_LEVEL LOG_LEVEL_INFO #define LOG_MODULE "CAL" #include "Log.h" // increment when Calibration struct format changed. If a wrong version is found in flash, it will revert to default values static constexpr uint16_t version = 0x0001; using CorrectionTable = struct { uint8_t usedPoints; uint32_t freq[Cal::maxPoints]; // LSB = 10Hz int16_t port1Correction[Cal::maxPoints]; // LSB = 0.01db int16_t port2Correction[Cal::maxPoints]; // LSB = 0.01db }; using Calibration = struct _calibration { uint16_t version; CorrectionTable Source; CorrectionTable Receiver; float TCXO_PPM_correction; }; static Calibration cal; static_assert(sizeof(cal) <= Cal::flash_size, "Reserved flash size is too small"); bool Cal::Load() { HWHAL::flash.read(flash_address, sizeof(cal), &cal); if(cal.version != version) { LOG_WARN("Invalid version in flash, expected %u, got %u", version, cal.version); SetDefault(); return false; } else if(cal.Source.usedPoints == 0 || cal.Receiver.usedPoints == 0){ LOG_WARN("Empty amplitude calibration, resetting to default"); SetDefault(); return false; } else { LOG_INFO("Loaded from flash"); return true; } } bool Cal::Save() { if(!HWHAL::flash.eraseRange(flash_address, flash_size)) { return false; } uint32_t write_size = sizeof(cal); if(write_size % Flash::PageSize != 0) { // round up to next page write_size += Flash::PageSize - write_size % Flash::PageSize; } return HWHAL::flash.write(flash_address, write_size, &cal); } void Cal::SetDefault() { memset(&cal, 0, sizeof(cal)); cal.version = version; cal.Source.usedPoints = 1; cal.Source.freq[0] = 100000000; cal.Receiver.usedPoints = 1; cal.Receiver.freq[0] = 100000000; LOG_INFO("Set to default"); } static Cal::Correction InterpolateCorrection(const CorrectionTable& table, uint64_t freq) { // adjust LSB to match table freq /= 10; Cal::Correction ret; // find first valid index that is higher than the given frequency uint8_t i = 0; for (; i < table.usedPoints; i++) { if (table.freq[i] >= freq) { break; } } if (i == 0) { // no previous index, nothing to interpolate ret.port1 = table.port1Correction[0]; ret.port2 = table.port2Correction[0]; } else if (i >= table.usedPoints) { // went beyond last point, nothing to interpolate ret.port1 = table.port1Correction[table.usedPoints - 1]; ret.port2 = table.port2Correction[table.usedPoints - 1]; } else { // frequency is between i and i-1, interpolate float alpha = (float) (freq - table.freq[i - 1]) / (table.freq[i] - table.freq[i - 1]); ret.port1 = table.port1Correction[i - 1] * (1.0f - alpha) + table.port1Correction[i] * alpha; ret.port2 = table.port2Correction[i - 1] * (1.0f - alpha) + table.port2Correction[i] * alpha; } return ret; } Cal::Correction Cal::SourceCorrection(uint64_t freq) { return InterpolateCorrection(cal.Source, freq); } Cal::Correction Cal::ReceiverCorrection(uint64_t freq) { return InterpolateCorrection(cal.Receiver, freq); } static void SendCorrectionTable(const CorrectionTable& table, Protocol::PacketType type) { for(uint8_t i=0;i= Cal::maxPoints) { // ignore out-of-bounds point return; } table.freq[p.pointNum] = p.freq; table.port1Correction[p.pointNum] = p.port1; table.port2Correction[p.pointNum] = p.port2; if(p.pointNum == p.totalPoints - 1 || p.pointNum == Cal::maxPoints - 1) { // this was the last point, update used points and save table.usedPoints = p.totalPoints; LOG_INFO("Last point received, saving to flash"); Cal::Save(); } } void Cal::AddSourcePoint(const Protocol::AmplitudeCorrectionPoint& p) { addPoint(cal.Source, p); } void Cal::AddReceiverPoint(const Protocol::AmplitudeCorrectionPoint& p) { addPoint(cal.Receiver, p); } uint64_t Cal::FrequencyCorrectionToDevice(uint64_t freq) { // The frequency calibration is only used when the internal reference is active. // If an external reference is in use, it is assumed to already be at the correct frequency if(!HW::Ref::usingExternal()) { freq -= (int32_t) (freq * cal.TCXO_PPM_correction * 1e-6f); } return freq; } uint64_t Cal::FrequencyCorrectionFromDevice(uint64_t freq) { if(!HW::Ref::usingExternal()) { // this formula is not exactly correct, it should actually be // freq *= (1+PPM*10^-6). However, this can not be used directly // due to floating point limitation. But the error of this approximation // is so small that is doesn't make a difference (as the result only has // 1Hz resolution anyway) freq += freq * cal.TCXO_PPM_correction * 1e-6f; } return freq; } float Cal::getFrequencyCal() { return cal.TCXO_PPM_correction; } bool Cal::setFrequencyCal(float ppm) { cal.TCXO_PPM_correction = ppm; return Save(); }