diff --git a/blocklib/picoscope/include/fair/picoscope/Picoscope.hpp b/blocklib/picoscope/include/fair/picoscope/Picoscope.hpp index c9da6c7c..943e82eb 100644 --- a/blocklib/picoscope/include/fair/picoscope/Picoscope.hpp +++ b/blocklib/picoscope/include/fair/picoscope/Picoscope.hpp @@ -135,6 +135,7 @@ struct Picoscope : gr::Block, gr::SupportedTy A digital_port_enable = false; // only used if digital ports are available: 3000a, 5000a series A digital_port_invert_output = false; // only used if digital ports are available: 3000a, 5000a series A> matcher_timeout = 10'000'000z; + A> polling_interval = 0ULL; A verbose_console = false; gr::PortIn timingIn; @@ -151,8 +152,11 @@ struct Picoscope : gr::Block, gr::SupportedTy detail::TriggerNameAndCtx _armTriggerNameAndCtx; // store parsed information to optimise performance detail::TriggerNameAndCtx _disarmTriggerNameAndCtx; - GR_MAKE_REFLECTABLE(Picoscope, timingIn, out, digitalOut, serial_number, sample_rate, pre_samples, post_samples, n_captures, auto_arm, trigger_once, channel_ids, signal_names, signal_units, signal_quantities, // - channel_ranges, channel_analog_offsets, signal_scales, signal_offsets, channel_couplings, trigger_source, trigger_threshold, trigger_direction, digital_port_enable, digital_port_invert_output, trigger_arm, trigger_disarm, matcher_timeout, verbose_console); + std::chrono::steady_clock::time_point _lastTry; // saves the timestamp of the last polling + + GR_MAKE_REFLECTABLE(Picoscope, timingIn, out, digitalOut, serial_number, sample_rate, pre_samples, post_samples, n_captures, auto_arm, trigger_once, channel_ids, signal_names, signal_units, signal_quantities, // + channel_ranges, channel_analog_offsets, signal_scales, signal_offsets, channel_couplings, trigger_source, trigger_threshold, trigger_direction, digital_port_enable, digital_port_invert_output, trigger_arm, // + trigger_disarm, matcher_timeout, verbose_console, polling_interval); private: std::optional> _picoscope; @@ -174,6 +178,14 @@ struct Picoscope : gr::Block, gr::SupportedTy template requires(acquisitionMode == AcquisitionMode::Streaming) gr::work::Status processBulk(gr::InputSpanLike auto& timingInSpan, std::span& outputs, gr::OutputSpanLike auto& digitalOutSpan) { + if (polling_interval != 0ULL) { + const auto intervalNs = std::chrono::nanoseconds{polling_interval.value}; + if (_lastTry + intervalNs > std::chrono::steady_clock::now()) { + return gr::work::Status::OK; + } + _lastTry = std::chrono::steady_clock::now(); + } + std::size_t nSamples = 0UZ; std::size_t samplesDropped = 0UZ; const std::size_t availableBuffer = std::min(std::ranges::min(outputs | std::views::transform(&TOutSpan::size)), digitalOutSpan.size()); diff --git a/blocklib/picoscope/include/fair/picoscope/TimingMatcher.hpp b/blocklib/picoscope/include/fair/picoscope/TimingMatcher.hpp index 5b59a801..82470773 100644 --- a/blocklib/picoscope/include/fair/picoscope/TimingMatcher.hpp +++ b/blocklib/picoscope/include/fair/picoscope/TimingMatcher.hpp @@ -183,7 +183,7 @@ struct TimingMatcher { break; // tag will be moved outside the current data chunk and has to be handled in the next iteration } if (realignedTag) { - result.processedSamples = std::max(result.processedSamples, realignedTag->index); + result.processedSamples = std::max(result.processedSamples, realignedTag->index + 1); pushTagOrdered(std::move(*realignedTag), "realign/no-trigger-left"); } else { result.messages.emplace_back(std::format("Failed to realign tag relative to last matched trigger: {}", currentTag)); @@ -220,7 +220,7 @@ struct TimingMatcher { if (realignedTag->index >= nSamples) { break; } - result.processedSamples = std::max(result.processedSamples, realignedTag->index); + result.processedSamples = std::max(result.processedSamples, realignedTag->index + 1); pushTagOrdered(std::move(*realignedTag), "realign/no-hw-or-flank-too-far"); } else { result.messages.emplace_back(std::format("Failed to realign tag relative to last matched trigger: {}", currentTag)); @@ -234,7 +234,7 @@ struct TimingMatcher { if ((currentFlankTime + timeout) < currentTagLocalTime) { // hw edge without corresponding event pushTagOrdered(createUnknownEventTag(currentFlankIndex, currentFlankTime), "unknown-event/no-matching-tag"); - result.processedSamples = std::max(result.processedSamples, currentFlankIndex); + result.processedSamples = std::max(result.processedSamples, currentFlankIndex + 1); triggerIndex++; // skip outdated timing message(s) continue; } @@ -259,7 +259,7 @@ struct TimingMatcher { result.processedTags++; } pushTagOrdered(getOffsetAdjustedTag(currentFlankIndex, currentTagOffset, currentTag), "regular-match"); - result.processedSamples = std::max(result.processedSamples, currentFlankIndex); + result.processedSamples = std::max(result.processedSamples, currentFlankIndex + 1); result.processedTags++; triggerIndex++; } // (result.processedTags < tags.size() || triggerIndex < triggerSampleIndices.size()) diff --git a/blocklib/picoscope/test/qa_TimingMatcher.cc b/blocklib/picoscope/test/qa_TimingMatcher.cc index 5867db0e..f8ad5b90 100644 --- a/blocklib/picoscope/test/qa_TimingMatcher.cc +++ b/blocklib/picoscope/test/qa_TimingMatcher.cc @@ -448,7 +448,7 @@ const boost::ut::suite<"TimingMatchers"> TimingMatcherTests = [] { auto result = matcher.match(tags, triggerSampleIndices, 8UZ, std::chrono::nanoseconds(acqTimestamp)); expect(eq(2UZ, result.processedTags)); - expect(eq(4UZ, result.processedSamples)); // processed up to the last matching tag + expect(eq(5UZ, result.processedSamples)); // processed up to the last matching tag expectRangesEquals( std::vector{ {1, generateTimingTag("EVT_CMD1"s, acqTimestamp + 1'000, 0.0f, true)}, @@ -604,6 +604,32 @@ const boost::ut::suite<"TimingMatchers"> TimingMatcherTests = [] { expect(eq(110UZ, result.tags[3].index)); expect(std::ranges::any_of(result.messages, [](const auto& m) { return m.contains("Dropping unordered tag"); })); }; + + "processedSamples test"_test = [&] { + using namespace boost::ut; + + unsigned long acqTimestamp = 123456789; + + // timeout=10us, sampleRate=1e6 -> maxDelaySamples = 10, nSamples=250 -> safeSamples = 240 + constexpr std::size_t nSamples = 250UZ; + constexpr std::size_t safeIndex = 240UZ; + constexpr std::uint64_t tagTimeNs = 240'000; // 240 samples * 1000ns @ 1MHz + + std::vector tags{ + generateTimingTag("EVT_BOUNDARY"s, acqTimestamp + tagTimeNs, 0.0f, true), + }; + std::vector triggerSampleIndices{safeIndex}; + + TimingMatcher matcher{.timeout = 10us, .sampleRate = 1e6f}; + auto result = matcher.match(tags, triggerSampleIndices, nSamples, std::chrono::nanoseconds(acqTimestamp)); + + expect(eq(1UZ, result.processedTags)); + expect(eq(1UZ, result.tags.size())); + expect(eq(safeIndex, result.tags[0].index)); + + expect(eq(safeIndex + 1UZ, result.processedSamples)); + expect(result.tags[0].index < result.processedSamples); + }; }; } // namespace fair::picoscope::test