diff options
Diffstat (limited to 'Radio/HW/BladeRF/src/device_calibration.c')
-rw-r--r-- | Radio/HW/BladeRF/src/device_calibration.c | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/Radio/HW/BladeRF/src/device_calibration.c b/Radio/HW/BladeRF/src/device_calibration.c new file mode 100644 index 0000000..4495671 --- /dev/null +++ b/Radio/HW/BladeRF/src/device_calibration.c @@ -0,0 +1,522 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2023 Nuand LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include "libbladeRF.h" +#include "board/board.h" +#include "helpers/version.h" +#include "device_calibration.h" +#include "log.h" +#include "common.h" + +#define GAIN_CAL_HEADER_RX "RX Chain,RX Gain,VSG Power into bladeRF RX (dBm),Frequency of signal (Hz),Frequency of bladeRF+PXI (Hz),AD9361 RSSI register value,Power of Signal from Full Scale (dBFS)\0" +#define GAIN_CAL_HEADER_TX "TX Chain,TX Gain,Frequency of Signal (Hz),Frequency of bladeRF+PXI (Hz),VSA Measured Power (dBm)\0" + +#define GAIN_CAL_VERSION (struct bladerf_version) { \ + .describe = "gain calibration table", \ + .major = 1, \ + .minor = 0, \ + .patch = 0, \ +} + +#define __round_int(x) (x >= 0 ? (int)(x + 0.5) : (int)(x - 0.5)) + +#define RETURN_ERROR_STATUS(_what, _status) \ + do { \ + log_error("%s: %s failed: %s\n", __FUNCTION__, _what, \ + bladerf_strerror(_status)); \ + return _status; \ + } while (0) + +#define CHECK_STATUS(_fn) \ + do { \ + int _s = _fn; \ + if (_s < 0) { \ + RETURN_ERROR_STATUS(#_fn, _s); \ + } \ + } while (0) + +static size_t count_csv_entries(const char *filename) { + char line[1024]; // Adjust buffer size as needed + int count = 0; + + FILE *file = fopen(filename, "r"); + if (file == NULL) { + perror("Error opening file"); + return -1; + } + + while (fgets(line, sizeof(line), file)) { + count++; + } + + fclose(file); + + return count - 2; // Subtract 2 to account for the serial number and header lines +} + +int gain_cal_csv_to_bin(struct bladerf *dev, const char *csv_path, const char *binary_path, bladerf_channel ch) +{ + int status = 0; + struct bladerf_image *image; + size_t data_size; + size_t entry_size; + size_t offset = 0; + + char line[256]; + char current_dir[1000]; + char expected_header[1024]; + char device_serial[BLADERF_SERIAL_LENGTH]; + char csv_serial[BLADERF_SERIAL_LENGTH]; + + uint64_t frequency; + float power; + uint64_t cw_freq; + uint8_t chain; + bladerf_gain gain; + int32_t rssi; + float vsg_power; + uint64_t signal_freq; + + FILE *csvFile = fopen(csv_path, "r"); + FILE *binaryFile = fopen(binary_path, "wb"); + if (!csvFile || !binaryFile) { + status = BLADERF_ERR_NO_FILE; + if (getcwd(current_dir, sizeof(current_dir)) != NULL) { + log_error("Error opening calibration file: %s\n", strcat(current_dir, csv_path)); + } else { + log_error("Error opening calibration file\n"); + } + goto error; + } + + strncpy(device_serial, dev->ident.serial, BLADERF_SERIAL_LENGTH); + device_serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; + + if (!fgets(line, sizeof(line), csvFile)) { + status = BLADERF_ERR_INVAL; + log_error("Error reading serial number from CSV file or file is empty.\n"); + goto error; + } + + sscanf(line, "Serial: %s", csv_serial); + if (strcmp(device_serial, csv_serial) != 0) { + log_warning("Gain calibration file serial (%s) does not match device serial (%s)\n", csv_serial, device_serial); + } + + size_t num_entries = count_csv_entries(csv_path); + if (num_entries == 0) { + status = BLADERF_ERR_INVAL; + log_error("Error reading header from CSV file or file is empty.\n"); + goto error; + } + + entry_size = (BLADERF_CHANNEL_IS_TX(ch)) + ? sizeof(chain) + sizeof(gain) + sizeof(cw_freq) + sizeof(frequency) + sizeof(power) + : sizeof(chain) + sizeof(gain) + sizeof(vsg_power) + sizeof(signal_freq) + sizeof(frequency) + sizeof(rssi) + sizeof(power); + + data_size = num_entries * entry_size + BLADERF_SERIAL_LENGTH; + + image = bladerf_alloc_image(dev, BLADERF_IMAGE_TYPE_GAIN_CAL, 0xffffffff, data_size); + if (image == NULL) { + log_error("Failed to allocate image\n"); + status = BLADERF_ERR_MEM; + goto error; + } + + if (!fgets(line, sizeof(line), csvFile)) { + status = BLADERF_ERR_INVAL; + log_error("Error reading header from CSV file or file is empty.\n"); + goto error; + } + + strncpy(expected_header, (BLADERF_CHANNEL_IS_TX(ch)) ? GAIN_CAL_HEADER_TX : GAIN_CAL_HEADER_RX, sizeof(expected_header)); + if (strncmp(line, expected_header, strlen(expected_header)) != 0) { + status = BLADERF_ERR_INVAL; + log_error("CSV format does not match expected %s headers\n", (BLADERF_CHANNEL_IS_TX(ch)) ? "TX" : "RX"); + goto error; + } + + image->version = GAIN_CAL_VERSION; + + memcpy(&image->data[offset], device_serial, BLADERF_SERIAL_LENGTH); + offset += BLADERF_SERIAL_LENGTH; + + for (size_t i = 0; i < num_entries; i++) { + if (!fgets(line, sizeof(line), csvFile)) { + break; + } + + if (BLADERF_CHANNEL_IS_TX(ch)) { + sscanf(line, "%" SCNu8 ",%" SCNi32 ",%" SCNu64 ",%" SCNu64 ",%f", + &chain, &gain, &cw_freq, &frequency, &power); + + memcpy(&image->data[offset], &chain, sizeof(chain)); + offset += sizeof(chain); + memcpy(&image->data[offset], &gain, sizeof(gain)); + offset += sizeof(gain); + memcpy(&image->data[offset], &cw_freq, sizeof(cw_freq)); + offset += sizeof(cw_freq); + memcpy(&image->data[offset], &frequency, sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&image->data[offset], &power, sizeof(power)); + offset += sizeof(power); + } else { + sscanf(line, "%" SCNu8 ",%" SCNi32 ",%f,%" SCNu64 ",%" SCNu64 ",%" SCNi32 ",%f", + &chain, &gain, &vsg_power, &signal_freq, &frequency, &rssi, &power); + + memcpy(&image->data[offset], &chain, sizeof(chain)); + offset += sizeof(chain); + memcpy(&image->data[offset], &gain, sizeof(gain)); + offset += sizeof(gain); + memcpy(&image->data[offset], &vsg_power, sizeof(vsg_power)); + offset += sizeof(vsg_power); + memcpy(&image->data[offset], &signal_freq, sizeof(signal_freq)); + offset += sizeof(signal_freq); + memcpy(&image->data[offset], &frequency, sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&image->data[offset], &rssi, sizeof(rssi)); + offset += sizeof(rssi); + memcpy(&image->data[offset], &power, sizeof(power)); + offset += sizeof(power); + } + } + + log_debug("Writing image to file: %s\n", binary_path); + bladerf_image_write(dev, image, binary_path); + bladerf_free_image(image); + +error: + if (csvFile) + fclose(csvFile); + if (binaryFile) + fclose(binaryFile); + return status; +} + +static int gain_cal_tbl_init(struct bladerf_gain_cal_tbl *tbl, uint32_t num_entries) { + if (tbl == NULL) { + log_error("calibration table is NULL\n"); + return BLADERF_ERR_MEM; + } + + tbl->version = (struct bladerf_version){0, 0, 0, NULL}; + tbl->n_entries = num_entries; + tbl->start_freq = 0; + tbl->stop_freq = 0; + tbl->file_path_len = PATH_MAX; + + tbl->entries = malloc(num_entries * sizeof(struct bladerf_gain_cal_entry)); + if (tbl->entries == NULL) { + log_error("failed to allocate memory for calibration table entries\n"); + return BLADERF_ERR_MEM; + } + + tbl->file_path = malloc(tbl->file_path_len + 1); + if (tbl->file_path == NULL) { + log_error("failed to allocate memory for calibration table file path\n"); + return BLADERF_ERR_MEM; + } + + tbl->state = BLADERF_GAIN_CAL_LOADED; + return 0; +} + +void gain_cal_tbl_free(struct bladerf_gain_cal_tbl *tbl) { + log_verbose("Freeing gain calibration table\n"); + + if (tbl->entries != NULL) { + free(tbl->entries); + tbl->entries = NULL; + } + + if (tbl->file_path != NULL) { + free(tbl->file_path); + tbl->file_path = NULL; + } + + tbl->version = (struct bladerf_version){0, 0, 0, NULL}; + tbl->enabled = false; + tbl->ch = 0; + tbl->n_entries = 0; + tbl->start_freq = 0; + tbl->stop_freq = 0; + tbl->gain_target = 0; + tbl->file_path_len = 0; + tbl->state = BLADERF_GAIN_CAL_UNLOADED; +} + +int load_gain_calibration(struct bladerf *dev, bladerf_channel ch, const char *binary_path) { + int num_channels = 4; + struct bladerf_gain_cal_tbl gain_tbls[num_channels]; + bladerf_gain current_gain; + uint64_t frequency; + float power; + size_t entry_counter; + size_t offset; + int status = 0; + + uint64_t cw_freq; + uint8_t chain; + bladerf_gain gain; + int32_t rssi; + float vsg_power; + bladerf_frequency signal_freq; + + struct bladerf_image *image = NULL; + size_t entry_size; + size_t num_entries; + char device_serial[BLADERF_SERIAL_LENGTH]; + char file_serial[BLADERF_SERIAL_LENGTH]; + + FILE *binaryFile = fopen(binary_path, "rb"); + if (!binaryFile) { + log_error("Error opening binary file.\n"); + status = BLADERF_ERR_NO_FILE; + goto error; + } + + status = dev->board->get_gain(dev, ch, ¤t_gain); + if (status != 0) { + log_error("Failed to get gain: %s\n", bladerf_strerror(status)); + goto error; + } + + status = gain_cal_tbl_init(&gain_tbls[ch], (uint32_t) 10e3); + if (status != 0) { + log_error("Error initializing gain calibration table\n"); + status = BLADERF_ERR_MEM; + goto error; + } + + entry_size = (BLADERF_CHANNEL_IS_TX(ch)) + ? sizeof(chain) + sizeof(gain) + sizeof(cw_freq) + sizeof(frequency) + sizeof(power) + : sizeof(chain) + sizeof(gain) + sizeof(vsg_power) + sizeof(signal_freq) + sizeof(frequency) + sizeof(rssi) + sizeof(power); + + image = bladerf_alloc_image(dev, BLADERF_IMAGE_TYPE_GAIN_CAL, 0, 0); + status = bladerf_image_read(image, binary_path); + if (status != 0) { + log_error("Failed to read image: %s\n", bladerf_strerror(status)); + goto error; + } + + if (version_equal(&image->version, &GAIN_CAL_VERSION) == false) { + log_error("Expected gain calibration table: v%i.%i.%i\n", + GAIN_CAL_VERSION.major, GAIN_CAL_VERSION.minor, GAIN_CAL_VERSION.patch); + log_error("Imported gain calibration table: v%i.%i.%i\n", + image->version.major, image->version.minor, image->version.patch); + status = BLADERF_ERR_INVAL; + goto error; + } + + strncpy(device_serial, dev->ident.serial, BLADERF_SERIAL_LENGTH); + device_serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; + memcpy(file_serial, image->data, BLADERF_SERIAL_LENGTH); + file_serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; + + if (strcmp(device_serial, file_serial) != 0) { + log_warning("Calibration file serial (%s) does not match device serial (%s)\n", file_serial, device_serial); + } + + offset = BLADERF_SERIAL_LENGTH; + entry_counter = 0; + num_entries = (image->length - BLADERF_SERIAL_LENGTH) / entry_size; + for (uint64_t i = 0; i < num_entries; i++) { + if (BLADERF_CHANNEL_IS_TX(ch)) { + memcpy(&chain, &image->data[offset], sizeof(chain)); + offset += sizeof(chain); + memcpy(&gain, &image->data[offset], sizeof(gain)); + offset += sizeof(gain); + memcpy(&cw_freq, &image->data[offset], sizeof(cw_freq)); + offset += sizeof(cw_freq); + memcpy(&frequency, &image->data[offset], sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&power, &image->data[offset], sizeof(power)); + offset += sizeof(power); + } else { + memcpy(&chain, &image->data[offset], sizeof(chain)); + offset += sizeof(chain); + memcpy(&gain, &image->data[offset], sizeof(gain)); + offset += sizeof(gain); + memcpy(&vsg_power, &image->data[offset], sizeof(vsg_power)); + offset += sizeof(vsg_power); + memcpy(&signal_freq, &image->data[offset], sizeof(signal_freq)); + offset += sizeof(signal_freq); + memcpy(&frequency, &image->data[offset], sizeof(frequency)); + offset += sizeof(frequency); + memcpy(&rssi, &image->data[offset], sizeof(rssi)); + offset += sizeof(rssi); + memcpy(&power, &image->data[offset], sizeof(power)); + offset += sizeof(power); + } + + if (BLADERF_CHANNEL_IS_TX(ch) && chain == 0 && gain == 60) { + gain_tbls[ch].entries[entry_counter].freq = frequency; + gain_tbls[ch].entries[entry_counter].gain_corr = power; + entry_counter++; + } + + if (!BLADERF_CHANNEL_IS_TX(ch) && chain == 0 && gain == 0) { + gain_tbls[ch].entries[entry_counter].freq = frequency; + gain_tbls[ch].entries[entry_counter].gain_corr = power - vsg_power; + entry_counter++; + } + } + + if (entry_counter == 0) { + log_error("No valid entries found: %s\n", binary_path); + status = BLADERF_ERR_UNEXPECTED; + goto error; + } + + gain_tbls[ch].version = image->version; + gain_tbls[ch].start_freq = gain_tbls[ch].entries[0].freq; + gain_tbls[ch].stop_freq = gain_tbls[ch].entries[entry_counter-1].freq; + gain_tbls[ch].n_entries = entry_counter; + gain_tbls[ch].ch = ch; + gain_tbls[ch].state = BLADERF_GAIN_CAL_LOADED; + gain_tbls[ch].enabled = true; + gain_tbls[ch].gain_target = current_gain; + strncpy(gain_tbls[ch].file_path, binary_path, gain_tbls[ch].file_path_len); + + gain_cal_tbl_free(&dev->gain_tbls[ch]); + dev->gain_tbls[ch] = gain_tbls[ch]; + +error: + if (status != 0) { + log_error("binary_path: %s\n", binary_path); + } + + if (binaryFile) + fclose(binaryFile); + if (image) + bladerf_free_image(image); + return status; +} + +static void find_floor_ceil_entries_by_frequency(const struct bladerf_gain_cal_tbl *tbl, bladerf_frequency freq, + struct bladerf_gain_cal_entry **floor, struct bladerf_gain_cal_entry **ceil) { + int mid = 0; + *floor = NULL; + *ceil = NULL; + + if (tbl == NULL || tbl->entries == NULL || tbl->n_entries == 0) { + return; + } + + int32_t low = 0; + int32_t high = tbl->n_entries - 1; + + /* Binary search for the entry with the closest frequency to 'freq' */ + while (low <= high && high >= 0) { + mid = (low + high) / 2; + if (tbl->entries[mid].freq == freq) { + *floor = &tbl->entries[mid]; + *ceil = &tbl->entries[mid]; + return; + } else if (tbl->entries[mid].freq < freq) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + /* At this point, 'low' points to the first entry greater than 'freq', + and 'high' points to the last entry less than 'freq'. */ + if ((uint32_t)low < tbl->n_entries) { + *ceil = &tbl->entries[low]; + } + + /* If 'high' is negative, then there are no entries less than 'freq' */ + *floor = (high >= 0) ? &tbl->entries[high] : &tbl->entries[0]; +} + +int get_gain_cal_entry(const struct bladerf_gain_cal_tbl *tbl, bladerf_frequency freq, struct bladerf_gain_cal_entry *result) { + struct bladerf_gain_cal_entry *floor_entry, *ceil_entry; + + if (tbl == NULL || result == NULL) { + return BLADERF_ERR_INVAL; + } + + find_floor_ceil_entries_by_frequency(tbl, freq, &floor_entry, &ceil_entry); + if (!floor_entry || !ceil_entry) { + log_error("Could not find ceil or floor entries in the calibration table\n"); + return BLADERF_ERR_UNEXPECTED; + } + + if (floor_entry->freq == ceil_entry->freq) { + result->freq = freq; + result->gain_corr = floor_entry->gain_corr; + return 0; + } + + double interpolated_gain_corr = floor_entry->gain_corr + + (freq - floor_entry->freq) * + (ceil_entry->gain_corr - floor_entry->gain_corr) / + (ceil_entry->freq - floor_entry->freq); + + result->freq = freq; + result->gain_corr = interpolated_gain_corr; + return 0; +} + +int get_gain_correction(struct bladerf *dev, bladerf_frequency freq, bladerf_channel ch, bladerf_gain *compensated_gain) { + int status = 0; + struct bladerf_gain_cal_tbl *cal_table = &dev->gain_tbls[ch]; + struct bladerf_gain_cal_entry entry_next; + + CHECK_STATUS(get_gain_cal_entry(cal_table, freq, &entry_next)); + + *compensated_gain = __round_int(cal_table->gain_target - entry_next.gain_corr); + + log_verbose("Target gain: %i, Compen. gain: %i\n", dev->gain_tbls[ch].gain_target, *compensated_gain); + return status; +} + +int apply_gain_correction(struct bladerf *dev, bladerf_channel ch, bladerf_frequency frequency) { + struct bladerf_range const *gain_range = NULL; + bladerf_frequency current_frequency; + bladerf_gain gain_compensated; + + if (dev->gain_tbls[ch].enabled == false) { + log_error("Gain compensation disabled. Can't apply gain correction.\n"); + return BLADERF_ERR_UNEXPECTED; + } + + CHECK_STATUS(dev->board->get_gain_range(dev, ch, &gain_range)); + CHECK_STATUS(dev->board->get_frequency(dev, ch, ¤t_frequency)); + CHECK_STATUS(get_gain_correction(dev, frequency, ch, &gain_compensated)); + + if (gain_compensated > gain_range->max || gain_compensated < gain_range->min) { + log_warning("Power compensated gain out of range [%i:%i]: %i\n", + gain_range->min, gain_range->max, gain_compensated); + gain_compensated = (gain_compensated > gain_range->max) ? gain_range->max : gain_range->min; + log_warning("Gain clamped to: %i\n", gain_compensated); + } + + CHECK_STATUS(dev->board->set_gain(dev, ch, gain_compensated);); + + return 0; +} |