Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/embeddedci-mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog — `embeddedci-mcp`

## Unreleased

- `calibration` and the calibrate tools report `adc_comp`, the ADC input's recorded frequency
response (firmware 3.9.5+). Reference only.

## 2.13.4

- New tool `adc_range`: read or switch the ADC input range (normal ±32.8 V, low ±5.46 V at 6x the
Expand Down
3 changes: 3 additions & 0 deletions packages/embeddedci-mcp/src/embeddedci_mcp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,9 @@ class CalibrationResult(BaseModel):
adc_gain_ppm: Dict[str, Optional[int]] = Field(default_factory=dict, description=(
"ADC gain correction per source (cal1, cal2, ext) in ppm; null = the built-in gain."))
dmm: bool = Field(False, description="True when any DMM-guided part is stored.")
adc_comp: Optional[Dict[str, float]] = Field(None, description=(
"The ADC input's recorded frequency response: hf_dc_gain (high-frequency gain against DC, 1.0 "
"is flat) and c_top_pf. Reference only, no reading is scaled with it. null: not measured."))
adc_low_gain_ppm: Optional[int] = Field(None, description=(
"The ADC low range's gain correction in ppm (analog module v3r4+); null = the nominal divider ratio."))
zero_count: Optional[float] = Field(None, description=(
Expand Down
2 changes: 2 additions & 0 deletions packages/embeddedci-mcp/src/embeddedci_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,8 @@ def _calibration_result(c: Any) -> m.CalibrationResult:
count=c.count, span=c.span, samples=c.samples, dac=dac,
adc_gain_ppm=dict(c.adc_gain_ppm or {}), dmm=c.dmm, measured=c.measured,
adc_low_gain_ppm=c.adc_low_gain_ppm,
adc_comp=None if c.adc_comp is None else {
"hf_dc_gain": c.adc_comp.hf_dc_gain, "c_top_pf": c.adc_comp.c_top_pf},
zero_count=c.zero_count)


Expand Down
6 changes: 6 additions & 0 deletions packages/embeddedci/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog — `embeddedci`

## Unreleased

- `BenchPod.store_adc_compensation(hf_dc_gain, c_top_pf)` records the ADC input's measured frequency
response on the pod (firmware 3.9.5+), and `Calibration.adc_comp` (`AdcCompensation`) reads it.
Reference only: no reading is scaled with it.

## 2.13.4

- The ADC input range (pod firmware 3.9.5+, analog module v3r4, `Capabilities.adc_low_range`):
Expand Down
5 changes: 4 additions & 1 deletion packages/embeddedci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,10 @@ bp.clear_calibration("dac") # one part: "offset", "dac" or
`calibrate_dac(path, points)` takes two `(code16, volts)` pairs: the 16-bit code `dac_output`
drove and the DMM reading. From then on `dac_output`, the calibrated `generate` and the DAC output
limits convert with this pod's line (`DacLine`: `a`, `b` per 16-bit code, `offset`, `gain_ppm`
against the built-in line). `calibrate_adc_gain(source)` measures `cal1` through the 5 V output,
against the built-in line). `store_adc_compensation(hf_dc_gain, c_top_pf)` records the ADC input's
measured frequency response on the pod (firmware 3.9.5+), read back as `Calibration.adc_comp`
(`AdcCompensation`), for
reference only: no reading is scaled with it. `calibrate_adc_gain(source)` measures `cal1` through the 5 V output,
`cal2` through the ±12 V output and `ext` (the front SMA, which `capture_adc` and the control loop
use) through an SMA cable from the ±12 V output to the ADC input; `current_in` follows `cal1`. It
needs the ADC offset and the driving output's DMM line, and `Calibration.measured` says what it
Expand Down
2 changes: 2 additions & 0 deletions packages/embeddedci/src/embeddedci/benchpod/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def test_boots(benchpod_target, firmware): # fixtures from the pytest plugin
AdcReading,
AnalogPathState,
Calibration,
AdcCompensation,
DacLine,
CurrentOutput,
DacOutput,
Expand Down Expand Up @@ -172,6 +173,7 @@ def test_boots(benchpod_target, firmware): # fixtures from the pytest plugin
"AdcRangeTestPoint",
"AdcReading",
"Calibration",
"AdcCompensation",
"DacLine",
"CurrentOutput",
"FpgaImageInfo",
Expand Down
14 changes: 14 additions & 0 deletions packages/embeddedci/src/embeddedci/benchpod/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,20 @@ def calibrate_adc_gain(self, source: AdcGainSource, *, range: AdcRange = "normal
self._follow_adc_range("normal")
return cal

def store_adc_compensation(self, hf_dc_gain: float, c_top_pf: float) -> Calibration:
"""Record the ADC input's measured frequency response on the pod (firmware 3.9.5+).

``hf_dc_gain`` is the high-frequency gain against DC and ``c_top_pf`` the divider's fitted
top-leg capacitance, both from ``adc_comp_sweep.py`` in the firmware's calibration scripts.
Reference only: no reading is scaled with them. Read it back as ``Calibration.adc_comp``.
"""
self._require_capability("calibrate", "calibration")
cal = Calibration.from_reply(self.command({"cmd": "calibrate", "comp_hf_dc_e4": round(hf_dc_gain * 1e4),
"comp_c_top_ff": round(c_top_pf * 1e3)}))
if cal.adc_comp is None:
raise BenchPodError("this pod's firmware cannot record the ADC frequency response (needs 3.9.5+)")
return cal

# -- CAN (FDCAN1 / TCAN1044 transceiver) -----------------------------------
# Single-node board: use a loopback ``mode`` to self-test on one pod. :meth:`open_can` and
# :class:`~embeddedci.benchpod.can.CanBus` are the convenient interface; these are the
Expand Down
20 changes: 20 additions & 0 deletions packages/embeddedci/src/embeddedci/benchpod/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,23 @@ def from_reply(cls, d: Any) -> Optional["DacLine"]:
offset=(_opt_int(d, "offset_uv") or 0) / 1e6, gain_ppm=_opt_int(d, "gain_ppm") or 0)


@dataclass(frozen=True)
class AdcCompensation:
"""The ADC input's frequency response recorded on this pod. Reference only: no reading is
scaled with it, and DC accuracy does not depend on it."""

#: The high-frequency gain of the ADC input divider against its DC gain. 1.0 is flat.
hf_dc_gain: float
#: The fitted capacitance of the divider's top leg, in pF.
c_top_pf: float

@classmethod
def from_reply(cls, d: Any) -> Optional["AdcCompensation"]:
if not isinstance(d, dict) or _opt_int(d, "hf_dc_e4") is None or _opt_int(d, "c_top_ff") is None:
return None
return cls(hf_dc_gain=(_opt_int(d, "hf_dc_e4") or 0) / 1e4, c_top_pf=(_opt_int(d, "c_top_ff") or 0) / 1e3)


@dataclass(frozen=True)
class Calibration:
"""The pod's own calibration (``BenchPod.calibrate`` / ``calibration``).
Expand Down Expand Up @@ -447,6 +464,8 @@ class Calibration:
#: The ADC low range's gain correction in ppm (:meth:`BenchPod.calibrate_adc_gain` with
#: ``range="low"``); ``None`` = the divider's nominal ratio, or a board without the low range.
adc_low_gain_ppm: Optional[int] = None
#: The ADC input's recorded frequency response; ``None`` = not measured, or firmware before 3.9.5.
adc_comp: Optional[AdcCompensation] = None
#: The raw zero the offset is derived from: the mean count ``current_in`` read with J8 open,
#: unwrapped. ``None`` when not calibrated or on firmware before 3.9.4.
zero_count: Optional[float] = None
Expand All @@ -470,6 +489,7 @@ def from_reply(cls, reply: Any) -> "Calibration":
dac={str(k): DacLine.from_reply(v) for k, v in dac.items()},
adc_gain_ppm={str(k): _opt_int(gains, k) for k in gains},
dmm=d.get("dmm") is True, adc_low_gain_ppm=_opt_int(d, "adc_low_gain_ppm"),
adc_comp=AdcCompensation.from_reply(d.get("adc_comp")),
zero_count=None if _opt_int(d, "zero_count_mc") is None else _opt_int(d, "zero_count_mc") / 1000.0,
measured=None if meas is None else {
k[:-3]: (_opt_int(meas, k) or 0) / 1e6 for k in meas if k.endswith("_uv")},
Expand Down
10 changes: 10 additions & 0 deletions packages/embeddedci/tests/api_surface.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"AdcCompensation": {
"__fields__": [
"hf_dc_gain",
"c_top_pf"
],
"from_reply": "(cls, d: 'Any') -> \"Optional['AdcCompensation']\""
},
"AdcGainSource": "Literal['cal1', 'cal2', 'ext']",
"AdcRange": "Literal['normal', 'low']",
"AdcRangeState": {
Expand Down Expand Up @@ -174,6 +181,7 @@
"signal": "(self, name: 'str') -> 'GpioPin'",
"spi_flash": "(self, image: \"Union[bytes, str, 'os.PathLike[str]']\", addr: 'int' = 0, *, erase: 'bool' = True, verify: 'bool' = True, hold_reset: 'bool' = False, sck: 'Union[Pin, int, str, None]' = None, mosi: 'Union[Pin, int, str, None]' = None, miso: 'Union[Pin, int, str, None]' = None, cs: 'Union[Pin, int, str, None]' = None, hz: 'int' = 6000000, mode: 'int' = 0, progress: 'Optional[_spi.Progress]' = None) -> '_spi.SpiFlashResult'",
"status": "(self) -> 'Dict[str, Any]'",
"store_adc_compensation": "(self, hf_dc_gain: 'float', c_top_pf: 'float') -> 'Calibration'",
"supports": "(self, feature: 'str') -> 'bool'",
"target_power": "(self, efuse: 'Optional[Union[Efuse, int]]' = None, *, on: 'bool', delay: 'Optional[float]' = None) -> 'None'",
"target_status": "(self) -> 'TargetStatus'",
Expand Down Expand Up @@ -211,6 +219,7 @@
"adc_gain_ppm",
"dmm",
"adc_low_gain_ppm",
"adc_comp",
"zero_count",
"measured",
"raw"
Expand Down Expand Up @@ -1037,6 +1046,7 @@
"with_changes": "(self, **changes: 'Any') -> \"'Wiring'\""
},
"__all__": [
"AdcCompensation",
"AdcGainSource",
"AdcRange",
"AdcRangeState",
Expand Down
Loading