#include "ps2000_internal.h" #include #include #include /* ========================================================================== */ /* CONFIGURATION CONSTANTS */ /* ========================================================================== */ #define PS2000_CFG_EEPROM_SIZE 256U #define PS2000_CFG_CALIB_OFFSET 0x200U #define PS2000_CFG_GAIN_SCALE 1.0e-6 #define PS2000_CFG_OFFSET_SCALE 1.0 /* EEPROM header structure (reconstructed from sub_2DDF0) */ #pragma pack(push, 1) typedef struct _ps2000_eeprom_header { uint32_t magic; /* 0x5053324B ('PS2K') */ uint16_t version; /* Format version */ uint16_t checksum; /* Checksum */ uint8_t serial_num[16]; /* Device serial number */ uint8_t reserved[32]; /* Reserved */ } ps2000_eeprom_header_t; typedef struct _ps2000_calib_entry { int16_t offset_raw; /* Raw offset value */ uint32_t gain_raw; /* Raw gain value (fixed point) */ uint8_t range_idx; /* Range index */ uint8_t channel; /* Channel number */ } ps2000_calib_entry_t; #pragma pack(pop) /* ========================================================================== */ /* INTERNAL CONFIG HELPERS */ /* ========================================================================== */ /** * @brief Calculate EEPROM checksum. * * Reconstructed from sub_27025 / sub_27050. * Simple byte sum with carry handling. */ static uint16_t _ps2000_cfg_calc_checksum(const uint8_t *data, uint32_t len) { uint32_t sum = 0; for (uint32_t i = 0; i < len; i++) { sum += data[i]; } return (uint16_t)(sum & 0xFFFF); } /** * @brief Parse calibration table from EEPROM. * * Reconstructed from sub_2DDF0+4F7 / sub_2E2A5. * Validates magic number and checksum, then extracts * calibration data for each channel. */ static ps2000_status_t _ps2000_cfg_parse_calib_table(ps2000_context_t *ctx, const uint8_t *eeprom_data, uint32_t eeprom_len) { if (!ctx || !eeprom_data) return PS2000_ERR_INVALID_PARAM; /* Check EEPROM size */ if (eeprom_len < sizeof(ps2000_eeprom_header_t)) { PS2000_LOG_ERR("EEPROM too small: %u < %zu", eeprom_len, sizeof(ps2000_eeprom_header_t)); return PS2000_ERR_CALIBRATION; } const ps2000_eeprom_header_t *header = (const ps2000_eeprom_header_t*)eeprom_data; /* Verify magic number */ if (header->magic != PS2000_EEPROM_MAGIC) { PS2000_LOG_ERR("Invalid EEPROM magic: 0x%08X (expected 0x%08X)", header->magic, PS2000_EEPROM_MAGIC); return PS2000_ERR_CALIBRATION; } /* Verify checksum */ uint16_t calc_cs = _ps2000_cfg_calc_checksum(eeprom_data, eeprom_len - 2); if (calc_cs != header->checksum) { PS2000_LOG_ERR("EEPROM checksum mismatch: calc=0x%04X stored=0x%04X", calc_cs, header->checksum); return PS2000_ERR_CALIBRATION; } /* Apply calibration for each channel */ for (uint32_t ch = 0; ch < PS2000_MAX_CHANNELS; ch++) { uint32_t cal_offset = sizeof(ps2000_eeprom_header_t) + (ch * sizeof(ps2000_calib_entry_t)); if (cal_offset + sizeof(ps2000_calib_entry_t) > eeprom_len) { PS2000_LOG_WRN("EEPROM truncated at channel %u", ch); break; } const ps2000_calib_entry_t *entry = (const ps2000_calib_entry_t*)(eeprom_data + cal_offset); /* Convert raw values to usable calibration data */ ctx->info.cal_offset[ch] = entry->offset_raw; ctx->info.cal_gain[ch] = (double)entry->gain_raw * PS2000_CFG_GAIN_SCALE; /* Protect against zero gain */ if (ctx->info.cal_gain[ch] == 0.0) { ctx->info.cal_gain[ch] = 1.0; } PS2000_LOG_DBG("Channel %u calibration: offset=%d gain=%f", ch, ctx->info.cal_offset[ch], ctx->info.cal_gain[ch]); } ctx->info.eeprom_valid = 1; return PS2000_OK; } /* ========================================================================== */ /* PUBLIC CONFIG API */ /* ========================================================================== */ /** * @brief Load and parse device EEPROM. * * Reconstructs ps2000_open_unit / sub_2DDF0. * Uses USB control transfer to read EEPROM data. * * @param ctx Device context * @return PS2000_OK on success, error code otherwise */ ps2000_status_t _ps2000_cfg_load_eeprom(ps2000_context_t *ctx) { if (!ctx) return PS2000_ERR_INVALID_PARAM; if (!ctx->usb_host.handle) return PS2000_ERR_NOT_OPENED; uint8_t eeprom_buf[PS2000_CFG_EEPROM_SIZE]; memset(eeprom_buf, 0, sizeof(eeprom_buf)); /* Read EEPROM via USB (Vendor Request) */ ps2000_status_t status = _ps2000_usb_control_transfer( ctx, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, PS2000_IO_CMD_READ_EEPROM, 0, /* Value: data type (0 = EEPROM) */ 0, /* Index: offset */ eeprom_buf, sizeof(eeprom_buf) ); if (status != PS2000_OK) { PS2000_LOG_ERR("Failed to read EEPROM: %d", status); return status; } /* Parse and apply calibration */ status = _ps2000_cfg_parse_calib_table(ctx, eeprom_buf, sizeof(eeprom_buf)); if (status == PS2000_OK) { ps2000_eeprom_header_t *hdr = (ps2000_eeprom_header_t*)eeprom_buf; PS2000_LOG_INF("EEPROM loaded: ver=%u, cs=%04X, serial=%s", hdr->version, hdr->checksum, hdr->serial_num); } else { PS2000_LOG_ERR("Failed to parse EEPROM: %d", status); } return status; } /** * @brief Apply calibration to raw data array. * * Reconstructs vectorized loop from sub_28A19 / sub_28CAA. * Formula: result = (raw * gain) - offset * * @param ctx Device context * @param raw_data Raw data array (modified in place) * @param num_samples Number of samples to process * @return PS2000_OK on success, error code otherwise */ ps2000_status_t _ps2000_cfg_apply_calibration(ps2000_context_t *ctx, int16_t *raw_data, uint32_t num_samples) { if (!ctx || !raw_data) return PS2000_ERR_INVALID_PARAM; if (num_samples == 0) return PS2000_OK; /* Use channel 0 calibration by default */ double gain = ctx->info.cal_gain[0]; double offset = (double)ctx->info.cal_offset[0] * PS2000_CFG_OFFSET_SCALE; /* Apply calibration to each sample */ for (uint32_t i = 0; i < num_samples; i++) { double val = (double)raw_data[i] * gain; val -= offset; /* Clamp to int16 range */ if (val > 32767.0) val = 32767.0; if (val < -32768.0) val = -32768.0; raw_data[i] = (int16_t)(int64_t)val; } return PS2000_OK; } /** * @brief Get device information. * * Wrapper around GET_INFO command. * Returns serial number and hardware version. * * @param ctx Device context * @param serial_buf Buffer for serial number string * @param buf_len Length of serial buffer * @param hw_version Pointer to store hardware version * @return PS2000_OK on success, error code otherwise */ ps2000_status_t _ps2000_cfg_get_device_info(ps2000_context_t *ctx, char *serial_buf, uint32_t buf_len, uint32_t *hw_version) { if (!ctx) return PS2000_ERR_INVALID_PARAM; if (!ctx->usb_host.handle) return PS2000_ERR_NOT_OPENED; uint8_t info_buf[64]; ps2000_status_t status = _ps2000_usb_control_transfer( ctx, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, PS2000_IO_CMD_READ_EEPROM, 1, /* Value: 1 = Device Info */ 0, info_buf, sizeof(info_buf) ); if (status != PS2000_OK) { PS2000_LOG_ERR("Failed to get device info: %d", status); return status; } /* Parse response */ if (serial_buf && buf_len > 0) { uint32_t copy_len = (buf_len - 1 < 16) ? (buf_len - 1) : 16; memcpy(serial_buf, info_buf, copy_len); serial_buf[copy_len] = '\0'; } if (hw_version) { *hw_version = (uint32_t)info_buf[16] | ((uint32_t)info_buf[17] << 8); } PS2000_LOG_INF("Device info: serial=%s hw_ver=%u", serial_buf ? serial_buf : "N/A", hw_version ? *hw_version : 0); return PS2000_OK; }