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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ set(SOURCES_LIST
capture/src/acquisition_loop.cpp
capture/src/raw_packet_queue.cpp
capture/src/waveform_parser.cpp
capture/src/waveform_ring_buffer.cpp
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
Expand All @@ -60,6 +61,7 @@ set(HEADERS_LIST
capture/inc/acquisition_loop.h
capture/inc/raw_packet_queue.h
capture/inc/waveform_parser.h
capture/inc/waveform_ring_buffer.h
)

set(RAW_PACKET_QUEUE_TEST_SOURCES_LIST
Expand All @@ -70,6 +72,10 @@ set(WAVEFORM_PARSER_TEST_SOURCES_LIST
tests/waveform_parser_test.cpp
)

set(WAVEFORM_RING_BUFFER_TEST_SOURCES_LIST
tests/waveform_ring_buffer_test.cpp
)

if(CMAKE_BUILD_TYPE MATCHES "Debug")
message(STATUS ">>> Debug build")
add_compile_definitions(_DEBUG)
Expand Down Expand Up @@ -136,6 +142,17 @@ if(BUILD_TESTING)
enable_project_warnings(waveform_parser_tests)
add_test(NAME waveform_parser COMMAND waveform_parser_tests)
add_dependencies(${APP_NAME} waveform_parser_tests)
add_executable(waveform_ring_buffer_tests
${WAVEFORM_RING_BUFFER_TEST_SOURCES_LIST}
capture/src/waveform_ring_buffer.cpp
)
target_include_directories(waveform_ring_buffer_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/capture/inc
)
target_link_libraries(waveform_ring_buffer_tests PRIVATE Threads::Threads)
enable_project_warnings(waveform_ring_buffer_tests)
add_test(NAME waveform_ring_buffer COMMAND waveform_ring_buffer_tests)
add_dependencies(${APP_NAME} waveform_ring_buffer_tests)
add_test(
NAME release_updater
COMMAND ${Python3_EXECUTABLE}
Expand Down
195 changes: 119 additions & 76 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,82 +5,6 @@ Records key decisions, structural changes, and completed development stages.

---

## 2026-09-05

### Stage 4 preparation - Release metadata coverage

- Registered standalone C++ test sources in dedicated CMake source lists.
- Extended the release updater to discover C++ test sources and update their
`@version` metadata together with production sources.
- Added deterministic CTest coverage that runs the updater in an isolated copy
of the project and verifies test-source version updates.
- Corrected the documented DSO-2250 operational endpoint configuration to
interface 0, alternate setting 0.

### Stage 4 - Sample parser foundation

- Added pure DSO-2250 parser functions for capture-state responses and complete
interleaved two-channel waveform buffers.
- Preserved the legacy byte layout: each sample pair is CH2 followed by CH1.
- Preserved the legacy capture-state trigger-point transformation in the new,
Qt-independent parser.
- Added deterministic CTest coverage for state parsing, trigger decoding,
channel order, and malformed capture buffers.

### Stage 4 - Complete DSO-2250 capture reads

- Start acquisition with the legacy capture-start and trigger-enable command
sequence before capture-state polling begins.
- On a complete DSO-2250 capture-state response, read the fixed two-channel
32768-sample buffer as 128 consecutive 512-byte bulk packets.
- Stop incomplete waveform publication at the first failed packet and preserve
the existing bounded recovery and device-loss behavior.
- Restart capture and re-enable the trigger only after a complete buffer was
read and queued for processing.
- Added acquisition-status diagnostics for channel-data, capture-start, and
trigger-enable failures.

### Stage 4 - Decoded waveform publication

- Preserved the decoded capture trigger point alongside its raw waveform bytes
while the frame passes through the FIFO.
- Decode complete queued DSO-2250 frames in the processing worker instead of
interpreting raw channel bytes as capture-state data.
- Added a mutex-protected latest-waveform snapshot and `getLatestWaveform()`
API for the future renderer.
- Extended FIFO tests to verify trigger-point metadata is retained with each
packet.

### Capture protocol profiles

- Moved capture endpoints, command bytes, packet length, channel layout,
sample count, and completion state into each supported-device entry.
- Preserve the selected profile in the active USB connection and use it for
acquisition and waveform parsing instead of model-named function contracts.
- Validate profile packetisation and fixed capture-storage bounds before
requesting sample data.

### Stage 4 - Hardware verification

- Verified a 60-second acquisition on the initial Hantek DSO-2250 profile.
- Verified six 10-second Start/Stop cycles: the UI stayed responsive and the
connected device was ready for every subsequent Start.
- Verified active USB removal: acquisition stopped without a crash, deadlock,
or unbounded wait and reported the lost device while beginning a command.
- Verified automatic rediscovery, explicit reconnection, and a subsequent
successful acquisition after the device was reattached.
- Verified application shutdown during acquisition without a crash, deadlock,
or unbounded wait; the instrument returned to its connected idle LED state,
then turned off after application exit.
- Verified the Release build without warnings. Normal acquisition showed no
persistent recovery state or unexpected acquisition stop.

### Code style - Immutable object naming

- Reserved `UPPER_CASE` for preprocessor macros.
- Renamed immutable module objects to the `kPascalCase` convention while
retaining `const` storage and unchanged runtime behavior.

## 2026-08-27

### Project inception
Expand Down Expand Up @@ -322,3 +246,122 @@ Records key decisions, structural changes, and completed development stages.
- Decode capture-state responses and acquired sample packets.
- Add deterministic fault-injection tests for timeout and transfer-error
recovery, then verify recovery with a connected physical device.

## 2026-09-05

### Stage 4 - Data ring buffer

- Added `WaveformRingBuffer`, a bounded thread-safe buffer of decoded
two-channel waveform frames connecting the capture processing thread to
render consumption.
- The producer side never blocks: pushing a frame while full discards the
oldest buffered frame and increments a thread-safe dropped-frame count.
- The consumer side is non-blocking as well: `popLatest()` drains any
backlog and returns only the freshest frame, matching a real-time render
loop that must never wait on stale or absent data.
- Replaced the single-slot mutex-protected waveform snapshot in
`SAcquisitionLoop` with a `WaveformRingBuffer` member; `getLatestWaveform()`
keeps its existing signature and now delegates to `popLatest()`.
- Wired the render loop to call `getLatestWaveform()` once per frame and
surface the decoded sample count and trigger point on the status line,
proving the capture-to-render path end to end; plotting the waveform shape
remains a separate future task.
- Added deterministic CTest coverage for latest-frame semantics, overflow
drop-oldest with its counter, reset/reopen, an empty buffer, and concurrent
producer/consumer operation.
- Verified warning-free Debug and Release builds and a full CTest pass.

### Stage 4 preparation - Release metadata coverage

- Registered standalone C++ test sources in dedicated CMake source lists.
- Extended the release updater to discover C++ test sources and update their
`@version` metadata together with production sources.
- Added deterministic CTest coverage that runs the updater in an isolated copy
of the project and verifies test-source version updates.
- Corrected the documented DSO-2250 operational endpoint configuration to
interface 0, alternate setting 0.

### Stage 4 - Sample parser foundation

- Added pure DSO-2250 parser functions for capture-state responses and complete
interleaved two-channel waveform buffers.
- Preserved the legacy byte layout: each sample pair is CH2 followed by CH1.
- Preserved the legacy capture-state trigger-point transformation in the new,
Qt-independent parser.
- Added deterministic CTest coverage for state parsing, trigger decoding,
channel order, and malformed capture buffers.

### Stage 4 - Complete DSO-2250 capture reads

- Start acquisition with the legacy capture-start and trigger-enable command
sequence before capture-state polling begins.
- On a complete DSO-2250 capture-state response, read the fixed two-channel
32768-sample buffer as 128 consecutive 512-byte bulk packets.
- Stop incomplete waveform publication at the first failed packet and preserve
the existing bounded recovery and device-loss behavior.
- Restart capture and re-enable the trigger only after a complete buffer was
read and queued for processing.
- Added acquisition-status diagnostics for channel-data, capture-start, and
trigger-enable failures.

### Stage 4 - Decoded waveform publication

- Preserved the decoded capture trigger point alongside its raw waveform bytes
while the frame passes through the FIFO.
- Decode complete queued DSO-2250 frames in the processing worker instead of
interpreting raw channel bytes as capture-state data.
- Added a mutex-protected latest-waveform snapshot and `getLatestWaveform()`
API for the future renderer.
- Extended FIFO tests to verify trigger-point metadata is retained with each
packet.

### Capture protocol profiles

- Moved capture endpoints, command bytes, packet length, channel layout,
sample count, and completion state into each supported-device entry.
- Preserve the selected profile in the active USB connection and use it for
acquisition and waveform parsing instead of model-named function contracts.
- Validate profile packetisation and fixed capture-storage bounds before
requesting sample data.

### Stage 4 - Hardware verification

- Verified a 60-second acquisition on the initial Hantek DSO-2250 profile.
- Verified six 10-second Start/Stop cycles: the UI stayed responsive and the
connected device was ready for every subsequent Start.
- Verified active USB removal: acquisition stopped without a crash, deadlock,
or unbounded wait and reported the lost device while beginning a command.
- Verified automatic rediscovery, explicit reconnection, and a subsequent
successful acquisition after the device was reattached.
- Verified application shutdown during acquisition without a crash, deadlock,
or unbounded wait; the instrument returned to its connected idle LED state,
then turned off after application exit.
- Verified the Release build without warnings. Normal acquisition showed no
persistent recovery state or unexpected acquisition stop.

### Code style - Immutable object naming

- Reserved `UPPER_CASE` for preprocessor macros.
- Renamed immutable module objects to the `kPascalCase` convention while
retaining `const` storage and unchanged runtime behavior.

## 2026-09-06

### Stage 4 - Data ring buffer hardware verification

- Verified the Release build succeeded without warnings.
- Verified the Connect/Start/Stop status-line and status-LED lifecycle on a
physical Hantek DSO-2250: `Stopped | Live mode | Connected` with a red LED
after Connect, `Acquiring` with a green LED after Start, and back to
`Stopped` with a red LED after Stop, with the connection retained.
- Verified a 60-second acquisition run and six repeated 10-second Start/Stop
cycles: the GUI stayed responsive and the connection remained usable for a
new Start after every Stop.
- Verified active USB removal during acquisition: the application did not
hang, acquisition stopped, and the status line reported
`Device disconnected: USB device lost while beginning command`.
- Verified automatic rediscovery after reattaching the device, followed by a
successful Connect and Start with the same status/LED lifecycle as above.
- Verified application shutdown during acquisition: no hang or crash; the
status LED blinked red briefly, then turned off.

32 changes: 30 additions & 2 deletions app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
#include "usb_device.h"

using oscilloscope::capture::SAcquisitionLoop;
using oscilloscope::capture::SWaveformSamples;
using oscilloscope::capture::EAcquisitionOperation;
using oscilloscope::capture::EAcquisitionState;
using oscilloscope::usb::EScanStatus;
Expand Down Expand Up @@ -322,6 +323,9 @@ int main (void) {
);
uint32_t nextUsbPresenceCheck =
SDL_GetTicks() + kUsbPresenceIntervalMs;
SWaveformSamples latestWaveform{};
uint32_t latestTriggerPoint = 0U;
bool hasWaveform = false;
const char* timebases[] = {
"4 ns/div", "20 ns/div", "100 ns/div", "1 us/div", "10 us/div",
"100 us/div", "1 ms/div", "10 ms/div", "100 ms/div", "1 s/div"
Expand Down Expand Up @@ -364,6 +368,14 @@ int main (void) {
&deviceStatus
);

if (
oscilloscope::capture::getLatestWaveform(
&acquisitionLoop, &latestWaveform, &latestTriggerPoint
)
) {
hasWaveform = true;
}

if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Exit")) {
Expand Down Expand Up @@ -550,9 +562,24 @@ int main (void) {
}
ImGui::EndChild();

char waveformStatus[64];

if (hasWaveform) {
snprintf(
waveformStatus,
sizeof(waveformStatus),
"Waveform %zu samples (trigger %u)",
latestWaveform.sampleCount,
static_cast<unsigned int>(latestTriggerPoint)
);
}
else {
snprintf(waveformStatus, sizeof(waveformStatus), "Waveform none");
}

ImGui::SetCursorScreenPos(statusPosition);
ImGui::Text(
"%s | %s | %s | CH1 %s | CH2 %s",
"%s | %s | %s | CH1 %s | CH2 %s | %s",
acquisitionRunning
? (acquisitionLoop.status.state.load() ==
EAcquisitionState::eRecovering
Expand All @@ -561,7 +588,8 @@ int main (void) {
demoMode ? "Demo mode" : "Live mode",
deviceStatus.c_str(),
channelEnabled[0] ? "on" : "off",
channelEnabled[1] ? "on" : "off"
channelEnabled[1] ? "on" : "off",
waveformStatus
);
ImGui::End();

Expand Down
9 changes: 4 additions & 5 deletions capture/inc/acquisition_loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@

/******************************* Included files ******************************/
#include <atomic>
#include <mutex>
#include <thread>

#include "raw_packet_queue.h"
#include "usb_device.h"
#include "waveform_parser.h"
#include "waveform_ring_buffer.h"

/********************************* Definitions ********************************/

Expand Down Expand Up @@ -82,11 +82,10 @@ struct SAcquisitionLoop {
RawPacketQueue rawPacketQueue{8U}; /**< Bounded raw response FIFO */
SAcquisitionStatus status; /**< Shared poll status */
usb::SUsbCaptureProtocol captureProtocol{}; /**< Active capture format */
mutable std::mutex waveformMutex; /**< Guards the latest waveform */
SWaveformSamples latestWaveform{}; /**< Most recently decoded frame */
uint32_t latestTriggerPoint{0U}; /**< Trigger point for latest frame */
mutable WaveformRingBuffer waveformRingBuffer{
kWaveformRingBufferCapacity
}; /**< Decoded frames awaiting render consumption */
std::atomic<bool> stopRequested{false}; /**< Set to request a stop */
bool hasWaveform{false}; /**< True after first decoded frame */
};

/********************* Application Programming Interface *********************/
Expand Down
Loading
Loading