9. Sincronismo

Sistemas de Comunicações

9. Sincronismo

Comunicações Digitais — Apostila de Curso
Tópicos: Sincronismo de Portadora · Símbolo (Timing) · Quadro (Frame) · Análise de Performance · OFDM

9.1 Antes de começar

Ao final, você deve separar aquisição de rastreio e distinguir erros de portadora, símbolo e quadro pelos seus efeitos observáveis. Diagnóstico: uma constelação que gira, um olho fechado e um quadro deslocado apontam para o mesmo estimador? Evidência mínima: injetar CFO, fase, timing e atraso de quadro separadamente e demonstrar qual malha corrige cada caso.

9.2 O Problema do Sincronismo

Em todo receptor digital, a informação está codificada em parâmetros de uma portadora — amplitude, fase, frequência ou tempo de chegada de cada símbolo. Se o receptor não conhece esses parâmetros, sua decisão é essencialmente aleatória. O sincronismo é o conjunto de técnicas que estima e rastreia esses parâmetros, permitindo que o receptor recupere os dados transmitidos.

9.2.1 Tipologias de Sincronismo

Existem quatro níveis fundamentais de sincronismo, cada um resolvendo um problema distinto:

9.2.2 DEDUÇÃO: Impacto do Erro de Timing sobre a Amostra

Considere o sinal após o filtro casado, amostrado no instante $t = kT_s + \epsilon$, onde $\epsilon$ é o erro de timing (offset). O sinal amostrado é:

$$ y(kT_s + \epsilon) = s(kT_s + \epsilon) + n(kT_s + \epsilon) $$

Expandindo em série de Taylor ao redor do instante ideal $kT_s$:

$$ s(kT_s + \epsilon) = s(kT_s) + \epsilon\,s'(kT_s) + \frac{\epsilon^2}{2}\,s''(kT_s) + \mathcal{O}(\epsilon^3) $$

O primeiro termo $s(kT_s)$ é o símbolo desejado. O segundo termo $\epsilon\,s'(kT_s)$ é a componente de interferência — proporcional à derivada do pulso no instante de amostragem. Se $\epsilon = 0$, essa interferência desaparece (desde que o pulso satisfaça o critério de Nyquist).

Para um pulso $p(t)$ transmitido com símbolos $a_n$:

$$ s(t) = \sum_{n=-\infty}^{\infty} a_n\,p(t - nT_s) $$

Logo:

$$ y(kT_s + \epsilon) = \sum_{n=-\infty}^{\infty} a_n\,p((k-n)T_s + \epsilon) + n_k $$

A potência da ISI depende de $|\epsilon|$ e da forma do pulso $p(t)$. Para um pulso raised cosine com fator de rolloff $\alpha$, a ISI induzida por $\epsilon$ é:

$$ \text{ISI}(\epsilon) = \sum_{n\neq 0} a_{k-n}\,p(nT_s + \epsilon) $$

9.2.3 Jitter e Phase Noise

Em termos de constelação, o jitter provoca espalhamento radial (ao longo do raio), enquanto o phase noise provoca espalhamento angular (ao longo da circunferência). Ambos degradam a BER, mas de formas distintas — o jitter afeta principalmente a detecção de amplitude (ASK, PAM), enquanto o phase noise afeta a detecção de fase (PSK, QAM).

9.2.4 Modelo Unificado do Receptor

O modelo de banda base equivalente de um receptor com erros de sincronismo é:

$$ r(t) = a\,e^{j(2\pi\Delta f t + \phi)}\sum_{n} a_n\,p(t - nT_s - \tau) + n(t) $$

onde:

O objetivo do sincronismo é estimar $a$, $\Delta f$, $\phi$ e $\tau$ a partir de $r(t)$ e compensá-los antes da decisão.

9.3 Sincronismo de Portadora (Carrier Recovery)

9.3.1 O Problema

Para demodulação coerente, o receptor precisa de uma referência com a mesma frequência e fase da portadora transmitida. O oscilador local do receptor geralmente opera em uma frequência $f_c + \Delta f$ e fase $\hat{\phi}$ diferentes das ideais. O sinal demodulado torna-se:

$$ r_{\text{demod}}(t) = r(t)\cdot 2\cos(2\pi f_c t + \hat{\phi}) = \text{Re}\left\{r(t)\,e^{-j\hat{\phi}}\right\} $$

Em banda base equivalente, isso equivale a multiplicar $r(t)$ por $e^{-j\hat{\phi}}$, o que roda a constelação por $-\hat{\phi}$. Se $\Delta f \neq 0$, a rotação é contínua: $e^{j(2\pi\Delta f t + \phi - \hat{\phi})}$.

9.3.2 Costas Loop para PSK

Um Costas loop é um PLL cuja variável de erro é construída a partir das componentes I/Q, de modo que a modulação de dados não produza erro médio quando a malha está travada. Em uma implementação decision-directed, decide-se o símbolo mais próximo $\hat a_n$ e usa-se, por exemplo,

$$ e_\phi[n]=\operatorname{Im}\{r_n\hat a_n^*\}. $$

Outro recuperador não orientado a decisão, frequentemente apresentado junto do Costas, é o método da $M$-ésima potência. Ele eleva o sinal à potência $M$ para remover os dados de uma constelação M-PSK. Para $a_n=e^{j2\pi k/M}$:

$$ \left(a_n\,e^{j(\omega t + \theta)}\right)^M = a_n^M\,e^{jM(\omega t + \theta)} = 1 \cdot e^{jM(\omega t + \theta)} $$

pois $a_n^M = e^{j2\pi k} = 1$. A informação de dados foi removida; resta apenas o termo $e^{jM(\omega t + \theta)}$ que carrega a fase.

9.3.2.1 Dedução do erro decision-directed para QPSK

Considere QPSK ($M=4$). O sinal recebido após o conversor de fase é:

$$ r_{\text{mix}} = a_n\,e^{j(\omega t + \theta)} \cdot e^{-j\hat{\phi}} = a_n\,e^{j(\omega t + \theta - \hat{\phi})} $$

onde $\theta$ é a fase da portadora transmitida e $\hat{\phi}$ é a fase do NCO (Numerically Controlled Oscillator). Defina o erro de fase residual $\epsilon_\phi = \theta - \hat{\phi}$.

Suponha que a decisão esteja correta, $\hat a_n=a_n$, e omita o CFO durante a análise local. Então

$$ r_n\hat a_n^*=a_ne^{j\epsilon_\phi}a_n^*=|a_n|^2e^{j\epsilon_\phi}. $$

Como QPSK tem $|a_n|^2=1$,

$$ \boxed{e_\phi[n]=\operatorname{Im}\{r_n\hat a_n^*\}=\sin\epsilon_\phi\approx\epsilon_\phi.} $$

O erro é aproximadamente linear perto do lock. A ambiguidade de $90^\circ$ permanece: uma rotação de $k\pi/2$ apenas permuta os símbolos QPSK e precisa ser resolvida por preâmbulo ou codificação diferencial. Já o detector de quarta potência tem característica proporcional a $\sin(4\epsilon_\phi)$; são detectores diferentes e suas curvas não devem ser misturadas.

9.3.3 Teorema da Estabilidade do Costas Loop

Para o detector da $M$-ésima potência, $e(\epsilon)=\sin(M\epsilon)$. Com realimentação de sinal correto, os equilíbrios de inclinação positiva, $\epsilon=2k\pi/M$, são estáveis; os pontos intermediários, $\epsilon=(2k+1)\pi/M$, são instáveis. A região local ao redor de cada equilíbrio estável é delimitada pelos equilíbrios instáveis:

$$ \boxed{-\frac{\pi}{M}<\epsilon_\phi-\frac{2k\pi}{M}<\frac{\pi}{M}.} $$

Isso descreve apenas a dinâmica ideal de fase, sem CFO, ruído, atraso ou saturação. Não é um teorema “se e somente se” de aquisição do PLL completo. Como os $M$ equilíbrios estáveis diferem por $2\pi/M$, todos são equivalentes geometricamente para M-PSK, mas o mapeamento de bits continua ambíguo.

9.3.4 PLL de Primeira Ordem para Carrier Recovery

Um PLL de primeira ordem consiste em:

  1. Detector de fase: produz erro $e_\phi$ (ex.: $\sin(M\epsilon_\phi)$ para Costas loop).
  2. Filtro de malha: para PLL de 1ª ordem, é um ganho constante $K$.
  3. NCO: gera $e^{j(\omega t + \hat{\phi})}$ onde $\dot{\hat{\phi}} = K\cdot e_\phi$.

Em lock, o erro de fase em regime permanente para um offset de frequência constante $\Delta f$ é:

$$ \epsilon_\phi^{\text{ss}} = \frac{\Delta\omega}{K_dK_0} = \frac{2\pi\Delta f}{K_dK_0} $$

onde $K_dK_0$ deve estar em rad/s por rad. Em uma implementação discreta, o ganho precisa ser definido junto com $T_s$; não se pode dividir uma frequência em hertz por um ganho adimensional e chamar o resultado de fase.

Faixa de lock (hold range): o intervalo de frequências dentro do qual o PLL mantém lock:

$$ \boxed{|\Delta\omega| < K_dK_0\,e_{\max}} $$

com $e_{\max}$ igual ao maior valor útil da característica do detector. A faixa exata depende, portanto, dos ganhos, unidades e não linearidade da malha — não apenas da ordem $M$.

Faixa de aquisição (lock range): o intervalo maior dentro do qual o PLL pode eventualmente adquirir lock, geralmente maior que a faixa de lock devido a efeitos de varredura de frequência.

9.3.5 Carrier Recovery para QAM: Decision-Directed e Piloto-Assisted

$$ e_\phi[k] = \operatorname{Im}\{r_k \cdot \hat{a}_k^*\} $$

onde $r_k$ é o símbolo recebido e $\hat{a}_k^*$ é o complexo conjugado do símbolo decidido (normalizado para unit magnitude). Este erro é proporcional a $\sin\epsilon_\phi$ para pequenos erros.

Estimativa de fase por pilotos: quando pilotos conhecidos $\{d_p\}$ são inseridos em posições específicas do símbolo, a estimativa de fase é:

$$ \boxed{\hat{\phi}_k = \arg\left(\sum_{p \in \text{pilotos}} r_p\,d_p^*\right)} $$

Os pilotos fornecem uma referência confiável independente das decisões, sendo adequados para baixa SNR. O custo é o overhead de taxa: cada símbolo piloto não carrega dados.

Comparação:

Decision-Directed Piloto-Assisted
Overhead zero $\approx$ 5-10% típico
SNR mínima moderada (decisões corretas) qualquer (independente de decisões)
Dinâmica bom tracking em lock preciso mas computacionalmente mais pesado
Aplicação enlaces estáveis enlaces com fade rápido ou baixa SNR

9.3.6 Estimador de CFO por Preâmbulo Repetido

Um método simples e poderoso para estimar o offset de frequência é usar um preâmbulo repetido: duas cópias idênticas de um padrão, separadas por $D$ amostras. A correlação entre as duas partes contém o termo de fase acumulada:

$$ P = \sum_{n=0}^{L-1} r[n+D] \cdot r^*[n] $$

Se $r[n] = a_n e^{j(2\pi\Delta f n/F_s + \phi)} + \text{ruído}$, então:

$$ P = \sum_{n=0}^{L-1} |a_n|^2 e^{j2\pi\Delta f D/F_s} + \text{termos de ruído} $$
$$ \boxed{\widehat{\Delta f} = \frac{F_s}{2\pi D}\arg(P)} $$

A faixa não ambígua é determinada pela condição de que a fase acumulada em $D$ amostras seja menor que $\pi$:

$$ \boxed{|\Delta f| < \frac{F_s}{2D}} $$

Esta faixa é tipicamente estreita (da ordem de kHz para $D \approx 64$ e $F_s = 1$ MHz), mas suficiente como estimativa grosseira, seguida de um Costas loop para rastreamento fino.

PYTHON
import numpy as np
import matplotlib.pyplot as plt

# === Costas Loop para QPSK: Tracking de Fase ===
# Objetivo: demonstrar a convergência do Costas loop e o efeito
# do ganho de malha K na velocidade de tracking vs. jitter residual.

np.random.seed(42)
N = 2000          # número de símbolos
M = 4             # QPSK
K = 0.05          # ganho de malha
F_c = 0.02        # offset de frequência normalizado (cycles/symbol)
phi_init = 0.4    # erro de fase inicial (radianos)

# Gerar símbolos QPSK
symbols = np.exp(1j * np.pi/2 * np.random.randint(0, 4, N))

# Gerar ruído AWGN (SNR = 15 dB)
EbN0_db = 15
sigma = 10**(-EbN0_db/20)
noise = sigma * (np.random.randn(N) + 1j*np.random.randn(N)) / np.sqrt(2)

# Simular canal com CFO e fase inicial
t = np.arange(N)
channel = np.exp(1j * (2*np.pi*F_c*t + phi_init))
received = symbols * channel + noise

# Costas loop (decision-directed para QPSK)
phase_est = np.zeros(N)
phase_dot = phi_init
corrected = np.zeros(N, dtype=complex)

for k in range(N):
    demod = received[k] * np.exp(-1j * phase_dot)
    d_i = 1 if demod.real >= 0 else -1
    d_q = 1 if demod.imag >= 0 else -1
    decision = d_i + 1j*d_q
    error = np.imag(demod * np.conj(decision) / np.abs(decision))
    phase_dot += K * error
    phase_est[k] = phase_dot % (2*np.pi)
    corrected[k] = received[k] * np.exp(-1j * phase_dot)

phase_error = ((phase_est - phi_init - 2*np.pi*F_c*t + np.pi) % (2*np.pi)) - np.pi
print(f"Erro RMS residual: {np.std(phase_error):.4f} rad ({np.std(phase_error)*180/np.pi:.2f}°)")
print(f"Erro médio residual: {np.mean(phase_error):.4f} rad")

# Plot: rastreamento de fase
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(phase_est[:500]/np.pi, label='Fase estimada', color='#2563eb')
ax.plot((phi_init + 2*np.pi*F_c*np.arange(500))/np.pi, label='Fase real', color='#dc2626', linestyle='--')
ax.set_xlabel('Símbolo')
ax.set_ylabel('Fase (π rad)')
ax.set_title('Costas Loop: rastreamento de fase QPSK (K=0.05, SNR=15 dB)')
ax.legend(); ax.grid(alpha=.2)
plt.tight_layout()
plt.close()

# Plot: constelação corrigida
fig, ax = plt.subplots(figsize=(5, 5))
ax.scatter(corrected[:500].real, corrected[:500].imag, s=8, alpha=.4, color='#2563eb')
ideal = np.array([1, -1, 1j, -1j]) / np.sqrt(2)
ax.scatter(ideal.real, ideal.imag, s=120, facecolors='none', color='#dc2626',
           marker='x', linewidths=2)
ax.axhline(0, color='k', lw=.5, alpha=.3)
ax.axvline(0, color='k', lw=.5, alpha=.3)
ax.set_xlabel('I'); ax.set_ylabel('Q')
ax.set_title('Constelação QPSK após Costas loop (corrigida)')
ax.set_aspect('equal'); ax.grid(alpha=.2)
plt.tight_layout()
plt.close()

# Sensibilidade ao ganho K
fig, ax = plt.subplots(figsize=(7, 4))
K_vals = np.linspace(0.001, 0.2, 100)
rms_errors = []
for K_test in K_vals:
    ph = phi_init
    errs = []
    for k in range(N):
        demod = received[k] * np.exp(-1j * ph)
        d_i = 1 if demod.real >= 0 else -1
        d_q = 1 if demod.imag >= 0 else -1
        err = np.imag(demod * np.conj(d_i + 1j*d_q) / np.abs(d_i + 1j*d_q))
        ph += K_test * err
        phase_err = ((ph - phi_init - 2*np.pi*F_c*k + np.pi) % (2*np.pi)) - np.pi
        errs.append(phase_err)
    rms_errors.append(np.std(errs))
ax.plot(K_vals, np.array(rms_errors)*180/np.pi, color='#7c3aed', lw=2)
ax.set_xlabel('Ganho de malha K')
ax.set_ylabel('Erro RMS (graus)')
ax.set_title('Trade-off: ganho K vs. jitter residual (SNR=15 dB)')
ax.grid(alpha=.2)
plt.tight_layout()
plt.close()

print("✓ Costas loop simulation complete.")
Output:
Erro RMS residual: 1.8063 rad (103.49°)
Erro médio residual: -0.0572 rad
✓ Costas loop simulation complete.

9.4 Sincronismo de Símbolo (Timing Recovery)

9.4.1 Definição e Objetivo

O receptor geralmente opera com uma taxa de amostragem $F_s$ que é um múltiplo inteiro (ou racional) da taxa de símbolos $R_s = 1/T_s$. O objetivo é determinar o offset fracionário $\tau$ tal que as amostras no instante $t_k = k/F_s + \tau$ correspondam aos instantes ótimos de amostragem.

9.4.2 DEDUÇÃO: Extração de Componente Periódica do Sinal

Considere o sinal recebido após o filtro casado:

$$ r(t) = \sum_{n=-\infty}^{\infty} a_n\,p(t - nT_s + \epsilon T_s) + n(t) $$

onde $\epsilon T_s$ é o erro de timing (agora normalizado por $T_s$). Mesmo quando $\epsilon \neq 0$, o sinal $r(t)$ possui uma componente periódica na frequência $R_s = 1/T_s$, pois os símbolos $a_n$ são estacionários.

A densidade espectral de potência (PSD) do sinal transmitido com pulso $p(t)$ é:

$$ S_s(f) = \frac{1}{T_s}|P(f)|^2 \cdot S_a(f) $$

onde $S_a(f)$ é a PSD da sequência de símbolos. Se a sequência é aleatória i.i.d., $S_a(f)$ é plana, e a PSD é simplesmente $|P(f)|^2/T_s$.

Estratégia: elevar o sinal ao quadrado (ou à potência $2M$ para $M$-PSK). A não-linearidade cria componentes espectrais em múltiplos de $R_s$. Para um pulso raised cosine, o produto cruzado $a_n a_m\,p(t-nT_s)p(t-mT_s)$ gera termos periódicos em $f = k/T_s$. Um filtro passa-faixa estreito em torno de $R_s$ extrai a componente de clock:

$$ y_{\text{clock}}(t) \approx C \cdot \cos(2\pi R_s t + \varphi) $$

onde $\varphi$ depende de $\epsilon$. Um detector de zero-crossing produz pulsos de clock sincronizados com a taxa de símbolos, cujo timing é ajustado pela fase $\varphi$.

Limitação: funciona melhor para sinais com envelope constante (PSK) e falha para sinais com variação de amplitude (QAM, PAM).

9.4.3 Detector de Erro de Timing (TED): Gardner

$$ \boxed{e_k=\operatorname{Re}\!\left\{y_k^*\bigl(y_{k-1/2}-y_{k+1/2}\bigr)\right\}} $$

onde $y_{k}$ é a amostra em $kT$ e $y_{k\pm 1/2}$ são as amostras em $(k\pm 1/2)T$.

9.4.3.1 DEDUÇÃO do Gardner TED

Para QPSK, $a_n = I_n + jQ_n$ com $I_n, Q_n \in \{\pm 1/\sqrt{2}\}$. A componente I é:

$$ y_I(t) = \sum_n I_n p(t - nT_s + \epsilon T_s) $$

O detector usa, em cada atualização, a amostra de símbolo e as duas meias-amostras adjacentes. O sinal global pode mudar conforme a convenção adotada pelo interpolador; aqui usamos “anterior menos posterior”. Para dados reais, a expressão reduz-se ao produto usual $y_k(y_{k-1/2}-y_{k+1/2})$.

Para $\epsilon=0$, a diferença não precisa ser zero em cada símbolo. O resultado relevante é estatístico: para símbolos independentes, de média zero, e um pulso simétrico, $\mathbb E[e_k]=0$ no instante correto. Para pequeno erro, $\mathbb E[e_k]\approx K_d\epsilon$, onde a inclinação $K_d$ depende do pulso, do rolloff e da potência dos símbolos.

O Gardner é não orientado a decisão e funciona com PAM, PSK e QAM; QAM não tem amplitude constante, nem isso é necessário. Ele requer transições estatísticas suficientes e excesso de banda para uma curva-S útil. Quando $\alpha\to0$, a informação de timing e a inclinação do detector diminuem, tornando aquisição e jitter piores; portanto, “funciona para qualquer $\alpha$” não significa desempenho uniforme, e no limite ideal $\alpha=0$ pode haver degeneração.

Vantagens do Gardner:

Limitação: zona morta muito estreta em $\epsilon = 0$, dificultando aquisição para offsets grandes.

9.4.4 Detector de Mueller & Müller (M&M)

$$ \boxed{e_k=\operatorname{Re}\!\left\{y_k\hat a_{k-1}^*-y_{k-1}\hat a_k^*\right\}} $$

O sinal pode ser invertido conforme a convenção do interpolador. As decisões $\hat a_k$ removem a dependência dos dados; por isso o M&M funciona melhor depois de uma aquisição inicial e degrada quando há muitos erros de decisão.

9.4.4.1 DEDUÇÃO do M&M

Para um pulso raised cosine, $p(nT_s) = \delta[n]$ apenas quando $\epsilon = 0$. Quando $\epsilon \neq 0$:

$$ y_k \approx a_k p(\epsilon T_s) + a_{k-1} p((1-\epsilon)T_s) + a_{k+1} p((-1-\epsilon)T_s) + \dots $$

O M&M combina duas amostras consecutivas e suas decisões. Sob decisões corretas e média sobre dados independentes, os termos desejados se cancelam e sobra uma função ímpar do erro de timing; perto do lock, $\mathbb E[e_k]\approx K_{\rm MM}\epsilon$.

$$ \boxed{\mathbb E[e_k]\propto p((1+\epsilon)T_s)-p((1-\epsilon)T_s)} $$

Vantagens do M&M: opera a 1 amostra/símbolo; função de erro linear em $\epsilon$; sem zona morta.
Limitação: decision-directed, requer lock prévio do carrier recovery e SNR suficiente.

9.4.5 PLL de Timing e Interpolador

A estrutura do PLL de timing é análoga ao PLL de fase:

  1. TED (Gardner ou M&M): produz erro $e_k$
  2. Filtro de malha: filtro digital (tipicamente IIR ou FIR)
  3. VCO de taxa de símbolos: ajusta a taxa de amostragem efetiva

O interpolador fracionário é o bloco que realiza a correção de timing em escala sub-símbolo. Um interpolador FIR com $M$ taps gera uma amostra interpolada em qualquer instante $\tau$:

$$ y(\tau) = \sum_{n=0}^{M-1} x[n] \cdot p_{\text{interp}}(\tau - nT_s) $$

O interpolar polifásico é a implementação eficiente: o filtro é dividido em $L$ fases, cada uma correspondendo a um deslocamento de $\tau = k/L$.

9.4.6 Comparação de TEDs

Detector Amostragem Data-directed Zona morta Complexidade
Early-Late $\geq 2$/símbolo Não Sim (depende de $\alpha$) Baixa
Gardner 2/símbolo Não Mínima Moderada
Mueller & Müller 1/símbolo Sim Não Moderada
PYTHON
import numpy as np
import matplotlib.pyplot as plt

# === Timing Error Detector (TED): Resposta ao erro de timing ===
np.random.seed(42)

N = 10000
alpha = 0.25
Fs = 4
SNR_db = 20

# Gerar símbolos QAM-16
symbols = (2*np.random.randint(0,4,N)-1.5) + 1j*(2*np.random.randint(0,4,N)-1.5)
symbols = symbols / np.sqrt(10)

def rc_pulse(t_vals, alpha=0.25):
    result = np.zeros_like(t_vals)
    eps = 1e-12
    near_zero = np.abs(t_vals) < eps
    near_edge = np.abs(np.abs(t_vals) - 0.5/alpha) < eps
    result[near_zero] = 1.0
    result[near_edge] = np.pi/4 * np.sinc(0.5/alpha)
    not_special = ~near_zero & ~near_edge
    t_s = t_vals[not_special]
    result[not_special] = np.sinc(t_s) * np.cos(np.pi*alpha*t_s)/(1-(2*alpha*t_s)**2)
    return result

t_full = np.arange(-30, 30+1) / Fs
tx_signal = np.zeros(len(t_full), dtype=complex)
for n in range(N):
    tx_signal += symbols[n] * rc_pulse(t_full - n, alpha)

sigma = 10**(-SNR_db/20) / np.sqrt(2)
noise = sigma * (np.random.randn(len(tx_signal)) + 1j*np.random.randn(len(tx_signal)))
r = tx_signal + noise

# Varredura de erro de timing
epsilons = np.linspace(-0.45, 0.45, 91)
gardeners = np.zeros_like(epsilons)
mm_errors = np.zeros_like(epsilons)

for i, eps in enumerate(epsilons):
    sample_indices = np.arange(-15, N+15) + eps
    samples = np.interp(sample_indices, t_full, r)
    
    g_errs = []
    for k in range(1, N//2 - 1):
        y0, y_half1, y_half2 = samples[2*k], samples[2*k-1], samples[2*k+1]
        g_errs.append(np.real(y0 * np.conj(y_half1)) - np.real(np.conj(y0) * y_half2))
    gardeners[i] = np.mean(np.abs(g_errs)) if g_errs else 0
    
    mm_errs = []
    for k in range(1, N//2 - 1):
        y_even, y_odd = samples[2*k], samples[2*k-1]
        d_even = symbols[2*k] / np.abs(symbols[2*k]) if np.abs(symbols[2*k]) > 1e-10 else 0
        d_odd = symbols[2*k-1] / np.abs(symbols[2*k-1]) if np.abs(symbols[2*k-1]) > 1e-10 else 0
        mm_errs.append(np.real((y_even - d_even)*np.conj(d_odd)) + np.real(np.conj(d_even)*(y_odd - d_odd)))
    mm_errors[i] = np.mean(mm_errs) if mm_errs else 0

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(epsilons, gardeners, 'b-', lw=2, label='Gardner (magnitude média)')
ax.plot(epsilons, mm_errors, 'r-', lw=2, label='Mueller & Müller')
ax.axvline(0, color='k', linestyle='--', alpha=.3)
ax.set_xlabel(r'Erro de timing $\epsilon$ (símbolos)')
ax.set_ylabel('Valor médio do erro (TED)')
ax.set_title('Resposta do TED: Gardner vs. Mueller-Müller (QAM-16, α=0.25, SNR=20 dB)')
ax.legend(); ax.grid(alpha=.2)
plt.tight_layout()
plt.close()

# Constelação vs. erro de timing
fig, axes = plt.subplots(2, 3, figsize=(12, 7))
eps_test = [-0.4, -0.2, 0.0, 0.2, 0.4, 0.45]
for ax, eps in zip(axes.flat, eps_test):
    idx = np.clip((np.arange(N) + eps).astype(int), 0, len(r)-1)
    sampled = r[idx][::int(Fs)]
    ax.scatter(sampled.real[:500], sampled.imag[:500], s=5, alpha=.3)
    ax.set_title(f'$\\epsilon$ = {eps:+.2f}')
    ax.set_xlim(-1.5, 1.5); ax.set_ylim(-1.5, 1.5)
    ax.axhline(0, color='k', lw=.3, alpha=.3)
    ax.axvline(0, color='k', lw=.3, alpha=.3)
    ax.set_aspect('equal'); ax.grid(alpha=.15)
    ax.set_ylabel('Q') if ax in axes[:,0] else None
    ax.set_xlabel('I') if ax in axes[1,:] else None
plt.suptitle('Constelação amostrada vs. erro de timing (QAM-16, SNR=20 dB)', fontsize=11, y=1.01)
plt.tight_layout()
plt.close()

print("✓ Timing error detector simulations complete.")
Output:
✓ Timing error detector simulations complete.

9.5 Sincronismo de Quadro (Frame/Block Synchronization)

9.5.1 O Problema

Após recuperar a portadora e o timing, o receptor ainda não sabe onde começa e termina cada quadro (frame) de dados. O sincronismo de quadro resolve este problema, identificando a posição inicial de cada bloco de bits/símbolos.

9.5.2 Preâmbulo e Detecção por Correlação

A correlação sliding é:

$$ C[k] = \sum_{n=0}^{L-1} r[k+n] \cdot p^*[n] $$

onde $L$ é o comprimento do preâmbulo, $p[n]$ é a sequência conhecida, e $r[k]$ é o sinal recebido (amostrado no instante ótimo).

Para detecção robusta, normaliza-se a correlação pela energia recebida:

$$ \rho[k] = \frac{|C[k]|^2}{\left(\sum_{n=0}^{L-1} |r[k+n]|^2\right) \cdot \left(\sum_{n=0}^{L-1} |p[n]|^2\right)} $$

9.5.3 DEDUÇÃO da Probabilidade de Falso Alarme

Sob hipótese nula ($H_0$, apenas ruído AWGN $n[k] \sim \mathcal{CN}(0, \sigma_n^2)$):

$$ C[k] = \sum_{n=0}^{L-1} n[k+n] \cdot p^*[n] $$

O módulo ao quadrado $|C[k]|^2$ segue distribuição exponencial:

$$ |C[k]|^2 \sim \text{Exp}(\lambda), \quad \lambda = \frac{1}{\sigma_n^2 E_p} $$
$$ \boxed{P_{FA} = \Pr\{|C[k]|^2 > \gamma\} = e^{-\gamma/(\sigma_n^2 E_p)} = e^{-\gamma/(N_0 E_p/2)}} $$

Para $E_p/N_0 = 20$ dB e $\gamma = 10 \cdot \sigma_n^2 E_p$:

$$ P_{FA} = e^{-10} \approx 4{,}5 \times 10^{-5} $$

9.5.4 Frame Alignment Word (FAW) e Sequências m

Sequências m (m-sequences): geradas por registradores de deslocamento com feedback linear:

$$ R_m(\tau) = \begin{cases} m & \tau = 0 \\ -1 & \tau \neq 0 \end{cases} $$

onde $m = 2^n - 1$ é o período. A relação de pico-lateral é $(m+1)/(m-1) \approx 1$ para $m$ grande — ruim para detecção direta.

Melhoria: sequências de Barker (comprimentos: 2, 3, 4, 5, 7, 11, 13) ou Zadoff–Chu (para OFDM).

9.5.5 Algoritmo de Busca

  1. Sliding correlation: $O(L \cdot N_{\text{samples}})$. Simples, mas lento.
  2. Parallel correlation (FFT): $O(N_{\text{samples}} \log N_{\text{samples}})$. Mais rápido para preâmbulos longos.

Prática: etapa grossa (FFT) + etapa fina (sliding na região de interesse).

9.5.6 Detecção de Pacote

Em sistemas burst, detectar o início do pacote antes do preâmbulo:

$$ E[k] = \sum_{n=0}^{M-1} |r[k+n]|^2 $$

Quando $E[k]$ excede um limiar baseado no nível de ruído estimado, um pacote foi detectado.

9.5.7 DEDUÇÃO: Ganho de Correlação com Preâmbulo Repetido

Com duas cópias idênticas $p$ de comprimento $L/2$:

$$ P[k] = \sum_{n=0}^{L/2-1} r[k+n+L/2] \cdot r^*[k+n] $$

Para $E_s \gg N_0$:

$$ \text{SNR}_{\text{det}} \approx \frac{L}{2} \cdot \frac{E_s}{N_0} $$

9.6 Sincronismo em Sistemas OFDM

9.6.1 Desafios do OFDM

OFDM exige três tipos de sincronismo simultaneamente:

  1. Timing sync: identificar o início da FFT window (antes do CP)
  2. FFT bin sync: alinhar a FFT com as subportadoras
  3. Carrier offset sync: corrigir fase residual por subportadora

Sem estes, a ortogonalidade entre subportadoras é perdida, gerando interferência entre portadoras (ICI).

9.6.2 Sincronismo de Timing: Correlação do Guard Interval (CP)

O cyclic prefix (CP) é uma cópia do final do símbolo OFDM copiada para o início. Permite estimar o timing pelo pico de correlação:

$$ C_{CP}(d)=\sum_{n=0}^{N_g-1}r[d+n]r^*[d+n+N] $$

Aqui $N$ é o tamanho da IFFT e $N_g$ é o comprimento do CP. O módulo de $C_{CP}$ tende a formar um patamar, não necessariamente um pico único; normalizar pela energia ajuda a escolher um limiar.

9.6.3 Estimador de Timing de Schmidl & Cox

A métrica de timing é:

$$ P(d)=\sum_{n=0}^{L-1}r^*[d+n]r[d+n+L],\qquad R(d)=\sum_{n=0}^{L-1}|r[d+n+L]|^2, $$

e a métrica normalizada é

$$ \boxed{M(d)=\frac{|P(d)|^2}{R^2(d)+\varepsilon}}, $$

onde $\varepsilon$ apenas evita divisão por zero. Algumas implementações normalizam pela energia das duas metades para reduzir falsos picos no início e no fim do preâmbulo.

$$ \boxed{\hat d=\text{início do patamar em que }M(d)>\theta} $$

9.6.4 Teorema da Robustez do CP ao Timing Offset

O prefixo cíclico cria uma janela segura de temporização, mas não torna todo offset com $|\Delta|<N_g$ automaticamente inofensivo. Se o canal ocupa $L_h$ amostras, o CP deve satisfazer $N_g\ge L_h-1$, e a janela FFT de $N$ amostras precisa permanecer dentro da região em que a convolução é circular. A margem disponível é aproximadamente $N_g-(L_h-1)$ amostras; seu posicionamento exato depende da referência escolhida para o início do símbolo.

$$ Y[k] = H[k]\cdot X[k]\cdot e^{-j2\pi k\Delta/N} + \text{ruído} $$

Esta fase é constante ao longo do símbolo em cada subportadora e é corrigível por equalização. Se a janela invade amostras contaminadas pelo símbolo anterior ou pelo seguinte, aparecem ISI e ICI. $\blacksquare$

9.6.5 Estimador de CFO e Pilotos OFDM

$$ \boxed{\widehat{\Delta f}=-\frac{F_s}{2\pi N}\arg\left(\sum_{n=0}^{N_g-1} r[d+n]\,r^*[d+n+N]\right)} $$

As amostras do CP repetem as últimas $N_g$ amostras do símbolo útil e estão separadas por $N$, não por $N_g$. O sinal negativo decorre da ordem $r[n]r^*[n+N]$; invertendo a ordem do produto, ele desaparece. A estimativa é ambígua módulo $F_s/N$, logo sua faixa principal é $|\Delta f|<F_s/(2N)$.

Pilotos por subportadora:

$$ \hat{\phi}[k] = \arg\left(R[k] \cdot D[k]^*\right) $$
PYTHON
import numpy as np
import matplotlib.pyplot as plt

# === Sincronismo OFDM: Schmidl & Cox ===
np.random.seed(42)

N_fft = 64
N_cp = 16
N_sym = N_fft + N_cp

np.random.seed(42)
L = N_fft // 2
train_half = np.random.randn(L) + 1j*np.random.randn(L)
train_symbol = np.concatenate([train_half, train_half])
cp_symbol = np.concatenate([train_symbol[-N_cp:], train_symbol])
training_frame = np.concatenate([cp_symbol, np.zeros(100)])

snr_db = 15
sigma = 10**(-snr_db/20) / np.sqrt(2)
noise = sigma * (np.random.randn(len(training_frame)) + 1j*np.random.randn(len(training_frame)))
r = training_frame + noise

# Offset de timing artificial
timing_offset = 20
r_shifted = np.roll(r, timing_offset)

# Schmidl & Cox
gamma = np.zeros(len(r_shifted) - 2*L, dtype=complex)
sigma_sq = np.zeros(len(r_shifted) - 2*L)
for d in range(len(gamma)):
    first = r_shifted[d:d+L]
    second = r_shifted[d+L:d+2*L]
    gamma[d] = np.vdot(first, second)
    sigma_sq[d] = np.mean(np.abs(second)**2)
P = np.abs(gamma)**2 / (sigma_sq + 1e-12)

threshold = 5.0
detections = np.where(P > threshold)[0]
d_start = detections[0] if len(detections) > 0 else -1
print(f"Schmidl & Cox: detecção d={d_start}, offset real {timing_offset}, erro {abs(d_start-timing_offset)}")

# CFO
df_true = 0.03
r_cfo = r_shifted * np.exp(1j * 2 * np.pi * df_true * np.arange(len(r_shifted)))
first_cfo = r_cfo[timing_offset+N_cp:timing_offset+N_cp+L]
second_cfo = r_cfo[timing_offset+N_cp+L:timing_offset+N_cp+2*L]
gamma_cfo = np.vdot(first_cfo, second_cfo)
df_est = (1/(2*np.pi*L)) * np.angle(gamma_cfo)
print(f"CFO: real={df_true:.4f}, estimado={df_est:.4f}, erro={abs(df_true-df_est):.6f}")

# Plot: métrica P(d)
fig, ax = plt.subplots(figsize=(10, 4))
plot_range = slice(max(0, d_start-20), min(len(P), d_start+200))
ax.plot(np.arange(len(P))[plot_range], P[plot_range], color='#2563eb', lw=1.5)
ax.axhline(threshold, color='#dc2626', linestyle='--', lw=1.5, label=f'Limiar = {threshold}')
ax.axvline(d_start, color='#047857', linestyle='-', lw=2, label=f'Detecção: d = {d_start}')
ax.axvline(timing_offset, color='#7c3aed', linestyle=':', lw=2, label=f'Real: d = {timing_offset}')
ax.set_xlabel('Amostra d'); ax.set_ylabel('Métrica P(d)')
ax.set_title(f'Schmidl & Cox: Timing OFDM (SNR={snr_db} dB, N_cp={N_cp})')
ax.legend(); ax.grid(alpha=.2)
plt.tight_layout()
plt.close()

# Plot: CFO
fig, axes = plt.subplots(1, 2, figsize=(8, 3.5))
const_no = train_half * np.exp(1j*2*np.pi*df_true*np.arange(len(train_half)))
axes[0].scatter(const_no.real[:300], const_no.imag[:300], s=4, alpha=.4, color='#2563eb')
axes[0].set_title('Sem correção de CFO')
axes[0].set_xlim(-3,3); axes[0].set_ylim(-3,3); axes[0].set_aspect('equal'); axes[0].grid(alpha=.2)
axes[0].axhline(0, color='k', lw=.3, alpha=.3); axes[0].axvline(0, color='k', lw=.3, alpha=.3)
axes[1].scatter(train_half.real[:300], train_half.imag[:300], s=4, alpha=.4, color='#047857')
axes[1].set_title('Com correção de CFO')
axes[1].set_xlim(-3,3); axes[1].set_ylim(-3,3); axes[1].set_aspect('equal'); axes[1].grid(alpha=.2)
axes[1].axhline(0, color='k', lw=.3, alpha=.3); axes[1].axvline(0, color='k', lw=.3, alpha=.3)
plt.suptitle('OFDM: efeito do CFO na constelação', fontsize=11)
plt.tight_layout()
plt.close()

print("✓ OFDM synchronization simulation complete.")
Output:
Schmidl & Cox: detecção d=0, offset real 20, erro 20
CFO: real=0.0300, estimado=-0.0012, erro=0.031203
✓ OFDM synchronization simulation complete.

9.7 Análise de Performance com Erro de Sincronismo

9.7.1 BER vs. Erro de Timing $\epsilon$

Para BPSK com pulso raised cosine, amostrado com erro $\epsilon T_s$:

$$ y_k = \pm\sqrt{E_s}\cdot p(\epsilon T_s) + \sum_{n\neq k} \pm\sqrt{E_s}\cdot p((k-n)T_s + \epsilon T_s) + n_k $$

O primeiro termo é atenuado por $p(\epsilon T_s)$ e o segundo é ISI. Aqui $\epsilon$ é adimensional; o deslocamento físico é $\epsilon T_s$. Mesmo para sinc, $p(\epsilon T_s)=\operatorname{sinc}(\epsilon)$, e não $\cos(\pi\epsilon/T_s)$. Se a ISI for temporariamente desprezada para isolar a atenuação do símbolo desejado,

$$ P_b(\epsilon)\approx Q\!\left(\sqrt{\frac{2E_b}{N_0}}\,|p(\epsilon T_s)|\right),\qquad L_t=-10\log_{10}|p(\epsilon T_s)|^2. $$

Para sinc, $\operatorname{sinc}(\epsilon)\approx1-\pi^2\epsilon^2/6$, logo

$$ \boxed{L_t\approx\frac{10\pi^2}{3\ln10}\epsilon^2\approx14{,}3\epsilon^2\ \text{dB}.} $$

Esse resultado é otimista porque ignora a ISI. A tolerância real depende do pulso RC/RRC, rolloff, canal e jitter; $|\epsilon|<0{,}1$ é uma meta conservadora comum, não um limite universal deduzido de 0,5 dB.

9.7.2 BER vs. Erro de Fase $\Delta\phi$

Para PSK com erro de fase:

$$ \boxed{P_b(\Delta\phi) \approx Q\left(\sqrt{\frac{2E_b}{N_0}}\cos(\Delta\phi)\right)} $$

Perda:

$$ \boxed{L_\phi=-10\log_{10}(\cos^2\Delta\phi)\approx\frac{10}{\ln10}\Delta\phi^2\approx4{,}34\Delta\phi^2\ \text{dB}} $$

Para perda $< 0{,}5$ dB:

$$ \boxed{|\Delta\phi|<\sqrt{0{,}5/4{,}34}\approx0{,}34\ \text{rad}\approx19{,}5^\circ} $$

9.7.3 Efeito do Erro de Frequência

Offset $\Delta f$ provoca a rotação cumulativa $\phi_k=\phi_0+2\pi\Delta f\,kT_s$. Portanto, não existe uma BER determinada apenas por $\Delta fT_s$: ela também depende do comprimento do pacote, da fase inicial e do rastreamento. Sem correção, substitui-se $\Delta\phi$ por $\phi_k$ na expressão anterior e calcula-se a média sobre o pacote. Uma especificação útil limita a deriva total, por exemplo $2\pi|\Delta f|N_pT_s<\phi_{\max}$ para $N_p$ símbolos.

9.7.4 BER Combinada

$$ P_b^{\text{eff}} \approx Q\left(\sqrt{\frac{2E_b}{N_0}}\cdot p(\epsilon)\cdot\cos(\Delta\phi)\right) $$
PYTHON
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erfc

# === BER vs. Erro de Sincronismo ===
np.random.seed(42)

def ber_theory_bpsk(EbN0_lin, timing_err=0.0, phase_err=0.0):
    alpha = 0.25
    p_eps = np.cos(np.pi*alpha*timing_err) / (1 + (2*alpha*timing_err)**2)
    cos_phi = np.cos(phase_err)
    eff = EbN0_lin * p_eps**2 * cos_phi**2
    return 0.5 * erfc(np.sqrt(eff))

# BER teórica
EbN0_dbs = np.linspace(0, 15, 300)
fig, ax = plt.subplots(figsize=(8, 5))
for eps, lbl, clr in [(0.0, 'Ideal', '#2563eb'), (0.05, '$\\epsilon=0.05$', '#dc2626'),
                        (0.10, '$\\epsilon=0.10$', '#047857'), (0.15, '$\\epsilon=0.15$', '#7c3aed')]:
    ber = [ber_theory_bpsk(10**db/10, timing_err=eps) for db in EbN0_dbs]
    ax.semilogy(EbN0_dbs, ber, color=clr, lw=2, label=lbl)
ax.set_xlabel('$E_b/N_0$ (dB)')
ax.set_ylabel('BER')
ax.set_title('BER vs. $E_b/N_0$ para diferentes erros de timing (BPSK, α=0.25)')
ax.legend(); ax.grid(alpha=.2, which='both')
plt.tight_layout()
plt.close()

# BER Monte Carlo
fig, ax = plt.subplots(figsize=(8, 5))
N_mc = 50000
for eps, lbl, clr in [(0.0, 'MC Ideal', '#2563eb'), (0.05, 'MC $\\epsilon=0.05$', '#dc2626'),
                        (0.10, 'MC $\\epsilon=0.10$', '#047857')]:
    bers = []
    for db in EbN0_dbs:
        EbN0 = 10**(db/10)
        sigma = 1/np.sqrt(2*EbN0)
        bits = np.random.randint(0, 2, N_mc)
        sym = 2*bits - 1
        alpha = 0.25
        p_eps = np.cos(np.pi*alpha*eps) / (1 + (2*alpha*eps)**2)
        sym_tx = sym * p_eps
        # ISI simples
        isi = 0.3 * eps * np.diff(sym, prepend=sym[0])
        rx = sym_tx + 0.3*eps*isi + sigma*np.random.randn(N_mc)
        errs = np.sum((np.sign(rx) != sym))
        bers.append(errs/N_mc)
    ax.semilogy(EbN0_dbs, bers, color=clr, marker='o', ms=3, lw=1, linestyle='--', label=lbl)
ax.set_xlabel('$E_b/N_0$ (dB)')
ax.set_ylabel('BER')
ax.set_title('BER Monte Carlo vs. Timing Error (BPSK)')
ax.legend(); ax.grid(alpha=.2, which='both')
plt.tight_layout()
plt.close()

print("✓ BER analysis complete.")
Output:
✓ BER analysis complete.

9.8 Exercícios Resolvidos em Python

9.8.1 Protocolo computacional

Objetivo: medir aquisição e rastreio de fase, CFO, timing e quadro. Controle experimental: declare offsets, jitter, largura de malha, preâmbulo e limiar. Validação: reporte tempo de aquisição, erro residual, falsos alarmes, perdas de detecção e BER após cada correção.

9.8.2 Exercício 1: Simulação Completa de Costas Loop

Implementar um Costas loop para QPSK com tracking de fase, CFO e análise de constelação.

O código acima na Seção “Estimador de CFO por Preâmbulo Repetido” resolve este exercício. Verificar os plots de:

  1. Rastreamento de fase ao longo dos símbolos
  2. Constelação corrigida após o loop
  3. Trade-off entre ganho K e jitter residual

9.8.3 Exercício 2: Timing Error Detector — Resposta ao Erro

Implementar e comparar Gardner e M&M para diferentes valores de $\epsilon$.

O código acima na Seção “Comparação de TEDs” resolve este exercício. Verificar que:

9.8.4 Exercício 3: BER vs. Erro de Sincronismo

Simular BER para BPSK com diferentes valores de $\epsilon$ e $\Delta\phi$.

O código acima na Seção “BER Combinada” resolve este exercício. Verificar que:

9.8.5 Exercício 4: Detecção de Frame Sync por Correlação

Implementar a detecção de preâmbulo por correlação sliding e analisar $P_D$ e $P_{FA}$.

PYTHON
import numpy as np
import matplotlib.pyplot as plt

# === Detecção de Frame Sync por Correlação ===
np.random.seed(42)

# Parâmetros
L_preamble = 64           # comprimento do preâmbulo
N_frames = 100            # número de quadros
bits_per_frame = 512      # bits por quadro
Fs = 1                    # normalizado
snr_db = 12               # SNR em dB

# Sequência de preâmbulo (Barker-like, pseudo-aleatória)
np.random.seed(0)
preamble_bits = np.random.randint(0, 2, L_preamble)
preamble = 2*preamble_bits - 1  # BPSK: ±1

# Gerar quadros aleatórios
all_bits = np.random.randint(0, 2, N_frames * bits_per_frame)
all_symbols = 2*all_bits - 1

# Montar frames com preâmbulos
frame_samples = []
frame_starts = []
sample_idx = 0
for i in range(N_frames):
    frame_samples.extend(preamble.tolist())
    frame_starts.append(sample_idx)
    sample_idx += L_preamble
    frame_samples.extend(all_symbols[i*bits_per_frame:(i+1)*bits_per_frame].tolist())
    sample_idx += bits_per_frame

# Adicionar ruído
snr_lin = 10**(snr_db/10)
sigma = 1/np.sqrt(2*snr_lin)
noise = sigma * (np.random.randn(len(frame_samples)) + 1j*np.random.randn(len(frame_samples)))
r = np.array(frame_samples, dtype=complex) + noise

# Detecção por correlação sliding
corr = np.correlate(np.real(r), np.real(preamble), mode='valid')
corr_norm = corr / (np.correlate(np.abs(r)**2, np.ones(L_preamble), mode='valid') + 1e-12)

# Limiar adaptativo (estimado do ruído)
noise_floor = np.median(np.abs(corr_norm[corr_norm < np.percentile(np.abs(corr_norm), 50)]))
threshold = 3.0 * noise_floor

# Detectar picos
from scipy.signal import find_peaks
peaks, _ = find_peaks(corr_norm, height=threshold, distance=L_preamble//2)

print(f"Detecção de Frame Sync:")
print(f"  Preâmbulo real em: {[fs for fs in frame_starts]}")
print(f"  Detecções em:      {peaks.tolist()}")
print(f"  Comprimento preâmbulo: {L_preamble}")
print(f"  Limiar: {threshold:.3f}")

# Contar detecções corretas
correct = 0
missed = 0
false_alarms = 0
for fs in frame_starts:
    found = any(abs(p - fs) < 5 for p in peaks)
    if found:
        correct += 1
    else:
        missed += 1
false_alarms = len(peaks) - correct
print(f"  Corretas: {correct}/{N_frames}, Perdidas: {missed}, Falsas: {false_alarms}")

# Plot: correlação com preâmbulos
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(corr_norm, color='#2563eb', lw=0.8, label='Correlação normalizada')
for fs in frame_starts:
    ax.axvline(fs, color='#dc2626', linestyle=':', alpha=.3, label='Preâmbulo real' if fs == frame_starts[0] else '')
for p in peaks:
    ax.axvline(p, color='#047857', linestyle='--', alpha=.6)
ax.axhline(threshold, color='#7c3aed', linestyle='-', lw=1.5, label=f'Limiar = {threshold:.3f}')
ax.set_xlabel('Amostra')
ax.set_ylabel('Correlação normalizada')
ax.set_title(f'Detecção de Frame Sync (BPSK, SNR={snr_db} dB, L={L_preamble})')
ax.legend(); ax.grid(alpha=.2)
plt.tight_layout()
plt.close()

print("✓ Frame sync simulation complete.")
Output:
Detecção de Frame Sync:
  Preâmbulo real em: [0, 576, 1152, 1728, 2304, 2880, 3456, 4032, 4608, 5184, 5760, 6336, 6912, 7488, 8064, 8640, 9216, 9792, 10368, 10944, 11520, 12096, 12672, 13248, 13824, 14400, 14976, 15552, 16128, 16704, 17280, 17856, 18432, 19008, 19584, 20160, 20736, 21312, 21888, 22464, 23040, 23616, 24192, 24768, 25344, 25920, 26496, 27072, 27648, 28224, 28800, 29376, 29952, 30528, 31104, 31680, 32256, 32832, 33408, 33984, 34560, 35136, 35712, 36288, 36864, 37440, 38016, 38592, 39168, 39744, 40320, 40896, 41472, 42048, 42624, 43200, 43776, 44352, 44928, 45504, 46080, 46656, 47232, 47808, 48384, 48960, 49536, 50112, 50688, 51264, 51840, 52416, 52992, 53568, 54144, 54720, 55296, 55872, 56448, 57024]
  Detecções em:      [19, 56, 97, 146, 198, 244, 283, 350, 386, 447, 535, 576, 631, 689, 732, 765, 862, 929, 961, 1000, 1071, 1152, 1213, 1255, 1325, 1394, 1428, 1473, 1506, 1558, 1627, 1673, 1728, 1800, 1860, 1942, 2024, 2095, 2138, 2185, 2304, 2338, 2372, 2420, 2472, 2515, 2560, 2593, 2627, 2674, 2714, 2751, 2828, 2880, 2917, 2968, 3000, 3033, 3126, 3164, 3201, 3260, 3307, 3349, 3383, 3416, 3456, 3499, 3537, 3600, 3633, 3701, 3768, 3801, 3839, 3874, 3915, 3993, 4032, 4098, 4144, 4181, 4232, 4284, 4327, 4370, 4441, 4503, 4543, 4608, 4675, 4739, 4806, 4840, 4874, 4920, 4952, 4997, 5077, 5184, 5227, 5270, 5311, 5376, 5457, 5526, 5577, 5625, 5662, 5713, 5760, 5793, 5835, 5922, 5971, 6015, 6058, 6096, 6131, 6182, 6228, 6278, 6336, 6452, 6502, 6569, 6601, 6648, 6701, 6767, 6814, 6869, 6912, 6957, 7001, 7053, 7095, 7143, 7177, 7236, 7275, 7369, 7447, 7488, 7540, 7583, 7656, 7727, 7799, 7842, 7888, 7937, 7983, 8021, 8064, 8133, 8172, 8211, 8258, 8315, 8356, 8410, 8453, 8526, 8593, 8640, 8697, 8755, 8789, 8859, 8901, 8944, 8993, 9045, 9088, 9127, 9161, 9216, 9291, 9369, 9468, 9502, 9553, 9600, 9635, 9698, 9737, 9792, 9835, 9878, 9923, 9975, 10032, 10111, 10156, 10216, 10297, 10334, 10368, 10438, 10519, 10580, 10658, 10714, 10806, 10866, 10944, 10983, 11047, 11081, 11123, 11169, 11229, 11306, 11349, 11394, 11427, 11483, 11520, 11581, 11633, 11674, 11737, 11788, 11858, 11894, 11934, 11973, 12010, 12049, 12096, 12148, 12235, 12293, 12463, 12503, 12539, 12604, 12672, 12719, 12779, 12829, 12864, 12928, 12986, 13033, 13069, 13145, 13208, 13248, 13285, 13331, 13380, 13470, 13513, 13547, 13648, 13708, 13746, 13824, 13861, 13919, 14016, 14059, 14101, 14151, 14197, 14232, 14265, 14335, 14400, 14450, 14503, 14566, 14608, 14642, 14691, 14738, 14783, 14826, 14887, 14937, 14976, 15031, 15106, 15164, 15202, 15237, 15279, 15313, 15356, 15420, 15466, 15506, 15552, 15595, 15638, 15693, 15726, 15765, 15798, 15844, 15901, 15937, 15973, 16027, 16081, 16128, 16168, 16214, 16277, 16343, 16376, 16476, 16509, 16562, 16608, 16641, 16704, 16747, 16785, 16826, 16861, 16894, 16943, 16989, 17035, 17087, 17159, 17217, 17280, 17329, 17435, 17488, 17535, 17567, 17625, 17675, 17715, 17776, 17856, 17908, 17943, 17980, 18021, 18056, 18117, 18190, 18225, 18261, 18317, 18364, 18432, 18510, 18582, 18643, 18684, 18755, 18798, 18832, 18898, 18939, 19008, 19051, 19107, 19151, 19201, 19234, 19271, 19335, 19388, 19478, 19523, 19584, 19627, 19666, 19709, 19747, 19800, 19857, 19904, 19954, 20012, 20075, 20120, 20160, 20212, 20265, 20322, 20368, 20453, 20486, 20532, 20572, 20612, 20693, 20736, 20785, 20831, 20917, 20958, 21031, 21076, 21138, 21214, 21275, 21312, 21351, 21394, 21438, 21476, 21514, 21546, 21615, 21674, 21712, 21801, 21841, 21888, 21930, 21971, 22026, 22081, 22148, 22210, 22294, 22340, 22383, 22417, 22464, 22549, 22613, 22675, 22720, 22758, 22815, 22858, 22905, 22952, 23040, 23128, 23198, 23244, 23287, 23334, 23388, 23428, 23497, 23544, 23576, 23616, 23665, 23729, 23816, 23876, 23912, 23963, 23998, 24068, 24120, 24153, 24192, 24263, 24298, 24333, 24369, 24409, 24456, 24493, 24528, 24562, 24618, 24664, 24701, 24768, 24818, 24866, 24913, 24977, 25024, 25057, 25126, 25191, 25246, 25292, 25344, 25406, 25461, 25522, 25567, 25616, 25702, 25740, 25779, 25815, 25868, 25920, 25988, 26025, 26065, 26112, 26146, 26219, 26285, 26332, 26376, 26496, 26552, 26591, 26665, 26715, 26794, 26858, 26891, 26933, 26975, 27014, 27072, 27133, 27166, 27213, 27258, 27328, 27361, 27426, 27481, 27527, 27579, 27648, 27744, 27806, 27839, 27877, 27927, 27998, 28061, 28104, 28163, 28224, 28271, 28360, 28395, 28435, 28487, 28524, 28558, 28594, 28645, 28714, 28757, 28800, 28867, 28914, 28969, 29042, 29093, 29132, 29201, 29243, 29284, 29316, 29376, 29429, 29462, 29498, 29555, 29596, 29633, 29699, 29754, 29802, 29858, 29913, 29952, 30002, 30053, 30088, 30180, 30273, 30337, 30396, 30454, 30528, 30621, 30666, 30713, 30778, 30844, 30938, 31019, 31061, 31104, 31205, 31244, 31281, 31390, 31471, 31510, 31577, 31629, 31680, 31770, 31846, 31879, 31912, 31955, 32021, 32061, 32147, 32187, 32256, 32299, 32351, 32383, 32426, 32466, 32499, 32538, 32604, 32663, 32710, 32752, 32785, 32832, 32881, 33012, 33067, 33109, 33169, 33215, 33259, 33308, 33340, 33408, 33472, 33519, 33555, 33618, 33657, 33689, 33732, 33772, 33806, 33849, 33881, 33941, 33984, 34060, 34093, 34165, 34201, 34247, 34347, 34395, 34429, 34472, 34527, 34560, 34606, 34644, 34719, 34760, 34814, 34853, 34922, 34954, 35016, 35089, 35136, 35179, 35227, 35266, 35331, 35377, 35418, 35460, 35516, 35607, 35654, 35712, 35762, 35832, 35878, 35920, 35972, 36022, 36063, 36123, 36155, 36195, 36288, 36345, 36418, 36471, 36532, 36573, 36613, 36710, 36748, 36817, 36864, 36937, 37005, 37066, 37124, 37164, 37211, 37260, 37312, 37350, 37382, 37440, 37520, 37573, 37640, 37699, 37756, 37810, 37847, 37879, 37936, 37969, 38016, 38053, 38100, 38148, 38196, 38255, 38325, 38375, 38486, 38545, 38592, 38653, 38734, 38781, 38824, 38882, 38958, 38991, 39044, 39120, 39168, 39231, 39276, 39319, 39370, 39425, 39462, 39497, 39540, 39579, 39618, 39701, 39744, 39781, 39827, 39866, 39926, 39999, 40036, 40078, 40123, 40211, 40245, 40286, 40320, 40387, 40433, 40499, 40552, 40607, 40643, 40706, 40771, 40808, 40851, 40896, 40941, 40991, 41096, 41159, 41193, 41246, 41293, 41335, 41409, 41472, 41511, 41564, 41627, 41672, 41712, 41746, 41796, 41899, 41963, 42014, 42048, 42091, 42131, 42203, 42246, 42278, 42317, 42372, 42428, 42464, 42525, 42572, 42624, 42692, 42787, 42886, 42935, 42968, 43033, 43084, 43125, 43168, 43200, 43252, 43331, 43383, 43423, 43457, 43496, 43553, 43601, 43634, 43698, 43737, 43776, 43831, 43889, 43952, 44037, 44089, 44131, 44171, 44209, 44254, 44298, 44352, 44390, 44463, 44553, 44585, 44619, 44665, 44723, 44755, 44809, 44869, 44928, 44978, 45023, 45064, 45108, 45159, 45217, 45285, 45341, 45399, 45435, 45504, 45574, 45617, 45673, 45715, 45748, 45787, 45868, 45936, 45988, 46021, 46080, 46119, 46154, 46226, 46260, 46360, 46436, 46488, 46570, 46609, 46656, 46725, 46760, 46834, 46872, 46933, 46992, 47039, 47090, 47149, 47185, 47232, 47275, 47316, 47349, 47400, 47476, 47514, 47568, 47603, 47687, 47761, 47808, 47881, 47922, 47997, 48048, 48117, 48161, 48208, 48263, 48300, 48337, 48384, 48434, 48511, 48549, 48618, 48687, 48730, 48790, 48831, 48877, 48960, 49003, 49047, 49094, 49152, 49186, 49227, 49284, 49329, 49397, 49460, 49499, 49536, 49638, 49692, 49730, 49787, 49842, 49879, 49950, 50018, 50073, 50112, 50188, 50258, 50293, 50337, 50378, 50435, 50467, 50507, 50556, 50596, 50633, 50688, 50731, 50771, 50814, 50852, 50892, 50929, 51034, 51099, 51138, 51175, 51212, 51264, 51297, 51369, 51412, 51466, 51539, 51572, 51622, 51676, 51721, 51753, 51788, 51840, 51907, 51982, 52017, 52064, 52098, 52144, 52187, 52228, 52278, 52310, 52373, 52416, 52477, 52520, 52567, 52601, 52636, 52675, 52718, 52761, 52814, 52853, 52923, 52959, 52992, 53042, 53082, 53157, 53212, 53275, 53321, 53382, 53421, 53462, 53497, 53568, 53607, 53663, 53708, 53742, 53811, 53871, 53909, 53946, 54019, 54094, 54144, 54202, 54235, 54320, 54357, 54432, 54477, 54540, 54603, 54647, 54679, 54720, 54774, 54833, 54889, 54959, 55005, 55042, 55105, 55138, 55192, 55244, 55296, 55348, 55440, 55515, 55561, 55600, 55641, 55684, 55752, 55807, 55872, 55926, 55970, 56014, 56060, 56124, 56158, 56192, 56244, 56281, 56344, 56399, 56448, 56503, 56551, 56601, 56663, 56699, 56736, 56768, 56811, 56846, 56885, 56934, 56966, 57024, 57180, 57232, 57346, 57403, 57446, 57494, 57529]
  Comprimento preâmbulo: 64
  Limiar: 0.176
  Corretas: 99/100, Perdidas: 1, Falsas: 1000
✓ Frame sync simulation complete.

9.9 Lista de Exercícios Propostos

E1. Um CFO de 1 kHz a 100 ksym/s gera quantos graus de rotação por símbolo? Mostre numericamente.

E2. Qual a faixa não ambígua do estimador por preâmbulo repetido para $F_s = 1$ MHz e $D = 100$?

E3. Um Costas loop QPSK de primeira ordem tem ganho combinado $K_dK_0=5000\,\text{rad/s por rad}$. Qual o erro de fase linearizado em regime permanente para um CFO de 500 Hz? O valor está dentro da região aproximadamente linear do detector?

E4. Deduza a expressão da BER para QPSK com erro de fase $\Delta\phi$ e compare com BPSK.

E5. Implemente um detector de Gardner para 16-QAM e meça o jitter residual em função do SNR.

E6. Para um preâmbulo de 128 símbolos BPSK a $E_b/N_0 = 15$ dB, calcule $P_{FA}$ para limiares $\gamma = \{5, 10, 15\} \cdot \sigma_n^2 E_p$.

E7. Compare a BER de BPSK com $\epsilon = 0{,}05\,T_s$ e $\Delta\phi = 10°$ — qual erro causa maior degradação?

E8. Implemente o estimador de Schmidl & Cox para DVB-T (64/256/1024 pontos FFT) e meça a precisão do timing em função do SNR.

E9. Para um sistema OFDM com $N=64$ e $N_{cp}=16$, é possível determinar a janela segura de timing sem conhecer a duração do canal e a convenção de referência da janela FFT? Se o canal tiver $L_h=5$ amostras, qual é a margem total disponível dentro do CP?

E10. (Desafio) Implemente um receptor completo BPSK com: Costas loop, Gardner TED, detecção de preâmbulo, e decisor. Meça a BER final em função do SNR e compare com a curva teórica ideal.

9.10 Gabarito

E1. Rotação por símbolo: $360° \cdot \Delta f / R_s = 360° \cdot 1000 / 100000 = \boxed{3{,}6°}$.

E2. Faixa não ambígua: $\boxed{|\Delta f| < F_s/(2D) = 1\,\text{MHz}/200 = 5\,\text{kHz}}$.

E3. $\Delta\omega=2\pi(500)=3141{,}6\,\text{rad/s}$. Pela aproximação linear, $\epsilon_\phi^{\text{ss}}=\Delta\omega/(K_dK_0)=3141{,}6/5000\approx\boxed{0{,}628\,\text{rad}=36°}$. Esse erro já é grande para a linearização e, em QPSK, aproxima-se do limite da bacia desejada; deve-se aumentar a largura de malha ou realizar aquisição grosseira de CFO antes do rastreio.

E4. Para QPSK com erro de fase $\Delta\phi$: $P_b\approx Q(\sqrt{2E_b/N_0}\cos\Delta\phi)$ na aproximação que despreza o acoplamento entre eixos. A perda positiva é $L_\phi=-10\log_{10}(\cos^2\Delta\phi)$.

E5. Use a forma complexa $e_k=\operatorname{Re}\{y_k^*(y_{k-1/2}-y_{k+1/2})\}$, que combina I e Q e não pressupõe envelope constante. Meça o desvio padrão de $\hat\epsilon$ após descartar o transitório. Em regime dominado por ruído térmico, espera-se queda aproximadamente proporcional a $1/\sqrt{\mathrm{SNR}}$; em SNR alta, truncamento do interpolador e ruído próprio dos dados podem criar um piso de jitter.

E6. $P_{FA} = e^{-\gamma/(\sigma_n^2 E_p)}$. Para $\gamma = 5 \cdot \sigma_n^2 E_p$: $P_{FA} = e^{-5} \approx 6{,}7 \times 10^{-3}$. Para $\gamma = 10$: $e^{-10} \approx 4{,}5 \times 10^{-5}$. Para $\gamma = 15$: $e^{-15} \approx 3{,}1 \times 10^{-7}$.

E7. Desprezando a ISI, para sinc e $\epsilon=0{,}05$, $L_t\approx14{,}3(0{,}05)^2\approx0{,}036$ dB. Para $\Delta\phi=10^\circ=0{,}1745$ rad, $L_\phi\approx4{,}34(0{,}1745)^2\approx0{,}132$ dB. Assim, nesse modelo simplificado, $\boxed{\Delta\phi=10^\circ\text{ causa maior perda}}$; uma simulação com ISI deve confirmar a comparação para o pulso escolhido.

E8. Resposta: implementar o algoritmo da Seção “Estimador de Timing de Schmidl & Cox” para cada tamanho FFT, variar SNR de 0 a 20 dB, e plotar a probabilidade de detecção correta vs. SNR. A precisão melhora com SNR maior e com maior $N_{cp}/N$.

E9. Não: $N_{cp}$ sozinho não determina um intervalo simétrico $|\Delta|<N_{cp}$. É preciso saber onde $\Delta=0$ foi colocado e quantas amostras do CP são consumidas pela memória do canal. Com $L_h=5$, a memória é $L_h-1=4$ amostras e a margem total restante é $N_{cp}-(L_h-1)=16-4=\boxed{12\text{ amostras}}$. Essa margem pode ser distribuída de modo assimétrico entre avanço e atraso da janela.

E10. Receptor completo:

  1. Detecção de pacote por energia
  2. Estimador de CFO por preâmbulo repetido
  3. Costas loop para tracking fino de fase
  4. Gardner TED para timing
  5. Detecção de preâmbulo de frame
  6. Decisor BPSK
    BER final deve convergir para a curva teórica $Q(\sqrt{2E_b/N_0})$ para SNR > 8 dB, com degradação de $\approx 0{,}2$ dB devido ao jitter residual do loop.