Files
templates/tools/dsview/native/scope_demo.h
T

30 lines
5.0 KiB
C++

#pragma once // Единственное определение переносимой модели при включении в несколько компонентов DSView.
#include <algorithm> // Перестановка границ и сравнение напряжений нужны для точной огибающей.
#include <cmath> // Синус и операции над фазой повторяют идеальный источник осциллографа Zynq.
namespace set_scope { // Модель не зависит от Qt, аппаратного захвата и состояния цифровых каналов.
static const double hz[] = {1000.0, 500.3, 1999.7, 750.2}; // Частоты CH1…CH4 в Гц перенесены из zynq/demo/hdmi_usb_menu/scope_signal.h.
static const double peak[] = {0.80, 0.60, 0.70, 0.60}; // Амплитуды в вольтах взяты из scope_peak_v того же демонстрационного источника Zynq.
static const unsigned rgb[] = {0xf9ce4b, 0x33d2ed, 0x72e08a, 0xdb86ee}; // Цвета RGB888 сохраняют соответствие каналам осциллографа Zynq.
static const double tau = 6.2831853071795864769; // Полный оборот 2π в радианах переводит фазу периода в аргумент синуса.
inline double value(unsigned channel, double seconds) { // Напряжение рассчитывается по абсолютному времени общей записи, выраженному в секундах.
if (channel >= 4 || !std::isfinite(seconds)) return 0; // Неверный канал либо время не должны приводить к чтению за пределами таблиц.
const double cycles = seconds * hz[channel], phase = cycles - std::floor(cycles); // Фаза остаётся в диапазоне 0…1 также для отрицательного времени.
return peak[channel] * ((channel & 1) ? (phase < 0.5 ? 1.0 : -1.0) : std::sin(tau * phase)); // CH1/CH3 дают синус, CH2/CH4 — меандр с заполнением 50%, как после scope_init Zynq.
} // Расчёт не читает АЦП и не выдаёт демонстрационный сигнал за измеренный.
struct Range { double low, high; }; // Минимум и максимум одного временного интервала задаются в вольтах.
inline Range extrema(unsigned channel, double first, double last) { // Аналитическая огибающая сохраняет быстрые колебания при уменьшении масштаба.
if (channel >= 4 || !std::isfinite(first) || !std::isfinite(last)) return {0, 0}; // Повреждённые параметры не поступают в вычисления фазы.
if (first > last) std::swap(first, last); // Обратное направление просмотра не меняет физический диапазон напряжения.
const double a = value(channel, first), b = value(channel, last); // Значения концов учитываются даже у интервала короче одного периода.
Range result = {std::min(a, b), std::max(a, b)}; // Начальный диапазон расширяется только при наличии внутреннего экстремума.
const double x = first * hz[channel], z = last * hz[channel]; // Время переводится в периоды соответствующего канала.
if (channel & 1) { // Для меандра скачок возможен на каждой границе половины периода.
if (std::floor(2 * x) != std::floor(2 * z)) result = {-peak[channel], peak[channel]}; // Наличие фронта включает оба уровня вместо ложной медленной волны.
} else { // Для синуса внутренние экстремумы приходятся на четверть и три четверти периода.
if (std::ceil(x - 0.25) <= std::floor(z - 0.25)) result.high = peak[channel]; // Фаза 1/4 периода задаёт положительный пик в вольтах.
if (std::ceil(x - 0.75) <= std::floor(z - 0.75)) result.low = -peak[channel]; // Фаза 3/4 периода задаёт отрицательный пик в вольтах.
} // Для длинного интервала расчёт остаётся постоянным по времени и не перебирает все периоды.
return result; // Оба экстремума используются при рисовании одного экранного пикселя.
} // Модель повторяет scope_value_v и scope_interval_extrema исходного проекта Zynq.
} // Пространство имён исключает совпадения с функциями аппаратного осциллографа DSView.