Files
templates/tools/dsview/pwm-waveform.patch
T

280 lines
28 KiB
Diff

--- a/DSView/pv/view/decodetrace.cpp
+++ b/DSView/pv/view/decodetrace.cpp
@@ -33,6 +33,7 @@
#include <QScrollArea>
#include <QApplication>
#include "decodetrace.h"
+#include "pwm_waveform.h" // Индекс экстремумов сохраняет форму PWM при любом масштабе времени.
#include "../sigsession.h"
#include "../data/decoderstack.h"
#include "../data/decode/decoder.h"
@@ -212,6 +213,14 @@
}
}
+static bool pwm_combined(pv::data::decode::Decoder *decoder) // Режим берётся из штатных настроек конкретного экземпляра декодера.
+{ // Старые записи без новой опции должны открываться с прежними отдельными графиками.
+ if (QString::fromUtf8(decoder->decoder()->id) != "pwm_waveform") 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 включает наложение цветных кривых.
+} // Отсутствующее или неизвестное значение безопасно оставляет режим split.
+
void DecodeTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColor back)
{
using namespace pv::data::decode;
@@ -261,6 +270,9 @@
for(auto dec :_decoder_stack->stack()) {
if (dec->shown()) {
+ const bool combined = pwm_combined(dec); // Каждый экземпляр PWM может независимо отображаться совместно или раздельно.
+ int combined_y = y; // Общая область начинается на месте первой видимой кривой этого экземпляра.
+ bool wave_shown = false; // Скрытые и ещё не заполненные строки не должны резервировать высоту общего графика.
const std::map<const pv::data::decode::Row, bool> rows = _decoder_stack->get_rows_gshow();
for (std::map<const pv::data::decode::Row, bool>::const_iterator i = rows.begin();
i != rows.end(); i++) {
@@ -268,6 +280,70 @@
_decoder_stack->has_annotations((*i).first)) {
if ((*i).second) {
const Row &row = (*i).first;
+ // Для обзорного масштаба берём индексированные экстремумы, сохраняя кривую и короткие выбросы.
+ if (QString::fromUtf8(dec->decoder()->id) == "pwm_waveform" // Специальная геометрия применяется только к декодеру восстановления ШИМ.
+ && row.row() && QString::fromUtf8(row.row()->id).startsWith("wave_")) { // Числовые строки duty продолжают использовать штатные аннотации.
+ std::vector<set_pwm::Line> points; // Геометрия ограничивается шириной видимой области вместо числа периодов во всей записи.
+ _decoder_stack->get_pwm_waveform(points, row, start_sample, end_sample, std::max(1, right - left)); // Индекс экстремумов сохраняет короткие пики при уменьшении масштаба.
+ const QString row_id = QString::fromUtf8(row.row()->id); // Идентификатор отделяет измеренные фазы от дополнительных расчётных графиков.
+ const bool phase_wave = row_id == "wave_a" || row_id == "wave_b" || row_id == "wave_c"; // θ и несущая не должны совмещаться с фазами даже при общей компоновке A/B/C.
+ const bool row_combined = combined && phase_wave; // Наложение применяется только к основным трём осциллограммам.
+ const bool allocate = !row_combined || !wave_shown; // Каждому дополнительному расчётному графику всегда выделяется собственная область.
+ if (allocate) combined_y = y; // Раздельный режим начинает новую область для каждого канала.
+ const int wave_top = combined_y; // Следующие совмещённые фазы используют ту же вертикальную систему координат.
+ p.save(); // Локальные настройки пера и отсечения не затрагивают остальные строки анализатора.
+ const int wave_y = wave_top + annotation_height; // Нулевая линия располагается в середине собственной либо общей трёхстрочной области PWM.
+ p.setClipRect(QRectF(left, wave_top - annotation_height * 0.5, right - left, annotation_height * 3)); // Совмещённые фазы получают одинаковую область отсечения.
+ p.setPen(QPen(fore, 1, Qt::DotLine)); // Пунктирная линия задаёт уровень заполнения 50 процентов.
+ if (allocate) p.drawLine(QPointF(left, wave_y), QPointF(right, wave_y)); // Общая нулевая линия рисуется один раз и не перекрывает уже выведенные фазы.
+ p.setRenderHint(QPainter::Antialiasing, true); // Сглаживание применяется уже к ограниченному числу отрезков.
+ QColor curve_colour = fore; // Общие θ и несущая сохраняют нейтральное оформление, поскольку не относятся к отдельной фазе.
+ if (phase_wave) { // Цвет фазной кривой определяется назначенным входом A, B или C, а не порядком физических каналов.
+ const QString input_id = row_id.right(1) + (QString::fromUtf8(dec->decoder()->id) == "pwm_motor" ? QStringLiteral("_p") : QString()); // PWM waveform использует входы a/b/c, а модель токов двигателя — a_p/b_p/c_p; оба варианта связывают фазу с её основной верхней командой.
+ for (const auto *probe : dec->binded_probe_list()) { // Рассматриваем реальные назначения входов из сохранённых настроек анализатора.
+ if (QString::fromUtf8(probe->id) != input_id) continue; // Дополнительные затворы NPC и входы других фаз не задают цвет текущей кривой.
+ const int physical_index = dec->binded_probe_index(probe); // Индекс физического канала учитывает произвольное назначение A/B/C на входы прибора.
+ for (auto *signal : _session->get_signals()) { // Читаем актуальные цвета цифровых дорожек при каждой перерисовке, без повторного декодирования.
+ if (signal->signal_type() == SR_CHANNEL_LOGIC && signal->get_index() == physical_index) { // Аналоговые сигналы с тем же номером не являются исходным цифровым входом фазы.
+ curve_colour = signal->get_colour().isValid() ? signal->get_colour() : fore; // Повторяем правило цифровой дорожки: явный цвет входа либо цвет текста темы, если пользовательский цвет ещё не задан.
+ break; // Назначению соответствует только одна цифровая дорожка.
+ } // При отсутствии входной дорожки остаётся безопасный нейтральный цвет.
+ } // Изменения цвета входа сразу отражаются на обеих связанных дорожках.
+ break; // Другие назначенные входы больше не влияют на найденную фазу.
+ } // Поиск выполняется один раз на строку графика, а не на каждую аннотацию или отсчёт.
+ } // Режимы огибающей, коммутаций и совмещённых фаз используют одно правило цвета.
+ p.setPen(QPen(curve_colour, phase_wave ? 1.6 : 1.0)); // Толщина фазных и общих расчётных графиков сохраняется; меняется только согласование цвета с входом.
+ const auto topology = dec->options().find("input_mode"); // Схема NPC требует двух смещённых треугольников вместо одного двухуровневого.
+ const bool npc_carrier = row_id == "wave_carrier" && topology != dec->options().end() && topology->second && g_variant_is_of_type(topology->second, G_VARIANT_TYPE_STRING) && QString::fromUtf8(g_variant_get_string(topology->second, nullptr)) == "npc-three-level"; // Старые записи и обычный ШИМ сохраняют один треугольник −1…+1.
+ QVector<QLineF> lines; // Qt получает готовую геометрию одним пакетным вызовом.
+ lines.reserve(int(points.size())); // Единственное выделение памяти исключает рост буфера при заполнении.
+ for (const auto &point : points) { // В плотном масштабе здесь не более двух отрезков на экранный пиксель.
+ const double x1 = point.start / samples_per_pixel - pixels_offset, x2 = point.end / samples_per_pixel - pixels_offset; // Обе смещённые несущие используют одинаковые времена начала и конца сегмента.
+ const double a = npc_carrier ? (point.a + 1) / 2 : point.a, b = npc_carrier ? (point.b + 1) / 2 : point.b; // Верхний треугольник NPC занимает диапазон 0…+1, остальные кривые сохраняют исходную нормировку.
+ lines.append(QLineF(x1, wave_y - a * annotation_height * 1.20, x2, wave_y - b * annotation_height * 1.20)); // Основная геометрия использует прежнюю общую шкалу времени и высоту графика.
+ if (npc_carrier) lines.append(QLineF(x1, wave_y - (a - 1) * annotation_height * 1.20, x2, wave_y - (b - 1) * annotation_height * 1.20)); // Нижний синфазный треугольник NPC смещён на единицу в диапазон −1…0.
+ } // Полные исходные сегменты автоматически возвращаются при увеличении масштаба.
+ p.drawLines(lines); // Пакетная отрисовка не создаёт отдельный вызов QPainter на каждый период длинной записи.
+ p.restore(); // Цвет, сглаживание и отсечение не переходят на числовые строки.
+ if (allocate) { // Число заголовков и вертикальный шаг должны совпадать с rows_size().
+ y += annotation_height * 3; // Область PWM занимает три высоты строки независимо от количества совмещённых фаз.
+ QString title = row.title(); // Раздельный график сохраняет прежнее название своего канала.
+ if (row_combined) { // Общая подпись перечисляет только основные видимые фазы, сохраняя собственные шкалы θ и несущей.
+ QStringList channels; // Список нужен для записи с одним, двумя или тремя видимыми входами.
+ for (const auto &entry : rows) { // Порядок совпадает с порядком строк и цветами A, B, C.
+ if (entry.first.decoder() == dec->decoder() && entry.second && entry.first.row() // Не включаем скрытые строки и строки других экземпляров стека.
+ && 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"; // Общая шкала остаётся нормированной для всех видимых каналов.
+ } // Значения PWM и числовые результаты не изменяются при переключении вида.
+ _cur_row_headings.push_back(title); // Единственный заголовок относится ко всей выделенной области.
+ _cur_row_headings.push_back(QString()); // Вторая строка резервирует место для верхней и нижней полуволн.
+ _cur_row_headings.push_back(QString()); // Третья строка завершает прежнюю высоту области PWM.
+ } // Повторная совмещённая фаза не сдвигает последующие аннотации.
+ wave_shown = wave_shown || phase_wave; // Отдельный θ или треугольник не заменяет первую видимую фазу общей области.
+ continue; // Числовой рендерер не должен рисовать служебный контракт PWM1 поверх кривой.
+ } // Остальные строки проходят штатную отрисовку текстовых аннотаций.
const uint64_t min_annotation =
_decoder_stack->get_min_annotation(row);
@@ -328,6 +391,7 @@
double samples_per_pixel, double pixels_offset, int y,
size_t base_colour, double min_annWidth, QColor fore, QColor back, double &last_x)
{
+ h = std::min(h, p.fontMetrics().height() + 2); // Ограничиваем фон высотой шрифта плюс два экранных пикселя: по одному сверху и снизу, независимо от растягивания дорожки.
const double start = max(a.start_sample() / samples_per_pixel -
pixels_offset, (double)left);
const double end = min(a.end_sample() / samples_per_pixel -
@@ -579,13 +643,22 @@
for(auto dec : _decoder_stack->stack()) {
if (dec->shown()) {
auto rows = _decoder_stack->get_rows_gshow();
+ const bool combined = pwm_combined(dec); // Раскладка использует ту же настройку, что и отрисовка кривых.
+ bool wave_shown = false; // Общая область учитывается один раз среди фактически видимых строк.
for (auto i = rows.begin(); i != rows.end(); i++) {
pv::data::decode::Row _row = (*i).first;
if (_row.decoder() == dec->decoder() &&
_decoder_stack->has_annotations((*i).first) &&
(*i).second)
- size++;
+ { // Учитываем только заполненные строки, разрешённые в настройках Decoders.
+ const bool waveform = QString::fromUtf8(dec->decoder()->id) == "pwm_waveform" // Особая высота относится только к кривым восстановления ШИМ.
+ && _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; // Следующая совмещённая основная фаза не увеличивает полную высоту дорожки.
+ size += waveform ? 3 : 1; // Кривая занимает три строки, текстовая аннотация — одну.
+ wave_shown = wave_shown || phase_wave; // Дополнительные кривые не влияют на учёт первой основной фазы.
+ } // Число строк совпадает с шагом y и количеством заголовков в paint_mid().
}
}
else {
--- a/DSView/pv/dock/protocoldock.cpp
+++ b/DSView/pv/dock/protocoldock.cpp
@@ -1060,7 +1060,7 @@
{
SearchComboBox *panel = new SearchComboBox(this);
- const QStringList customIds = {"gate_driver_timing", "transistor_pair", "set_uart", "set_can", "pm35_uart", "tms_uart", "plates_pbus", "plates_sbus"}; // При сборке пользовательский раздел включает анализаторы обеих шин 96-контактного интерфейса.
+ const QStringList customIds = {"gate_driver_timing", "transistor_pair", "pwm_waveform", "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);
--- a/DSView/pv/data/decode/rowdata.h
+++ b/DSView/pv/data/decode/rowdata.h
@@ -27,6 +27,7 @@
#include <mutex>
#include "annotation.h"
+#include "../../view/pwm_waveform.h" // Индекс PWM хранится рядом с исходными аннотациями и использует ту же блокировку.
namespace pv {
namespace data {
@@ -35,7 +36,7 @@
class RowData
{
public:
- RowData();
+ RowData(bool pwm_waveform = false); // Только строки wave_a…wave_c создают индекс; остальные декодеры сохраняют прежнее поведение.
~RowData();
public:
@@ -60,6 +61,8 @@
void get_annotation_subset(std::vector<pv::data::decode::Annotation*> &dest,
uint64_t start_sample, uint64_t end_sample);
+ void get_pwm_waveform(std::vector<set_pwm::Line> &dest, uint64_t start, uint64_t end, size_t pixels); // Возвращаем ограниченную шириной окна геометрию с обоими экстремумами.
+
void clear();
private:
@@ -68,6 +71,8 @@
uint64_t _item_count;
std::vector<Annotation*> _annotations;
static std::mutex _global_visitor_mutex;
+ bool _pwm_waveform; // Признак кривой исключает разбор текстов обычных протокольных аннотаций.
+ set_pwm::Envelope _pwm_envelope; // Полные сегменты и пирамида экстремумов принадлежат текущей строке.
};
}
--- a/DSView/pv/data/decode/rowdata.cpp
+++ b/DSView/pv/data/decode/rowdata.cpp
@@ -35,11 +35,12 @@
std::mutex RowData::_global_visitor_mutex;
-RowData::RowData() :
+RowData::RowData(bool pwm_waveform) : // Тип строки задаётся один раз при создании таблицы декодера.
_max_annotation(0),
_min_annotation(0)
{
_item_count = 0;
+ _pwm_waveform = pwm_waveform; // Индекс нужен только графическим строкам декодера PWM waveform.
}
RowData::~RowData()
@@ -56,6 +57,7 @@
delete p;
}
_annotations.clear();
+ _pwm_envelope.clear(); // Новое декодирование не должно использовать экстремумы предыдущего захвата.
_item_count = 0;
_min_annotation = 0;
}
@@ -96,6 +98,12 @@
}
}
+void RowData::get_pwm_waveform(std::vector<set_pwm::Line> &dest, uint64_t start, uint64_t end, size_t pixels) // Геометрия строится без полного перебора списка аннотаций.
+{ // Чтение индекса синхронизируется с поступлением сегментов рабочего потока.
+ std::lock_guard<std::mutex> lock(_global_visitor_mutex); // Та же блокировка защищает исходные аннотации и их индекс.
+ _pwm_envelope.lines(dest, start, end, pixels); // Сложность запроса зависит от экранной ширины, а не от длительности захвата.
+} // После возврата интерфейс рисует собственную копию геометрии без блокировки декодирования.
+
uint64_t RowData::get_annotation_index(uint64_t start_sample)
{
std::lock_guard<std::mutex> lock(_global_visitor_mutex);
@@ -118,6 +126,11 @@
try {
_annotations.push_back(a);
+ if (_pwm_waveform && !a->annotations().empty()) { // Только известный контракт PWM1 содержит нормированные концы кривой.
+ double first, last; // Значения разбираются один раз при поступлении вместо каждого кадра интерфейса.
+ if (set_pwm::segment(a->annotations().back(), first, last)) // Проверка контракта исключает повреждённые и нечисловые значения.
+ _pwm_envelope.append(a->start_sample(), a->end_sample(), first, last); // Пирамида экстремумов обновляется постепенно в рабочем потоке.
+ } // Таблица и экспорт по-прежнему получают каждую исходную аннотацию.
_item_count = _annotations.size();
_max_annotation = max(_max_annotation, a->end_sample() - a->start_sample());
--- a/DSView/pv/data/decoderstack.h
+++ b/DSView/pv/data/decoderstack.h
@@ -30,6 +30,7 @@
#include <QString>
#include <mutex>
+#include "../view/pwm_waveform.h" // Общий формат ограниченной геометрии связывает строку данных с нативным представлением.
#include "decode/row.h"
#include "../data/signaldata.h"
#include "decode/decoderstatus.h"
@@ -113,6 +114,8 @@
const decode::Row &row, uint64_t start_sample,
uint64_t end_sample);
+ void get_pwm_waveform(std::vector<set_pwm::Line> &dest, const decode::Row &row, uint64_t start, uint64_t end, size_t pixels); // Запрос видимой кривой не копирует миллионы исходных аннотаций.
+
uint64_t get_annotation_index(
const decode::Row &row, uint64_t start_sample);
uint64_t get_max_annotation(const decode::Row &row);
--- a/DSView/pv/data/decoderstack.cpp
+++ b/DSView/pv/data/decoderstack.cpp
@@ -188,7 +188,8 @@
const Row row(decc, ann_row, order);
// Add a new empty row data object
- _rows[row] = new decode::RowData();
+ const bool waveform = QString::fromUtf8(decc->id) == "pwm_waveform" && QString::fromUtf8(ann_row->id).startsWith("wave_"); // Только графические строки PWM требуют многоуровневого индекса.
+ _rows[row] = new decode::RowData(waveform); // Индекс создаётся вместе со строкой и очищается при повторном анализе.
std::map<const decode::Row, bool>::const_iterator iter = _rows_gshow.find(row);
if (iter == _rows_gshow.end()) {
_rows_gshow[row] = true;
@@ -227,6 +228,12 @@
start_sample, end_sample);
}
+
+void DecoderStack::get_pwm_waveform(std::vector<set_pwm::Line> &dest, const decode::Row &row, uint64_t start, uint64_t end, size_t pixels) // Передаём запрос только строке выбранного канала PWM.
+{ // Набор строк стабилен во время декодирования и отрисовки.
+ auto iter = _rows.find(row); // Находим данные по существующему ключу декодера и строки.
+ if (iter != _rows.end()) iter->second->get_pwm_waveform(dest, start, end, pixels); // Отсутствующая строка корректно возвращает пустую кривую.
+} // Вектор содержит независимую геометрию без указателей на изменяемые аннотации.
uint64_t DecoderStack::get_annotation_index(
const Row &row, uint64_t start_sample)