본문 바로가기

통신이론

동기화(Synchronization)

이번에는 제가 느끼기에 가장 어려운 분야이면서 한편으로 가장 중요한 통신파트를 정리해 보려고 합니다.

동기화라는 말을 많이 듣고 익숙하지만, 정확히 무엇을 의미하고 왜 하는 지 깊이 생각해본 사람은 많지 않을 것 같아요.

저도 그동안 피상적으로만 여기고 있었지만, 이번 기회에 좋은 (참고자료) 예제를 가지고 구체적으로 공부를 해봤어요.

그래서 세가지 동기화 기술(시간, 주파수, 프레임)을 중요한 내용은 과감히 요약하면서 어려운 부분은 부연설명해 보겠습니다!

예제는 앞서 언급한 BPSK 변조를 이어서 계속 사용하며, Mueller & Muller 클럭 복구 기술과 Costas Loop를 활용합니다.

 


 

먼저, 디지털 변조하고 pulse shaping을 적용하여 대역폭을 제한하여 무선으로 전송하고 복조 및 디코드하는 일련의 통신 과정을 간략히 설명하는 블록 다이어그램입니다. (일반적인 이퀄라이제이션 및 멀티플렉싱은 생략)

디지털 통신 블록 선도

여기서 동기화는 복조 및 채널 디코딩 전에 발생하는 일련의 처리로써 노란색으로 강조 표시되어 있습니다. 

우리의 관심사인 동기화를 구현하기 위해서는, 좀 더 실제같은 무선 채널 시뮬레이션이 선행적으로 필요합니다.

즉 이를 위해서 시간 지연 - fractional delay(소수점 지연)을 하며,

samples = np.convolve(samples, np.hamming(N)*np.sinc(n - delay)  # fractional delay = 0.4, in samples

주파수 옵셋을 추가합니다.

samples = samples * np.exp(1j*2*np.pi*fo*t)   # perform freq shift fo(freq offset 13000 Hz)

 

① 시간 동기화

신호를 무선으로 전송하면 이동 시간으로 인해 임의의 위상 편이가 발생하여 수신기에서 (송신기와 같은) 정확한 시점에서 샘플링할 가능성이 낮게 되고 이는 곧 에러가 발생가능성이 높아집니다.
따라서 시간 이동을 통해 디지털 심볼의 정점을 추적하여 샘플링을할 수 있어야하는데, 여기서는 이처럼 시간 동기화를 구현한 Mueller & Muller 클럭 복구 기술을 소개하겠습니다.

먼저 수학적으로 엄밀하게 이해할 수 있다면야 최선이겠지만, 그렇기하기는 너무 힘든 증명과정이었고, 대신 나 나름의 한가지 방안으로써 직관에 기대어 꼼수를 이용해 이해할 수 있도록 설명해 보려 해요!

Mueller & Muller timing-recovery scheme

 

h0 , h1 , h2 은 오름차순 연속 샘플값 이고, 이에 대응하는 h0 , h1 , h2 은 오름차순 연속 심볼값 이라고 하면,

( h1* h0 ) > ( h0* h1 ) : sampling early

( h1* h0 ) = ( h0* h1 ) : Correct sampling

( h1* h0 ) < ( h0* h1 ) : sampling late

따라서 mu = ( h1* h0 ) - ( h0* h1 ) + ( h2* h1 ) - ( h1* h2 ) 두면, mu = y - x 이다.

여기서 x = -( h2 - h0 ) * h1 , y = h2 - h0 ) * h1

샘플값, 심볼값이 복소수이므로 순수 실수(cos) 일때와 허수(sin)일 때 계산을 나누어서 생각해 보면,

conjugation 하고 mu = real(y - x) 하면 맞을 것이다!

그럼 이제,,, 파이썬으로 이를 구현한 소스코드와 결과를 보시죠!

순수 시간동화에 집중하기 위해 delay와 fo는 제외했습니다. 

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import correlate, resample_poly
from scipy.special import erfc


# this part came from pulse shaping exercise
num_symbols = 1000
sps = 8
bits = np.random.randint(0, 2, num_symbols) # Our data to be transmitted, 1's and 0's
pulse_train = np.array([])
for bit in bits:
    pulse = np.zeros(sps)
    pulse[0] = bit*2-1 # set the first value to either a 1 or -1
    pulse_train = np.concatenate((pulse_train, pulse)) # add the 8 samples to the samples
# Create our raised-cosine filter
num_taps = 101  
beta = 0.35
Ts = sps # symbol time, Assume sample rate is 1 Hz, so sample period is 1, so *symbol* period is 8
t = np.arange(-num_taps//2+1, num_taps//2+1) # remember it's not inclusive of final number
h = np.sinc(t/Ts) * np.cos(np.pi*beta*t/Ts) / (1 - (2*beta*t/Ts)**2)
rh = np.sqrt(1) * h/np.sqrt(sum(h**2))
samples = np.convolve(pulse_train, rh)
s = samples[:sps*100]
p = np.arange(num_taps//2, num_symbols*sps + num_taps//2, sps)


# Time Synchronization(no delay, no fo)
fig10, (ax9, ax10, ax11) = plt.subplots(nrows=3, num=10)
fig10.suptitle('Time Synchronization(no delay, no fo)')
i=np.arange(0,len(samples))
ax9.plot(i[:sps*100], pulse_train[:sps*100], '.-')
ax10.plot(i[p[0]:p[0]+sps*100], samples[p[0]:p[0]+sps*100], '.-')
ax10.plot(p[:100], samples[p][:100], 'r.', label='origin bit')
mu = 0 # initial estimate of phase of sample
out = np.zeros(len(samples) + 10, dtype=np.complex64)
out_rail = np.zeros(len(samples) + 10, dtype=np.complex64) # stores values, each iteration we need the previous 2 values plus current value
i_in = 0 # input samples index
i_out = 2 # output index (let first two outputs be 0)
MMi = np.array([], dtype='int64')   # start
MM = np.empty((0,8))   # end
while i_out < len(samples) and i_in+16 < len(samples):
    out[i_out] = samples[i_in + int(mu)] # grab what we think is the "best" sample
    out_rail[i_out] = int(np.real(out[i_out]) > 0) + 1j*int(np.imag(out[i_out]) > 0)
    x = (out_rail[i_out] - out_rail[i_out-2]) * np.conj(out[i_out-1])
    y = (out[i_out] - out[i_out-2]) * np.conj(out_rail[i_out-1])
    mm_val = np.real(y - x)
    MMi = np.append( MMi, i_in + int(mu) )
    mu += sps + 0.4*mm_val
    MM = np.append(MM, [[out[i_out-2], out[i_out-1], out[i_out], out_rail[i_out-2], out_rail[i_out-1], out_rail[i_out], y-x, mu]], axis=0)
    i_in += int(np.floor(mu)) # round down to nearest int since we are using it as an index
    mu = mu - np.floor(mu) # remove the integer part of mu
    i_out += 1 # increment output index
out = out[2:i_out] # remove the first two, and anything after i_out (that was never filled out)
i0 = p[0]//sps
ax11.plot(i[:sps*100:sps], out[i0:i0+100].real, '.-', label='I')
ax11.plot(i[:sps*100:sps], out[i0:i0+100].imag, '.-', label='Q')
plt.legend()
plt.show(block=False)


fig11, ax12 = plt.subplots(num=11)
for i in range(0, 800, 40) :
    ax12.cla()
    ax12.plot(range(i,i+40), samples[i:i+40], '.-')
    ax12.vlines(x= p[(p//40) == i//40], ymin=-1, ymax=1, color='red', linestyle='solid')
    ax12.vlines(x= MMi[(MMi//40) == i//40], ymin=-1, ymax=1, color='green', linestyle='dotted')
    ax12.text(i+20, 1, np.real(MM[:,6])[(MMi//40) == i//40], ha='center')    
    ax12.text(i+20, 0.9, np.real(MM[:,7])[(MMi//40) == i//40], ha='center')
    ax12.set_ylim([-1, 1])
    plt.draw()
    plt.pause(1)
    fig11.savefig('mm/'+str(i))

실행결과를 보면, 440 샘플타임(55 심볼타임)에 첫 동기화를 볼 수 있네요!

(a) BPSK 심볼 (b) 펄스성형 후 동기화전 샘플 (c) 동기화 출력

실행결과를 보면, 임의로 시작한 정점추정 샘플이 440번 샘플타임(55 심볼타임)에 첫 동기화를 볼 수 있네요!

초록점선이 빨간실선을 추적하여 시간동기화

 

② 주파수 동기화

이번에는 위 블랙 다이어그램 순서대로,,, 

fractional delay(소수점 지연)와 주파수옵션을 적용한 더 리얼한 무선 채널 환경을 아래처럼 만들고,

 

delay = 0.4 # fractional delay, in samples
fo = 13000 Hz # simulate freq offset

coarse frequency sync 를 아래처럼 수행하면, 대략적인 주파수동기화 max_freq = 13025.003079196962 를 얻습니다.

psd = np.fft.fftshift(np.abs(np.fft.fft( samples**2 )))
f = np.linspace(-fs/2.0, fs/2.0, len(psd))
max_freq = f[np.argmax(psd)]/2

 

이렇게 구한 대략적인 주파수옵셋을 보상해 주고, 다시 시간동기화를 합니다.

이제 대망에 주파수동기화 차례입니다. (소스코드는 원문과 함께, 앞서 제 PLL 포스트를 참고해 주시기 바랍니다.)

참 신통방통하게도 Costas Loop를 이용하여 나머지 미세주파수도 동기화 ( -25Hz 에 수렴 ) 되는 것을 약 400 샘플타임부터 확인할 수 있습니다. Q신호들도 0에 수렴하고요.

Costas Loop

그럼 최종적으로 성상도를 통해서 원신호를 어떻게 프로세싱해서 얻는 지 확인해 볼까요?

Synchronization

무선채널에서 더럽혀진(?) 비트신호를 동기화 수행해서 수신기에서 명확히 복원할 수 있겠네요! 대박!!!

 

③ 프레임 동기화

대부분의 통신 프로토콜은 패킷/프레임을 대부분 사용합니다. 수신기에서 우리는 새 프레임이 언제 시작하는지 식별할 수 있어야 하는데, prefix에는 수신기가 프레임의 시작을 감지하는데 사용하는 동기화 시퀀스가 포함되어 있으며 수신자가 미리 알고 있는 시퀀스입니다. 간단히 정합필터 출력과 비슷하게 아래처럼 구할 수 있습니다.

import numpy as np
import matplotlib.pyplot as plt
x = [1,1,1,-1,-1,-1,1,-1,-1,1,-1]
plt.plot(np.correlate(x,x,'same'),'.-')
plt.grid()
plt.show()

 

 

끝까지 들어주셔서 감사합니다!
이제 어느정도 통신주제에 대해서 기본적인 분야는 포스팅해 본 것 같아요. 맘에는 썩 안들지만,,, ㅎㅎㅎ
그래도 시도한게 어딘가요! 내식대로 정리해보는 과정에서 분명 얻는 것이 있었고,,,
부디 누군가에게 조그만 도움이 됐으면 더할나위없이 좋겠네요^^;

 

 

<참고자료>

1. 동기화 | PySDR: Python을 사용한 SDR 및 DSP 가이드

2. Mueller-Müller timing recovery scheme. (a) Impulse response. (b)... | Download Scientific Diagram