291 lines
9.9 KiB
C
291 lines
9.9 KiB
C
#include "ps2000_internal.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
/* ========================================================================== */
|
|
/* 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;
|
|
} |