Двухтональный тест
Двухтональный тест и точка пересечения третьего порядка
В примере двухтональным тестом измеряется точка пересечения третьего порядка малошумящего усилителя. Модель Engee с блоком Amplifier (IB) запускается несколько раз при разной мощности тонов. Скрипт находит в спектре выхода основные тоны и продукты интермодуляции третьего порядка, строит зависимость их мощности от входной и определяет . Измеренное значение сравнивается с параметром блока. Пример рассчитан на инженеров, которые проверяют линейность приёмного тракта и хотят воспроизвести в модели стандартное лабораторное измерение.
Если на нелинейный усилитель подать два близких тона, на выходе кроме них появятся продукты интермодуляции (IM, intermodulation). Самые опасные из них — продукты третьего порядка на частотах и . Они лежат рядом с полезными тонами, и фильтр их не подавляет.
В примере тоны расположены симметрично относительно несущей: . Комплексная огибающая равна
а продукты третьего порядка попадают на частоты . Мощность каждого тона на Ом.
Для кубической нелинейности амплитуда основного тона на выходе растёт как , а амплитуда продукта IM3 — как . В логарифмическом масштабе это прямые с наклоном и дБ/дБ. Точка их пересечения — точка пересечения третьего порядка, по выходу , по входу .
По одному измерению мощностей основного тона и продукта на выходе:
Формула точна, пока основной тон ещё не сжат.
Схема модели Engee:

Модель TwoToneIP3.engee содержит:
- Блоки From Workspace
In_IиIn_Qи блок Real-Imag to ComplexIQ— формируют комплексную огибающую из переменныхIиQ. - Блок Amplifier (IB)
LNA— модельcubic, линейное усиление дБ, дБм. Отсюда дБм. - Блок Terminator
Out.
Сигналы IQ и LNA записываются в результаты симуляции. Частота дискретизации МГц, длина записи отсчётов, разрешение по частоте Гц. Тоны стоят точно на частотах сетки БПФ (быстрого преобразования Фурье), поэтому спектр получается без растекания и без оконной функции.
Подключим пакеты и определите вспомогательные функции. Функция run_model загружает модель, задаёт шаг и время моделирования, запускает симуляцию и возвращает записанные сигналы.
using Plots, Printf, Statistics, FFTW
gr()
name = "TwoToneIP3"
model_path = joinpath(@__DIR__, name * ".engee")
@assert isfile(model_path) "Не найден файл модели: $model_path"
fs = 1.024e6
Ts = 1 / fs # шаг модели, с
N = 4096 # длина записи
k_tone = 400 # номер отсчёта БПФ для тона
Δf = k_tone * fs / N # отстройка тонов от несущей, Гц
signal(df) = ComplexF64[v isa AbstractArray ? v[1] : v for v in df.value]
sample_times(N) = max.(0.0, (collect(0:N-1) .- 0.5) .* Ts)
function run_model(name, path; nsamples, settings = Pair{String, Vector{Pair{String, String}}}[])
try engee.close(name; force = true) catch end
model = engee.load(path; force = true)
engee.open(name)
for (block, params) in settings
engee.set_param!("$name/$block", params...)
end
engee.set_param!(name, "StopTime" => string((nsamples - 1) * Ts), "FixedStep" => string(Ts))
results = engee.run(model)
signals = Dict(string(k) => signal(collect(results[k])) for k in keys(results))
engee.close(name; force = true)
signals
end
tone_dbm(X, k) = 10log10(abs2(X[mod(k, N) + 1] / N) / 1e-3)
@printf("Отстройка тонов ±%.0f кГц, продукты IM3 на ±%.0f кГц\n", Δf / 1e3, 3Δf / 1e3)
Зададим мощности тонов от до дБм с шагом дБ. Для каждого уровня скрипт формирует двухтональную огибающую, записывает её в переменные I и Q, запускает модель и вычисляет спектр выхода.
pin_levels = collect(-40.0:5.0:-5.0)
n = 0:N-1
spectra = Dict{Float64, Vector{ComplexF64}}()
for pin in pin_levels
A = sqrt(1e-3 * 10^(pin / 10))
x = A .* (cis.(2π * k_tone .* n ./ N) .+ cis.(-2π * k_tone .* n ./ N))
t = sample_times(N)
global I = hcat(t, real.(x))
global Q = hcat(t, imag.(x))
res = run_model(name, model_path; nsamples = N)
@assert length(res["LNA.1"]) == N
@assert maximum(abs.(res["IQ.u"] .- x)) < 1e-12 "Вход модели не совпал с заданным сигналом"
spectra[pin] = fft(res["LNA.1"])
@printf("Pвх = %6.1f дБм на тон: модель выполнена\n", pin)
end
Построим спектр выхода усилителя при мощности дБм на тон. Кроме основных тонов на кГц в спектре должны появиться продукты третьего порядка на кГц.
pin_show = -10.0
X = spectra[pin_show]
freqs_khz = fftshift(fftfreq(N, fs)) ./ 1e3
psd = 10 .* log10.(max.(abs2.(fftshift(X) ./ N), 1e-30) ./ 1e-3)
pspec = plot(freqs_khz, psd; lw = 1.5, color = :royalblue, label = "выход LNA",
xlabel = "Отстройка от несущей, кГц", ylabel = "Мощность, дБм",
title = "Спектр выхода при Pвх = $(Int(pin_show)) дБм на тон",
ylims = (-160, 20), size = (1000, 400), left_margin = 5Plots.mm, bottom_margin = 5Plots.mm)
for (k, lbl) in ((k_tone, "основной тон"), (3k_tone, "IM3"))
for s in (1, -1)
p = tone_dbm(X, s * k)
annotate!(pspec, s * k * fs / N / 1e3, p + 8, text(@sprintf("%s\n%.1f дБм", lbl, p), 8))
end
end
pspec
В спектре выхода четыре составляющие. Основные тоны на кГц усилены почти на дБ: дБм вместо дБм, недостающая дБ — начало сжатия. На кГц появились продукты IM3 мощностью дБм, то есть на дБ ниже основных тонов.
Других составляющих нет. Кубическая модель порождает только продукты третьего порядка, а тоны стоят точно на сетке БПФ, поэтому уровень ниже дБм определяется только точностью вычислений.
Для каждого уровня найдите мощность основного тона и продукта IM3 на выходе и вычислите по формуле из введения. Мощности на положительной и отрицательной частоте равны, поэтому используется положительная.
p_fund = [tone_dbm(spectra[p], k_tone) for p in pin_levels]
p_im3 = [tone_dbm(spectra[p], 3k_tone) for p in pin_levels]
oip3_est = p_fund .+ (p_fund .- p_im3) ./ 2
println(rpad("Pвх, дБм", 11), rpad("P1 вых, дБм", 14), rpad("P3 вых, дБм", 14), "OIP3, дБм")
for (p, f, i, o) in zip(pin_levels, p_fund, p_im3, oip3_est)
println(rpad(@sprintf("%.1f", p), 11), rpad(@sprintf("%.2f", f), 14), rpad(@sprintf("%.2f", i), 14), @sprintf("%.2f", o))
end
# Наклоны по трём нижним уровням, где сжатие пренебрежимо мало
sl(y) = (y[3] - y[1]) / (pin_levels[3] - pin_levels[1])
@printf("\nНаклон основного тона: %.3f дБ/дБ, наклон IM3: %.3f дБ/дБ\n", sl(p_fund), sl(p_im3))
oip3_meas = oip3_est[1]
@printf("Измеренная OIP3 = %.2f дБм, параметр блока 30 дБм; IIP3 = %.2f дБм\n", oip3_meas, oip3_meas - 15)
Построим классическую диаграмму точки пересечения. Сплошные линии — результаты моделирования, пунктир — продолжение прямых с наклонами и из области малых сигналов.
pin_ext = collect(-45.0:0.5:25.0)
G = p_fund[1] - pin_levels[1]
line1 = pin_ext .+ G
line3 = p_im3[1] .+ 3 .* (pin_ext .- pin_levels[1])
iip3_meas = oip3_meas - G
pip3 = plot(pin_ext, line1; ls = :dash, color = :royalblue, label = "наклон 1",
xlabel = "Входная мощность на тон, дБм", ylabel = "Выходная мощность на тон, дБм",
title = "Точка пересечения третьего порядка", legend = :bottomright,
ylims = (-160, 50), size = (1000, 450), left_margin = 5Plots.mm, bottom_margin = 5Plots.mm)
plot!(pip3, pin_ext, line3; ls = :dash, color = :orangered, label = "наклон 3")
scatter!(pip3, pin_levels, p_fund; color = :royalblue, ms = 6, label = "основной тон")
scatter!(pip3, pin_levels, p_im3; color = :orangered, ms = 6, label = "IM3")
scatter!(pip3, [iip3_meas], [oip3_meas]; marker = :star5, ms = 11, color = :black,
label = @sprintf("IP3: IIP3 = %.1f дБм, OIP3 = %.1f дБм", iip3_meas, oip3_meas))
pip3
Точки основного тона лежат на прямой с наклоном дБ/дБ, точки IM3 — на прямой с наклоном дБ/дБ. Прямые пересекаются в точке дБм, дБм. Это совпадает с параметром блока.
Таблица показывает и ограничение метода. При мощности тонов от до дБм оценка по одному измерению даёт ровно дБм. При дБм оценка снижается до дБм, потому что основной тон уже сжат на дБ. Поэтому измеряют на уровнях, где продукты IM3 заметно выше шумов, но основной тон ещё не сжимается.
s = @sprintf("OIP3 measured = %.2f dBm; IIP3 = %.2f dBm", oip3_meas, iip3_meas)
println(s)
s
Вывод
Пример воспроизвёл в Engee стандартное лабораторное измерение линейности усилителя. Модель с блоком Amplifier (IB) запускалась программно в цикле по мощности тонов, а спектральный анализ выполнялся в скрипте.
Результаты совпали с теорией: наклоны характеристик и дБ/дБ, измеренная дБм равна заданной в блоке. Сжатие основного тона на высоких уровнях занижает оценку, и это нужно учитывать при выборе уровня измерения.
Тот же сценарий подходит для проверки любого нелинейного звена: достаточно заменить блок или его параметры и повторить цикл запусков. Важная деталь подготовки данных для From Workspace: метки времени сдвинуты на полшага, иначе ошибки округления сдвигают часть отсчётов и создают ложный уровень продуктов интермодуляции.

