254 lines
8.6 KiB
C
254 lines
8.6 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_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;
|
|
} |