--- a/DSView/pv/view/decodetrace.cpp +++ b/DSView/pv/view/decodetrace.cpp @@ -188,9 +188,17 @@ } +static QString pwm_motor_scale(pv::data::decode::Decoder *decoder) // Физическая шкала тока относится к тому же экземпляру, который создал нормированные сегменты. +{ // Старый декодер заполнения сохраняет прежнюю безразмерную шкалу. + if (QString::fromUtf8(decoder->decoder()->id) != "pwm_motor") return ": -1 .. +1"; // Только модель двигателя имеет значения в амперах. + const auto option = decoder->options().find("plot_range_a"); // Шкала сохраняется штатным механизмом настроек DSL. + const double range = option != decoder->options().end() && option->second && g_variant_is_of_type(option->second, G_VARIANT_TYPE_DOUBLE) ? g_variant_get_double(option->second) : 100.0; // Сто ампер соответствует исходному значению адаптера при отсутствии опции в записи. + return QString(": -%1 .. +%1 A (estimate)").arg(range, 0, 'g', 6); // Единицы и расчётный характер значения видны рядом с общей либо отдельной кривой. +} // Физический ток в таблице остаётся ненормированным независимо от выбранной шкалы. + static bool pwm_combined(pv::data::decode::Decoder *decoder) // Режим берётся из штатных настроек конкретного экземпляра декодера. { // Старые записи без новой опции должны открываться с прежними отдельными графиками. - if (QString::fromUtf8(decoder->decoder()->id) != "pwm_waveform") return false; // Настройка PWM не меняет компоновку остальных анализаторов. + if ((QString::fromUtf8(decoder->decoder()->id) != "pwm_waveform" && QString::fromUtf8(decoder->decoder()->id) != "pwm_motor")) return false; // Настройка PWM не меняет компоновку остальных анализаторов. const auto option = decoder->options().find("waveform_layout"); // Ищем сохранённый параметр без добавления отсутствующего ключа в карту. return option != decoder->options().end() && option->second && g_variant_is_of_type(option->second, G_VARIANT_TYPE_STRING) // Проверяем тип перед чтением, чтобы повреждённая настройка не завершила приложение. && QString::fromUtf8(g_variant_get_string(option->second, nullptr)) == "combined"; // Только явно выбранный combined включает наложение цветных кривых. @@ -260,7 +268,7 @@ if ((*i).second) { const Row &row = (*i).first; // Для обзорного масштаба берём индексированные экстремумы, сохраняя кривую и короткие выбросы. - if (QString::fromUtf8(dec->decoder()->id) == "pwm_waveform" // Специальная геометрия применяется только к декодеру восстановления ШИМ. + if ((QString::fromUtf8(dec->decoder()->id) == "pwm_waveform" || QString::fromUtf8(dec->decoder()->id) == "pwm_motor") // Специальная геометрия применяется только к декодеру восстановления ШИМ. && row.row() && QString::fromUtf8(row.row()->id).startsWith("wave_")) { // Числовые строки duty продолжают использовать штатные аннотации. std::vector points; // Геометрия ограничивается шириной видимой области вместо числа периодов во всей записи. _decoder_stack->get_pwm_waveform(points, row, start_sample, end_sample, std::max(1, right - left)); // Индекс экстремумов сохраняет короткие пики при уменьшении масштаба. @@ -295,7 +303,7 @@ _cur_row_heading_offsets.push_back(row_top); // Заголовок графика привязывается к его фактической верхней границе. row_top += annotation_height * 3; // Масштабируемая область сохраняет прежнюю высоту из трёх строк на график. y += annotation_height * 3; // Следующая графическая область начинается сразу после текущей. - QString title = row.title(); // Раздельный график сохраняет прежнее название своего канала. + QString title = row.title() + (QString::fromUtf8(dec->decoder()->id) == "pwm_motor" ? pwm_motor_scale(dec) : QString()); // Раздельный график сохраняет прежнее название своего канала. if (row_combined) { // Общая подпись перечисляет только основные видимые фазы, сохраняя собственные шкалы θ и несущей. QStringList channels; // Список нужен для записи с одним, двумя или тремя видимыми входами. for (const auto &entry : rows) { // Порядок совпадает с порядком строк и цветами A, B, C. @@ -303,7 +311,7 @@ && QString::fromUtf8(entry.first.row()->id).size() == 6 && QString::fromUtf8(entry.first.row()->id).startsWith("wave_") && _decoder_stack->has_annotations(entry.first)) // Из общей подписи исключены неподключённые фазы и дополнительные расчётные графики. channels.append(QString::fromUtf8(entry.first.row()->id).right(1).toUpper()); // Суффикс wave_a…wave_c даёт обозначение фазы без разбора текста аннотаций. } // Полученный список соответствует кривым, которые действительно будут нарисованы. - title = QString::fromUtf8(dec->decoder()->name) + ": " + channels.join(" / ") + ": -1 .. +1"; // Общая шкала остаётся нормированной для всех видимых каналов. + title = QString::fromUtf8(dec->decoder()->name) + ": " + channels.join(" / ") + pwm_motor_scale(dec); // Общая шкала остаётся нормированной для всех видимых каналов. } // Значения PWM и числовые результаты не изменяются при переключении вида. _cur_row_headings.push_back(title); // Единственный заголовок относится ко всей выделенной области. } // Повторная совмещённая фаза не сдвигает последующие аннотации. @@ -627,6 +635,7 @@ { on_new_decode_data(); _session->decode_done(); + if (_view) _view->data_updated(); // Финальная перерисовка обновляет кэш после последних сегментов и диагностических строк, добавленных в end декодера. } int DecodeTrace::rows_size() @@ -646,7 +655,7 @@ _decoder_stack->has_annotations((*i).first) && (*i).second) { // Учитываем только заполненные строки, разрешённые в настройках Decoders. - const bool waveform = QString::fromUtf8(dec->decoder()->id) == "pwm_waveform" // Особая высота относится только к кривым восстановления ШИМ. + const bool waveform = (QString::fromUtf8(dec->decoder()->id) == "pwm_waveform" || QString::fromUtf8(dec->decoder()->id) == "pwm_motor") // Особая высота относится только к кривым восстановления ШИМ. && _row.row() && QString::fromUtf8(_row.row()->id).startsWith("wave_"); // Числовые duty/frequency строки сохраняют собственное место. const bool phase_wave = waveform && QString::fromUtf8(_row.row()->id).size() == 6; // Только wave_a…wave_c относятся к общей области фаз; θ и несущая имеют собственную высоту. if (phase_wave && combined && wave_shown) continue; // Следующая совмещённая основная фаза не увеличивает полную высоту дорожки. @@ -674,7 +683,7 @@ int size = 0; // Текстовый анализатор без графиков не имеет масштабируемой области. const auto rows = _decoder_stack->get_rows_gshow(); // Используем тот же порядок видимых строк, что нативный рендерер. for (auto dec : _decoder_stack->stack()) { // У каждого экземпляра стека собственная настройка совмещения фаз. - if (!dec->shown() || QString::fromUtf8(dec->decoder()->id) != "pwm_waveform") continue; // Скрытые и обычные текстовые декодеры не меняют фиксированную геометрию. + if (!dec->shown() || (QString::fromUtf8(dec->decoder()->id) != "pwm_waveform" && QString::fromUtf8(dec->decoder()->id) != "pwm_motor")) continue; // Скрытые и обычные текстовые декодеры не меняют фиксированную геометрию. bool phase_shown = false; // Совмещённая область A/B/C учитывается отдельно от самостоятельных графиков θ и несущей. for (const auto &entry : rows) { // Неподключённые и скрытые каналы исключаются до выделения высоты. if (entry.first.decoder() != dec->decoder() || !entry.second || !entry.first.row() || !_decoder_stack->has_annotations(entry.first)) continue; // Должны совпасть экземпляр декодера, наличие данных и разрешение показа строки. --- a/DSView/pv/data/decoderstack.cpp +++ b/DSView/pv/data/decoderstack.cpp @@ -187,7 +187,7 @@ const Row row(decc, ann_row, order); // Add a new empty row data object - const bool waveform = QString::fromUtf8(decc->id) == "pwm_waveform" && QString::fromUtf8(ann_row->id).startsWith("wave_"); // Только графические строки PWM требуют многоуровневого индекса. + const bool waveform = (QString::fromUtf8(decc->id) == "pwm_waveform" || QString::fromUtf8(decc->id) == "pwm_motor") && QString::fromUtf8(ann_row->id).startsWith("wave_"); // Только графические строки PWM требуют многоуровневого индекса. _rows[row] = new decode::RowData(waveform); // Индекс создаётся вместе со строкой и очищается при повторном анализе. std::map::const_iterator iter = _rows_gshow.find(row); if (iter == _rows_gshow.end()) { --- a/DSView/pv/data/decode/decoder.cpp +++ b/DSView/pv/data/decode/decoder.cpp @@ -83,7 +83,7 @@ if (_setted) { bool changed = !_committed || _probes != _probes_back || _decode_start != _decode_start_back || _decode_end != _decode_end_back; // Повторный анализ нужен при первом применении, новых входах или изменении границ отсчётов. for (const auto &option : _options_back) { // Диалог повторно передаёт и неизменённые параметры, поэтому сравниваем значения, а не факт вызова setter. - if (std::string(_decoder->id) == "pwm_waveform" && option.first == "waveform_layout") continue; // Наложение фаз меняет только отрисовку уже рассчитанных кривых. + if ((std::string(_decoder->id) == "pwm_waveform" || std::string(_decoder->id) == "pwm_motor") && option.first == "waveform_layout") continue; // Наложение фаз меняет только отрисовку уже рассчитанных кривых. const auto previous = _options.find(option.first); // Ищем значение, с которым построены сохранённые аннотации. GVariant *value = previous == _options.end() ? nullptr : previous->second; // Отсутствующий ключ означает штатное значение декодера, а не автоматическое изменение. if (!value) for (const GSList *item = _decoder->options; item; item = item->next) { // Сравниваем явно сохранённый параметр с его исходным значением по умолчанию. --- a/DSView/pv/dock/protocoldock.cpp +++ b/DSView/pv/dock/protocoldock.cpp @@ -383,7 +383,7 @@ log(QString("display cache phase %1: %2").arg(phase).arg(ok ? "ok" : "FAIL")); // Журнал различает видимость, неизменённый диалог и раскладку графиков. } // После всех принятий остаются результаты единственного исходного декодирования. auto decoder = stack->stack().front(); // Контрольное изменение реального параметра должно по-прежнему требовать нового анализа. - if (QString::fromUtf8(decoder->decoder()->id) == "pwm_waveform") { // Проверка использует математический параметр сглаживания штатного PWM-декодера. + if ((QString::fromUtf8(decoder->decoder()->id) == "pwm_waveform" || QString::fromUtf8(decoder->decoder()->id) == "pwm_motor")) { // Проверка использует математический параметр сглаживания штатного PWM-декодера. decoder->set_option("smoothing_ms", g_variant_new_double(1.0)); // Отличие на одну миллисекунду меняет вычисляемую кривую, поэтому игнорировать его нельзя. ok = decoder->commit() && ok; // Новое вычислительное значение обязано инвалидировать прежние результаты. decoder->set_option("smoothing_ms", g_variant_new_double(0.0)); // Возвращаем стандартный режим без сглаживания для проверочной записи. @@ -465,8 +465,8 @@ const auto after = _protocol_lay_items.size(); ok = phase % 2 == 0 ? after == before : after == (phase == 1 ? before - 1 : 0); } - } else if (phase < 9) { // Проверяем также новый парсер терминальных UART-посылок ТМС. - const QString id = QStringList({"gate_driver_timing", "set_uart", "set_can", "pm35_uart", "tms_uart"})[phase - 4]; // Индекс отсчитывается после четырёх проверок удаления анализаторов. + } else if (phase < 10) { // Проверяем штатное добавление пяти протокольных анализаторов и модели токов двигателя через реальные диалоги DSView. + const QString id = QStringList({"gate_driver_timing", "set_uart", "set_can", "pm35_uart", "tms_uart", "pwm_motor"})[phase - 4]; // Индекс отсчитывается после четырёх проверок удаления анализаторов. if (get_protocol_index_by_id(id) < 0) ok = false; else { QTimer::singleShot(80, this, [report, id]() { @@ -486,7 +486,7 @@ ok = QString(trace->decoder()->stack().back()->decoder()->id) == id; } } - } else if (phase == 9) { // После пяти прикладных анализаторов проверяем удаление всего набора дорожек. + } else if (phase == 10) { // После шести анализаторов, включая фазные токи двигателя, проверяем удаление всего набора дорожек. answer(QMessageBox::Yes); on_del_all_protocol(); ok = _protocol_lay_items.empty() && _session->get_decode_signals().empty(); @@ -1155,7 +1155,7 @@ { SearchComboBox *panel = new SearchComboBox(this); - const QStringList customIds = {"gate_driver_timing", "transistor_pair", "pwm_waveform", "set_uart", "set_can", "pm35_uart", "tms_uart", "plates_pbus", "plates_sbus"}; // При сборке пользовательский раздел включает анализаторы обеих шин 96-контактного интерфейса. + const QStringList customIds = {"gate_driver_timing", "transistor_pair", "pwm_waveform", "pwm_motor", "set_uart", "set_can", "pm35_uart", "tms_uart", "plates_pbus", "plates_sbus"}; // При сборке пользовательский раздел включает анализаторы обеих шин 96-контактного интерфейса. for (bool custom : {true, false}) { for (auto info : _decoderInfoList) { srd_decoder *dec = (srd_decoder *)(info->_data_handle);