16. Sincronizaci贸n

Este cap铆tulo cubre la sincronizaci贸n de se帽ales inal谩mbricas tanto en tiempo como en frecuencia, para corregir los desplazamientos de frecuencia de la portadora y realizar la alineaci贸n de tiempos a nivel de s铆mbolo y cuadro. Utilizaremos la t茅cnica de recuperaci贸n de reloj de Mueller y Muller, y el Costas Loop, en Python.

Introducci贸n a la sincronizaci贸n

Hemos discutido c贸mo transmitir digitalmente por aire, utilizando un esquema de modulaci贸n digital como QPSK y aplicando configuraci贸n de pulso para limitar el ancho de banda de la se帽al. La codificaci贸n de canales se puede utilizar para tratar canales ruidosos, como cuando la SNR del receptor es baja. Filtrar tanto como sea posible antes de procesar digitalmente la se帽al siempre ayuda. En este cap铆tulo investigaremos c贸mo se realiza la sincronizaci贸n en el extremo receptor. La sincronizaci贸n es un conjunto de procesamiento que ocurre antes de la demodulaci贸n y decodificaci贸n del canal. La cadena tx-channel-rx general se muestra a continuaci贸n, con los bloques analizados en este cap铆tulo resaltados en amarillo. (Este diagrama no lo abarca todo; la mayor铆a de los sistemas tambi茅n incluyen ecualizaci贸n y multiplexaci贸n).

The transmit receive chain, with the blocks discussed in this chapter highlighted in yellow, including time and frequency synchronization

Simulaci贸n de canal inal谩mbrico

Antes de aprender c贸mo implementar la sincronizaci贸n de tiempo y frecuencia, debemos hacer que nuestras se帽ales simuladas sean m谩s realistas. Sin agregar alg煤n retraso de tiempo aleatorio, el acto de sincronizar en el tiempo es trivial. De hecho, s贸lo necesita tener en cuenta el retraso de la muestra de cualquier filtro que utilice. Tambi茅n queremos simular un desplazamiento de frecuencia porque, como veremos, los osciladores no son perfectos; Siempre habr谩 cierta compensaci贸n entre la frecuencia central del transmisor y del receptor.

Examinemos el c贸digo Python para simular un retraso no entero y un desplazamiento de frecuencia. El c贸digo Python de este cap铆tulo comenzar谩 a partir del c贸digo que escribimos durante el ejercicio de Python para dar forma al pulso (haga clic a continuaci贸n si lo necesita); puede considerarlo el punto de partida del c贸digo de este cap铆tulo, y todo el c贸digo nuevo vendr谩 despu茅s.

Python Code from Pulse Shaping
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
import math

# this part came from pulse shaping exercise
num_symbols = 100
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 signal

# Create our raised-cosine filter
num_taps = 101
beta = 0.35
Ts = sps # Assume sample rate is 1 Hz, so sample period is 1, so *symbol* period is 8
t = np.arange(-51, 52) # 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)

# Filter our signal, in order to apply the pulse shaping
samples = np.convolve(pulse_train, h)

Dejaremos de lado el c贸digo relacionado con el grafico porque probablemente ya haya aprendido a graficar cualquier se帽al que desee. Hacer que las graficas se vean bonitas, como suele ocurrir en este libro de texto, requiere una gran cantidad de c贸digo adicional que no es necesario comprender.

Agregando el retardo

Podemos simular f谩cilmente un retardo desplazando muestras, pero solo simular un retraso que es un m煤ltiplo entero del per铆odo de nuestra muestra. En el mundo real, el retardo ser谩 de una fracci贸n de un per铆odo de muestra. Podemos simular el retardo de una fracci贸n de una muestra creando un filtro de 鈥渞etardo fraccional鈥, que pasa todas las frecuencias pero retrasa las muestras en una cantidad que no se limita al intervalo de la muestra. Puedes considerarlo como un filtro de paso total que aplica el mismo cambio de fase a todas las frecuencias. (Recuerde que un retardo de tiempo y un cambio de fase son equivalentes). El c贸digo Python para crear este filtro se muestra a continuaci贸n:

# Create and apply fractional delay filter
delay = 0.4 # fractional delay, in samples
N = 21 # number of taps
n = np.arange(-N//2, N//2) # ...-3,-2,-1,0,1,2,3...
h = np.sinc(n - delay) # calc filter taps
h *= np.hamming(N) # window the filter to make sure it decays to 0 on both sides
h /= np.sum(h) # normalize to get unity gain, we don't want to change the amplitude/power
samples = np.convolve(samples, h) # apply filter

Como puede ver, estamos calculando los taps del filtro usando una funci贸n sinc(). Un sinc en el dominio del tiempo es un rect谩ngulo en el dominio de la frecuencia, y nuestro rect谩ngulo para este filtro abarca todo el rango de frecuencia de nuestra se帽al. Este filtro no recontruye la se帽al, simplemente la retrasa en el tiempo. En nuestro ejemplo estamos retrasando 0,4 de una muestra. Tenga en cuenta que la aplicaci贸n de cualquier filtro retrasa la se帽al a la mitad de los taps del filtro menos uno, debido al acto de convolucionar la se帽al a trav茅s del filtro.

Si trazamos el 鈥渁ntes鈥 y el 鈥渄espu茅s鈥 del filtrado de una se帽al, podemos observar el retraso fraccionario. En nuestra trama nos acercamos s贸lo a un par de s铆mbolos. De lo contrario, el retraso fraccionario no ser谩 visible.

../_images/fractional-delay-filter.svg

Agregando desplazamiento en frecuencia

Para hacer nuestra se帽al simulada m谩s realista, aplicaremos un desplazamiento de frecuencia. Digamos que nuestra frecuencia de muestreo en esta simulaci贸n es 1 MHz (en realidad no importa cu谩l sea, pero ver谩s por qu茅 hace que sea m谩s f谩cil elegir un n煤mero). Si queremos simular un desplazamiento de frecuencia de 13 kHz (alg煤n n煤mero arbitrario), podemos hacerlo mediante el siguiente c贸digo:

# apply a freq offset
fs = 1e6 # assume our sample rate is 1 MHz
fo = 13000 # simulate freq offset
Ts = 1/fs # calc sample period
t = np.arange(0, Ts*len(samples), Ts) # create time vector
samples = samples * np.exp(1j*2*np.pi*fo*t) # perform freq shift

A continuaci贸n se muestra la se帽al antes y despu茅s de aplicar el desplazamiento de frecuencia.

Python simulation showing a signal before and after applying a frequency offset

No hemos estado graficando la porci贸n Q desde que transmitimos BPSK, por lo que la porci贸n Q siempre es cero. Ahora que estamos agregando un cambio de frecuencia para simular canales inal谩mbricos, la energ铆a se distribuye entre I y Q. A partir de este punto deber铆amos trazar tanto I como Q. Si茅ntase libre de sustituir su c贸digo por un desplazamiento de frecuencia diferente. Si reduce el desplazamiento a aproximadamente 1 kHz, podr谩 ver la sinusoide en la envolvente de la se帽al porque oscila lo suficientemente lenta como para abarcar varios s铆mbolos.

En cuanto a elegir una frecuencia de muestreo arbitraria, si examina el c贸digo, notar谩 que lo que importa es la relaci贸n entre fo y fs.

Puedes pretender que los dos bloques de c贸digo presentados anteriormente simulan un canal inal谩mbrico. El c贸digo debe aparecer despu茅s del c贸digo del lado de transmisi贸n (lo que hicimos en el cap铆tulo sobre configuraci贸n de pulsos) y antes del c贸digo del lado de recepci贸n, que es lo que exploraremos en el resto de este cap铆tulo.

Sincronizaci贸n en tiempo

Cuando transmitimos una se帽al de forma inal谩mbrica, llega al receptor con un cambio de fase aleatorio debido al tiempo recorrido. No podemos simplemente comenzar a muestrear los s铆mbolos a nuestra velocidad de s铆mbolo porque es poco probable que lo hagamos en el punto correcto del pulso, como se explica al final del cap铆tulo. Formador de Pulso. Revise las tres figuras al final de ese cap铆tulo si no las est谩 siguiendo.

La mayor铆a de las t茅cnicas de sincronizaci贸n de tiempo toman la forma de un bucle de bloqueo de fase (PLL); no estudiaremos los PLL aqu铆, pero es importante conocer el t茅rmino y, si est谩 interesado, puede leer sobre ellos por su cuenta. Los PLL son sistemas de circuito cerrado que utilizan retroalimentaci贸n para ajustar algo continuamente; en nuestro caso, un cambio de tiempo nos permite muestrear en el pico de los s铆mbolos digitales.

Puede imaginarse la recuperaci贸n de tiempo como un bloque en el receptor, que acepta un flujo de muestras y genera otro flujo de muestras (similar a un filtro). Programamos este bloque de recuperaci贸n de temporizaci贸n con informaci贸n sobre nuestra se帽al, siendo la m谩s importante la cantidad de muestras por s铆mbolo (o nuestra mejor suposici贸n, si no estamos 100% seguros de lo que se transmiti贸). Este bloque act煤a como un 鈥渄ecimador鈥, es decir, nuestra la muestra de salida ser谩 una fracci贸n del n煤mero de muestras entrantes. Queremos una muestra por s铆mbolo digital, por lo que la tasa de diezmado es simplemente las muestras por s铆mbolo. Si el transmisor transmite a 1 mill贸n de s铆mbolos por segundo y tomamos muestras a 16 Msps, recibiremos 16 muestras por s铆mbolo. Esa ser谩 la frecuencia de muestreo que entrar谩 en el bloque de sincronizaci贸n de tiempo. La frecuencia de muestreo que sale del bloque ser谩 de 1 Msps porque queremos una muestra por s铆mbolo digital.

La mayor铆a de los m茅todos de recuperaci贸n de tiempo se basan en el hecho de que nuestros s铆mbolos digitales suben y luego bajan, y la cresta es el punto en el que queremos muestrear el s铆mbolo. Para decirlo de otra manera, tomamos una muestra del punto m谩ximo despu茅s de tomar el valor absoluto:

../_images/symbol_sync2.png

Existen muchos m茅todos para recuperar la sincronizaci贸n, la mayor铆a parecidos a un PLL. Generalmente, la diferencia entre ellos es la ecuaci贸n utilizada para realizar la 鈥渃orrecci贸n鈥 en el desplazamiento de tiempo, que denotamos como \mu o mu en el c贸digo. El valor de mu se actualiza en cada iteraci贸n del bucle. Est谩 en unidades de muestras, y se puede considerar cu谩nto tenemos que desplazarnos para poder tomar muestras en el momento 鈥減erfecto鈥. Entonces, si mu = 3.61 entonces eso significa que tenemos que cambiar la entrada en 3.61 muestras para muestrear en el lugar correcto. Debido a que tenemos 8 muestras por s铆mbolo, si mu supera 8, simplemente volver谩 a cero.

El siguiente c贸digo Python implementa la t茅cnica de recuperaci贸n del reloj de Mueller y Muller.

mu = 0 # initial estimate of phase of sample
out = np.zeros(len(samples) + 10, dtype=np.complex)
out_rail = np.zeros(len(samples) + 10, dtype=np.complex) # 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)
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)
    mu += sps + 0.3*mm_val
    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)
samples = out # only include this line if you want to connect this code snippet with the Costas Loop later on

El bloque de recuperaci贸n de temporizaci贸n 鈥渞ecibe鈥 las muestras y produce una muestra de salida una a la vez (tenga en cuenta que i_out se incrementa en 1 en cada iteraci贸n del bucle). El bloque de recuperaci贸n no solo usa las muestras 鈥渞ecibidas鈥 una tras otra debido a la forma en que se ajusta el bucle i_in. Saltar谩 algunas muestras en un intento de extraer la muestra 鈥渃orrecta鈥, que ser铆a la que se encuentra en el pico del pulso. A medida que el bucle procesa muestras, se sincroniza lentamente con el s铆mbolo, o al menos lo intenta ajustando mu. Dada la estructura del c贸digo, la parte entera de mu se agrega a i_in y luego se elimina de mu (tenga en cuenta que mm_val puede ser negativo o positivo cada bucle). Una vez que est茅 completamente sincronizado, el bucle solo debe extraer la muestra central de cada s铆mbolo/pulso. Puede ajustar la constante 0.3 , lo que cambiar谩 la rapidez con la que reacciona el circuito de retroalimentaci贸n; un valor m谩s alto har谩 que reaccione m谩s r谩pido, pero con mayor riesgo de problemas de estabilidad.

El siguiente gr谩fico muestra un ejemplo de resultado en el que hemos deshabilitado el retardo de tiempo fraccionario as铆 como el desplazamiento de frecuencia. Solo mostramos I porque Q es todo ceros con el desplazamiento de frecuencia desactivado. Los tres gr谩ficos est谩n apilados uno encima del otro para mostrar c贸mo se alinean verticalmente los bits.

Grafica superior

S铆mbolos BPSK originales, es decir, 1 y -1. Recuerde que hay ceros en el medio porque queremos 8 muestras por s铆mbolo.

Grafica intermedia

Muestras despu茅s de la conformaci贸n del pulso pero antes del sincronizador.

Grafica inferior

Salida del sincronizador de s铆mbolos, que proporciona solo 1 muestra por s铆mbolo. Es decir, estas muestras se pueden alimentar directamente a un demodulador, que para BPSK verifica si el valor es mayor o menor que 0.

../_images/time-sync-output.svg

Centr茅monos en el gr谩fico inferior, que es la salida del sincronizador. Se necesitaron casi 30 s铆mbolos para que la sincronizaci贸n se fijara en el retardo correcto. Inevitablemente al tiempo que tardan los sincronizadores en ajustarse; muchos protocolos de comunicaciones utilizan un pre谩mbulo que contiene una secuencia de sincronizaci贸n: act煤a como una forma de anunciar que ha llegado un nuevo paquete y le da tiempo al receptor para sincronizarse con 茅l. Pero despu茅s de estas ~30 muestras, el sincronizador funciona perfectamente. Nos quedan 1 y -1 perfectos que coinciden con los datos de entrada. Ayuda que a este ejemplo no se le haya agregado ning煤n ruido. Si茅ntase libre de agregar ruido o cambios de tiempo y ver c贸mo se comporta el sincronizador. Si us谩ramos QPSK entonces estar铆amos tratando con n煤meros complejos, pero el enfoque ser铆a el mismo.

Time Synchronization with Interpolation

Los sincronizadores de s铆mbolos tienden a interpolar las muestras de entrada en alg煤n n煤mero, por ejemplo, 16, de modo que puedan cambiar en una fracci贸n de muestra. Es poco probable que el retraso aleatorio causado por el canal inal谩mbrico sea un m煤ltiplo exacto de una muestra, por lo que es posible que el pico del s铆mbolo no se produzca en una muestra. Esto es especialmente cierto en un caso en el que solo se reciben 2 o 4 muestras por s铆mbolo. Al interpolar las muestras, nos brinda la capacidad de muestrear 鈥渆ntre鈥 muestras reales, para alcanzar el pico de cada s铆mbolo. La salida del sincronizador sigue siendo solo 1 muestra por s铆mbolo. Las propias muestras de entrada se interpolan.

Nuestro c贸digo Python de sincronizaci贸n de tiempo que implementamos anteriormente no inclu铆a ninguna interpolaci贸n. Para expandir nuestro c贸digo, habilite el retraso de tiempo fraccionario que implementamos al principio de este cap铆tulo para que nuestra se帽al recibida tenga un retraso m谩s realista. Deje la compensaci贸n de frecuencia desactivada por ahora. Si vuelve a ejecutar la simulaci贸n, encontrar谩 que el sincronizador no logra sincronizarse completamente con la se帽al. Esto se debe a que no estamos interpolando, por lo que el c贸digo no tiene forma de 鈥渕uestrear entre muestras鈥 para compensar el retraso fraccionario. Agreguemos la interpolaci贸n.

Una forma r谩pida de interpolar una se帽al en Python es usar scipy. signal.resample o signal.resample_poly. Ambas funciones hacen lo mismo pero funcionan de manera diferente por dentro. Usaremos la 煤ltima funci贸n porque tiende a ser m谩s r谩pida. Interpolaremos por 16 (esto se elige arbitrariamente, puede probar con diferentes valores), es decir, insertaremos 15 muestras adicionales entre cada muestra. Se puede hacer en una l铆nea de c贸digo y deber铆a suceder antes de realizar la sincronizaci贸n de tiempo (antes del fragmento de c贸digo grande previo). Gr谩ficamos el antes y el despu茅s para ver la diferencia:

samples_interpolated = signal.resample_poly(samples, 16, 1)

# Plot the old vs new
plt.figure('before interp')
plt.plot(samples,'.-')
plt.figure('after interp')
plt.plot(samples_interpolated,'.-')
plt.show()

Si nos acercamos mucho, vemos que es la misma se帽al, solo que con 16 veces m谩s puntos:

Example of interpolation a signal, using Python

Con suerte, la raz贸n por la que necesitamos interpolar dentro del bloque de sincronizaci贸n de tiempo se est谩 aclarando. Estas muestras adicionales nos permitir谩n tener en cuenta una fracci贸n del retraso de una muestra. Adem谩s de calcular samples_interpolated, tambi茅n tenemos que modificar una l铆nea de c贸digo en nuestro sincronizador de tiempo. Cambiaremos la primera l铆nea dentro del bucle while para que se convierta en:

out[i_out] = samples_interpolated[i_in*16 + int(mu*16)]

Hicimos un par de cosas aqu铆. Primero, ya no podemos usar simplemente i_in como 铆ndice de muestra de entrada. Tenemos que multiplicarlo por 16 porque interpolamos nuestras muestras de entrada por 16. Recuerde que el bucle de retroalimentaci贸n ajusta la variable mu. Representa el retraso que nos lleva a muestrear en el momento adecuado. Recuerde tambi茅n que despu茅s de calcular el nuevo valor de mu, agregamos la parte entera a i_in. Ahora usaremos la parte restante, que es un flotador de 0 a 1, y representa la fracci贸n de una muestra que necesitamos retrasar. Antes no pod铆amos retrasar una fracci贸n de muestra, pero ahora s铆 lo podemos hacer, al menos en incrementos de 16avos de muestra. Lo que hacemos es multiplicar mu por 16 para calcular cu谩ntas muestras de nuestra se帽al interpolada necesitamos retrasar. Y luego tenemos que redondear ese n煤mero, ya que el valor entre par茅ntesis en 煤ltima instancia es un 铆ndice y debe ser un n煤mero entero. Si este p谩rrafo no tiene sentido, intente volver al c贸digo inicial de recuperaci贸n del reloj de Mueller y Muller y lea tambi茅n los comentarios junto a cada l铆nea de c贸digo.

El resultado de la gr谩fica actual de este nuevo c贸digo deber铆a verse m谩s o menos igual que antes. Todo lo que realmente hicimos fue hacer nuestra simulaci贸n m谩s realista agregando el retraso de la muestra fraccionaria, y luego agregamos el interpolador al sincronizador para compensar ese retraso de muestra fraccionaria.

Si茅ntete libre de jugar con diferentes factores de interpolaci贸n, es decir, cambiar todos los 16 a alg煤n otro valor. Tambi茅n puede intentar habilitar el desplazamiento de frecuencia o agregar ruido blanco gaussiano a la se帽al antes de que se reciba, para ver c贸mo eso afecta el rendimiento de la sincronizaci贸n (pista: es posible que deba ajustar ese multiplicador de 0,3).

Si habilitamos solo el desplazamiento de frecuencia usando una frecuencia de 1 kHz, obtenemos el siguiente rendimiento de sincronizaci贸n de tiempo. Tenemos que mostrar tanto I como Q ahora que agregamos un desplazamiento de frecuencia:

A python simulated signal with a slight frequency offset

Puede que sea dif铆cil de ver, pero la sincronizaci贸n en tiempo sigue funcionando bien. Se necesitan entre 20 y 30 s铆mbolos antes de que quede enganchado. Sin embargo, hay un patr贸n sinusoide porque todav铆a tenemos un desplazamiento de frecuencia, y aprenderemos c贸mo manejarlo en la siguiente secci贸n.

A continuaci贸n se muestra el gr谩fico IQ (tambi茅n conocido como gr谩fico de constelaci贸n) de la se帽al antes y despu茅s de la sincronizaci贸n. Recuerde que puede trazar muestras en un diagrama IQ usando un diagrama de dispersi贸n: plt.plot(np.real(samples), np.imag(samples), '.'). En la siguiente animaci贸n hemos omitido espec铆ficamente los primeros 30 s铆mbolos. Ocurrieron antes de que finalizara la sincronizaci贸n en tiempo. Los s铆mbolos de la izquierda est谩n todos aproximadamente en el c铆rculo unitario debido al desplazamiento de frecuencia.

An IQ plot of a signal before and after time synchronization

Para obtener a煤n m谩s informaci贸n, podemos observar la constelaci贸n a lo largo del tiempo para discernir qu茅 est谩 sucediendo realmente con los s铆mbolos. Al principio, durante un breve per铆odo de tiempo, los s铆mbolos no son 0 ni est谩n en el c铆rculo unitario. Ese es el per铆odo en el que la sincronizaci贸n de tiempo encuentra el retraso correcto. Es muy r谩pido, 隆observa atentamente! El giro es solo el desplazamiento de frecuencia. La frecuencia es un cambio constante de fase, por lo que un desplazamiento de frecuencia provoca el giro del BPSK (creando un c铆rculo en el gr谩fico est谩tico/persistente anterior).

Animation of an IQ plot of BPSK with a frequency offset, showing spinning clusters

Con suerte, al ver un ejemplo de sincronizaci贸n de tiempo que este sucediendo, tendr谩 una idea de lo que hace y una idea general de c贸mo funciona. En la pr谩ctica, el bucle while que creamos solo funcionar铆a en una peque帽a cantidad de muestras a la vez (por ejemplo, 1000). Debe recordar el valor de mu entre llamadas a la funci贸n de sincronizaci贸n, as铆 como los 煤ltimos valores de out y out_rail.

A continuaci贸n examinaremos la sincronizaci贸n de frecuencia, que dividimos en sincronizaci贸n de frecuencia gruesa y fina. Lo grueso suele aparecer antes de la sincronizaci贸n de tiempo, mientras que lo fino viene despu茅s.

Sincronizaci贸n de frecuencia no granular

Aunque le decimos al transmisor y al receptor que operen en la misma frecuencia central, habr谩 un ligero desplazamiento de frecuencia entre los dos debido a imperfecciones en el hardware (por ejemplo, el oscilador) o a un desplazamiento Doppler debido al movimiento. Este desplazamiento de frecuencia ser谩 peque帽o en relaci贸n con la frecuencia portadora, pero incluso un desplazamiento peque帽o puede alterar una se帽al digital. Es probable que la compensaci贸n cambie con el tiempo, lo que requerir谩 un circuito de retroalimentaci贸n siempre activo para corregir la compensaci贸n. Como ejemplo, el oscilador dentro del Pluto tiene una especificaci贸n de compensaci贸n m谩xima de 25 PPM. Eso es 25 partes por mill贸n en relaci贸n con la frecuencia central. Si est谩 sintonizado a 2,4 GHz, el desplazamiento m谩ximo ser铆a de +/- 60 kHz. Las muestras que nos proporciona nuestro SDR est谩n en banda base, lo que hace que cualquier compensaci贸n de frecuencia se manifieste en esa se帽al de banda base. Una se帽al BPSK con un peque帽o desplazamiento de portadora se parecer谩 al gr谩fico de tiempo siguiente, lo que obviamente no es bueno para demodular bits. Debemos eliminar cualquier compensaci贸n de frecuencia antes de la demodulaci贸n.

../_images/carrier-offset.png

La sincronizaci贸n de frecuencia generalmente se divide en sincronizaci贸n gruesa y sincronizaci贸n fina, donde la sincronizaci贸n gruesa corrige grandes desplazamientos del orden de kHz o m谩s, mientras que la sincronizaci贸n fina corrige lo que queda. Lo grueso ocurre antes de la sincronizaci贸n de tiempo, mientras que lo fino ocurre despu茅s.

Matem谩ticamente, si tenemos una se帽al de banda base s(t) y est谩 experimentando un desplazamiento de frecuencia (tambi茅n conocido como portadora) de f_o Hz, podemos representar lo que se recibe como:

r(t) = s(t) e^{j2\pi f_o t} + n(t)

donde n(t) es el ruido.

El primer truco que aprenderemos, para realizar una estimaci贸n aproximada del desplazamiento de frecuencia (si podemos estimar el desplazamiento de frecuencia, entonces podemos deshacerlo), es tomar el cuadrado de nuestra se帽al. Ignoremos el ruido por ahora, para simplificar las matem谩ticas:

r^2(t) = s^2(t) e^{j4\pi f_o t}

Veamos qu茅 sucede cuando tomamos el cuadrado de nuestra se帽al s(t) considerando lo que har铆a QPSK. Elevar al cuadrado n煤meros complejos conduce a un comportamiento interesante, especialmente cuando hablamos de constelaciones como BPSK y QPSK. La siguiente animaci贸n muestra lo que sucede cuando elevas QPSK al cuadrado y luego lo vuelves a elevar al cuadrado. Utilic茅 espec铆ficamente QPSK en lugar de BPSK porque puedes ver que cuando elevas QPSK al cuadrado una vez, b谩sicamente obtienes BPSK. Y luego, despu茅s de un cuadrado m谩s, se convierte en un grupo. (Gracias a http://ventrella.com/ComplexSquaring/ que cre贸 esta interesante aplicaci贸n web).

../_images/squaring-qpsk.gif

Veamos qu茅 sucede cuando a nuestra se帽al QPSK se le aplica una peque帽a rotaci贸n de fase y escala de magnitud, lo cual es m谩s realista:

../_images/squaring-qpsk2.gif

Todav铆a se convierte en un grupo, s贸lo que con un cambio de fase. La conclusi贸n principal aqu铆 es que si elevas QPSK al cuadrado dos veces (y BPSK una vez), fusionar谩s los cuatro grupos de puntos en un solo grupo. 驴Por qu茅 es eso 煤til? Bueno, al fusionar los grupos, 隆b谩sicamente estamos eliminando la modulaci贸n! Si todos los puntos est谩n ahora en el mismo grupo, es como tener un mont贸n de constantes en fila. Es como si ya no hubiera modulaci贸n, y lo 煤nico que queda es la sinusoide causada por el desplazamiento de frecuencia (tambi茅n tenemos ruido pero sigamos ignor谩ndolo por ahora). Resulta que tienes que elevar al cuadrado la se帽al N veces, donde N es el orden del esquema de modulaci贸n utilizado, lo que significa que este truco s贸lo funciona si conoces el esquema de modulaci贸n de antemano. La ecuaci贸n es realmente:

r^N(t) = s^N(t) e^{j2N\pi f_o t}

Para el caso de BPSK tenemos un esquema de modulaci贸n de orden 2, por lo que usaremos la siguiente ecuaci贸n para la sincronizaci贸n en frecuencia gruesa:

r^2(t) = s^2(t) e^{j4\pi f_o t}

Descubrimos qu茅 sucede con la parte s(t) de la ecuaci贸n, pero 驴qu茅 pasa con la parte sinusoide (tambi茅n conocida como exponencial compleja)? Como podemos ver, est谩 agregando el t茅rmino N, lo que lo hace equivalente a una sinusoide con una frecuencia de Nf_o en lugar de solo f_o. Un m茅todo simple para calcular f_o es tomar la FFT de la se帽al despu茅s de elevarla al cuadrado N veces y ver d贸nde ocurre el pico. Simul茅moslo en Python. Volveremos a generar nuestra se帽al BPSK y, en lugar de aplicarle un retraso fraccionario, aplicaremos un desplazamiento de frecuencia multiplicando la se帽al por e^{j2\pi f_o t} tal como lo hicimos en cap铆tulo Filtros para convertir un filtro paso bajo en un filtro paso alto.

Utilizando el c贸digo del principio de este cap铆tulo, aplique un desplazamiento de frecuencia de +13 kHz a su se帽al digital. Podr铆a suceder justo antes o despu茅s de que se agregue el retraso fraccionario; no importa cu谩l. De todos modos, debe suceder despu茅s del formador de pulso, pero antes de realizar cualquier funci贸n del lado de recepci贸n, como la sincronizaci贸n de tiempo.

Ahora que tenemos una se帽al con un desplazamiento de frecuencia de 13 kHz, grafiquemos la FFT antes y despu茅s de elevar al cuadrado, para ver qu茅 sucede. A estas alturas ya deber铆as saber c贸mo realizar una FFT, incluidas las operaciones abs() y fftshift(). Para este ejercicio no importa si tomas el log o si lo elevas al cuadrado despu茅s de tomar los abs().

Primero mire la se帽al antes de elevarla al cuadrado (solo una FFT normal):

psd = np.fft.fftshift(np.abs(np.fft.fft(samples)))
f = np.linspace(-fs/2.0, fs/2.0, len(psd))
plt.plot(f, psd)
plt.show()
../_images/coarse-freq-sync-before.svg

En realidad, no vemos ning煤n pico asociado con el desplazamiento de la portadora. Est谩 cubierto por nuestra se帽al.

Ahora con el cuadrado agregado (solo una potencia de 2 porque es BPSK):

# Add this before the FFT line
samples = samples**2

Tenemos que acercarnos mucho para ver en qu茅 frecuencia est谩 el pico:

../_images/coarse-freq-sync.svg

Puede intentar aumentar la cantidad de s铆mbolos simulados (por ejemplo, 1000 s铆mbolos) para que tengamos suficientes muestras con las que trabajar. Cuantas m谩s muestras entren en nuestra FFT, m谩s precisa ser谩 nuestra estimaci贸n del desplazamiento de frecuencia. S贸lo como recordatorio, el c贸digo anterior debe aparecer antes del sincronizador de tiempo.

El pico de frecuencia desplazada aparece en Nf_o. Necesitamos dividir este contenedor (26,6 kHz) por 2 para encontrar nuestra respuesta final, que est谩 muy cerca del desplazamiento de frecuencia de 13 kHz que aplicamos al comienzo del cap铆tulo. Si hubieras jugado con ese n煤mero y ya no es 13 kHz, est谩 bien. Solo aseg煤rese de saber en qu茅 lo configur贸.

Debido a que nuestra frecuencia de muestreo es de 1 MHz, las frecuencias m谩ximas que podemos ver son de -500 kHz a 500 kHz. Si llevamos nuestra se帽al a la potencia de N, eso significa que solo podemos 鈥渧er鈥 desplazamientos de frecuencia hasta 500e3/N, o en el caso de BPSK +/- 250 kHz. Si estuvi茅ramos recibiendo una se帽al QPSK, entonces solo ser铆a +/- 125 kHz, y el desplazamiento de la portadora mayor o menor que eso estar铆a fuera de nuestro rango usando esta t茅cnica. Para darle una idea del cambio Doppler, si estuviera transmitiendo en la banda de 2,4 GHz y el transmisor o el receptor viajaba a 60 mph (lo que importa es la velocidad relativa), causar铆a un cambio de frecuencia de 214 Hz. La compensaci贸n debida a un oscilador de baja calidad probablemente ser谩 el principal culpable de esta situaci贸n.

En realidad, la correcci贸n de este desplazamiento de frecuencia se realiza exactamente como simulamos el desplazamiento en primer lugar: multiplicando por una exponencial compleja, excepto que con un signo negativo ya que queremos eliminar el desplazamiento.

max_freq = f[np.argmax(psd)]
Ts = 1/fs # calc sample period
t = np.arange(0, Ts*len(samples), Ts) # create time vector
samples = samples * np.exp(-1j*2*np.pi*max_freq*t/2.0)

Depende de ti si desea corregirlo o cambiar el desplazamiento de frecuencia inicial que aplicamos al principio a un n煤mero m谩s peque帽o (como 500 Hz) para probar la sincronizaci贸n fina de frecuencia que ahora aprenderemos a hacer.

Sincronizaci贸n de frecuencia fina

A continuaci贸n cambiaremos de marcha a sincronizaci贸n fina en frecuencia. El truco anterior es m谩s para sincronizaci贸n aproximada y no es una operaci贸n de bucle cerrado (tipo retroalimentaci贸n). Pero para una sincronizaci贸n precisa de frecuencias necesitaremos un bucle de retroalimentaci贸n a trav茅s del cual transmitamos muestras, que una vez m谩s ser谩 una forma de PLL. Nuestro objetivo es conseguir que la compensaci贸n de frecuencia sea cero y mantenerla all铆, incluso si la compensaci贸n cambia con el tiempo. Tenemos que realizar un seguimiento continuo de la compensaci贸n. Las t茅cnicas de sincronizaci贸n fina en frecuencia funcionan mejor con una se帽al que ya se ha sincronizado en el tiempo a nivel de s铆mbolo, por lo que el c贸digo que analizamos en esta secci贸n vendr谩 despu茅s de la sincronizaci贸n de tiempo.

Usaremos una t茅cnica llamada Costas Loop. Es una forma de PLL dise帽ada espec铆ficamente para la correcci贸n de compensaci贸n de frecuencia portadora para se帽ales digitales como BPSK y QPSK. Fue inventado por John P. Costas en General Electric en la d茅cada de 1950 y tuvo un gran impacto en las comunicaciones digitales modernas. Costas Loop eliminar谩 el desplazamiento de frecuencia y al mismo tiempo arreglar谩 cualquier desplazamiento de fase. La energ铆a est谩 alineada con el eje I. La frecuencia es solo un cambio de fase para que puedan rastrearse como uno solo. El Costas Loop se resume utilizando el siguiente diagrama (tenga en cuenta que los 1/2 se han omitido de las ecuaciones porque funcionalmente no importan).

Costas loop diagram including math expressions, it is a form of PLL used in RF signal processing

El oscilador controlado por voltaje (VCO) es simplemente un generador de ondas sen/cos que utiliza una frecuencia basada en la entrada. En nuestro caso, al estar simulando un canal inal谩mbrico, no se trata de un voltaje, sino de un nivel representado por una variable. Determina la frecuencia y fase de las ondas sinusoidales y coseno generadas. Lo que hace es multiplicar la se帽al recibida por una sinusoide generada internamente, en un intento de deshacer el desplazamiento de frecuencia y fase. Este comportamiento es similar a c贸mo un SDR realiza una conversi贸n descendente y crea las ramas I y Q.

A continuaci贸n se muestra el c贸digo Python que es nuestro Costas Loop:

N = len(samples)
phase = 0
freq = 0
# These next two params is what to adjust, to make the feedback loop faster or slower (which impacts stability)
alpha = 0.132
beta = 0.00932
out = np.zeros(N, dtype=np.complex)
freq_log = []
for i in range(N):
    out[i] = samples[i] * np.exp(-1j*phase) # adjust the input sample by the inverse of the estimated phase offset
    error = np.real(out[i]) * np.imag(out[i]) # This is the error formula for 2nd order Costas Loop (e.g. for BPSK)

    # Advance the loop (recalc phase and freq offset)
    freq += (beta * error)
    freq_log.append(freq * fs / (2*np.pi)) # convert from angular velocity to Hz for logging
    phase += freq + (alpha * error)

    # Optional: Adjust phase so its always between 0 and 2pi, recall that phase wraps around every 2pi
    while phase >= 2*np.pi:
        phase -= 2*np.pi
    while phase < 0:
        phase += 2*np.pi

# Plot freq over time to see how long it takes to hit the right offset
plt.plot(freq_log,'.-')
plt.show()

Hay mucho aqu铆, as铆 que repas茅moslo. Algunas l铆neas son simples y otras s煤per complicadas. samples es nuestra entrada y out son las muestras de salida. phase y frequency son como mu del c贸digo de sincronizaci贸n en tiempo. Contienen las estimaciones para los desplazamientos actuales, y en cada iteraci贸n del bucle creamos las muestras de salida multiplicando las muestras de entrada por np.exp(-1j*phase). La variable error contiene la m茅trica de 鈥渆rror鈥, y para que Costas Loop de segundo orden es una ecuaci贸n muy simple. Multiplicamos la parte real de la muestra (I) por la parte imaginaria (Q), y como Q debe ser igual a cero para BPSK, la funci贸n de error se minimiza cuando no hay ning煤n desplazamiento de fase o frecuencia que provoque que la energ铆a se desplace de I. a Q. Para un Costas Loop de cuarto orden, sigue siendo relativamente simple, pero no es una sola l铆nea, ya que tanto I como Q tendr谩n energ铆a incluso cuando no haya compensaci贸n de fase o frecuencia, para QPSK. Si tiene curiosidad sobre c贸mo se ve, haga clic a continuaci贸n, pero no lo usaremos en nuestro c贸digo por ahora. La raz贸n por la que esto funciona para QPSK es porque cuando tomas el valor absoluto de I y Q, obtendr谩s +1+1j, y si no hay compensaci贸n de fase o frecuencia, entonces la diferencia entre el valor absoluto de I y Q deber铆a ser cercana. a cero.

Ecuaci贸n de error Costas Loop de 4to orden (para aquellos curiosos)
# For QPSK
def phase_detector_4(sample):
    if sample.real > 0:
        a = 1.0
    else:
        a = -1.0
    if sample.imag > 0:
        b = 1.0
    else:
        b = -1.0
    return a * sample.imag - b * sample.real

Las variables alpha y beta definen qu茅 tan r谩pido se actualiza la fase y la frecuencia, respectivamente. Hay alguna teor铆a detr谩s de por qu茅 eleg铆 esos dos valores; sin embargo, no lo abordaremos aqu铆. Si tienes curiosidad, puedes intentar modificar alpha y/o beta para ver qu茅 sucede.

Registramos el valor de freq en cada iteraci贸n para poder graficarlo al final, para ver c贸mo el Costas Loop converge hacia el desplazamiento de frecuencia correcto. Tenemos que multiplicar freq por la frecuencia de muestreo y convertir de frecuencia angular a Hz, dividiendo por 2\pi. Tenga en cuenta que si realiz贸 la sincronizaci贸n de tiempo antes del Costas Loop, tambi茅n tendr谩 que dividir por su sps (por ejemplo, 8), porque las muestras que salen de la sincronizaci贸n de tiempo tienen una velocidad igual a su original. frecuencia de muestreo dividida por sps.

Por 煤ltimo, despu茅s de recalcular la fase, agregamos o eliminamos suficientes 2 \pi para mantener la fase entre 0 y 2 \pi, lo que ajusta la fase.

Nuestra se帽al antes y despu茅s del Costas Loop se ve as铆:

Python simulation of a signal before and after using a Costas Loop

Y la estimaci贸n del desplazamiento de frecuencia a lo largo del tiempo, estableciendo el desplazamiento correcto (en esta se帽al de ejemplo se utiliz贸 un desplazamiento de -300 Hz):

../_images/costas-loop-freq-tracking.svg

Se necesitan casi 70 muestras para que el algoritmo se enganche en el desplazamiento de frecuencia correcto. Puede ver que en mi ejemplo simulado quedaron alrededor de -300 Hz despu茅s de la sincronizaci贸n de frecuencia aproximada. El tuyo puede variar. Como mencion茅 antes, puedes desactivar la sincronizaci贸n de frecuencia aproximada y establecer el desplazamiento de frecuencia inicial en el valor que desees y ver si Costas Loop lo resuelve.

Costas Loop, adem谩s de eliminar el desplazamiento de frecuencia, aline贸 nuestra se帽al BPSK para que est茅 en la porci贸n I, haciendo que Q vuelva a ser cero. Es un efecto secundario conveniente del Costas Loop y permite que el Costas Loop act煤e esencialmente como nuestro demodulador. Ahora todo lo que tenemos que hacer es tomar I y ver si es mayor o menor que cero. En realidad, no sabremos c贸mo hacer que 0 y 1 sean negativos y positivos porque puede haber o no una inversi贸n; no hay forma de que Costas Loop (o nuestra sincronizaci贸n en tiempo) lo sepa. Ah铆 es donde entra en juego la codificaci贸n diferencial. Elimina la ambig眉edad porque los 1 y 0 se basan en si el s铆mbolo cambi贸 o no, no en si era +1 o -1. Si agregamos codificaci贸n diferencial, todav铆a estar铆amos usando BPSK. Estar铆amos agregando un bloque de codificaci贸n diferencial justo antes de la modulaci贸n en el lado de transmisi贸n y justo despu茅s de la demodulaci贸n en el lado de recepci贸n.

A continuaci贸n se muestra una animaci贸n de la sincronizaci贸n de tiempo m谩s la sincronizaci贸n de frecuencia en ejecuci贸n. La sincronizaci贸n de tiempo en realidad ocurre casi de inmediato, pero la sincronizaci贸n de frecuencia requiere casi toda la animaci贸n para establecerse por completo, y esto se debe a que alpha y beta se establecieron demasiado bajos, en 0,005 y 0,001 respectivamente. El c贸digo utilizado para generar esta animaci贸n se puede encontrar aqui.

Costas loop animation

Sincronizaci贸n de Trama

Hemos discutido c贸mo corregir cualquier desplazamiento en tiempo, frecuencia y fase en nuestra se帽al recibida. Pero la mayor铆a de los protocolos de comunicaciones modernos no se limitan a transmitir bits al 100% del ciclo de trabajo. En su lugar, utilizan paquetes/tramas. En el receptor debemos poder identificar cu谩ndo comienza una nueva trama. Habitualmente, el encabezado de la trama (en la capa MAC) contiene cu谩ntos bytes hay en la trama. Podemos usar esa informaci贸n para saber cu谩nto mide la trama, por ejemplo, en unidades, muestras o s铆mbolos. No obstante, detectar el inicio del fotograma es una tarea completamente independiente. A continuaci贸n se muestra un ejemplo de estructura de trama WiFi. Observe c贸mo lo primero que se transmite es un encabezado de capa PHY, y la primera mitad de ese encabezado es un 鈥減re谩mbulo鈥. Este pre谩mbulo contiene una secuencia de sincronizaci贸n que el receptor utiliza para detectar el inicio de las tramas, y es una secuencia conocida por el receptor de antemano.

../_images/wifi-frame.png

Un m茅todo com煤n y sencillo para detectar estas secuencias en el receptor es correlacionar las muestras recibidas con la secuencia conocida. Cuando ocurre la secuencia, esta correlaci贸n cruzada se asemeja a una autocorrelaci贸n (con ruido agregado). Normalmente, las secuencias elegidas para los pre谩mbulos tendr谩n buenas propiedades de autocorrelaci贸n, como que la autocorrelaci贸n de la secuencia crea un 煤nico pico fuerte en 0 y ning煤n en otro pico. Un ejemplo son los c贸digos Barker, en 802.11/WiFi se utiliza una secuencia Barker de longitud 11 para las velocidades de 1 y 2 Mbit/s:

+1 +1 +1 鈭1 鈭1 鈭1 +1 鈭1 鈭1 +1 鈭1

Puedes considerarlo como 11 s铆mbolos BPSK. Podemos observar la autocorrelaci贸n de esta secuencia muy f谩cilmente en Python:

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()
../_images/barker-code.svg

Puedes ver que 11 es longitud de la secuencia con un pico en el centro y -1 o 0 para todos los dem谩s retrasos. Funciona bien para encontrar el inicio de una trama porque esencialmente integra 11 s铆mbolos de energ铆a en un intento de crear un bit pico en la salida de la correlaci贸n cruzada. De hecho, la parte m谩s dif铆cil de realizar la detecci贸n de inicio de trama es encontrar un buen umbral. No se desea que lo activen tramas que en realidad no son parte de su protocolo. Eso significa que, adem谩s de la correlaci贸n cruzada, tambi茅n hay que realizar alg煤n tipo de normalizaci贸n de potencia, que no consideraremos aqu铆. Al decidir un umbral, hay que hacer un equilibrio entre la probabilidad de detecci贸n y la probabilidad de falsas alarmas. Recuerde que el encabezado de la trama en s铆 tendr谩 informaci贸n, por lo que algunas falsas alarmas est谩n bien; r谩pidamente descubrir谩 que en realidad no es una trama cuando vaya a decodificar el encabezado y el CRC inevitablemente falla (porque en realidad no era una trama). Sin embargo, si bien algunas falsas alarmas est谩n bien, pasar por alto la detecci贸n de una trama es malo.

Otra secuencia con grandes propiedades de autocorrelaci贸n son las secuencias de Zadoff-Chu, que se utilizan en LTE. Tienen la ventaja de estar en conjuntos; puede tener varias secuencias diferentes que tengan buenas propiedades de autocorrelaci贸n, pero no se activar谩n entre s铆 (es decir, tambi茅n buenas propiedades de correlaci贸n cruzada, cuando correlacione diferentes secuencias en el conjunto). Gracias a esa caracter铆stica, a diferentes estaciones celulares se les asignar谩n diferentes secuencias para que un tel茅fono no solo pueda encontrar el inicio de la trama sino tambi茅n saber de qu茅 torre est谩 recibiendo.