REMI Lab강의 노트

Wireless Embedded Systems E12

실습 2. I/Q 불균형

I와 Q 두 갈래의 이득·위상 어긋남은 톤의 거울 자리에 이미지를 만들며, 그 크기는 이득비 g와 위상 오차 φ로 정해진다.

이 실습의 자리: Pluto 실습: 송수신기 이해 — 실습 1에서 수신 설정을 바꾸며 톤을 관찰했고, 이 실습은 I와 Q의 불균형이 만드는 이미지를 재며, 다음 실습 3에서 대역폭과 aliasing을 본다.

준비

활동은 셋이다. 톤의 이미지를 관찰하고, 이미지 억압비 함수를 써서 식과 측정 곡선을 비교한 뒤, I만·Q만·복소 신호의 스펙트럼을 비교한다.

그림과 표 옆의 기록에 넣기를 누르고 글 상자에 설명을 쓴 뒤, 도구 막대의 저장을 누른다. 손잡이는 값을 고르는 장치이며, 값을 고른 뒤 받기를 눌러 새로 측정한다. 셀을 처음 실행할 때는 기본값으로 한 번 받는다.

연결 방식, 송신 규칙, 장치에서 알아 둘 것은 실습 1의 준비와 같다. 첫 셀의 MODE만 고른다.

루프백 (MODE = 'loopback') 수신 전용 (MODE = 'receive')
받는 신호 내가 보내는 복소 톤 +200 kHz (RF 2442.2 MHz) 강의실 송신 Pluto가 보내는 목록의 cw: 2442.1 MHz(LO + 100 kHz)
일부러 넣는 불일치 송신 버퍼의 Q에 넣는다 받은 표본의 Q에 Python으로 넣는다
송신 쪽 준비 없음 강의실 송신 Pluto가 cw를 보내는지 확인한다

장치 없이 할 때는 ?device=fake(루프백) 또는 ?device=fake&signal=cw(수신 전용)를 주소에 붙인다. 합성 장치의 이미지는 칩을 흉내 낸 모형의 값이라 Pluto의 측정값으로 옮겨 적지 않는다.

MODE = 'loopback'  # 수신 전용은 'receive'
from sdrlab import pluto, show, dsp
from sdrlab.front_end import ReceiverLab, rc_spectrum
import numpy as np

sdr = await pluto.connect()
await sdr.setup('rx-basic', mode=MODE)
lab = ReceiverLab(sdr, MODE)

활동 1. 복소 톤과 그 이미지

톤 반대편의 작은 선은 어디서 올까? 6장 직교 혼합의 수신기에서 I 갈래와 Q 갈래의 이득이나 LO의 90° 위상이 조금 어긋나면, 받은 표본은 제대로 된 표본 xx와 그 켤레 xˉ\bar x가 섞인 것이 된다.

y=K1x+K2xˉy=K_1x+K_2\bar x

xx가 +f0+f_0의 복소 톤 ej2πf0te^{j2\pi f_0t}이면 xˉ=e−j2πf0t\bar x=e^{-j2\pi f_0t}는 −f0-f_0의 톤이다. 그래서 톤의 거울 자리 −f0-f_0에 크기 ∣K2∣|K_2|인 이미지가 선다. 톤과 이미지의 전력 비를 dB로 적은 것이 이미지 억압비(image rejection ratio, IRR)이다.

IRR=10log⁡10∣K1∣2∣K2∣2 dB\mathrm{IRR}=10\log_{10}\frac{|K_1|^2}{|K_2|^2}\ \mathrm{dB}

이미지를 톤에 견준 dBc로 적으면 −IRR-\mathrm{IRR}이다. 송신 쪽의 직교 변조기가 어긋나도 같은 일이 생긴다. 4장 직교 변조의 Icos⁡−Qsin⁡I\cos-Q\sin에서 II와 QQ가 서로 다른 이득과 위상으로 실리면, RF에서 LO를 사이에 두고 반대편에 이미지가 생긴다.

루프백에서는 송신기와 수신기의 이미지가 같은 자리에 더해진다. 수신 전용에서는 두 Pluto의 LO 차 때문에 두 이미지가 갈라질 수 있다. 표의 두 자리에서 선이 구별될 때만 송신기와 수신기의 몫을 나누어 읽는다. 잡음 바닥에서 솟지 않으면 정확한 이미지 크기를 잰 것이 아니라 그 바닥보다 작다는 것만 안다.

먼저 전체 스펙트럼에서 톤을 찾고, 거울 자리 확대 그림에서 이미지와 잡음 바닥을 비교한다. 수신 전용은 강의실 송신 신호가 cw인지 확인한다.

await show.control(lab.image, capture=True)

이미지가 잡음 바닥과 구별되는가? 수신 전용이면 표의 TX·RX 이미지 자리에서 선이 나뉘어 보이는가?

활동 2. 불일치에서 IRR 계산하기

I와 Q의 불일치를 키우면 이미지도 커질까? 이번에는 불일치를 우리가 정해서 넣는다. 버퍼를 I=cos⁡θI=\cos\theta, Q=gsin⁡(θ+φ)Q=g\sin(\theta+\varphi)로 만든다. sin⁡\sin을 두 복소 지수로 풀면

x=cos⁡θ+jgsin⁡(θ+φ)=1+gejφ2 ejθ+1−ge−jφ2 e−jθ\begin{aligned} x&=\cos\theta+jg\sin(\theta+\varphi)\\ &=\frac{1+ge^{j\varphi}}{2}\,e^{j\theta}+\frac{1-ge^{-j\varphi}}{2}\,e^{-j\theta} \end{aligned}

이므로 K1=(1+gejφ)/2K_1=(1+ge^{j\varphi})/2, K2=(1−ge−jφ)/2K_2=(1-ge^{-j\varphi})/2이고, 크기의 제곱을 풀어 쓰면

IRR=10log⁡101+2gcos⁡φ+g21−2gcos⁡φ+g2 dB\mathrm{IRR}=10\log_{10}\frac{1+2g\cos\varphi+g^2}{1-2g\cos\varphi+g^2}\ \mathrm{dB}

이다. 식은 정방향 회전과 역방향 회전의 전력 비를 계산한다.

쓸 함수 irr_db(gain_ratio, phase_error)

  • 입력: 양의 실수 gain_ratio(Q/I 이득비, 단위 없음), 실수 phase_error(위상 오차, 도)
  • 출력: 이미지 억압비(dB). 불일치가 없으면 np.inf

계산 순서

  1. np.radians(phase_error)로 위상 오차를 라디안으로 바꾼다.
  2. np.cos를 써서 분자의 1+2gcos⁡φ+g21+2g\cos\varphi+g^2와 분모의 1−2gcos⁡φ+g21-2g\cos\varphi+g^2를 구한다.
  3. 분모가 영이면 np.inf를 돌려준다. 그 밖에는 두 값의 비에 10 * np.log10을 적용한다.
def irr_db(gain_ratio, phase_error):
    """I = cos θ, Q = gain_ratio sin(θ + φ)의 IRR (dB), phi의 단위는 도."""
    # TODO: 위의 계산 순서대로 irr_db 함수를 구현하세요.
    raise NotImplementedError('irr_db 함수를 구현하세요.')

확인 셀의 이득비·위상 오차 쌍에서 IRR은 각각 30.46 dB와 17.98 dB이다. 함수와 FFT로 읽은 전력 비의 차가 0.01 dB보다 작으면 맞게 쓴 것이다. 불일치가 없을 때 무한대인지도 확인한다.

tone_frequency = 50  # Hz
sample_rate = 1000  # Hz
phase = 2 * np.pi * tone_frequency * np.arange(1000) / sample_rate
checks = []

for gain_ratio, phase_error in [(1.05, 2), (1.2, 10)]:  # 이득비, 도
    predicted_irr = irr_db(gain_ratio, phase_error)
    z = np.cos(phase) + 1j * gain_ratio * np.sin(phase + np.radians(phase_error))
    spectrum = np.fft.fft(z)
    measured_irr = 20 * np.log10(abs(spectrum[50]) / abs(spectrum[-50]))

    checks.append(abs(predicted_irr - measured_irr) < 0.01)
    print(
        f'g = {gain_ratio}, φ = {phase_error}°: '
        f'식 {predicted_irr:.2f} dB, FFT {measured_irr:.2f} dB'
    )

print('→ 맞음' if all(checks) and np.isinf(irr_db(1, 0)) else '→ 다시 본다')

이제 불일치를 키울 때 식과 측정 곡선이 가까워지는지 본다. 루프백은 송신 버퍼의 Q에 불일치를 넣고 직교 보정을 끈 채 측정한 뒤 다시 켠다. 수신 전용은 받은 표본의 Q를 g(Im ycos⁡φ+Re ysin⁡φ)g(\mathrm{Im}\,y\cos\varphi+\mathrm{Re}\,y\sin\varphi)로 바꾸므로 칩의 보정이 그 뒤의 불일치에 닿지 않는다.

표의 첫 줄은 불일치를 일부러 넣지 않은 장치 자신의 IRR이다. 작은 불일치에서는 장치 이미지와 일부러 넣은 이미지가 복소수로 더해져 커지거나 지워지므로 식보다 크거나 작을 수 있다. 큰 불일치에서는 일부러 넣은 몫이 지배적이다. 먼저 기준 줄을 보고, 이득비와 위상 오차를 차례로 골라 잰 점과 식의 곡선을 비교한다.

async def draw_irr(axis):
    await lab.imbalance(irr_db, axis)

await show.control(
    draw_irr,
    capture=True,
    axis=show.choice(['이득비 g', '위상 오차 φ'], label='바꿀 것'),
)

불일치를 키우면 이미지와 IRR은 각각 어떻게 바뀌는가? 작은 불일치에서 식과 측정이 어긋나는 까닭을 장치 자신의 이미지로 설명한다.

활동 3. I만, Q만, 복소 신호

한 갈래만 남겨도 주파수의 부호를 알 수 있을까? 실수 정현파는 반대 방향으로 도는 성분을 같은 크기로 담는다. I만 보내거나 Q만 보내면 양쪽 선의 크기가 같고, 복소 톤은 두 갈래가 합쳐 한 방향으로 돈다. 케이블의 위상이 곱해진 한 갈래 신호는 I/Q 평면에서 기울어진 선분이 된다. 수신 전용에서 실수부만 꺼내면 I 축, 허수부만 남기면 Q 축에 놓인다.

루프백은 보낼 갈래를 고르고, 수신 전용은 받은 표본에서 남길 갈래를 고른다. 먼저 스펙트럼의 양쪽 선을 보고, 그다음 I/Q 평면의 원과 선분을 비교한다.

await show.control(
    lab.branches,
    capture=True,
    branch=show.choice(['복소', 'I만', 'Q만'], label='보낼 갈래'),
)

한 갈래만 남길 때 양쪽 봉우리의 높이는 어떠한가? I와 Q를 함께 받아야 주파수의 부호를 구별하는 이유는 무엇인가?

송신 끄기

await sdr.tx_stop()
print('송신을 멈췄습니다.' if MODE == 'loopback' else '수신 전용: 송신하지 않았습니다.')

탭을 닫기 전에 실행한다. 도구 막대의 송신 멈춤도 같은 일을 한다.