#include "ps2000_internal.h" #include #include #include /* ========================================================================== */ /* CONFIGURATION CONSTANTS */ /* ========================================================================== */ #define PS2000_CFG_EEPROM_SIZE 256U #define PS2000_CFG_CALIB_OFFSET 0x200U #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; /* CRC-16 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 (signed, scaled by /256.0) */ uint32_t gain_raw; /* Raw gain value (16.16 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 using CRC-16. * * FIX #6: Reconstructed from sub_27025 / sub_27050. * Uses CRC-16-CCITT with polynomial 0x1021 and initial value 0xFFFF. * This is NOT a simple byte sum. * * @param data Data buffer to checksum * @param len Length of data in bytes * @return 16-bit CRC value */ static uint16_t _ps2000_cfg_calc_checksum(const uint8_t *data, uint32_t len) { uint16_t crc = 0xFFFF; for (uint32_t i = 0; i < len; i++) { crc ^= (uint16_t)data[i] << 8; for (int j = 0; j < 8; j++) { if (crc & 0x8000) { crc = (uint16_t)((crc << 1) ^ 0x1021); } else { crc = (uint16_t)(crc << 1); } } } return crc; } /** * @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. * * FIX #10: gain_raw is 16.16 fixed-point format (divide by 65536.0). * offset_raw is signed and scaled by /256.0. */ 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; } /* FIX #6: Verify CRC-16 checksum (computed over all bytes except last 2) */ uint16_t calc_cs = _ps2000_cfg_calc_checksum(eeprom_data, eeprom_len - 2); if (calc_cs != header->checksum) { PS2000_LOG_ERR("EEPROM CRC-16 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); /* * FIX #10: Convert raw values using correct interpretation. * * gain_raw is 16.16 fixed-point: actual gain = gain_raw / 65536.0 * offset_raw is signed 16-bit: actual offset = offset_raw / 256.0 * * Reconstructed from sub_2E2A5: * mov eax, [rdi+gain_raw] * cvtsi2sd xmm0, eax * divsd xmm0, cs:qword_65536 * movsd [rdi+cal_gain], xmm0 */ double gain = (double)entry->gain_raw / 65536.0; double offset = (double)entry->offset_raw / 256.0; /* Protect against zero gain */ if (gain == 0.0) { gain = 1.0; } ctx->info.cal_gain[ch] = gain; ctx->info.cal_offset[ch] = (int16_t)(offset); PS2000_LOG_DBG("Channel %u calibration: offset=%.3f gain=%.6f", ch, offset, gain); } 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, crc=%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 / 16384.0 * gain + offset) * 32768.0 * * @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] / 256.0; /* Apply calibration to each sample using correct formula */ for (uint32_t i = 0; i < num_samples; i++) { /* Normalize to [-1.0, +1.0] */ double normalized = (double)raw_data[i] / PS2000_ADC_FULL_SCALE; /* Apply gain and offset */ double calibrated = (normalized * gain) + offset; /* Scale back to int16 range */ double scaled = calibrated * PS2000_ADC_INT16_SCALE; /* Round and clamp */ int32_t result = (int32_t)_ps2000_math_round_fast(scaled); if (result > 32767) result = 32767; if (result < -32768) result = -32768; raw_data[i] = (int16_t)result; } 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; }