diff --git a/CMakeLists.txt b/CMakeLists.txt index add9c02f..47ff8264 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,6 +273,7 @@ set(pulseview_SOURCES pv/dialogs/storeprogress.cpp pv/popups/deviceoptions.cpp pv/popups/channels.cpp + pv/popups/triggermode.cpp pv/prop/bool.cpp pv/prop/double.cpp pv/prop/enum.cpp @@ -342,6 +343,7 @@ set(pulseview_HEADERS pv/dialogs/storeprogress.hpp pv/popups/channels.hpp pv/popups/deviceoptions.hpp + pv/popups/triggermode.hpp pv/prop/bool.hpp pv/prop/double.hpp pv/prop/enum.hpp diff --git a/icons/status-yellow.svg b/icons/status-yellow.svg new file mode 100644 index 00000000..48433542 --- /dev/null +++ b/icons/status-yellow.svg @@ -0,0 +1,24 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/icons/trigger-repeat-rearm.svg b/icons/trigger-repeat-rearm.svg new file mode 100644 index 00000000..f481f7c7 --- /dev/null +++ b/icons/trigger-repeat-rearm.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/pulseview.qrc b/pulseview.qrc index 706fe8d0..49d72e47 100644 --- a/pulseview.qrc +++ b/pulseview.qrc @@ -26,6 +26,7 @@ icons/status-green.svg icons/status-grey.svg icons/status-red.svg + icons/status-yellow.svg icons/show-cursors.svg icons/trigger-change.svg icons/trigger-falling.svg @@ -36,6 +37,7 @@ icons/trigger-marker-high.svg icons/trigger-marker-low.svg icons/trigger-marker-rising.svg + icons/trigger-repeat-rearm.svg icons/trigger-none.svg icons/trigger-rising.svg icons/view-displaymode-last_complete_segment.svg diff --git a/pv/mainwindow.cpp b/pv/mainwindow.cpp index 34938e73..d288a65a 100644 --- a/pv/mainwindow.cpp +++ b/pv/mainwindow.cpp @@ -74,6 +74,7 @@ MainWindow::MainWindow(DeviceManager &device_manager, QWidget *parent) : device_manager_(device_manager), session_selector_(this), icon_red_(":/icons/status-red.svg"), + icon_yellow_(":/icons/status-yellow.svg"), icon_green_(":/icons/status-green.svg"), icon_grey_(":/icons/status-grey.svg") { @@ -583,7 +584,8 @@ void MainWindow::update_acq_button(Session *session) run_caption = tr("Run"); } - const QIcon *icons[] = {&icon_grey_, &icon_red_, &icon_green_}; + // Maps to pv::Session::capture_state enum elements + const QIcon *icons[] = {&icon_grey_, &icon_grey_, &icon_red_, &icon_green_, &icon_yellow_}; run_stop_button_->setIcon(*icons[state]); run_stop_button_->setText((state == pv::Session::Stopped) ? run_caption : tr("Stop")); @@ -681,6 +683,7 @@ void MainWindow::on_run_stop_clicked() bool any_running = any_of(hw_sessions.begin(), hw_sessions.end(), [](const shared_ptr &s) { return (s->get_capture_state() == Session::AwaitingTrigger) || + (s->get_capture_state() == Session::AwaitingRearm) || (s->get_capture_state() == Session::Running); }); for (shared_ptr s : hw_sessions) @@ -701,6 +704,8 @@ void MainWindow::on_run_stop_clicked() session->start_capture([&](QString message) { show_session_error("Capture failed", message); }); break; + case Session::Starting: + case Session::AwaitingRearm: case Session::AwaitingTrigger: case Session::Running: session->stop_capture(); diff --git a/pv/mainwindow.hpp b/pv/mainwindow.hpp index e10d1817..c208f119 100644 --- a/pv/mainwindow.hpp +++ b/pv/mainwindow.hpp @@ -163,6 +163,7 @@ private Q_SLOTS: QTabWidget session_selector_; QIcon icon_red_; + QIcon icon_yellow_; QIcon icon_green_; QIcon icon_grey_; diff --git a/pv/popups/triggermode.cpp b/pv/popups/triggermode.cpp new file mode 100644 index 00000000..91ce9a6b --- /dev/null +++ b/pv/popups/triggermode.cpp @@ -0,0 +1,86 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012-2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "triggermode.hpp" + +#include + +namespace pv { +namespace popups { + +TriggerMode::TriggerMode(Session &session, QWidget *parent) : + Popup(parent), + session_(session), + layout_(this), + form_(this), + form_layout_(&form_) +{ + setWindowTitle(tr("Trigger Mode")); + + form_.setLayout(&form_layout_); + +// mode + + QCheckBox *chkbox_repetitive_ = new QCheckBox(tr("&Repetitive"), this); + + QVBoxLayout *vbox_mode_ = new QVBoxLayout; + vbox_mode_->addWidget(chkbox_repetitive_); + + QGroupBox *groupbox_mode_ = new QGroupBox(tr("Select the trigger mode")); + groupbox_mode_->setLayout(vbox_mode_); + form_layout_.addRow(groupbox_mode_); + +// delay + + rearm_delay_ = new QSpinBox; + rearm_delay_->setRange(100, 2000000000); + rearm_delay_->setValue(5000); + + QVBoxLayout *vbox_rearm_delay_ = new QVBoxLayout; + vbox_rearm_delay_->addWidget(rearm_delay_); + + QGroupBox *groupbox_rearm_delay_ = new QGroupBox(tr("Set the trigger rearm delay in mSec")); + groupbox_rearm_delay_->setLayout(vbox_rearm_delay_); + form_layout_.addRow(groupbox_rearm_delay_); + +// connections + + connect(chkbox_repetitive_, SIGNAL(toggled(bool)), this, SLOT(chkbox_toggled(bool))); + connect(rearm_delay_, SIGNAL(valueChanged(int)), this, SLOT(rearm_time_changed(int))); + + layout_.addWidget(&form_); + +// sync with session + + chkbox_repetitive_->setChecked(session_.get_capture_mode() == pv::Repetitive); + rearm_delay_->setValue(session_.get_repetitive_rearm_time()); +} + +void TriggerMode::chkbox_toggled(bool checked) +{ + session_.set_capture_mode(checked ? pv::Repetitive : pv::Single); +} + +void TriggerMode::rearm_time_changed(int value) +{ + session_.set_repetitive_rearm_time(value); +} + +} // namespace dialogs +} // namespace pv diff --git a/pv/popups/triggermode.hpp b/pv/popups/triggermode.hpp new file mode 100644 index 00000000..eae71782 --- /dev/null +++ b/pv/popups/triggermode.hpp @@ -0,0 +1,79 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012-2013 Joel Holdsworth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef PULSEVIEW_PV_POPUPS_TRIGGERMODE_HPP +#define PULSEVIEW_PV_POPUPS_TRIGGERMODE_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using std::shared_ptr; + +namespace pv { +class Session; +namespace popups { + +class TriggerMode : public pv::widgets::Popup +{ + Q_OBJECT + +public: + TriggerMode(Session &session, QWidget *parent); + +private: + Session &session_; + +private Q_SLOTS: + void chkbox_toggled(bool checked); + void rearm_time_changed(int value); + +private: + QVBoxLayout layout_; + QWidget form_; + QFormLayout form_layout_; + +// mode + + QCheckBox *chkbox_repetitive_; + + QVBoxLayout *vbox_mode_; + QGroupBox *groupbox_mode_; + +// delay + + QSpinBox *rearm_delay_; + + QVBoxLayout *vbox_rearm_delay_; + QGroupBox *groupbox_rearm_delay_; +}; + +} // namespace dialogs +} // namespace pv + +#endif // PULSEVIEW_PV_POPUPS_TRIGGERMODE_HPP diff --git a/pv/session.cpp b/pv/session.cpp index f992d9f1..f5256253 100644 --- a/pv/session.cpp +++ b/pv/session.cpp @@ -118,6 +118,10 @@ namespace pv { shared_ptr Session::sr_context; Session::Session(DeviceManager &device_manager, QString name) : + repetitive_rearm_permitted_(false), + capture_mode_(DefaultCaptureMode), + repetitive_rearm_time_(DefaultRearmTime), + repetitive_rearm_timer_(), shutting_down_(false), device_manager_(device_manager), default_name_(name), @@ -128,10 +132,24 @@ Session::Session(DeviceManager &device_manager, QString name) : { // Use this name also for the QObject instance setObjectName(name_); + + qRegisterMetaType("util::Timestamp"); + qRegisterMetaType("uint64_t"); + + repetitive_rearm_timer_.stop(); + repetitive_rearm_timer_.setSingleShot(true); + repetitive_rearm_timer_.setInterval(repetitive_rearm_time_); + + connect(&repetitive_rearm_timer_, SIGNAL(timeout()), + this, SLOT(on_repetitive_rearm_timeout())); + connect(this, SIGNAL(repetitive_rearm()), + this, SLOT(on_repetitive_rearm())); } Session::~Session() { + repetitive_rearm_timer_.stop(); + shutting_down_ = true; // Stop and join to the thread @@ -829,11 +847,42 @@ void Session::start_capture(function error_handler) name_changed(); } + // Init Segments + { + lock_guard lock(data_mutex_); + cur_logic_segment_.reset(); + cur_analog_segments_.clear(); + for (shared_ptr sb : signalbases_) + sb->clear_sample_data(); + } + highest_segment_id_ = -1; + frame_began_ = false; + + repetitive_rearm_permitted_ = true; + + set_capture_state(capture_state::Starting); + start_sampling_thread(error_handler); +} + +void Session::start_sampling_thread(function error_handler) +{ + stop_sampling_thread(); + // Begin the session sampling_thread_ = std::thread(&Session::sample_thread_proc, this, error_handler); } void Session::stop_capture() +{ + repetitive_rearm_timer_.stop(); + repetitive_rearm_permitted_ = false; + + stop_sampling_thread(); + + set_capture_state(Stopped); +} + +void Session::stop_sampling_thread() { if (get_capture_state() != Stopped) device_->stop(); @@ -841,6 +890,7 @@ void Session::stop_capture() // Check that sampling stopped if (sampling_thread_.joinable()) sampling_thread_.join(); + } void Session::register_view(shared_ptr view) @@ -1052,9 +1102,13 @@ void Session::set_capture_state(capture_state state) if (state == Running) acq_time_.restart(); - if (state == Stopped) + if (state == Stopped || state == AwaitingRearm) qDebug("Acquisition took %.2f s", acq_time_.elapsed() / 1000.); + if (state == AwaitingRearm) { + repetitive_rearm(); + } + { lock_guard lock(sampling_mutex_); capture_state_ = state; @@ -1258,16 +1312,6 @@ void Session::sample_thread_proc(function error_handler) out_of_memory_ = false; - { - lock_guard lock(data_mutex_); - cur_logic_segment_.reset(); - cur_analog_segments_.clear(); - for (shared_ptr sb : signalbases_) - sb->clear_sample_data(); - } - highest_segment_id_ = -1; - frame_began_ = false; - try { device_->start(); } catch (Error& e) { @@ -1290,7 +1334,12 @@ void Session::sample_thread_proc(function error_handler) return; } - set_capture_state(Stopped); + signal_segment_completed(); + if (repetitive_rearm_permitted_ && capture_mode_ == Repetitive) { + set_capture_state(AwaitingRearm); + } else { + set_capture_state(Stopped); + } // Confirm that SR_DF_END was received if (cur_logic_segment_) @@ -1723,4 +1772,43 @@ void Session::on_new_decoders_selected(vector decoders) } #endif +void Session::on_repetitive_rearm_timeout() +{ + repetitive_rearm_timer_.stop(); + if (capture_mode_ == Repetitive) + start_sampling_thread([&](QString message) { + // TODO Emulate noquote() + qDebug() << "Capture failed:" << message; }); +} + +void Session::on_repetitive_rearm() +{ + repetitive_rearm_timer_.setInterval(repetitive_rearm_time_); + repetitive_rearm_timer_.start(); +} + +capture_mode Session::get_capture_mode() +{ + return capture_mode_; +} + +void Session::set_capture_mode(capture_mode mode) +{ + capture_mode_ = mode; + if (capture_mode_ == Single) { + repetitive_rearm_permitted_ = false; + repetitive_rearm_timer_.stop(); + } +} + +int Session::get_repetitive_rearm_time() +{ + return repetitive_rearm_time_; +} + +void Session::set_repetitive_rearm_time(int repetitive_rearm_time) +{ + repetitive_rearm_time_ = repetitive_rearm_time; +} + } // namespace pv diff --git a/pv/session.hpp b/pv/session.hpp index 91f98b58..72f8985c 100644 --- a/pv/session.hpp +++ b/pv/session.hpp @@ -85,6 +85,14 @@ using sigrok::Option; namespace pv { +// TODO rename to? +// - repeat_mode +// - rearm_mode +enum capture_mode { + Single, + Repetitive +}; + class DeviceManager; namespace data { @@ -119,8 +127,10 @@ class Session : public QObject public: enum capture_state { Stopped, + Starting, AwaitingTrigger, - Running + Running, + AwaitingRearm, }; static shared_ptr sr_context; @@ -212,6 +222,11 @@ class Session : public QObject MetadataObjManager* metadata_obj_manager(); + capture_mode get_capture_mode(); + void set_capture_mode(capture_mode); + int get_repetitive_rearm_time(); + void set_repetitive_rearm_time(int); + private: void set_capture_state(capture_state state); @@ -248,6 +263,16 @@ class Session : public QObject void data_feed_in(shared_ptr device, shared_ptr packet); + capture_mode const DefaultCaptureMode = Single; + int const DefaultRearmTime = 5000; // mSec + + bool repetitive_rearm_permitted_; + capture_mode capture_mode_; + int repetitive_rearm_time_; // in mSec + QTimer repetitive_rearm_timer_; + void start_sampling_thread(function error_handler); + void stop_sampling_thread(); + Q_SIGNALS: void capture_state_changed(int state); void device_changed(); @@ -264,10 +289,14 @@ class Session : public QObject void data_received(); void add_view(ViewType type, Session *session); + void repetitive_rearm(); public Q_SLOTS: void on_data_saved(); + void on_repetitive_rearm_timeout(); + void on_repetitive_rearm(); + #ifdef ENABLE_DECODE void on_new_decoders_selected(vector decoders); #endif diff --git a/pv/toolbars/mainbar.cpp b/pv/toolbars/mainbar.cpp index 39c290df..d6396981 100644 --- a/pv/toolbars/mainbar.cpp +++ b/pv/toolbars/mainbar.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,8 @@ MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view configure_button_action_(nullptr), channels_button_(this), channels_button_action_(nullptr), + triggermode_button_(this), + triggermode_button_action_(nullptr), sample_count_(" samples", this), sample_rate_("Hz", this), updating_sample_rate_(false), @@ -269,6 +272,11 @@ MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view channels_button_.setToolTip(tr("Configure Channels")); channels_button_.setIcon(QIcon(":/icons/channels.svg")); + triggermode_button_.setToolTip(tr("Configure Repeating Trigger Rearm")); + triggermode_button_.setIcon(QIcon(":/icons/trigger-repeat-rearm.svg")); + pv::popups::TriggerMode *const triggermode_popup = new pv::popups::TriggerMode(session_, this); + triggermode_button_.set_popup(triggermode_popup); + add_toolbar_widgets(); sample_count_.installEventFilter(this); @@ -305,6 +313,7 @@ void MainBar::set_capture_state(pv::Session::capture_state state) device_selector_.setEnabled(ui_enabled); configure_button_.setEnabled(ui_enabled); channels_button_.setEnabled(ui_enabled); + triggermode_button_.setEnabled(ui_enabled); sample_count_.setEnabled(ui_enabled); sample_rate_.setEnabled(ui_enabled); } @@ -925,6 +934,7 @@ void MainBar::add_toolbar_widgets() addWidget(&device_selector_); configure_button_action_ = addWidget(&configure_button_); channels_button_action_ = addWidget(&channels_button_); + triggermode_button_action_ = addWidget(&triggermode_button_); addWidget(&sample_count_); addWidget(&sample_rate_); #ifdef ENABLE_DECODE diff --git a/pv/toolbars/mainbar.hpp b/pv/toolbars/mainbar.hpp index e4aa39b6..1f4ef03b 100644 --- a/pv/toolbars/mainbar.hpp +++ b/pv/toolbars/mainbar.hpp @@ -176,6 +176,9 @@ private Q_SLOTS: pv::widgets::PopupToolButton channels_button_; QAction *channels_button_action_; + pv::widgets::PopupToolButton triggermode_button_; + QAction *triggermode_button_action_; + pv::widgets::SweepTimingWidget sample_count_; pv::widgets::SweepTimingWidget sample_rate_; bool updating_sample_rate_; diff --git a/pv/views/trace/standardbar.cpp b/pv/views/trace/standardbar.cpp index 48d92891..bab0e59f 100644 --- a/pv/views/trace/standardbar.cpp +++ b/pv/views/trace/standardbar.cpp @@ -93,7 +93,7 @@ StandardBar::StandardBar(Session &session, QWidget *parent, action_sdm_single_->setIcon(QIcon(":/icons/view-displaymode-single_segment.svg")); action_sdm_single_->setText(tr("Display a single segment")); - connect(action_view_show_cursors_, SIGNAL(triggered(bool)), + connect(action_sdm_single_, SIGNAL(triggered(bool)), this, SLOT(on_actionSDMSingle_triggered())); segment_display_mode_selector_->addAction(action_sdm_last_); diff --git a/pv/views/trace/view.cpp b/pv/views/trace/view.cpp index f1de3b20..ef7ac512 100644 --- a/pv/views/trace/view.cpp +++ b/pv/views/trace/view.cpp @@ -782,9 +782,23 @@ void View::set_segment_display_mode(Trace::SegmentDisplayMode mode) for (const shared_ptr& signal : signals_) signal->set_segment_display_mode(mode); + update_current_segment(); + + segment_selectable_ = true; + + if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity)) + segment_selectable_ = false; + + viewport_->update(); + + segment_display_mode_changed((int)mode, segment_selectable_); +} + +void View::update_current_segment() +{ uint32_t last_segment = session_.get_highest_segment_id(); - switch (mode) { + switch (segment_display_mode_) { case Trace::ShowLastSegmentOnly: if (current_segment_ != last_segment) set_current_segment(last_segment); @@ -810,15 +824,6 @@ void View::set_segment_display_mode(Trace::SegmentDisplayMode mode) // Current segment remains as-is break; } - - segment_selectable_ = true; - - if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity)) - segment_selectable_ = false; - - viewport_->update(); - - segment_display_mode_changed((int)mode, segment_selectable_); } void View::zoom(double steps) @@ -1979,7 +1984,7 @@ void View::capture_state_updated(int state) { GlobalSettings settings; - if (state == Session::Running) { + if (state == Session::Starting) { set_time_unit(util::TimeUnit::Samples); trigger_markers_.clear(); @@ -2002,7 +2007,6 @@ void View::capture_state_updated(int state) sticky_scrolling_ = !restoring_state_ && settings.value(GlobalSettings::Key_View_StickyScrolling).toBool(); - // Reset all traces to segment 0 current_segment_ = 0; set_current_segment(current_segment_); } @@ -2038,33 +2042,21 @@ void View::capture_state_updated(int state) void View::on_new_segment(int new_segment_id) { - on_segment_changed(new_segment_id); + update_current_segment(); } void View::on_segment_completed(int segment_id) { - on_segment_changed(segment_id); + update_current_segment(); } void View::on_segment_changed(int segment) { - switch (segment_display_mode_) { - case Trace::ShowLastSegmentOnly: - case Trace::ShowSingleSegmentOnly: - set_current_segment(segment); - break; + // Triggered when a segment is selected by UI action from the user + // In this case, we assume segment_display_mode_ has already been + // reset to ShowSingleSegmentOnly and switch to the given segment. - case Trace::ShowLastCompleteSegmentOnly: - // Only update if all segments are complete - if (session_.all_segments_complete(segment)) - set_current_segment(segment); - break; - - case Trace::ShowAllSegments: - case Trace::ShowAccumulatedIntensity: - default: - break; - } + set_current_segment(segment); } void View::on_settingViewTriggerIsZeroTime_changed(const QVariant new_value) diff --git a/pv/views/trace/view.hpp b/pv/views/trace/view.hpp index f8506cf4..abca632e 100644 --- a/pv/views/trace/view.hpp +++ b/pv/views/trace/view.hpp @@ -422,6 +422,7 @@ public Q_SLOTS: void update_view_range_metaobject() const; void update_hover_point(); + void update_current_segment(); public: void row_item_appearance_changed(bool label, bool content);