Repository navigation
Tags: Wasabules/picoscope-libusb
Tags
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 — 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 — 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.
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.
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.
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).
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.