Skip to content

Tags: Wasabules/picoscope-libusb

Tags

v0.3.1

Toggle v0.3.1's commit message
v0.3.1 — measurement accuracy, ETS from broken to working, full Andro…

…id surface

Everything below was measured against the official SDK under usbmon, range by
range or configuration by configuration. Nothing here was inferred.

Accuracy:

- The factory EEPROM trim is now the default calibration. It was decoded but
  not applied, because a test had asked whether one AWG signal measured the
  same across four ranges and concluded the gain-block assignment was a guess.
  That test conflates the calibration with the generator's own accuracy and
  with the 8-bit quantisation floor -- a 250 mV signal is 1.6 counts on the
  20 V range -- and it pointed the wrong way. Scored properly, nine ranges and
  three passes each: mean absolute error against the SDK 0.88 % with the
  EEPROM table against 4.34 % with the built-in one, whose 50 mV gain is out
  by 19.6 %. The protocol notes had suspected that gain of being a bad
  measurement for a long time; it was.

- The stored DC offsets are no longer applied. Both tables carry the same
  ~-3.5 ADC counts, and applying it put us a constant +4.3 counts away from
  the SDK on every range. The device already compensates that term, so the
  stored word records a correction that has been made rather than one still
  owed. Not applying it lands within a count everywhere. Flipping the sign is
  not the answer either; that gives -3.5. With no 0 V reference on hand this
  is agreement with the reference instrument, not proof of absolute zero.

Trigger:

- The window trigger was the last conjecture in the trigger path: channel B's
  layout was extrapolated from LEVEL and never traced, EXIT was not
  implemented at all (falling silently behaved as entering), and thresholds
  divided by 288 with rounding instead of 295 with floor. Traced in full from
  the SDK's advanced-trigger path, one configuration per second so each
  command is attributable by timestamp. All eleven configurations now
  reproduce byte for byte, locked in as unit tests (47 checks -> 69, no
  hardware needed).

- The idle band of the inactive channel is not a constant either: it shifts by
  a count with that channel's enable state (A 0x7d/0x7c, B 0x81/0x7f). The old
  constants were each right for the case they were captured in.

ETS:

- ETS did not work at all. capture_ets forced the trigger to the start of the
  block, working around the missing pre-trigger count that was fixed in
  b7317d8. With the trigger at index 0 there are no samples before the
  crossing to interpolate against, so phase estimation failed every round and
  every timebase returned PS_ERROR_TIMEOUT. Instrumenting the loop showed 20
  of 20 rounds capturing cleanly and 20 of 20 failing phase estimation.
  Mid-block fixes it. Measured usable range is tb >= 3: tb=0 never completes,
  tb=1 loses 60 % of the amplitude, tb=2 loses 30 %, and the loss is phase
  blur rather than bandwidth -- the traces stay smooth while shrinking.

Consumers:

- The JNI shim goes from 32 of 45 driver functions to 42, adding sweeps, DC
  offset and duty cycle (nativeSetSiggenEx), DC-offset calibration, raw EEPROM
  and raw capture, the SDK-streaming tuners, and the state queries. Three stay
  unbound on purpose and the header says why.

- Both UIs now warn when the timebase is too fast for ETS, which otherwise
  fails quietly enough to look like a weak signal.

Verified: 69 unit checks, strict C11, driver and JNI cross-compile clean for
arm64 and armv7, the CMake build exports all 44 methods, and the relinked GUI
binary carries the current driver.

Known gaps are unchanged from v0.3.0 except where closed above: LED control is
characterised but needs someone to look at the device, open() blocks for 4.0 s,
and window EXIT does not lock on a sine -- though neither does the SDK's.

v0.3.0

Toggle v0.3.0's commit message
v0.3.0 — trigger parity with the official SDK, and a real AWG amplitude

The block trigger goes from "never fires" to matching libps2000's timing
jitter. Everything below was measured on hardware against the official SDK,
not inferred.

Trigger, in the order the faults were found:

- The trigger never fired on either channel. The status-config byte after
  `85 05 95` selects the source: 0x55 for channel A, 0x33 for channel B. We
  sent 0x55 always, so a capture triggered on B waited on a condition the
  device had never been told to watch and fell back to the poll timeout with
  a free-running buffer.
- The returned block was not aligned on the trigger position.
- The device was never told how many pre-trigger samples to keep. `85 08 93`
  carries the timebase constant plus the pre-trigger count; sending the bare
  constant asks for zero, so everything the parser took from before the
  trigger was unmanaged circular-buffer residue. 79 mV of overlay spread on a
  10 kHz sine where the SDK got 5.9 mV.
- The overshoot past the requested post-trigger count is not a constant. The
  two bytes at the head of every transfer are a status marker, and they say
  which of two values applies: 0x57a7 means 33 samples, 0x52a2 means 30. An
  earlier measurement recorded this as "31 +- 2, constant" -- the +-2 was the
  two states averaging out, and 31 is never the right answer. This was the
  last factor of two.

Jitter in samples, single channel, ours then the SDK: 0.90/0.86 on a 10 kHz
square, 0.51/0.46 on a 10 kHz sine, 1.16/1.15 at 2 kHz. Dual channel over
three runs each: 0.89/0.93, 0.52/0.56, 1.15/1.21 -- fully overlapping ranges.

Signal generator:

- The AWG DAC table was built with 297 counts per volt instead of 472, a
  documented 475 with a "0.625 calibration factor" on top that was cancelling
  an error elsewhere. The generator genuinely under-drove by 1.6x: 1.25 Vpp
  when asked for 2. Output RMS now matches the SDK to within 1 %.

Method note. The LD_PRELOAD interceptor is blind to most of libps2000's
traffic -- 150 outbound packets and 2 inbound across a run reading 24 blocks
of 16 KB. All of the above came from usbmon at the kernel level instead. The
decisive technique was capturing the SDK while dumping what ps2000_get_values
returned, then correlating the returned array against the raw block: the
return is a plain affine map of the raw bytes, so alignment hits correlation
1.000000 and the SDK's chosen offset is exact rather than fitted.

Also in: a dual-channel jitter bench for both the driver and the SDK, and a
fast path that re-arms an unchanged capture config instead of resending it.

Known gaps are listed in the release notes rather than here.

v0.2.0

Toggle v0.2.0's commit message
v0.2.0 — measurement correctness, full API surface, strict C11

Measurement fixes (all found and verified on hardware):

- Waveform segmentation anchored on the padding run rather than the first
  non-zero byte. 0x00 is a legal sample, so the old landmark broke twice: it
  inverted the dual-channel assignment when an odd number of trailing samples
  sat on the negative rail, and — after any dual capture — spliced kilobytes
  of padding into every subsequent single capture, permanently. Measured 1629
  of 2000 samples pinned to the rail, never recovering.
- Single-channel streaming ignored which channel was enabled: streaming B
  alone filled ring A and returned zeros for B.
- Resolution enhancement is no longer re-seeded per poll, which used to stamp
  a discontinuity into the stream at the poll rate.
- Dead condition in the SDK streaming callback that scaled channel B on every
  sample pair even when disabled.

New capability:

- EEPROM factory trim decoded and exposed. Offsets confirmed (Pearson
  r = 0.9989 against a hand-measured reference); gain blocks identified but
  not confirmed, so the built-in table stays the default and the EEPROM one
  is opt-in.
- Overflow reporting: raw ADC rail counts, before the clamp that hides them.
- Aggregated streaming reads (min/max per bucket).
- ETS, window and pulse-width triggers, edge hysteresis and arbitrary
  waveforms reach the desktop GUI and Android for the first time.

Android: the JNI shim goes from 19 of 45 driver functions to the full
surface, 34 native methods. Verified without an NDK by compiling against real
JDK headers, cross-checking javac -h symbol names against the
implementations, and linking the shared library.

Build: the driver now compiles as strict ISO C11 — usleep, M_PI and setenv
were GNU/POSIX-only and worked purely because CMake defaults to gnu11.
C_EXTENSIONS OFF is pinned on both CMake targets and `make strict` guards it.

Tests: 47 unit checks over the parsing and DSP helpers, and the hardware
suite passes in full for the first time — 11/11 timebases, 9/9 ranges,
344 kS/s streaming. The dual-channel test previously judged the parser on
floating-input noise; it now compares DC levels, which is a controlled
quantity.

v0.1.3

Toggle v0.1.3's commit message
Android: disable libusb device discovery, expose firmware dir setter

- Driver: on __ANDROID__, call libusb_set_option(LIBUSB_OPTION_NO_DEVICE_DISCOVERY) before libusb_init. App sandboxes can't enumerate /dev/bus/usb, and we only ever open via UsbDeviceConnection.getFileDescriptor() anyway.
- JNI: add nativeSetFirmwareDir(String) that setenv's PS2204A_FIRMWARE_DIR, plus __android_log_print on open failure so diagnostics land in logcat instead of vanishing to stderr.

v0.1.2

Toggle v0.1.2's commit message
Fix android-lib JNI build: libusb header shim + siggen signature

- nativeSetSiggen was passing 3 args to ps2204a_set_siggen (takes 4: dev, type, freq_hz, pkpk_uv). Add pkpkUv param to JNI + Java signatures.
- Driver includes libusb as <libusb-1.0/libusb.h> (Linux layout); libusb-cmake exposes it as <libusb.h>. Add a one-line shim at android-lib/src/main/cpp/shim/libusb-1.0/libusb.h and put the shim dir on the include path so the same driver source compiles on both targets.

v0.1.1

Toggle v0.1.1's commit message
Fix JitPack build: pin Gradle 8.11.1, bump AGP to 8.7.3

The default Gradle on the JitPack runner (4.8.1) cannot load AGP 8.x plugins. Install a modern Gradle via sdkman in before_install, and bump the library AGP to match what consumers are now using (Scope204 on AGP 8.7.3 / Kotlin 2.1.0).

v0.1.0

Toggle v0.1.0's commit message
Add android-lib Gradle module + JitPack packaging

Ships the reverse-engineered driver as an Android AAR consumable via
JitPack — no submodule, no NDK wiring needed in downstream apps:

    implementation("com.github.Wasabules:picoscope-libusb:v0.1.0")

Module layout:
- android-lib/src/main/cpp/           CMake build: fetches libusb-cmake
                                      1.0.27 via FetchContent, compiles
                                      driver/picoscope2204a.c in place,
                                      produces libpicoscope_jni.so
- android-lib/src/main/java/          Neutral package io.github.wasabules.ps2204
                                      (was com.picoscope — trademark-safer)
- android-lib/build.gradle.kts        AGP 8.2, minSdk 24, ABIs:
                                      arm64-v8a, armeabi-v7a, x86_64
- consumer-rules.pro                  Preserves native methods under R8

Root-level Gradle scaffolding (settings/build.gradle.kts,
gradle.properties, jitpack.yml) so JitPack can resolve :android-lib
standalone. gui/ and driver/ standalone builds are untouched.

Moved driver/jni/* into android-lib/ (single source of truth now lives
there). Driver CMakeLists no longer exposes the PS2204A_BUILD_JNI
option — the Android build is fully owned by the Gradle module.

docs/android.md rewritten around the JitPack flow. Root README gains
an android-lib row in the 'What's in the box' table.