Усилитель мощности с памятью и расширение спектра
Усилитель мощности с памятью и расширение спектра передатчика
В примере моделируется выходной каскад передатчика: усилитель мощности (УМ) с эффектами памяти и полосовой фильтр. Модель Engee построена на блоках Power Amplifier (IB) и Filter (IB). Через тракт проходит широкополосный многотональный сигнал с высоким пик-фактором, как у OFDM. Скрипт показывает, как нелинейность УМ расширяет спектр в соседние каналы, сравнивает усилитель с памятью и без неё, оценивает коэффициент утечки в соседний канал до и после фильтра. Пример рассчитан на разработчиков передатчиков, которые оценивают требования к линейности УМ и к выходному фильтру.
Усилитель мощности работает вблизи насыщения, поэтому его нелинейность заметна. Для широкополосного сигнала выход зависит не только от текущего отсчёта входа, но и от предыдущих: так проявляются эффекты памяти — тепловые процессы и цепи питания. Блок Power Amplifier (IB) описывает такой усилитель полиномом с памятью, частным случаем ряда Вольтерры:
Строка матрицы коэффициентов отвечает задержке на интервалов Measured interval of PA data, столбец — степени нелинейности.
Нечётные степени нелинейности порождают продукты интермодуляции, которые расширяют спектр сигнала за пределы его полосы. Этот эффект характеризует коэффициент утечки мощности в соседний канал (ACPR, Adjacent Channel Power Ratio):
где — мощность в полосе своего канала, — мощность в полосе соседнего канала.

Модель Memory_ACPR.engee содержит:
- Блоки From Workspace
In_IиIn_Qи блок Real-Imag to ComplexIQ— формируют огибающую из переменныхIиQ. Параметр Interpret 1-D arrays as scalars снят: блок УМ принимает сигнал в виде столбца. - Блок Power Amplifier (IB)
PA— модельMemory Polynomial, матрица размером : три отвода памяти, степени нелинейности до четвёртой. Интервал памяти равен шагу модели. - Блок Filter (IB)
TxFilter— полосовой фильтр Баттерворта 8-го порядка с полосой пропускания кГц, реализацияFrequency (Digital filter), КИХ-фильтр длиной . - Блок Terminator
Out.
Записываются сигналы IQ, PA и TxFilter. Частота дискретизации МГц.
Матрица коэффициентов УМ:
Для сравнения скрипт запускает модель ещё раз с усилителем без памяти. Его матрица из одной строки равна сумме строк , поэтому для медленно меняющегося сигнала оба усилителя дают одинаковый результат.
Подключим пакеты и определите вспомогательные функции. Функция welch_psd оценивает спектральную плотность мощности методом Уэлча с окном Ханна, функция band_power интегрирует её в полосе частот.
using Plots, Printf, Statistics, Random, FFTW
gr()
name = "Memory_ACPR"
model_path = joinpath(@__DIR__, name * ".engee")
@assert isfile(model_path) "Не найден файл модели: $model_path"
fs = 1.024e6
Ts = 1 / fs # шаг модели и интервал памяти УМ, с
N = 8192 # длина записи
signal(df) = ComplexF64[v isa AbstractArray ? v[1] : v for v in df.value]
# Метки времени сдвинуты на полшага раньше, чтобы ошибка округления n*Ts не сдвигала отсчёты
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
function welch_psd(x; nseg = 1024)
w = 0.5 .- 0.5 .* cos.(2π .* (0:nseg-1) ./ nseg)
starts = 1:(nseg ÷ 2):(length(x) - nseg + 1)
P = zeros(nseg)
for s in starts
P .+= abs2.(fft(x[s:s+nseg-1] .* w))
end
P ./= (length(starts) * fs * sum(abs2, w)) # Вт/Гц на 1 Ом
f = fftshift(fftfreq(nseg, fs))
f, fftshift(P)
end
band_power(f, P, f1, f2) = sum(P[(f .>= f1) .& (f .<= f2)]) * (f[2] - f[1])
dbm(p) = 10log10(p / 1e-3)
println("Модель: ", model_path)
Сформируем многотональный сигнал: тон с шагом Гц заполняют полосу кГц, фазы тонов случайны. Такой сигнал имеет плоский спектр и распределение амплитуд, близкое к OFDM. Среднеквадратичное значение огибающей равно В. Скрипт вычисляет пик-фактор PAPR (Peak-to-Average Power Ratio).
Random.seed!(11)
k_band = -800:800
n = 0:N-1
x = zeros(ComplexF64, N)
for k in k_band
x .+= cis.(2π * k .* n ./ N .+ 2π * rand())
end
x .*= 0.3 / sqrt(mean(abs2, x))
papr = 10log10(maximum(abs2, x) / mean(abs2, x))
t = sample_times(N)
I = hcat(t, real.(x))
Q = hcat(t, imag.(x))
@printf("Средняя мощность %.2f дБм, пиковая амплитуда %.3f В, PAPR %.2f дБ\n", dbm(mean(abs2, x)), maximum(abs, x), papr)
Запустим модель дважды: с усилителем с памятью, заданным в модели, и с эквивалентным усилителем без памяти. Для второго запуска скрипт меняет параметр Coefficient matrix блока PA.
C_mem = [1.0+0.0im 0.0 -0.35+0.10im 0.02;
0.15-0.05im 0.0 -0.08+0.03im 0.0;
0.05+0.0im 0.0 -0.02+0.01im 0.0]
C_static = sum(C_mem; dims = 1)
matrix_string(C) = "[" * join([join(string.(C[i, :]), " ") for i in axes(C, 1)], "; ") * "]"
res_mem = run_model(name, model_path; nsamples = N)
res_static = run_model(name, model_path; nsamples = N,
settings = ["PA" => ["CoefficientMatrix" => matrix_string(C_static)]])
@assert maximum(abs.(res_mem["IQ.1"] .- x)) < 1e-12 "Вход модели не совпал с заданным сигналом"
println("Матрица без памяти: ", matrix_string(round.(C_static; digits = 3)))
println("Сигналы: ", join(sort(collect(keys(res_mem))), ", "))
Построим характеристики AM/AM и AM/PM по мгновенным значениям входа и выхода. У усилителя без памяти каждая амплитуда входа даёт одно значение выхода. У усилителя с памятью выход зависит ещё и от предыдущих отсчётов, поэтому точки расходятся в облако.
function am_curves(y, x)
a = abs.(x)
(a, abs.(y), rad2deg.(angle.(y ./ x)))
end
a_s, g_s, ph_s = am_curves(res_static["PA.1"], x)
a_m, g_m, ph_m = am_curves(res_mem["PA.1"], x)
p1 = scatter(a_m, g_m; ms = 1.5, alpha = 0.3, markerstrokewidth = 0, color = :orangered, label = "с памятью",
xlabel = "|x|, В", ylabel = "|y|, В", title = "AM/AM", legend = :topleft)
scatter!(p1, a_s, g_s; ms = 1.5, markerstrokewidth = 0, color = :royalblue, label = "без памяти")
p2 = scatter(a_m, ph_m; ms = 1.5, alpha = 0.3, markerstrokewidth = 0, color = :orangered, label = "с памятью",
xlabel = "|x|, В", ylabel = "Сдвиг фазы, град", title = "AM/PM", legend = :topleft,
ylims = (-40, 40))
scatter!(p2, a_s, ph_s; ms = 1.5, markerstrokewidth = 0, color = :royalblue, label = "без памяти")
plot(p1, p2; layout = (1, 2), size = (1050, 420), left_margin = 5Plots.mm, bottom_margin = 5Plots.mm)
Усилитель без памяти даёт гладкие кривые. Коэффициент передачи сжимается с при малом входе до на пике В. Фаза меняется от , что задаёт мнимая часть линейного коэффициента, до на пиках.
У усилителя с памятью точки расходятся облаком. Разброс особенно велик по фазе при малых амплитудах: там вклад предыдущих отсчётов сравним с вкладом текущего. Такую характеристику нельзя исправить предыскажением без памяти, которое учитывает только текущую амплитуду.
Оценим спектральную плотность мощности входа, выхода УМ в обоих вариантах и выхода фильтра. Первые отсчётов выхода фильтра отбрасываются как переходный процесс.
skip = 257
signals = [("вход", x, :gray),
("УМ без памяти", res_static["PA.1"], :royalblue),
("УМ с памятью", res_mem["PA.1"], :orangered),
("УМ с памятью + фильтр", res_mem["TxFilter.1"], :black)]
psds = Dict(lbl => welch_psd(sig[skip:end]) for (lbl, sig, _) in signals)
pp = plot(xlabel = "Отстройка от несущей, кГц", ylabel = "СПМ, дБм/Гц", title = "Спектр сигнала передатчика",
ylims = (-110, -20), legend = :bottom, size = (1050, 450), left_margin = 5Plots.mm, bottom_margin = 5Plots.mm)
for (lbl, _, col) in signals
f, P = psds[lbl]
plot!(pp, f ./ 1e3, dbm.(max.(P, 1e-30)); lw = lbl == "вход" ? 1 : 1.8, color = col, label = lbl)
end
vspan!(pp, [150, 350]; alpha = 0.08, color = :green, label = "соседние каналы")
vspan!(pp, [-350, -150]; alpha = 0.08, color = :green, label = "")
pp
Рассчитаем ACPR для нижнего и верхнего соседних каналов шириной кГц с центром на кГц, а также нормированную среднеквадратичную ошибку (NMSE) выхода УМ относительно линейно усиленного входа.
function acpr(lbl)
f, P = psds[lbl]
main = band_power(f, P, -100e3, 100e3)
lower = band_power(f, P, -350e3, -150e3)
upper = band_power(f, P, 150e3, 350e3)
dbm(main), 10log10(lower / main), 10log10(upper / main)
end
function nmse(y, x)
h = sum(conj.(x) .* y) / sum(abs2, x)
10log10(sum(abs2, y .- h .* x) / sum(abs2, y))
end
println(rpad("Сигнал", 26), rpad("P осн, дБм", 13), rpad("ACPR ниж, дБ", 15), "ACPR верх, дБ")
acprs = Dict{String, NTuple{3, Float64}}()
for (lbl, _, _) in signals
acprs[lbl] = acpr(lbl)
m, lo, up = acprs[lbl]
println(rpad(lbl, 26), rpad(@sprintf("%.2f", m), 13), rpad(@sprintf("%.2f", lo), 15), @sprintf("%.2f", up))
end
@printf("\nNMSE выхода УМ: без памяти %.2f дБ, с памятью %.2f дБ\n",
nmse(res_static["PA.1"], x), nmse(res_mem["PA.1"], x))
@printf("Ослабление фильтра в основной полосе: %.2f дБ\n",
acprs["УМ с памятью"][1] - acprs["УМ с памятью + фильтр"][1])
Входной сигнал занимает полосу кГц, мощность в соседних каналах на дБ ниже основной. После УМ спектр расширился до кГц: продукты третьего порядка занимают утроенную полосу сигнала. ACPR на выходе УМ составляет от до дБ.
- Память УМ почти не влияет на расширение спектра: у варианта с памятью ACPR даже на – дБ лучше. Зато ошибка всей огибающей NMSE у него хуже: дБ против дБ, потому что к нелинейным искажениям добавляется частотная зависимость линейного усиления.
- Фильтр улучшает ACPR примерно на дБ, до дБ, и почти не ослабляет основной канал: потери менее дБ.
- Край полосы. От до кГц продукты интермодуляции проходят через фильтр, потому что его полоса пропускания шире полосы сигнала. Эту часть расширения спектра фильтр подавить не может, её уменьшают только линеаризацией УМ.
m, lo, up = acprs["УМ с памятью + фильтр"]
string("Memory_ACPR ok; ACPR after filter = ", round(max(lo, up), digits = 2), " dB")
Вывод
Для воспроизведения используем матрицу коэффициентов и входной сигнал в виде столбца. Это настройки данного примера; по ним нельзя делать вывод об общем ограничении размерности матриц блока.

