From adcf9a7e79048adad042c7bcb1037ff663bdae81 Mon Sep 17 00:00:00 2001 From: Anton Chernov Date: Mon, 7 Sep 2026 16:56:23 +0300 Subject: [PATCH 1/2] Replaced DSO-2090 command sequences with the new DSO-2250 command sequence --- HISTORY.md | 57 ++++++++++ capture/src/acquisition_loop.cpp | 175 +++++++++++++++++++++++-------- 2 files changed, 190 insertions(+), 42 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 22a6c1d..eace3a0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -427,3 +427,60 @@ Records key decisions, structural changes, and completed development stages. root cause remains open. Treated as a known, unresolved bug pending further hardware-side USB capture analysis. +## 2026-09-07 + +### Root cause found - DSO-2250 needs the extended 0x0b-0x0f command group + +- Correlated the Windows reference-driver capture against the application's + own USB capture with `tshark` and found the application implements only the + DSO-2090 command set (`0x00`-`0x07`), while the DSO-2250 requires the + extended acquisition-configuration commands `0x0b`-`0x0f`. The DSO-2090 + `SetTriggerAndSampleRate` command (`0x01`) is silently ignored by the + DSO-2250, so the capture engine never arms and `GetCaptureState` stays in + its power-on state forever. +- Bulk-OUT command comparison (reference vs application): `0x01` sent 0 vs 2 + (the ignored 2090 command), `0x05` GetData 237 vs 0 (analog data never + read), `0x09` GetLogicalData 78 vs 0, and each of `0x0b`-`0x0f` 78 vs 0 + (the arming group is entirely absent from the application). + +### Recorded - full DSO-2250 bulk-command semantics from OpenHantek + +- Documented the DSO-2250 bulk-command semantics decoded from the OpenHantek + project (`hantekprotocol/bulkcode.h`) for future protocol work: + - `0x00` SetFilter, `0x01` SetTriggerAndSampleRate (DSO-2090 only, ignored + by the DSO-2250), `0x02` ForceTrigger, `0x03` StartSampling, `0x04` + EnableTrigger, `0x05` GetData, `0x06` GetCaptureState, `0x07` SetGain. + - `0x08` SetLogicalData, `0x09` GetLogicalData, `0x0a` unknown. + - `0x0b` BSetChannels - enabled-channel selection, `[0b][00][channels][00]`. + - `0x0c` CSetTriggerOrSampleRate - trigger source/bits, + `[0c][00][triggerBits][00]...`. + - `0x0d` DSetBuffer - record-length id, `[0d][00][recordLenId][00]`. + - `0x0e` ESetTriggerOrSampleRate - sample-rate bits, + `[0e][00][srBits][00][sr0][sr1][00][00]`. + - `0x0f` FSetBuffer - trigger position, `[0f][00][postTrig0-2][00]` + `[preTrig0-2][00][00][00]`. + - Completed-capture state value for the DSO-2250 is `3` + (`CAPTURE_READY2250`). +- Recorded the known-good reference configuration bytes captured immediately + before the first successful capture, for the arming group used below: + `0x0c` `0c 0f 02 00 02 00 00 00`, `0x0b` `0b 0f 00 00`, `0x0d` + `0d 0f 01 00`, `0x0e` `0e 00 01 00 00 00 00 00`, `0x0f` + `0f 00 fe d7 07 00 fe ff 07 00 00 00`, sent in the order + `0c, 0b, 0d, 0e, 0f`. + +### Stage 4 - Send the DSO-2250 acquisition-configuration command group + +- Replaced both DSO-2090 `SetTriggerAndSampleRate` (`0x01`) sends in + `configureCapture()` with a new `configureDso2250Timebase()` helper that + issues the extended arming group `0x0c, 0x0b, 0x0d, 0x0e, 0x0f` using the + known-good reference bytes, reusing the existing begin-command/speed-check + bulk framing. +- Prepended `0x09` GetLogicalData to the arming group and drained the single + 512-byte bulk-IN (`0x86`) response it returns, mirroring the reference + driver, so the logic/auto-range subsystem is initialised the same way + before analog capture. +- Removed the now-unused DSO-2090 timebase constants and added the DSO-2250 + command payloads as named constants. +- Kept the calibration-derived `SetOffset` (`0xB4`) write, relay + (`0xB5`) configuration, and calibration reads (`0xA2`) unchanged. + diff --git a/capture/src/acquisition_loop.cpp b/capture/src/acquisition_loop.cpp index 3b9dc43..4bbe3b3 100644 --- a/capture/src/acquisition_loop.cpp +++ b/capture/src/acquisition_loop.cpp @@ -70,15 +70,43 @@ static const unsigned int kForceRestartThreshold = 3U; */ static const uint8_t kDefaultVoltageRangeCode = 0U; /**< VOLTAGE_5V */ static const uint8_t kDefaultCouplingDc = 0U; /**< COUPLING_AC */ -static const uint8_t kDefaultSelectedChannel = 2U; /**< SELECT_CH1CH2 */ -static const uint8_t kDefaultTriggerSource = 1U; /**< TRIGGER_CH1 */ -static const uint8_t kDefaultTriggerSlope = 0U; /**< SLOPE_POSITIVE */ -static const uint8_t kDefaultSampleSizeCode = 2U; /**< BUFFER_LARGE */ -static const uint16_t kDefaultTimeBaseValue = 0xFFF7U; /**< TIME_1ms preset */ -/** 1ms/div is past the fast range */ -static const uint8_t kDefaultTimeBaseFastCode = 4U; -/** No-offset trigger position */ -static const uint32_t kDefaultTriggerPosition = 0x77660U; + +/** + * @brief DSO-2250 acquisition-configuration command group (0x0b-0x0f) + * @details The DSO-2090 setTriggerNSampleRate command (0x01) does NOT arm + * the DSO-2250 capture engine. This model needs the extended command group + * 0x0b-0x0f - set channels, trigger source, record length, sample rate and + * trigger position. Without it GetCaptureState never leaves its power-on + * state and no waveform is ever returned. The byte values below reproduce + * the known-good sequence captured from the vendor Windows driver (see + * WorkingDocs USB captures): both channels, internal CH1 trigger, large + * record buffer and a centered trigger position. TEMPORARY fixed + * configuration until Stage 5 wires the Timebase/Scale/Trigger UI controls. + */ +static const uint8_t kDso2250SetTriggerSource[8] = { + 0x0CU, 0x0FU, 0x02U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U +}; +/** Channel-enable command: both channels active */ +static const uint8_t kDso2250SetChannels[4] = { + 0x0BU, 0x0FU, 0x00U, 0x00U +}; +/** Record-length command: large capture buffer */ +static const uint8_t kDso2250SetRecordLength[4] = { + 0x0DU, 0x0FU, 0x01U, 0x00U +}; +/** Sample-rate command: default acquisition rate */ +static const uint8_t kDso2250SetSampleRate[8] = { + 0x0EU, 0x00U, 0x01U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U +}; +/** Trigger-position command: centered post/pre-trigger window */ +static const uint8_t kDso2250SetTriggerPosition[12] = { + 0x0FU, 0x00U, 0xFEU, 0xD7U, 0x07U, 0x00U, + 0xFEU, 0xFFU, 0x07U, 0x00U, 0x00U, 0x00U +}; +/** GetLogicalData command: primes the logic/auto-range subsystem */ +static const uint8_t kDso2250GetLogicalData[2] = { + 0x09U, 0x00U +}; /** * @brief Channel/trigger offset DAC configuration sent once before the @@ -158,6 +186,22 @@ static usb::SUsbTransferResult configureCapture( EAcquisitionOperation *failedOperation ); +/** + * @brief Sends the DSO-2250 acquisition-configuration command group + * @details Primes the logic/auto-range subsystem with GetLogicalData + * (0x09), draining its bulk-IN response, then issues the extended commands + * 0x0b-0x0f that arm the DSO-2250 capture engine (channels, trigger source, + * record length, sample rate, trigger position). Replaces the DSO-2090 + * setTriggerNSampleRate (0x01) command, which the DSO-2250 ignores. + * @param[in] connection USB connection to configure + * @param[out] failedOperation First operation that failed + * @returns Result of the first failed operation or the final successful write + */ +static usb::SUsbTransferResult configureDso2250Timebase( + const usb::SUsbConnection &connection, + EAcquisitionOperation *failedOperation +); + /** * @brief Computes the centered offset DAC byte for one channel * @param[in] channelLevels Calibration table read via the channel-level @@ -612,6 +656,84 @@ static uint8_t channelLevelCenterByte( /*----------------------------------------------------------------------------*/ +/** @fn configureDso2250Timebase */ +static usb::SUsbTransferResult configureDso2250Timebase( + const usb::SUsbConnection &connection, + EAcquisitionOperation *failedOperation +) { + struct SConfigCommand { + const uint8_t *payload; /**< Command bytes */ + int length; /**< Number of command bytes */ + }; + const SConfigCommand commands[5] = { + { + kDso2250SetTriggerSource, + static_cast(sizeof(kDso2250SetTriggerSource)) + }, + { + kDso2250SetChannels, + static_cast(sizeof(kDso2250SetChannels)) + }, + { + kDso2250SetRecordLength, + static_cast(sizeof(kDso2250SetRecordLength)) + }, + { + kDso2250SetSampleRate, + static_cast(sizeof(kDso2250SetSampleRate)) + }, + { + kDso2250SetTriggerPosition, + static_cast(sizeof(kDso2250SetTriggerPosition)) + } + }; + uint8_t logicalData[kRawUsbPacketMaxSize]; + usb::SUsbTransferResult result = { + usb::EUsbTransferStatus::eSuccess, 0, "" + }; + size_t index = 0U; + + /* GetLogicalData (0x09) primes the logic/auto-range subsystem and + returns one bulk-IN packet that the vendor driver drains before the + analog arming group; the payload itself is unused here. */ + result = executeCommand( + connection, + kDso2250GetLogicalData, + static_cast(sizeof(kDso2250GetLogicalData)), + EAcquisitionOperation::eSetTriggerNSampleRateCmd, + failedOperation + ); + if (result.status == usb::EUsbTransferStatus::eSuccess) { + *failedOperation = EAcquisitionOperation::eSetTriggerNSampleRateCmd; + result = usb::bulkRead( + connection, + connection.captureProtocol.bulkInEndpoint, + logicalData, + connection.captureProtocol.bulkInPacketLength, + kTransferTimeoutMs, + kTransferAttempts, + connection.captureProtocol.bulkInPacketLength + ); + } + + for (index = 0U; + (index < 5U) && + (result.status == usb::EUsbTransferStatus::eSuccess); + ++index) { + result = executeCommand( + connection, + commands[index].payload, + commands[index].length, + EAcquisitionOperation::eSetTriggerNSampleRateCmd, + failedOperation + ); + } + + return result; +} + +/*----------------------------------------------------------------------------*/ + /** @fn configureCapture */ static usb::SUsbTransferResult configureCapture( const usb::SUsbConnection &connection, @@ -621,25 +743,6 @@ static usb::SUsbTransferResult configureCapture( connection.captureProtocol.setFilterCmd, 0x0FU, 0U, 0U, 0U, 0U, 0U, 0U }; - const uint8_t tsrByte1 = static_cast( - kDefaultTriggerSource | - (kDefaultSampleSizeCode << 2U) | - (kDefaultTimeBaseFastCode << 5U) - ); - const uint8_t tsrByte2 = static_cast( - kDefaultSelectedChannel | (kDefaultTriggerSlope << 3U) - ); - const uint8_t triggerNSampleRateCmd[12] = { - connection.captureProtocol.setTriggerNSampleRateCmd, 0U, - tsrByte1, tsrByte2, - static_cast(kDefaultTimeBaseValue), - static_cast(kDefaultTimeBaseValue >> 8U), - static_cast(kDefaultTriggerPosition), - static_cast(kDefaultTriggerPosition >> 8U), - 0U, 0U, - static_cast(kDefaultTriggerPosition >> 16U), - 0U - }; const uint8_t voltageByte = static_cast( (2U - (kDefaultVoltageRangeCode % 3U)) | ((2U - (kDefaultVoltageRangeCode % 3U)) << 2U) | @@ -684,13 +787,7 @@ static usb::SUsbTransferResult configureCapture( failedOperation ); if (result.status == usb::EUsbTransferStatus::eSuccess) { - result = executeCommand( - connection, - triggerNSampleRateCmd, - sizeof(triggerNSampleRateCmd), - EAcquisitionOperation::eSetTriggerNSampleRateCmd, - failedOperation - ); + result = configureDso2250Timebase(connection, failedOperation); } if (result.status == usb::EUsbTransferStatus::eSuccess) { result = executeCommand( @@ -744,13 +841,7 @@ static usb::SUsbTransferResult configureCapture( ); } if (result.status == usb::EUsbTransferStatus::eSuccess) { - result = executeCommand( - connection, - triggerNSampleRateCmd, - sizeof(triggerNSampleRateCmd), - EAcquisitionOperation::eSetTriggerNSampleRateCmd, - failedOperation - ); + result = configureDso2250Timebase(connection, failedOperation); } if (result.status == usb::EUsbTransferStatus::eSuccess) { *failedOperation = EAcquisitionOperation::eNone; From 50e3ef4ce68e65c34764ede4979e2c6ed6ad9074 Mon Sep 17 00:00:00 2001 From: Anton Chernov Date: Mon, 7 Sep 2026 17:07:32 +0300 Subject: [PATCH 2/2] Fixed bug with no-waveform --- HISTORY.md | 19 +++++++++++++++++++ usb/src/usb_device.cpp | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index eace3a0..9f20835 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -484,3 +484,22 @@ Records key decisions, structural changes, and completed development stages. - Kept the calibration-derived `SetOffset` (`0xB4`) write, relay (`0xB5`) configuration, and calibration reads (`0xA2`) unchanged. +### Stage 4 - Corrected the capture read size and confirmed the data path + +- Verified on hardware that the arming group works: the acquisition error + changed from silent no-waveform to `USB timeout while reading channel + data`, proving the capture engine now reaches `captureCompleteState` (```3```) + and accepts the `0x05` GetData command. +- Measured the true capture size from the reference dump with `tshark`: one + capture is 40 bulk-IN (`0x86`) packets of 512 bytes = 20480 bytes = 10240 + samples per channel (two channels interleaved), selected by record-length + id `1` (`0x0d 0f 01 00`). The profile declared `sampleCount = 32768`, so + the application requested 128 packets, received 40, and blocked on the + 41st packet until the read timed out. +- Corrected `sampleCount` from `32768` to `10240` in both DSO-2250 USB + profiles (bootloader and operational) in `usb/src/usb_device.cpp`; the + fixed sample and packet buffers accommodate the smaller size unchanged. +- Confirmed on hardware: the oscilloscope now returns valid waveform data + over USB. On-screen rendering is not yet implemented, so Stage 4 remains + open until the captured samples are drawn. + diff --git a/usb/src/usb_device.cpp b/usb/src/usb_device.cpp index d82a247..ba26816 100644 --- a/usb/src/usb_device.cpp +++ b/usb/src/usb_device.cpp @@ -60,14 +60,14 @@ static const SSupportedDevice kSupportedDevices[] = { /* The bootloader exposes the bulk pair on alt setting 1. */ { "Hantek DSO-2250 Bootloader", "DSO2250", - { 32768U, 512U, 0x02U, 0x86U, 2U, true, 3U, 6U, 5U, 3U, 4U, 2U, 0U, 1U, + { 10240U, 512U, 0x02U, 0x86U, 2U, true, 3U, 6U, 5U, 3U, 4U, 2U, 0U, 1U, 7U, 0xB5U, 0xA2U, 0xB4U }, core::EInstrumentModel::eHantekDso2250, 0x04B4U, 0x2250U, 0x04B5U, 0U, 1U, true }, { "Hantek DSO-2250", "DSO2250", - { 32768U, 512U, 0x02U, 0x86U, 2U, true, 3U, 6U, 5U, 3U, 4U, 2U, 0U, 1U, + { 10240U, 512U, 0x02U, 0x86U, 2U, true, 3U, 6U, 5U, 3U, 4U, 2U, 0U, 1U, 7U, 0xB5U, 0xA2U, 0xB4U }, core::EInstrumentModel::eHantekDso2250, 0x04B5U, 0x2250U, 0x04B5U, 0U, 0U, false