Дисбаланс квадратурного демодулятора в приёмнике
Дисбаланс квадратурного демодулятора в приёмнике
В примере моделируется входная часть приёмника прямого преобразования: малошумящий усилитель (МШУ) с собственным шумом и квадратурный демодулятор с дисбалансом амплитуд и фаз каналов I и Q. Модель Engee построена на блоках Amplifier (IB) и Mixer (IB). Скрипт измеряет подавление зеркального канала на тональном сигнале и сравнивает его с теорией, затем пропускает через тракт сигнал 16-QAM и оценивает искажение созвездия по модулю вектора ошибки. В конце дисбаланс компенсируется по статистике принятого сигнала. Пример полезен разработчикам приёмников и цифровой обработки, которые задают допуски на квадратурный тракт.
Квадратурный демодулятор переносит радиосигнал на нулевую частоту двумя смесителями, гетеродины которых сдвинуты на . На практике плечи I и Q различаются по коэффициенту передачи на дБ и по фазе на градусов. Комплексная огибающая на выходе тогда равна
где — комплексно-сопряжённый входной сигнал. Составляющая — зеркальная помеха: тон на частоте даёт на выходе копию на частоте .
Качество квадратурного тракта характеризует коэффициент подавления зеркального канала (IRR, Image Rejection Ratio):
Для сигнала с цифровой модуляцией зеркальная помеха действует как добавочный шум. Её вклад в модуль вектора ошибки (EVM, Error Vector Magnitude) оценивается как .

Модель IQImbalance.engee содержит:
- Блоки From Workspace
In_IиIn_Qи блок Real-Imag to ComplexIQ— формируют комплексную огибающую принятого сигнала из переменныхIиQ. - Блок Amplifier (IB)
LNA— линейный МШУ с усилением дБ и коэффициентом шума дБ. Шум включён, начальное значение генератора фиксировано, поэтому результаты воспроизводимы. - Блок Mixer (IB)
IQDemod— режимiqdemod, частоты несущей и гетеродина ГГц. Параметры IQ gain imbalance и IQ phase imbalance скрипт задаёт перед каждым запуском. - Блок Terminator
Out.
Записываются сигналы IQ, LNA и IQDemod. Частота дискретизации МГц.
Подключим пакеты и определите вспомогательные функции. Функция run_model загружает модель, применяет параметры блоков из списка settings, запускает симуляцию и возвращает записанные сигналы. Функция set_input! записывает огибающую в переменные рабочей области.
using Plots, Printf, Statistics, Random, FFTW
gr()
name = "IQImbalance"
model_path = joinpath(@__DIR__, name * ".engee")
@assert isfile(model_path) "Не найден файл модели: $model_path"
fs = 1.024e6
Ts = 1 / fs # шаг модели, с
N = 4096 # длина записи
G_lna = 20.0 # усиление МШУ, дБ
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 set_input!(x)
t = sample_times(length(x))
global I = hcat(t, real.(x))
global Q = hcat(t, imag.(x))
end
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
imbalance(g_db, φ_deg) = ["IQDemod" => ["GainImbalance" => string(g_db), "PhaseImbalance" => string(φ_deg)]]
irr_theory(g_db, φ_deg) = (g = 10^(g_db / 20); c = cosd(φ_deg);
10log10((1 + 2g * c + g^2) / (1 - 2g * c + g^2)))
dbm(p) = 10log10(p / 1e-3)
println("Модель: ", model_path)
Подадим на вход нулевой сигнал и измерьте мощность шума на выходе при идеальном демодуляторе. Пересчёт ко входу делением на усиление даёт шум, который добавляет МШУ. Сравним его с теоретической мощностью , где — постоянная Больцмана, К, .
set_input!(zeros(ComplexF64, N))
res0 = run_model(name, model_path; nsamples = N, settings = imbalance(0, 0))
p_noise_out = mean(abs2, res0["IQDemod.1"])
p_noise_in = p_noise_out / 10^(G_lna / 10)
k_B, T0, F = 1.380649e-23, 290.0, 10^(3 / 10)
@printf("Шум на выходе: %.2f дБм, приведённый ко входу: %.2f дБм\n", dbm(p_noise_out), dbm(p_noise_in))
@printf("Теория kT0(F-1)fs = %.2f дБм, kT0·F·fs = %.2f дБм\n", dbm(k_B * T0 * (F - 1) * fs), dbm(k_B * T0 * F * fs))
Шум, добавленный МШУ, равен дБм при теоретическом значении дБм. Отличие меньше дБ.
Значит, блок Amplifier (IB) добавляет только собственный шум усилителя в полосе, равной частоте дискретизации модели. Тепловой шум источника в модели отсутствует: если он нужен, его добавляют во входной сигнал отдельно.
Подадим тон мощностью дБм на частоте кГц и запустите модель для нескольких сочетаний дисбаланса по амплитуде дБ и по фазе . Для каждого запуска IRR равен разности мощностей тона на кГц и его зеркальной копии на кГц.
k_tone = 400
n = 0:N-1
x_tone = sqrt(1e-3 * 10^(-60 / 10)) .* cis.(2π * k_tone .* n ./ N)
set_input!(x_tone)
line_dbm(X, k) = dbm(abs2(X[mod(k, N) + 1] / N))
irr_meas = Dict{Tuple{Float64, Float64}, Float64}()
spectrum_example = ComplexF64[]
for g_db in (0.0, 1.0, 2.0), φ_deg in (0.0, 2.0, 5.0, 10.0)
(g_db == 0 && φ_deg == 0) && continue
res = run_model(name, model_path; nsamples = N, settings = imbalance(g_db, φ_deg))
X = fft(res["IQDemod.1"])
irr_meas[(g_db, φ_deg)] = line_dbm(X, k_tone) - line_dbm(X, -k_tone)
(g_db, φ_deg) == (1.0, 5.0) && (global spectrum_example = X)
@printf("ε = %.0f дБ, φ = %4.1f°: IRR модель %6.2f дБ, теория %6.2f дБ\n",
g_db, φ_deg, irr_meas[(g_db, φ_deg)], irr_theory(g_db, φ_deg))
end
Построим спектр выхода демодулятора при дБ и и сравните измеренные значения IRR с теоретическими кривыми.
freqs_khz = fftshift(fftfreq(N, fs)) ./ 1e3
psd = dbm.(max.(abs2.(fftshift(spectrum_example) ./ N), 1e-30))
pa = plot(freqs_khz, psd; color = :royalblue, lw = 1, label = "",
xlabel = "Частота, кГц", ylabel = "Мощность, дБм", ylims = (-140, -20),
title = "Спектр при ε = 1 дБ, φ = 5°")
for (k, lbl) in ((k_tone, "сигнал"), (-k_tone, "зеркало"))
annotate!(pa, k * fs / N / 1e3, line_dbm(spectrum_example, k) + 8,
text(@sprintf("%s %.1f дБм", lbl, line_dbm(spectrum_example, k)), 8))
end
φs = range(0.3, 12; length = 200)
colors = Dict(0.0 => :royalblue, 1.0 => :darkorange, 2.0 => :seagreen)
pb = plot(xlabel = "Фазовый дисбаланс φ, град", ylabel = "IRR, дБ", title = "Подавление зеркального канала",
ylims = (10, 55), legend = :topright)
for g_db in (0.0, 1.0, 2.0)
plot!(pb, φs, irr_theory.(g_db, φs); color = colors[g_db], lw = 2, label = "теория, ε = $(Int(g_db)) дБ")
pts = sort([(φ, v) for ((g, φ), v) in irr_meas if g == g_db])
scatter!(pb, first.(pts), last.(pts); color = colors[g_db], ms = 6, label = "")
end
plot(pa, pb; layout = (1, 2), size = (1050, 420), left_margin = 5Plots.mm, bottom_margin = 5Plots.mm)
В спектре виден тон дБм, то есть дБм плюс дБ усиления МШУ, и его зеркальная копия дБм на частоте кГц: дБ. Остальные отсчёты — шум МШУ, около дБм на отсчёт БПФ.
Измеренные значения IRR для всех одиннадцати сочетаний совпали с формулой с точностью дБ. Графики показывают, какой дисбаланс опаснее. Фазовый дисбаланс без амплитудного даёт дБ. Амплитудный дисбаланс дБ сам по себе ограничивает IRR уровнем дБ, и при нём точная подстройка фазы мало что даёт.
Сформируем символов 16-QAM со средней мощностью дБм, по одному отсчёту на символ. Запустим модель дважды: с идеальным демодулятором и с дисбалансом дБ, . EVM вычисляется после выравнивания комплексного коэффициента передачи тракта методом наименьших квадратов:
Random.seed!(7)
levels = [-3, -1, 1, 3]
s = complex.(rand(levels, N), rand(levels, N)) ./ sqrt(10) # средняя мощность 1
p_in_qam = -85.0
x_qam = sqrt(1e-3 * 10^(p_in_qam / 10)) .* s
set_input!(x_qam)
function evm(y, s)
h = sum(conj.(s) .* y) / sum(abs2, s)
sqrt(sum(abs2, y ./ h .- s) / sum(abs2, s)), y ./ h
end
res_ideal = run_model(name, model_path; nsamples = N, settings = imbalance(0, 0))
res_imb = run_model(name, model_path; nsamples = N, settings = imbalance(1, 5))
evm_ideal, z_ideal = evm(res_ideal["IQDemod.1"], s)
evm_imb, z_imb = evm(res_imb["IQDemod.1"], s)
snr_expected = p_in_qam - dbm(p_noise_in)
evm_image = 10^(-irr_theory(1, 5) / 20)
@printf("Ожидаемое ОСШ на входе: %.1f дБ, EVM от шума %.2f %%\n", snr_expected, 100 * 10^(-snr_expected / 20))
@printf("EVM, идеальный демодулятор: %.2f %%\n", 100evm_ideal)
@printf("EVM, дисбаланс 1 дБ / 5°: %.2f %% (оценка sqrt(шум² + 1/IRR) = %.2f %%)\n",
100evm_imb, 100 * sqrt(evm_ideal^2 + evm_image^2))
Дисбаланс можно оценить по самому принятому сигналу. Для сигнала QAM составляющие I и Q в идеале некоррелированы и имеют одинаковую мощность. Нормируйте I и Q по мощности, измерьте их корреляцию и уберите из Q составляющую, пропорциональную I (процедура Грама — Шмидта):
Такая оценка не требует пилотных символов.
function iq_compensate(y)
I, Q = real.(y), imag.(y)
In, Qn = I ./ std(I), Q ./ std(Q)
ρ = mean(In .* Qn)
Qc = (Qn .- ρ .* In) ./ sqrt(1 - ρ^2)
complex.(In, Qc), ρ
end
y_comp, ρ = iq_compensate(res_imb["IQDemod.1"])
evm_comp, z_comp = evm(y_comp, s)
@printf("Оценка: σQ/σI = %.3f дБ, ρ = %.4f (фаза ≈ %.2f°)\n",
20log10(std(imag.(res_imb["IQDemod.1"])) / std(real.(res_imb["IQDemod.1"]))), ρ, rad2deg(asin(abs(ρ))))
@printf("EVM после компенсации: %.2f %%\n", 100evm_comp)
Построим созвездия для трёх случаев. Красные крестики — идеальные точки 16-QAM.
ideal_pts = [complex(a, b) / sqrt(10) for a in levels for b in levels]
function constellation(z, title_text)
p = scatter(real.(z), imag.(z); ms = 1.5, alpha = 0.35, color = :royalblue, markerstrokewidth = 0,
label = "", aspect_ratio = 1, xlims = (-1.5, 1.5), ylims = (-1.5, 1.5),
xlabel = "I", ylabel = "Q", title = title_text, titlefontsize = 10)
scatter!(p, real.(ideal_pts), imag.(ideal_pts); marker = :x, ms = 5, color = :red, label = "")
p
end
plot(constellation(z_ideal, @sprintf("Идеальный демодулятор\nEVM = %.2f %%", 100evm_ideal)),
constellation(z_imb, @sprintf("Дисбаланс 1 дБ, 5°\nEVM = %.2f %%", 100evm_imb)),
constellation(z_comp, @sprintf("После компенсации\nEVM = %.2f %%", 100evm_comp));
layout = (1, 3), size = (1050, 380), left_margin = 4Plots.mm, bottom_margin = 5Plots.mm)
С идеальным демодулятором точки созвездия размыты только шумом МШУ: , что соответствует отношению сигнал/шум (ОСШ) дБ.
Дисбаланс дБ и растягивает созвездие по оси I относительно оси Q и скашивает его. EVM вырастает до , что совпадает с оценкой .
Слепая компенсация оценила дисбаланс в дБ и и вернула EVM к , то есть к пределу, который задаёт шум.
string("IQImbalance ok; EVM ideal/imbalance/compensated = ", round(100evm_ideal, digits = 2), "/",
round(100evm_imb, digits = 2), "/", round(100evm_comp, digits = 2), " %")
Вывод
В примере собран приёмный тракт из блоков Amplifier (IB) и Mixer (IB), и каждый эффект проверен количественно.
- Шум МШУ совпал с с точностью до дБ.
- Подавление зеркального канала блока Mixer (IB) в режиме
iqdemodсовпало с теоретической формулой во всех одиннадцати точках. - Для сигнала 16-QAM дисбаланс дБ и увеличил EVM с до , в точном соответствии с оценкой через IRR.
- Слепая компенсация по статистике I и Q вернула EVM к шумовому пределу.
Практический вывод: если для 16-QAM допустима EVM около , а шум уже даёт , квадратурный тракт должен обеспечивать IRR не хуже дБ. Без цифровой компенсации это соответствует дисбалансу порядка дБ и . С компенсацией требования к аналоговой части можно заметно ослабить.

