embeddedci 2.13.2__tar.gz → 2.13.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {embeddedci-2.13.2 → embeddedci-2.13.3}/CHANGELOG.md +10 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/PKG-INFO +45 -7
- {embeddedci-2.13.2 → embeddedci-2.13.3}/README.md +44 -6
- {embeddedci-2.13.2 → embeddedci-2.13.3}/pyproject.toml +1 -1
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/__init__.py +8 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/capabilities.py +6 -1
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/client.py +65 -3
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/constants.py +10 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/state.py +51 -4
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/transport/cloud.py +2 -1
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/api_surface.json +25 -1
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_client_v2.py +62 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/.gitignore +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/LICENSE +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/docs/event-uart-design.md +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/examples/README.md +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/examples/test_bmp280.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/__init__.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/can.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/capture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/ci.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/cli_config.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/cli_login.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/cloud_auth.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/connection.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/control_loop.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/decode.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/discovery.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/dsp.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/errors.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/flash.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/gpio.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/gps.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/i2c.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/lease.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/lowlevel.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/motor_emulator.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/power.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/protocol.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/pytest_plugin.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/replay.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/results.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/sensor.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/server_api.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/spi.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/transport/__init__.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/transport/base.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/transport/serial.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/transport/tcp.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/transport/tls.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/transport/usblink.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/uart.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/waveforms.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/benchpod/wiring.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/py.typed +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/src/embeddedci/upload_build.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/conftest.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/README.md +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/conftest.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/e2e_helpers.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_cloud.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_digital.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_dut.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_gpio.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_la13_14.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_pod.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_rev3.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_spi.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/e2e/test_e2e_usb.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/examples/test_bmp280_hil.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/examples/test_can_ecu_demo.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/examples/test_can_loopback_hil.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/examples/test_smoke.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_adc_fft_hw.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_api_surface.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_can.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_capabilities.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_capabilities_scope.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_capture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_capture_replay_hw.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_ci.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_cloud.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_connection.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_control_loop.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_control_loop_hw.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_current_out_waveforms.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_dac_limits_fixture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_dac_path_default.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_dac_path_ranges.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_decode.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_decode_parity.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_docs_coverage.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_dsp.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_flash_bridge.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_flash_build.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_generate16.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_gpio.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_i2c_decode.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_i2c_parity.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_image_switching.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_integration.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_la_voltage_fixture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_lan_tls.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_lease.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_motor_emulator.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_one_source.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_pins_bias.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_protocol.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_refusal_errors.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_replay.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_sensor.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_serial_autodetect.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_serial_text_console.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_spi.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_tcp_transport.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_timed_power.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_timing.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_transport_failures.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_trigger_power.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_uart_capture.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_uart_session.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_unsupported_feature.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_upload_build.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_usb_net.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_waveforms.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/test_wiring.py +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/testdata/i2c_decode_vectors.json +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/testdata/spi_decode_vectors.json +0 -0
- {embeddedci-2.13.2 → embeddedci-2.13.3}/tests/testdata/uart_decode_vectors.json +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog — `embeddedci`
|
|
2
2
|
|
|
3
|
+
## 2.13.3
|
|
4
|
+
|
|
5
|
+
- The DMM-guided calibration (pod firmware 3.9.4+, `Capabilities.cal_dmm`):
|
|
6
|
+
`BenchPod.calibrate_dac(path, points)` stores this pod's own line for a DAC output from two
|
|
7
|
+
`(code16, volts)` DMM readings, `calibrate_adc_gain(source)` measures an ADC gain (`cal1`,
|
|
8
|
+
`cal2`, `ext`) through a loopback, and `clear_calibration(part)` clears one part (`offset`,
|
|
9
|
+
`dac`, `adc`). `Calibration` gains `dac` (`DacLine`), `adc_gain_ppm`, `dmm` and `measured`;
|
|
10
|
+
`BenchPod.DMM_SET_POINTS` has the set points. New types `DacCalPath`, `AdcGainSource`,
|
|
11
|
+
`CalibrationPart`. `Calibration.zero_count` is the raw J8-open zero the pod derives its offset from.
|
|
12
|
+
|
|
3
13
|
## 2.13.2
|
|
4
14
|
|
|
5
15
|
- Docs: `calibrate()` corrects every ADC source from firmware 3.9.3 (they share the front end) and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: embeddedci
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.3
|
|
4
4
|
Summary: BenchPod SDK and pytest plugin for EmbeddedCI hardware-in-the-loop testing
|
|
5
5
|
Project-URL: Homepage, https://embeddedci.com
|
|
6
6
|
Project-URL: Repository, https://github.com/embeddedci-com/embeddedci-python
|
|
@@ -1046,8 +1046,45 @@ and keeps what it had. `a` and `b` are the fit the pod now uses for `current_in`
|
|
|
1046
1046
|
Every ADC source shares the same front end after its input switch, so from firmware 3.9.3 the pod
|
|
1047
1047
|
takes the offset out of all of them: `adc_read` on `ext`, `cal1` and `cal2`, and `capture_adc`,
|
|
1048
1048
|
whose `volts` use the fit the pod reports (on the LAN in `status`, through embeddedci.com in its
|
|
1049
|
-
capabilities). Older firmware corrects `current_in` only.
|
|
1050
|
-
|
|
1049
|
+
capabilities). Older firmware corrects `current_in` only. Needs firmware 3.4.0 (capability
|
|
1050
|
+
`calibrate`, `Capabilities.calibrate`). The web app has it in the pod's Settings tab, under
|
|
1051
|
+
Calibration.
|
|
1052
|
+
|
|
1053
|
+
#### Calibrating with a DMM
|
|
1054
|
+
|
|
1055
|
+
With a bench DMM the pod can also take out what the offset leaves (firmware 3.9.4+, capability
|
|
1056
|
+
`cal_dmm`, `Capabilities.cal_dmm`): its own DAC line per output, then the ADC gain per source.
|
|
1057
|
+
Every step is optional and stored on its own; an output or source without one keeps the built-in
|
|
1058
|
+
fit. Disconnect the DUT from the DAC outputs first: this drives up to ±5 V on them.
|
|
1059
|
+
|
|
1060
|
+
```python
|
|
1061
|
+
points = []
|
|
1062
|
+
for volts in bp.DMM_SET_POINTS["5v"]: # 1.0 and 4.0 V; 3v3: 0.5 / 3.0, 12v: -5 / +5
|
|
1063
|
+
out = bp.dac_output("5v", volts=volts)
|
|
1064
|
+
points.append((out.code16, read_dmm())) # the DMM on the 5 V output SMA, DC volts
|
|
1065
|
+
cal = bp.calibrate_dac("5v", points) # this pod's line for the 5 V output
|
|
1066
|
+
bp.dac_output("off")
|
|
1067
|
+
print(cal.dac["5v"]) # DacLine(a, b, offset=-0.0031, gain_ppm=-996)
|
|
1068
|
+
|
|
1069
|
+
bp.calibrate_adc_gain("cal1") # 5 V loopback, against the line just stored
|
|
1070
|
+
bp.calibrate_adc_gain("ext") # needs the 12v line and an SMA cable ±12 V -> ADC in
|
|
1071
|
+
bp.clear_calibration("dac") # one part: "offset", "dac" or "adc"; no part = all
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
`calibrate_dac(path, points)` takes two `(code16, volts)` pairs: the 16-bit code `dac_output`
|
|
1075
|
+
drove and the DMM reading. From then on `dac_output`, the calibrated `generate` and the DAC output
|
|
1076
|
+
limits convert with this pod's line (`DacLine`: `a`, `b` per 16-bit code, `offset`, `gain_ppm`
|
|
1077
|
+
against the built-in line). `calibrate_adc_gain(source)` measures `cal1` through the 5 V output,
|
|
1078
|
+
`cal2` through the ±12 V output and `ext` (the front SMA, which `capture_adc` and the control loop
|
|
1079
|
+
use) through an SMA cable from the ±12 V output to the ADC input; `current_in` follows `cal1`. It
|
|
1080
|
+
needs the ADC offset and the driving output's DMM line, and `Calibration.measured` says what it
|
|
1081
|
+
saw. The pod refuses (`BenchPodError`) and keeps what it had when a reading cannot be right: two
|
|
1082
|
+
codes too close, a reading far from what the output should give (the DMM on another SMA), a gain
|
|
1083
|
+
more than 2 % off. It also refuses while DAC output limits are set. `Calibration.dac`,
|
|
1084
|
+
`Calibration.adc_gain_ppm` and `Calibration.dmm` show what is stored. The pod stores raw
|
|
1085
|
+
measurements (the J8-open zero as `Calibration.zero_count`, the DMM points) and derives every fit
|
|
1086
|
+
from them, so a later firmware with re-measured built-in fits keeps them right. Going back to
|
|
1087
|
+
firmware 3.4.0 to 3.9.3 keeps the offset; the DMM parts are not used there.
|
|
1051
1088
|
|
|
1052
1089
|
### The 4-20 mA output
|
|
1053
1090
|
|
|
@@ -1631,7 +1668,7 @@ lease_wait=600.0, lease_ttl=120, wiring=None)`. A context manager; `close()` is
|
|
|
1631
1668
|
| GPIO and pins | `gpio(la, mode="output", *, level=None)`, `gpio_pins(las, mode="output", *, level=None)`, `configure_gpio(las, mode="output", *, level=None)`, `set_gpio(la, level)`, `read_gpio(la)`, `wait_for_level(la, level, *, timeout, poll=0.005)` (`False` when `timeout` passes first), `pin_levels()`, `la_pins()`, `release_gpio(*las)`, `la_step(la, *, steps, delay, dir_la=None, direction=0)` |
|
|
1632
1669
|
| I2C sensor | `enable_i2c_sensor(sensor=Sensor.BMP280, *, sda=None, scl=None, address=None, **values)`, `set_i2c_sensor(**values)`, `disable_i2c_sensor()`, `i2c_sensor_status()`, `i2c_sensor_types()`, `i2c_sensor_regs()`, `i2c_sensor_capture(...)` |
|
|
1633
1670
|
| GPS receiver | `enable_gps(tx, *, baud=9600, rate_hz=1, sentences=None, utc=None, **fix)`, `set_gps(*, utc=None, **fix)`, `disable_gps()`, `gps_status()` |
|
|
1634
|
-
| Analog | `analog_path(path)`, `adc_read(source="ext")`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration()` |
|
|
1671
|
+
| Analog | `analog_path(path)`, `adc_read(source="ext")`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration(part=None)`, `calibrate_dac(path, points)`, `calibrate_adc_gain(source)`, `DMM_SET_POINTS` |
|
|
1635
1672
|
| Captures | `capture_adc(...)`, `capture_la(...)`, `capture_correlated(...)`, `decode(source, protocol="i2c", ...)` |
|
|
1636
1673
|
| DAC | `generate(...)`, `replay(...)`, `replay_waveform(...)`, `dac_stop()`, `control_loop(...)`, `loop_input(...)`, `loop_probe()` |
|
|
1637
1674
|
| CAN | `open_can(*, bitrate=500000, mode="normal", term=False, fd=False)`, `can_config(...)`, `can_disable()`, `can_status()`, `can_term(on)`, `can_write(...)`, `can_read(...)`, `can_respond(...)`, `can_respond_clear()` |
|
|
@@ -1676,7 +1713,8 @@ few built from other shapes); `to_dict()` goes the other way where a type is sen
|
|
|
1676
1713
|
| `AdcReading` | `adc_read` | `source`, `voltage`, `count`, `span`, `offset`, `current` |
|
|
1677
1714
|
| `DacOutput` | `dac_output` | `path`, `voltage`, `code` |
|
|
1678
1715
|
| `CurrentOutput` | `current_out`, `current_out_range` | `current`, `code`, `min_current`, `max_current` |
|
|
1679
|
-
| `Calibration` | `calibrate`, `calibration`, `clear_calibration` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples` |
|
|
1716
|
+
| `Calibration` | `calibrate`, `calibration`, `clear_calibration`, `calibrate_dac`, `calibrate_adc_gain` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples`, `dac`, `adc_gain_ppm`, `dmm`, `measured`, `zero_count` |
|
|
1717
|
+
| `DacLine` | `Calibration.dac` | `a`, `b`, `offset`, `gain_ppm` |
|
|
1680
1718
|
| `Capture` | `capture_adc` | `counts`, `volts`, `currents`, `sample_rate_hz`, `source`, `trigger`, `duration`, `times()`, `mean()`, `min()`, `max()`, `rms()`, `rms_ac()`, `peak_to_peak()`, `first_crossing()`, `crossing_times()`, `fft()`, `dominant_frequency()` |
|
|
1681
1719
|
| `LaCapture` | `capture_la` | `words`, `channels`, `sample_rate_hz`, `trigger`, `duration`, `channel(la)`, `level_at()`, `edges()`, `edge_times()`, `first_edge()`, `delay()`, `frequency()`, `duty_cycle()`, `pulse_widths()`, `decode()` |
|
|
1682
1720
|
| `CorrelatedCapture` | `capture_correlated` | `adc` (a `Capture`), `la` (a `LaCapture`), sharing one hardware trigger |
|
|
@@ -1720,7 +1758,7 @@ reported keeps its default (`False`, `0` or `""`), so a feature check is just
|
|
|
1720
1758
|
| Identity | `board`, `firmware_version`, `board_rev` (`"v2"`, `"v3"`, `"unknown"`, or `""`), `la_vccio_mv` (the LA bank voltage the pod reports) |
|
|
1721
1759
|
| ADC | `adc_bits`, `adc_fullscale_mv`, `adc_channels`, `adc_offset_counts`, `adc_affine` (the front-end fit), `adc_max_count`, `counts_to_volts(count)` |
|
|
1722
1760
|
| DAC and replay | `dac`, `dac_dc`, `dac_replay`, `dac_deep_replay`, `dac_control_loop`, `dac_loop_sources` (gateware v29+), `dac_loop_input_map` (v30+), `dac_cotrig` (v27+), `generate16` (firmware 3.9.1+: `generate` in calibrated 16-bit volts), `dac_bits`, `dac_replay_bits`, `dac_replay_max_samples`, `dac_fullscale_mv`, `dac_channels`, `dac_limits` (output limits on the DAC paths) |
|
|
1723
|
-
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1761
|
+
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `cal_dmm` (firmware 3.9.4+: `calibrate_dac`, `calibrate_adc_gain`), `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1724
1762
|
| Firmware updates and policies | `flash_kb` (MCU flash in KiB, 0 when not reported), `blob_slots`, `ota_sig`, `sig_policy` (`"audit"`, `"required"`, ...), `sig_keys`, `sig_policy_cmd`, `lan_policy` (`"open"`, `"locked"`, `"off"`), `lan_policy_cmd`, `tunnel_max_tier`, `lease_state` (the pod refuses LAN writes while a cloud job holds it), `cloud_ca`, `cloud_proxy`, `ws_auth_v2` (server only) |
|
|
1725
1763
|
| Boot health | `safe_mode`, `safe_reason`, `last_crash`, `reset_cause`, `boot_warning()` (a one-line warning after safe mode or a crash, else `None`) |
|
|
1726
1764
|
| Source | `raw` (the map it was parsed from) |
|
|
@@ -1761,7 +1799,7 @@ timeout=60.0, lease_id=None)`. Errors raise `ServerApiError` (`status`), and an
|
|
|
1761
1799
|
| `curve_output_at(curve, input_code)`, `input_percent_to_code(percent)` | evaluate a curve the way the gateware does; an input percentage as a code |
|
|
1762
1800
|
| `encode_curve_b64url(codes)` | 16-bit DAC codes as little-endian base64url, the wire form of a curve |
|
|
1763
1801
|
| `INTERNAL`, `EXTERNAL`, `PIN1` … `PIN14`, `BMP280_ADDR_PRIMARY`, `BMP280_ADDR_SECONDARY` | named constants (see [API conventions](#api-conventions-and-stability)) |
|
|
1764
|
-
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1802
|
+
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `DacCalPath` (`"3v3"`, `"5v"`, `"12v"`), `AdcGainSource` (`"cal1"`, `"cal2"`, `"ext"`), `CalibrationPart` (`"offset"`, `"dac"`, `"adc"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1765
1803
|
| `decode`, `i2c`, `can`, `control_loop`, `motor_emulator`, `dsp` | submodules: off-device decoders, I2C helpers, CAN types, curve helpers, the emulator's registers, and `dsp`, a pure-Python mirror of the server's record-to-replay processing |
|
|
1766
1804
|
|
|
1767
1805
|
## Examples
|
|
@@ -1008,8 +1008,45 @@ and keeps what it had. `a` and `b` are the fit the pod now uses for `current_in`
|
|
|
1008
1008
|
Every ADC source shares the same front end after its input switch, so from firmware 3.9.3 the pod
|
|
1009
1009
|
takes the offset out of all of them: `adc_read` on `ext`, `cal1` and `cal2`, and `capture_adc`,
|
|
1010
1010
|
whose `volts` use the fit the pod reports (on the LAN in `status`, through embeddedci.com in its
|
|
1011
|
-
capabilities). Older firmware corrects `current_in` only.
|
|
1012
|
-
|
|
1011
|
+
capabilities). Older firmware corrects `current_in` only. Needs firmware 3.4.0 (capability
|
|
1012
|
+
`calibrate`, `Capabilities.calibrate`). The web app has it in the pod's Settings tab, under
|
|
1013
|
+
Calibration.
|
|
1014
|
+
|
|
1015
|
+
#### Calibrating with a DMM
|
|
1016
|
+
|
|
1017
|
+
With a bench DMM the pod can also take out what the offset leaves (firmware 3.9.4+, capability
|
|
1018
|
+
`cal_dmm`, `Capabilities.cal_dmm`): its own DAC line per output, then the ADC gain per source.
|
|
1019
|
+
Every step is optional and stored on its own; an output or source without one keeps the built-in
|
|
1020
|
+
fit. Disconnect the DUT from the DAC outputs first: this drives up to ±5 V on them.
|
|
1021
|
+
|
|
1022
|
+
```python
|
|
1023
|
+
points = []
|
|
1024
|
+
for volts in bp.DMM_SET_POINTS["5v"]: # 1.0 and 4.0 V; 3v3: 0.5 / 3.0, 12v: -5 / +5
|
|
1025
|
+
out = bp.dac_output("5v", volts=volts)
|
|
1026
|
+
points.append((out.code16, read_dmm())) # the DMM on the 5 V output SMA, DC volts
|
|
1027
|
+
cal = bp.calibrate_dac("5v", points) # this pod's line for the 5 V output
|
|
1028
|
+
bp.dac_output("off")
|
|
1029
|
+
print(cal.dac["5v"]) # DacLine(a, b, offset=-0.0031, gain_ppm=-996)
|
|
1030
|
+
|
|
1031
|
+
bp.calibrate_adc_gain("cal1") # 5 V loopback, against the line just stored
|
|
1032
|
+
bp.calibrate_adc_gain("ext") # needs the 12v line and an SMA cable ±12 V -> ADC in
|
|
1033
|
+
bp.clear_calibration("dac") # one part: "offset", "dac" or "adc"; no part = all
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
`calibrate_dac(path, points)` takes two `(code16, volts)` pairs: the 16-bit code `dac_output`
|
|
1037
|
+
drove and the DMM reading. From then on `dac_output`, the calibrated `generate` and the DAC output
|
|
1038
|
+
limits convert with this pod's line (`DacLine`: `a`, `b` per 16-bit code, `offset`, `gain_ppm`
|
|
1039
|
+
against the built-in line). `calibrate_adc_gain(source)` measures `cal1` through the 5 V output,
|
|
1040
|
+
`cal2` through the ±12 V output and `ext` (the front SMA, which `capture_adc` and the control loop
|
|
1041
|
+
use) through an SMA cable from the ±12 V output to the ADC input; `current_in` follows `cal1`. It
|
|
1042
|
+
needs the ADC offset and the driving output's DMM line, and `Calibration.measured` says what it
|
|
1043
|
+
saw. The pod refuses (`BenchPodError`) and keeps what it had when a reading cannot be right: two
|
|
1044
|
+
codes too close, a reading far from what the output should give (the DMM on another SMA), a gain
|
|
1045
|
+
more than 2 % off. It also refuses while DAC output limits are set. `Calibration.dac`,
|
|
1046
|
+
`Calibration.adc_gain_ppm` and `Calibration.dmm` show what is stored. The pod stores raw
|
|
1047
|
+
measurements (the J8-open zero as `Calibration.zero_count`, the DMM points) and derives every fit
|
|
1048
|
+
from them, so a later firmware with re-measured built-in fits keeps them right. Going back to
|
|
1049
|
+
firmware 3.4.0 to 3.9.3 keeps the offset; the DMM parts are not used there.
|
|
1013
1050
|
|
|
1014
1051
|
### The 4-20 mA output
|
|
1015
1052
|
|
|
@@ -1593,7 +1630,7 @@ lease_wait=600.0, lease_ttl=120, wiring=None)`. A context manager; `close()` is
|
|
|
1593
1630
|
| GPIO and pins | `gpio(la, mode="output", *, level=None)`, `gpio_pins(las, mode="output", *, level=None)`, `configure_gpio(las, mode="output", *, level=None)`, `set_gpio(la, level)`, `read_gpio(la)`, `wait_for_level(la, level, *, timeout, poll=0.005)` (`False` when `timeout` passes first), `pin_levels()`, `la_pins()`, `release_gpio(*las)`, `la_step(la, *, steps, delay, dir_la=None, direction=0)` |
|
|
1594
1631
|
| I2C sensor | `enable_i2c_sensor(sensor=Sensor.BMP280, *, sda=None, scl=None, address=None, **values)`, `set_i2c_sensor(**values)`, `disable_i2c_sensor()`, `i2c_sensor_status()`, `i2c_sensor_types()`, `i2c_sensor_regs()`, `i2c_sensor_capture(...)` |
|
|
1595
1632
|
| GPS receiver | `enable_gps(tx, *, baud=9600, rate_hz=1, sentences=None, utc=None, **fix)`, `set_gps(*, utc=None, **fix)`, `disable_gps()`, `gps_status()` |
|
|
1596
|
-
| Analog | `analog_path(path)`, `adc_read(source="ext")`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration()` |
|
|
1633
|
+
| Analog | `analog_path(path)`, `adc_read(source="ext")`, `dac_output(path, *, volts=None)`, `current_out(current)`, `current_out_range()`, `calibrate(source="current_in")`, `calibration()`, `clear_calibration(part=None)`, `calibrate_dac(path, points)`, `calibrate_adc_gain(source)`, `DMM_SET_POINTS` |
|
|
1597
1634
|
| Captures | `capture_adc(...)`, `capture_la(...)`, `capture_correlated(...)`, `decode(source, protocol="i2c", ...)` |
|
|
1598
1635
|
| DAC | `generate(...)`, `replay(...)`, `replay_waveform(...)`, `dac_stop()`, `control_loop(...)`, `loop_input(...)`, `loop_probe()` |
|
|
1599
1636
|
| CAN | `open_can(*, bitrate=500000, mode="normal", term=False, fd=False)`, `can_config(...)`, `can_disable()`, `can_status()`, `can_term(on)`, `can_write(...)`, `can_read(...)`, `can_respond(...)`, `can_respond_clear()` |
|
|
@@ -1638,7 +1675,8 @@ few built from other shapes); `to_dict()` goes the other way where a type is sen
|
|
|
1638
1675
|
| `AdcReading` | `adc_read` | `source`, `voltage`, `count`, `span`, `offset`, `current` |
|
|
1639
1676
|
| `DacOutput` | `dac_output` | `path`, `voltage`, `code` |
|
|
1640
1677
|
| `CurrentOutput` | `current_out`, `current_out_range` | `current`, `code`, `min_current`, `max_current` |
|
|
1641
|
-
| `Calibration` | `calibrate`, `calibration`, `clear_calibration` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples` |
|
|
1678
|
+
| `Calibration` | `calibrate`, `calibration`, `clear_calibration`, `calibrate_dac`, `calibrate_adc_gain` | `source`, `calibrated`, `offset`, `a`, `b`, `count`, `span`, `samples`, `dac`, `adc_gain_ppm`, `dmm`, `measured`, `zero_count` |
|
|
1679
|
+
| `DacLine` | `Calibration.dac` | `a`, `b`, `offset`, `gain_ppm` |
|
|
1642
1680
|
| `Capture` | `capture_adc` | `counts`, `volts`, `currents`, `sample_rate_hz`, `source`, `trigger`, `duration`, `times()`, `mean()`, `min()`, `max()`, `rms()`, `rms_ac()`, `peak_to_peak()`, `first_crossing()`, `crossing_times()`, `fft()`, `dominant_frequency()` |
|
|
1643
1681
|
| `LaCapture` | `capture_la` | `words`, `channels`, `sample_rate_hz`, `trigger`, `duration`, `channel(la)`, `level_at()`, `edges()`, `edge_times()`, `first_edge()`, `delay()`, `frequency()`, `duty_cycle()`, `pulse_widths()`, `decode()` |
|
|
1644
1682
|
| `CorrelatedCapture` | `capture_correlated` | `adc` (a `Capture`), `la` (a `LaCapture`), sharing one hardware trigger |
|
|
@@ -1682,7 +1720,7 @@ reported keeps its default (`False`, `0` or `""`), so a feature check is just
|
|
|
1682
1720
|
| Identity | `board`, `firmware_version`, `board_rev` (`"v2"`, `"v3"`, `"unknown"`, or `""`), `la_vccio_mv` (the LA bank voltage the pod reports) |
|
|
1683
1721
|
| ADC | `adc_bits`, `adc_fullscale_mv`, `adc_channels`, `adc_offset_counts`, `adc_affine` (the front-end fit), `adc_max_count`, `counts_to_volts(count)` |
|
|
1684
1722
|
| DAC and replay | `dac`, `dac_dc`, `dac_replay`, `dac_deep_replay`, `dac_control_loop`, `dac_loop_sources` (gateware v29+), `dac_loop_input_map` (v30+), `dac_cotrig` (v27+), `generate16` (firmware 3.9.1+: `generate` in calibrated 16-bit volts), `dac_bits`, `dac_replay_bits`, `dac_replay_max_samples`, `dac_fullscale_mv`, `dac_channels`, `dac_limits` (output limits on the DAC paths) |
|
|
1685
|
-
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1723
|
+
| Features | `scope`, `analyzer`, `serial`, `tunnel`, `command`, `ota`, `la_pins`, `gpio_read` (direct pin reads, v35+), `capture_trigger` (v35+), `power_profile`, `capture_b64` (faster ADC read-back, used automatically), `nrst_pin` (the dedicated reset pin, rev3), `usb_cc` (rev3), `spi_master` (v45+), `spi_stream`, `calibrate`, `cal_dmm` (firmware 3.9.4+: `calibrate_dac`, `calibrate_adc_gain`), `current_out`, `can`, `pod_current` (`PowerStatus.pod`), `sensor_types` (the BME280/SHT4x/MPU-6050 models), `gps` (the GPS receiver, gateware v48+), `analog` (`False` on the digital-only board, `None` when not announced) |
|
|
1686
1724
|
| Firmware updates and policies | `flash_kb` (MCU flash in KiB, 0 when not reported), `blob_slots`, `ota_sig`, `sig_policy` (`"audit"`, `"required"`, ...), `sig_keys`, `sig_policy_cmd`, `lan_policy` (`"open"`, `"locked"`, `"off"`), `lan_policy_cmd`, `tunnel_max_tier`, `lease_state` (the pod refuses LAN writes while a cloud job holds it), `cloud_ca`, `cloud_proxy`, `ws_auth_v2` (server only) |
|
|
1687
1725
|
| Boot health | `safe_mode`, `safe_reason`, `last_crash`, `reset_cause`, `boot_warning()` (a one-line warning after safe mode or a crash, else `None`) |
|
|
1688
1726
|
| Source | `raw` (the map it was parsed from) |
|
|
@@ -1723,7 +1761,7 @@ timeout=60.0, lease_id=None)`. Errors raise `ServerApiError` (`status`), and an
|
|
|
1723
1761
|
| `curve_output_at(curve, input_code)`, `input_percent_to_code(percent)` | evaluate a curve the way the gateware does; an input percentage as a code |
|
|
1724
1762
|
| `encode_curve_b64url(codes)` | 16-bit DAC codes as little-endian base64url, the wire form of a curve |
|
|
1725
1763
|
| `INTERNAL`, `EXTERNAL`, `PIN1` … `PIN14`, `BMP280_ADDR_PRIMARY`, `BMP280_ADDR_SECONDARY` | named constants (see [API conventions](#api-conventions-and-stability)) |
|
|
1726
|
-
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1764
|
+
| `DacPath`, `DacOutputPath`, `AnalogPath`, `AdcSource`, `CalibrateSource` (`"current_in"`), `DacCalPath` (`"3v3"`, `"5v"`, `"12v"`), `AdcGainSource` (`"cal1"`, `"cal2"`, `"ext"`), `CalibrationPart` (`"offset"`, `"dac"`, `"adc"`), `LoopSource`, `Waveshape`, `ReplayMapping`, `DecodeProtocol`, `CanMode`, `FaultType`, `GpioMode`, `Edge`, `TriggerEdge` | the `Literal` string types |
|
|
1727
1765
|
| `decode`, `i2c`, `can`, `control_loop`, `motor_emulator`, `dsp` | submodules: off-device decoders, I2C helpers, CAN types, curve helpers, the emulator's registers, and `dsp`, a pure-Python mirror of the server's record-to-replay processing |
|
|
1728
1766
|
|
|
1729
1767
|
## Examples
|
|
@@ -50,6 +50,9 @@ from .constants import (
|
|
|
50
50
|
PIN14,
|
|
51
51
|
AdcSource,
|
|
52
52
|
CalibrateSource,
|
|
53
|
+
CalibrationPart,
|
|
54
|
+
DacCalPath,
|
|
55
|
+
AdcGainSource,
|
|
53
56
|
AnalogPath,
|
|
54
57
|
CanMode,
|
|
55
58
|
DacOutputPath,
|
|
@@ -117,6 +120,7 @@ from .state import (
|
|
|
117
120
|
AdcReading,
|
|
118
121
|
AnalogPathState,
|
|
119
122
|
Calibration,
|
|
123
|
+
DacLine,
|
|
120
124
|
CurrentOutput,
|
|
121
125
|
DacOutput,
|
|
122
126
|
EfuseState,
|
|
@@ -161,6 +165,7 @@ __all__ = [
|
|
|
161
165
|
"DacOutput",
|
|
162
166
|
"AdcReading",
|
|
163
167
|
"Calibration",
|
|
168
|
+
"DacLine",
|
|
164
169
|
"CurrentOutput",
|
|
165
170
|
"FpgaImageInfo",
|
|
166
171
|
"LoopState",
|
|
@@ -254,6 +259,9 @@ __all__ = [
|
|
|
254
259
|
"AnalogPath",
|
|
255
260
|
"AdcSource",
|
|
256
261
|
"CalibrateSource",
|
|
262
|
+
"CalibrationPart",
|
|
263
|
+
"DacCalPath",
|
|
264
|
+
"AdcGainSource",
|
|
257
265
|
"Waveshape",
|
|
258
266
|
"ReplayMapping",
|
|
259
267
|
"LoopSource",
|
|
@@ -176,6 +176,10 @@ class Capabilities:
|
|
|
176
176
|
spi_stream: bool = False
|
|
177
177
|
#: The pod can calibrate its own ADC and keeps the result: :meth:`BenchPod.calibrate`.
|
|
178
178
|
calibrate: bool = False
|
|
179
|
+
#: ``calibrate`` also takes the DMM-guided forms (firmware 3.9.4+): this pod's own DAC line
|
|
180
|
+
#: per output and ADC gain per source, :meth:`BenchPod.calibrate_dac`,
|
|
181
|
+
#: :meth:`BenchPod.calibrate_adc_gain`.
|
|
182
|
+
cal_dmm: bool = False
|
|
179
183
|
#: The pod can hold a current on its 4-20 mA output (J9): :meth:`BenchPod.current_out`.
|
|
180
184
|
current_out: bool = False
|
|
181
185
|
#: Classic CAN on the pod's CAN+/CAN- terminals: :meth:`BenchPod.open_can`. Firmware older
|
|
@@ -319,6 +323,7 @@ class Capabilities:
|
|
|
319
323
|
("spi_master", "spi_master"),
|
|
320
324
|
("spi_stream", "spi_stream"),
|
|
321
325
|
("calibrate", "calibrate"),
|
|
326
|
+
("cal_dmm", "cal_dmm"),
|
|
322
327
|
("current_out", "current_out"),
|
|
323
328
|
("can", "can"),
|
|
324
329
|
("pod_current", "pod_current"),
|
|
@@ -379,7 +384,7 @@ class Capabilities:
|
|
|
379
384
|
("capture_b64", "cap.capture_b64"),
|
|
380
385
|
("nrst_pin", "cap.nrst_pin"), ("usb_cc", "cap.usb_cc"),
|
|
381
386
|
("spi_master", "cap.spi_master"), ("spi_stream", "cap.spi_stream"),
|
|
382
|
-
("calibrate", "cap.calibrate"), ("current_out", "cap.current_out"),
|
|
387
|
+
("calibrate", "cap.calibrate"), ("cal_dmm", "cap.cal_dmm"), ("current_out", "cap.current_out"),
|
|
383
388
|
("can", "cap.can"), ("pod_current", "cap.pod_current"),
|
|
384
389
|
("sensor_types", "cap.sensor_types"), ("gps", "cap.gps"),
|
|
385
390
|
("dac_limits", "cap.dac_limits"),
|
|
@@ -48,9 +48,12 @@ from . import uart as _uart
|
|
|
48
48
|
from .capabilities import ADC_WRAP_SPAN, ADC_WRAP_THRESHOLD, Capabilities
|
|
49
49
|
from .constants import (
|
|
50
50
|
ADC_SOURCE_PATHS,
|
|
51
|
+
ADC_GAIN_SOURCES,
|
|
51
52
|
ADC_SOURCES,
|
|
52
53
|
ANALOG_PATHS,
|
|
53
54
|
CALIBRATE_SOURCES,
|
|
55
|
+
CALIBRATION_PARTS,
|
|
56
|
+
DAC_CAL_PATHS,
|
|
54
57
|
CAN_MODES,
|
|
55
58
|
DAC_OUTPUT_PATHS,
|
|
56
59
|
DAC_PATHS,
|
|
@@ -65,9 +68,12 @@ from .constants import (
|
|
|
65
68
|
PULLUP_CHANNELS,
|
|
66
69
|
REPLAY_MAPPINGS,
|
|
67
70
|
WAVESHAPES,
|
|
71
|
+
AdcGainSource,
|
|
68
72
|
AdcSource,
|
|
69
73
|
AnalogPath,
|
|
70
74
|
CalibrateSource,
|
|
75
|
+
CalibrationPart,
|
|
76
|
+
DacCalPath,
|
|
71
77
|
CanMode,
|
|
72
78
|
DacOutputPath,
|
|
73
79
|
DacPath,
|
|
@@ -1255,10 +1261,66 @@ class BenchPod:
|
|
|
1255
1261
|
self._require_capability("calibrate", "calibration")
|
|
1256
1262
|
return Calibration.from_reply(self.command({"cmd": "calibrate"}))
|
|
1257
1263
|
|
|
1258
|
-
def clear_calibration(self) -> Calibration:
|
|
1259
|
-
"""Remove the pod's stored calibration. It goes back to the built-in
|
|
1264
|
+
def clear_calibration(self, part: Optional[CalibrationPart] = None) -> Calibration:
|
|
1265
|
+
"""Remove the pod's stored calibration. It goes back to the built-in fits.
|
|
1266
|
+
|
|
1267
|
+
``part`` removes one part only (firmware 3.9.4+): ``"offset"`` (the ADC offset),
|
|
1268
|
+
``"dac"`` (the DMM lines of every DAC output) or ``"adc"`` (the ADC gains).
|
|
1269
|
+
"""
|
|
1260
1270
|
self._require_capability("calibrate", "calibration")
|
|
1261
|
-
|
|
1271
|
+
if part is None:
|
|
1272
|
+
return Calibration.from_reply(self.command({"cmd": "calibrate", "clear": True}))
|
|
1273
|
+
check_choice(part, CALIBRATION_PARTS, "part")
|
|
1274
|
+
self._require_capability("cal_dmm", "partial calibration clears")
|
|
1275
|
+
return Calibration.from_reply(self.command({"cmd": "calibrate", "clear": part}))
|
|
1276
|
+
|
|
1277
|
+
#: The set points a DMM calibration drives on each output, in volts: well inside the range,
|
|
1278
|
+
#: away from the output floor near 0 V. The pod's ADC gain step uses the same ones.
|
|
1279
|
+
DMM_SET_POINTS: Dict[str, Tuple[float, float]] = {"3v3": (0.5, 3.0), "5v": (1.0, 4.0), "12v": (-5.0, 5.0)}
|
|
1280
|
+
|
|
1281
|
+
def calibrate_dac(self, path: DacCalPath,
|
|
1282
|
+
points: Sequence[Tuple[int, float]]) -> Calibration:
|
|
1283
|
+
"""Store this pod's own line for a DAC output from two DMM readings (firmware 3.9.4+).
|
|
1284
|
+
|
|
1285
|
+
``points`` is two ``(code16, volts)`` pairs: the 16-bit code the pod drove (the
|
|
1286
|
+
``code16`` of :meth:`dac_output`) and what a DMM read on that output's SMA. Disconnect
|
|
1287
|
+
the DUT from the output first. A typical run::
|
|
1288
|
+
|
|
1289
|
+
for v in pod.DMM_SET_POINTS["5v"]:
|
|
1290
|
+
out = pod.dac_output("5v", volts=v)
|
|
1291
|
+
points.append((out.code16, dmm.read_volts()))
|
|
1292
|
+
pod.calibrate_dac("5v", points)
|
|
1293
|
+
pod.dac_output("off")
|
|
1294
|
+
|
|
1295
|
+
From then on :meth:`dac_output`, the calibrated :meth:`generate` and the DAC output
|
|
1296
|
+
limits convert with this line. Raises :class:`BenchPodError` when the pod refuses: the
|
|
1297
|
+
codes are too close, a reading is far from what the output should give (the DMM is on
|
|
1298
|
+
another SMA), or the gain is more than 2 % off. The stored line is then unchanged.
|
|
1299
|
+
"""
|
|
1300
|
+
check_choice(path, DAC_CAL_PATHS, "path")
|
|
1301
|
+
pts = list(points)
|
|
1302
|
+
if len(pts) != 2:
|
|
1303
|
+
raise ValueError("calibrate_dac needs exactly two (code16, volts) points")
|
|
1304
|
+
(c1, v1), (c2, v2) = pts
|
|
1305
|
+
self._require_capability("cal_dmm", "DMM calibration")
|
|
1306
|
+
return Calibration.from_reply(self.command({
|
|
1307
|
+
"cmd": "calibrate", "dac": path, "code1": int(c1), "uv1": int(round(float(v1) * 1e6)),
|
|
1308
|
+
"code2": int(c2), "uv2": int(round(float(v2) * 1e6))}))
|
|
1309
|
+
|
|
1310
|
+
def calibrate_adc_gain(self, source: AdcGainSource) -> Calibration:
|
|
1311
|
+
"""Measure and store an ADC source's gain through a loopback (firmware 3.9.4+).
|
|
1312
|
+
|
|
1313
|
+
The pod drives two set points on a DAC output that has its DMM line
|
|
1314
|
+
(:meth:`calibrate_dac`) and compares what the ADC reads with what the output puts out:
|
|
1315
|
+
``cal1`` through the 5 V output, ``cal2`` through the ±12 V output, and ``ext`` (the front
|
|
1316
|
+
SMA, which captures and the control loop use) through an SMA cable from the ±12 V output
|
|
1317
|
+
to the ADC input. ``current_in`` follows ``cal1``. Needs the ADC offset first
|
|
1318
|
+
(:meth:`calibrate`). ``Calibration.measured`` says what was measured. Takes about 0.7 s
|
|
1319
|
+
and switches the outputs off afterwards.
|
|
1320
|
+
"""
|
|
1321
|
+
check_choice(source, ADC_GAIN_SOURCES, "source")
|
|
1322
|
+
self._require_capability("cal_dmm", "DMM calibration")
|
|
1323
|
+
return Calibration.from_reply(self.command({"cmd": "calibrate", "adc": source}))
|
|
1262
1324
|
|
|
1263
1325
|
# -- CAN (FDCAN1 / TCAN1044 transceiver) -----------------------------------
|
|
1264
1326
|
# Single-node board: use a loopback ``mode`` to self-test on one pod. :meth:`open_can` and
|
|
@@ -112,6 +112,13 @@ AnalogPath = Literal["off", "dac_3v3", "dac_5v", "dac_12v", "adc_ext", "cal1", "
|
|
|
112
112
|
AdcSource = Literal["ext", "cal1", "cal2", "current_in"]
|
|
113
113
|
#: ADC sources a pod can calibrate on its own (:meth:`BenchPod.calibrate`): the 4-20 mA input.
|
|
114
114
|
CalibrateSource = Literal["current_in"]
|
|
115
|
+
#: DAC outputs a DMM line can be stored for (:meth:`BenchPod.calibrate_dac`).
|
|
116
|
+
DacCalPath = Literal["3v3", "5v", "12v"]
|
|
117
|
+
#: ADC sources whose gain the pod measures through a loopback (:meth:`BenchPod.calibrate_adc_gain`);
|
|
118
|
+
#: ``current_in`` follows ``cal1``.
|
|
119
|
+
AdcGainSource = Literal["cal1", "cal2", "ext"]
|
|
120
|
+
#: One part of the pod's calibration (:meth:`BenchPod.clear_calibration`).
|
|
121
|
+
CalibrationPart = Literal["offset", "dac", "adc"]
|
|
115
122
|
#: Parametric DAC waveforms the firmware generator produces.
|
|
116
123
|
Waveshape = Literal["sine", "square", "sawtooth"]
|
|
117
124
|
#: How replayed volts map onto DAC codes: reproduce them (``faithful``) or auto-scale (``fit``).
|
|
@@ -139,6 +146,9 @@ DAC_OUTPUT_PATHS: Tuple[str, ...] = get_args(DacOutputPath)
|
|
|
139
146
|
ANALOG_PATHS: Tuple[str, ...] = get_args(AnalogPath)
|
|
140
147
|
ADC_SOURCES: Tuple[str, ...] = get_args(AdcSource)
|
|
141
148
|
CALIBRATE_SOURCES: Tuple[str, ...] = get_args(CalibrateSource)
|
|
149
|
+
DAC_CAL_PATHS: Tuple[str, ...] = get_args(DacCalPath)
|
|
150
|
+
ADC_GAIN_SOURCES: Tuple[str, ...] = get_args(AdcGainSource)
|
|
151
|
+
CALIBRATION_PARTS: Tuple[str, ...] = get_args(CalibrationPart)
|
|
142
152
|
WAVESHAPES: Tuple[str, ...] = get_args(Waveshape)
|
|
143
153
|
REPLAY_MAPPINGS: Tuple[str, ...] = get_args(ReplayMapping)
|
|
144
154
|
LOOP_SOURCES: Tuple[str, ...] = get_args(LoopSource)
|
|
@@ -314,13 +314,35 @@ class AdcReading:
|
|
|
314
314
|
current=None if ua is None else ua / 1e6, raw=d)
|
|
315
315
|
|
|
316
316
|
|
|
317
|
+
@dataclass(frozen=True)
|
|
318
|
+
class DacLine:
|
|
319
|
+
"""This pod's own line for a DAC output, from two DMM readings (``BenchPod.calibrate_dac``)."""
|
|
320
|
+
|
|
321
|
+
#: ``volts = a + b * code16``: volts at code 0 and volts per 16-bit code.
|
|
322
|
+
a: float
|
|
323
|
+
b: float
|
|
324
|
+
#: Against the built-in line: the offset in volts (at 0 V on ``12v``, at code 0 on the
|
|
325
|
+
#: others) and the gain difference in ppm.
|
|
326
|
+
offset: float
|
|
327
|
+
gain_ppm: int
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def from_reply(cls, d: Any) -> Optional["DacLine"]:
|
|
331
|
+
if not isinstance(d, dict):
|
|
332
|
+
return None
|
|
333
|
+
return cls(a=(_opt_int(d, "a_uv") or 0) / 1e6, b=(_opt_int(d, "b_pv") or 0) / 1e12,
|
|
334
|
+
offset=(_opt_int(d, "offset_uv") or 0) / 1e6, gain_ppm=_opt_int(d, "gain_ppm") or 0)
|
|
335
|
+
|
|
336
|
+
|
|
317
337
|
@dataclass(frozen=True)
|
|
318
338
|
class Calibration:
|
|
319
|
-
"""The pod's own
|
|
339
|
+
"""The pod's own calibration (``BenchPod.calibrate`` / ``calibration``).
|
|
320
340
|
|
|
321
341
|
Every ADC source is scaled with a fit, ``volts = a + b * count``, that is built into the
|
|
322
|
-
firmware and the same on every pod. A pod that calibrated itself keeps its own
|
|
323
|
-
of that, in flash
|
|
342
|
+
firmware and the same on every pod. A pod that calibrated itself keeps its own values on top
|
|
343
|
+
of that, in flash: the ADC offset, measured on ``current_in`` (the 4-20 mA input, J8), and from
|
|
344
|
+
firmware 3.9.4 the DMM-guided parts, a DAC line per output (``dac``) and an ADC gain per
|
|
345
|
+
source (``adc_gain_ppm``).
|
|
324
346
|
"""
|
|
325
347
|
|
|
326
348
|
source: str
|
|
@@ -338,16 +360,41 @@ class Calibration:
|
|
|
338
360
|
count: Optional[int] = None
|
|
339
361
|
span: Optional[int] = None
|
|
340
362
|
samples: Optional[int] = None
|
|
363
|
+
#: This pod's DMM line per DAC output (``3v3``, ``5v``, ``12v``); ``None`` = the built-in
|
|
364
|
+
#: line. Empty on firmware before 3.9.3.
|
|
365
|
+
dac: Dict[str, Optional[DacLine]] = field(default_factory=dict)
|
|
366
|
+
#: The ADC gain correction per source (``cal1``, ``cal2``, ``ext``) in ppm; ``None`` = the
|
|
367
|
+
#: built-in gain. Empty on firmware before 3.9.3.
|
|
368
|
+
adc_gain_ppm: Dict[str, Optional[int]] = field(default_factory=dict)
|
|
369
|
+
#: ``True`` when any DMM part is stored.
|
|
370
|
+
dmm: bool = False
|
|
371
|
+
#: The raw zero the offset is derived from: the mean count ``current_in`` read with J8 open,
|
|
372
|
+
#: unwrapped. ``None`` when not calibrated or on firmware before 3.9.4.
|
|
373
|
+
zero_count: Optional[float] = None
|
|
374
|
+
#: After :meth:`BenchPod.calibrate_adc_gain`: what the DAC put out at the two set points
|
|
375
|
+
#: (``v1``, ``v2``), what the ADC read before the gain (``r1``, ``r2``) and the mean offset left
|
|
376
|
+
#: after it (``residual``), all in volts. ``None`` otherwise.
|
|
377
|
+
measured: Optional[Dict[str, float]] = None
|
|
341
378
|
raw: Dict[str, Any] = _raw()
|
|
342
379
|
|
|
343
380
|
@classmethod
|
|
344
381
|
def from_reply(cls, reply: Any) -> "Calibration":
|
|
345
382
|
d = _d(reply)
|
|
383
|
+
dac = d.get("dac") if isinstance(d.get("dac"), dict) else {}
|
|
384
|
+
gains = d.get("adc_gain_ppm") if isinstance(d.get("adc_gain_ppm"), dict) else {}
|
|
385
|
+
meas = d.get("measured") if isinstance(d.get("measured"), dict) else None
|
|
346
386
|
return cls(source=str(d.get("source", "")), calibrated=d.get("calibrated") is True,
|
|
347
387
|
offset=(_opt_int(d, "offset_uv") or 0) / 1e6,
|
|
348
388
|
a=(_opt_int(d, "a_uv") or 0) / 1e6, b=(_opt_int(d, "b_nv") or 0) / 1e9,
|
|
349
389
|
count=_opt_int(d, "count"), span=_opt_int(d, "span"),
|
|
350
|
-
samples=_opt_int(d, "samples"),
|
|
390
|
+
samples=_opt_int(d, "samples"),
|
|
391
|
+
dac={str(k): DacLine.from_reply(v) for k, v in dac.items()},
|
|
392
|
+
adc_gain_ppm={str(k): _opt_int(gains, k) for k in gains},
|
|
393
|
+
dmm=d.get("dmm") is True,
|
|
394
|
+
zero_count=None if _opt_int(d, "zero_count_mc") is None else _opt_int(d, "zero_count_mc") / 1000.0,
|
|
395
|
+
measured=None if meas is None else {
|
|
396
|
+
k[:-3]: (_opt_int(meas, k) or 0) / 1e6 for k in meas if k.endswith("_uv")},
|
|
397
|
+
raw=d)
|
|
351
398
|
|
|
352
399
|
|
|
353
400
|
@dataclass(frozen=True)
|
|
@@ -157,7 +157,8 @@ def _read_form(req: dict) -> bool:
|
|
|
157
157
|
if cmd == "dac_limits":
|
|
158
158
|
return "path" not in req and req.get("enabled") is not False
|
|
159
159
|
if cmd == "calibrate":
|
|
160
|
-
return
|
|
160
|
+
return (not any(k in req for k in ("source", "dac", "adc"))
|
|
161
|
+
and req.get("clear") in (None, False))
|
|
161
162
|
if cmd in ("sig_policy", "lan_policy"):
|
|
162
163
|
return "set" not in req
|
|
163
164
|
if cmd in ("cloud_ca", "cloud_proxy"):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"AdcGainSource": "Literal['cal1', 'cal2', 'ext']",
|
|
2
3
|
"AdcReading": {
|
|
3
4
|
"__fields__": [
|
|
4
5
|
"source",
|
|
@@ -42,6 +43,8 @@
|
|
|
42
43
|
"adc_read": "(self, source: 'AdcSource' = 'ext') -> 'AdcReading'",
|
|
43
44
|
"analog_path": "(self, path: 'AnalogPath') -> 'AnalogPathState'",
|
|
44
45
|
"calibrate": "(self, source: 'CalibrateSource' = 'current_in') -> 'Calibration'",
|
|
46
|
+
"calibrate_adc_gain": "(self, source: 'AdcGainSource') -> 'Calibration'",
|
|
47
|
+
"calibrate_dac": "(self, path: 'DacCalPath', points: 'Sequence[Tuple[int, float]]') -> 'Calibration'",
|
|
45
48
|
"calibration": "(self) -> 'Calibration'",
|
|
46
49
|
"can_analog": "property",
|
|
47
50
|
"can_calibrate": "property",
|
|
@@ -64,7 +67,7 @@
|
|
|
64
67
|
"capture_correlated": "(self, *, adc_samples: 'int' = 4096, adc_sample_rate_hz: 'Optional[float]' = None, la_samples: 'int' = 4096, la_sample_rate_hz: 'Optional[float]' = None, stop_dac_after: 'Optional[float]' = None, trigger: 'Optional[Trigger]' = None, trigger_timeout: 'float' = 10.0, source: 'Optional[AdcSource]' = None) -> 'CorrelatedCapture'",
|
|
65
68
|
"capture_la": "(self, samples: 'int' = 4096, *, sample_rate_hz: 'Optional[float]' = None, stop_dac_after: 'Optional[float]' = None, trigger: 'Optional[Trigger]' = None, trigger_timeout: 'float' = 10.0) -> 'LaCapture'",
|
|
66
69
|
"capture_uart": "(self, *, rx: 'Union[Pin, int, str, None]' = None, tx: 'Union[Pin, int, str, None]' = None, baud: 'Optional[int]' = None, duration: 'float', until: 'Optional[_uart.Until]' = None) -> '_uart.UartCapture'",
|
|
67
|
-
"clear_calibration": "(self) -> 'Calibration'",
|
|
70
|
+
"clear_calibration": "(self, part: 'Optional[CalibrationPart]' = None) -> 'Calibration'",
|
|
68
71
|
"close": "(self) -> 'None'",
|
|
69
72
|
"command": "(self, req: 'Dict[str, Any]') -> 'Any'",
|
|
70
73
|
"configure_gpio": "(self, las: 'Sequence[Union[Pin, int, str]]', mode: 'GpioMode' = 'output', *, level: 'Optional[int]' = None) -> 'List[LaPinState]'",
|
|
@@ -168,10 +171,16 @@
|
|
|
168
171
|
"count",
|
|
169
172
|
"span",
|
|
170
173
|
"samples",
|
|
174
|
+
"dac",
|
|
175
|
+
"adc_gain_ppm",
|
|
176
|
+
"dmm",
|
|
177
|
+
"zero_count",
|
|
178
|
+
"measured",
|
|
171
179
|
"raw"
|
|
172
180
|
],
|
|
173
181
|
"from_reply": "(cls, reply: 'Any') -> \"'Calibration'\""
|
|
174
182
|
},
|
|
183
|
+
"CalibrationPart": "Literal['offset', 'dac', 'adc']",
|
|
175
184
|
"CanBus": {
|
|
176
185
|
"__init__": "(self, bp: \"'BenchPod'\", *, bitrate: 'int', mode: 'str', term: 'bool' = False, fd: 'bool' = False) -> 'None'",
|
|
177
186
|
"add_responder": "(self, match_id: 'int', reply_id: 'int', reply_data: 'Union[bytes, Sequence[int], None]' = None, *, match_ext: 'bool' = False, reply_ext: 'bool' = False) -> 'dict'",
|
|
@@ -249,6 +258,7 @@
|
|
|
249
258
|
"spi_master",
|
|
250
259
|
"spi_stream",
|
|
251
260
|
"calibrate",
|
|
261
|
+
"cal_dmm",
|
|
252
262
|
"current_out",
|
|
253
263
|
"can",
|
|
254
264
|
"pod_current",
|
|
@@ -342,10 +352,20 @@
|
|
|
342
352
|
],
|
|
343
353
|
"from_reply": "(cls, reply: 'Any') -> \"'CurrentOutput'\""
|
|
344
354
|
},
|
|
355
|
+
"DacCalPath": "Literal['3v3', '5v', '12v']",
|
|
345
356
|
"DacHandle": {
|
|
346
357
|
"__init__": "(self, *, stop: 'Callable[[], Any]', dac_path: 'str' = '', cotrig: 'bool' = False, data: 'Optional[dict]' = None) -> 'None'",
|
|
347
358
|
"stop": "(self) -> 'None'"
|
|
348
359
|
},
|
|
360
|
+
"DacLine": {
|
|
361
|
+
"__fields__": [
|
|
362
|
+
"a",
|
|
363
|
+
"b",
|
|
364
|
+
"offset",
|
|
365
|
+
"gain_ppm"
|
|
366
|
+
],
|
|
367
|
+
"from_reply": "(cls, d: 'Any') -> \"Optional['DacLine']\""
|
|
368
|
+
},
|
|
349
369
|
"DacOutput": {
|
|
350
370
|
"__fields__": [
|
|
351
371
|
"path",
|
|
@@ -974,6 +994,7 @@
|
|
|
974
994
|
"with_changes": "(self, **changes: 'Any') -> \"'Wiring'\""
|
|
975
995
|
},
|
|
976
996
|
"__all__": [
|
|
997
|
+
"AdcGainSource",
|
|
977
998
|
"AdcReading",
|
|
978
999
|
"AdcSource",
|
|
979
1000
|
"AnalogPath",
|
|
@@ -986,6 +1007,7 @@
|
|
|
986
1007
|
"BuildReporter",
|
|
987
1008
|
"CalibrateSource",
|
|
988
1009
|
"Calibration",
|
|
1010
|
+
"CalibrationPart",
|
|
989
1011
|
"CanBus",
|
|
990
1012
|
"CanFrame",
|
|
991
1013
|
"CanMode",
|
|
@@ -1000,7 +1022,9 @@
|
|
|
1000
1022
|
"ControlLoopHandle",
|
|
1001
1023
|
"CorrelatedCapture",
|
|
1002
1024
|
"CurrentOutput",
|
|
1025
|
+
"DacCalPath",
|
|
1003
1026
|
"DacHandle",
|
|
1027
|
+
"DacLine",
|
|
1004
1028
|
"DacOutput",
|
|
1005
1029
|
"DacOutputPath",
|
|
1006
1030
|
"DacPath",
|
|
@@ -13,6 +13,7 @@ from embeddedci.benchpod.state import (
|
|
|
13
13
|
AdcReading,
|
|
14
14
|
AnalogPathState,
|
|
15
15
|
Calibration,
|
|
16
|
+
DacLine,
|
|
16
17
|
CurrentOutput,
|
|
17
18
|
DacOutput,
|
|
18
19
|
LaVoltage,
|
|
@@ -373,6 +374,67 @@ def test_calibrate_needs_the_capability():
|
|
|
373
374
|
assert BenchPod(transport=CalPod(), lease=False).capabilities.calibrate
|
|
374
375
|
|
|
375
376
|
|
|
377
|
+
class DmmCalPod(FakeTransport):
|
|
378
|
+
"""A pod that takes the DMM-guided calibration (firmware 3.9.4+)."""
|
|
379
|
+
|
|
380
|
+
def status(self) -> Any:
|
|
381
|
+
return {**super().status(), "version": "3.9.4", "caps": ["calibrate", "cal_dmm"]}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
_DMM = {**_CAL, "dmm": True, "zero_count_mc": 65585123,
|
|
385
|
+
"dac": {"3v3": None, "5v": {"a_uv": -609, "b_pv": 76249841, "offset_uv": -3127, "gain_ppm": -996},
|
|
386
|
+
"12v": None},
|
|
387
|
+
"adc_gain_ppm": {"cal1": 1850, "cal2": None, "ext": None}}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_calibrate_dac_sends_both_points_in_microvolts():
|
|
391
|
+
t = DmmCalPod({"calibrate": lambda req: _DMM})
|
|
392
|
+
bp = BenchPod(transport=t, lease=False)
|
|
393
|
+
assert bp.capabilities.cal_dmm
|
|
394
|
+
c = bp.calibrate_dac("5v", [(13069, 0.9959), (52374, 3.9929)])
|
|
395
|
+
assert t.commands[-1] == {"cmd": "calibrate", "dac": "5v", "code1": 13069, "uv1": 995900,
|
|
396
|
+
"code2": 52374, "uv2": 3992900}
|
|
397
|
+
line = c.dac["5v"]
|
|
398
|
+
assert isinstance(line, DacLine) and c.dac["3v3"] is None and c.dmm
|
|
399
|
+
assert line.a == pytest.approx(-609e-6) and line.b == pytest.approx(76.249841e-6)
|
|
400
|
+
assert line.offset == pytest.approx(-3.127e-3) and line.gain_ppm == -996
|
|
401
|
+
assert c.adc_gain_ppm == {"cal1": 1850, "cal2": None, "ext": None}
|
|
402
|
+
assert c.zero_count == pytest.approx(65585.123)
|
|
403
|
+
with pytest.raises(ValueError):
|
|
404
|
+
bp.calibrate_dac("5v", [(13069, 0.9959)])
|
|
405
|
+
with pytest.raises(ValueError):
|
|
406
|
+
bp.calibrate_dac("off", [(1, 0.0), (2, 1.0)]) # type: ignore[arg-type]
|
|
407
|
+
assert set(bp.DMM_SET_POINTS) == {"3v3", "5v", "12v"}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def test_calibrate_adc_gain_and_partial_clear():
|
|
411
|
+
meas = {"v1_uv": 995900, "v2_uv": 3992900, "r1_uv": 994100, "r2_uv": 3985500, "residual_uv": 1200}
|
|
412
|
+
t = DmmCalPod({"calibrate": lambda req: {**_DMM, "measured": meas} if "adc" in req else _DMM})
|
|
413
|
+
bp = BenchPod(transport=t, lease=False)
|
|
414
|
+
c = bp.calibrate_adc_gain("cal1")
|
|
415
|
+
assert t.commands[-1] == {"cmd": "calibrate", "adc": "cal1"}
|
|
416
|
+
assert c.measured == pytest.approx({"v1": 0.9959, "v2": 3.9929, "r1": 0.9941, "r2": 3.9855, "residual": 0.0012})
|
|
417
|
+
with pytest.raises(ValueError):
|
|
418
|
+
bp.calibrate_adc_gain("current_in") # type: ignore[arg-type]
|
|
419
|
+
bp.clear_calibration("dac")
|
|
420
|
+
assert t.commands[-1] == {"cmd": "calibrate", "clear": "dac"}
|
|
421
|
+
with pytest.raises(ValueError):
|
|
422
|
+
bp.clear_calibration("everything") # type: ignore[arg-type]
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def test_dmm_calibration_needs_cal_dmm():
|
|
426
|
+
t = CalPod({"calibrate": lambda req: _CAL}) # 3.4.0 to 3.9.2: offset only
|
|
427
|
+
bp = BenchPod(transport=t, lease=False)
|
|
428
|
+
for call in (lambda: bp.calibrate_dac("5v", [(1, 1.0), (40000, 3.0)]), lambda: bp.calibrate_adc_gain("cal1"),
|
|
429
|
+
lambda: bp.clear_calibration("adc")):
|
|
430
|
+
with pytest.raises(BenchPodError, match="cal_dmm"):
|
|
431
|
+
call()
|
|
432
|
+
assert t.commands == []
|
|
433
|
+
old = bp.calibration() # an old reply has no DMM parts
|
|
434
|
+
assert old.dac == {} and old.adc_gain_ppm == {} and not old.dmm and old.measured is None
|
|
435
|
+
assert old.zero_count is None
|
|
436
|
+
|
|
437
|
+
|
|
376
438
|
def test_capture_adc_routes_the_source_first():
|
|
377
439
|
bp, t = _bp({"analog_path": {"path": "cal1"}})
|
|
378
440
|
cap = bp.capture_adc(2, source="cal1", sample_rate_hz=100_000)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|