FM (Frequency Modulation ou Modulação em Freqüência) é a tecnologia mais empregada hoje em rádio analógico, tanto nas FMs comerciais quanto em comunicação de walkie-talkie e radioamador. (Uma grande exceção é a comunicação dos aviões, que ainda é AM.)
Até mesmo as modalidades de rádio digital como DMR e D-Star, cada vez mais utilizadas, possuem um estágio final de FM, o que permite o compartilhamento de circuitos dentro do rádio, e a convivência pacífica dos modos analógico e digital na mesma banda.
Neste artigo, tentarei demonstrar como um sinal é transformado durante esta modulação, e exibirei uma simples implementação em Python.
Temos este áudio com minha voz — o mesmo que usei no artigo sobre modulação AM:
Agora farei a modulação FM, fazendo uso de uma portadora de 10.000Hz. A largura de banda do sinal original foi limitada a 1000Hz, e o desvio máximo de freqüência também foi fixado em 1000Hz. Por favor, não ouça o resultado em volume alto, sob pena de arrancar sua cabeça fora!
Parece impossível recuperar voz disso aí, mas de fato ela pode ser recuperada. Há alguma perda de qualidade devido à filtragem na "recepção":
Agora, vamos ver como a salsicha é feita. Primeiro, um pouco de teoria. A modulação FM consiste em desviar a freqüência da portadora, conforme o sinal de entrada.
A saída modulada tem potência constante, mesmo quando o sinal é puro silêncio. Isto torna o FM muito resistente a ruídos e interferência. Também permite que o transmissor seja simples e eficiente, pois ele não precisa modular a potência.
Em AM e principalmente em SSB, a potência de saída guarda proporção com o sinal de entrada, o que deixa o sinal vulnerável nos instantes de baixa potência. Como transmissores AM e SSB precisam variar a potência de saída de forma linear, são mais complexos e menos eficientes.
A priori, AM e SSB usam a banda de forma mais eficiente que FM, pois modulam tanto freqüência quanto amplitude, enquanto FM modula apenas freqüência.
Um transmissor FM analógico pode ser incrivelmente simples. Usando um varicap (diodo que funciona como capacitor variável controlado por tensão) no oscilador, o sinal de áudio pode influenciar diretamente o varicap, e portanto a freqüência de rádio.
Modular digitalmente é mais enrolado. A ideia é ir adiantando ou atrasando a fase da portadora conforme o sinal de entrada. Um atraso continuado corresponde a um desvio da freqüência para baixo, e vice-versa.
O maior cuidado que temos de ter, é relacionar o sinal com o desvio de fase da portadora, de modo que o resultado final não caia fora da banda (no exemplo, de 9000 a 11000Hz). O outro detalhe é que o sinal de entrada é integrado. O desvio de fase não volta a zero quando a entrada é silêncio; apenas deixamos o desvio corrente intocado.
Em vez de mexer na fase da modulação diretamente, traduzimos a fase para coordenadas cartesianas (I/Q) e usamos um modulador de quadratura:
#!/usr/bin/env python3 # FM modulator based on I/Q (quadrature) modulation import wave, struct, math input_src = wave.open("paralelepipedo_lopass.wav", "r") FM_CARRIER = 10000.0 MAX_DEVIATION = 1000.0 # Hz fm = wave.open("iq_fm.wav", "w") fm.setnchannels(1) fm.setsampwidth(2) fm.setframerate(44100) phase = 0 # in radians for n in range(0, input_src.getnframes()): # rush or drag phase accordingly to input signal # this is analog to integrating inputsgn = struct.unpack('h', input_src.readframes(1))[0] / 32768.0 # translate input into a phase change that changes frequency # up to MAX_DEVIATION Hz phase += inputsgn * math.pi * MAX_DEVIATION / 44100 phase %= 2 * math.pi # calculate quadrature I/Q i = math.cos(phase) q = math.sin(phase) carrier = 2 * math.pi * FM_CARRIER * (n / 44100.0) output = i * math.cos(carrier) - q * math.sin(carrier) fm.writeframes(struct.pack('h', int(output * 32767)))
No lado receptor, FM é bem mais complicado que AM. Não existe "rádio de galena" FM. Mesmo os kits de revista de eletrônica fazem uso de algum circuito integrado ASIC, como o TDA7000. (Recepção de FM comercial tem o complicador adicional do sinal stereo, que envolve uma segunda demodulação AM-SC, que o TDA7000 também faz.)
Existem inúmeros métodos analógicos e digitais para demodular FM. No código abaixo, seguimos a receita padrão, que é essencialmente o caminho inverso do modulador. O demodulador de quadratura extrai os valores I e Q, transformamos para coordenada polar para obter o desvio de fase, e vemos quanto a fase "girou" em relação à amostra anterior para extrair o sinal.
Um detalhe importante é que o sinal I/Q demodulado tem de passar por um filtro passa-baixas. Conforme dissemos no artigo sobre AM, a demodulação cria um sinal-fantasma de alta freqüência. Em AM é apenas um aborrecimento, em FM ele realmente impede o "receptor" de funcionar.
#!/usr/bin/env python3 # FM demodulator based on I/Q (quadrature) import wave, struct, math, random, filters input_src = wave.open("iq_fm.wav", "r") FM_CARRIER = 10000.0 MAX_DEVIATION = 1000.0 # Hz demod = wave.open("iq_fm_demod.wav", "w") demod.setnchannels(1) demod.setsampwidth(2) demod.setframerate(44100) # Prove we don't need synchronized carrier oscilators initial_carrier_phase = random.random() * 2 * math.pi last_angle = 0.0 istream = [] qstream = [] for n in range(0, input_src.getnframes()): inputsgn = struct.unpack('h', input_src.readframes(1))[0] / 32768.0 # I/Q demodulation, not unlike QAM carrier = 2 * math.pi * FM_CARRIER * (n / 44100.0) + initial_carrier_phase istream.append(inputsgn * math.cos(carrier)) qstream.append(inputsgn * -math.sin(carrier)) istream = filters.lowpass(istream, 1500) qstream = filters.lowpass(qstream, 1500) last_output = 0 for n in range(0, len(istream)): i = istream[n] q = qstream[n] # Determine phase (angle) of I/Q pair angle = math.atan2(q, i) # Change of angle = baseband signal # Were you rushing or were you dragging?! angle_change = last_angle - angle # Just for completeness; big angle changes are not # really expected, this is FM, not QAM if angle_change > math.pi: angle_change -= 2 * math.pi elif angle_change < -math.pi: angle_change += 2 * math.pi last_angle = angle # Convert angle change to baseband signal strength output = angle_change / (math.pi * MAX_DEVIATION / 44100) if abs(output) >= 1: # some unexpectedly big angle change happened output = last_output last_output = output demod.writeframes(struct.pack('h', int(output * 32767)))
O filtro é bastante "cru" e foi tomado emprestado do artigo sobre filtros FIR, mas funcionou bem o suficiente para nossos fins:
#!/usr/bin/env python import numpy, math from numpy import fft SAMPLE_RATE = 44100 # Hz NYQUIST_RATE = SAMPLE_RATE / 2.0 FFT_LENGTH = 512 def lowpass_coefs(cutoff): cutoff /= (NYQUIST_RATE / (FFT_LENGTH / 2.0)) # create FFT filter mask mask = [] negatives = [] l = FFT_LENGTH // 2 for f in range(0, l+1): rampdown = 1.0 if f > cutoff: rampdown = 0 mask.append(rampdown) if f > 0 and f < l: negatives.append(rampdown) negatives.reverse() mask = mask + negatives # Convert FFT filter mask to FIR coefficients impulse_response = fft.ifft(mask).real.tolist() # swap left and right sides left = impulse_response[:FFT_LENGTH // 2] right = impulse_response[FFT_LENGTH // 2:] impulse_response = right + left b = FFT_LENGTH // 2 # apply triangular window function for n in range(0, b): impulse_response[n] *= (n + 0.0) / b for n in range(b + 1, FFT_LENGTH): impulse_response[n] *= (FFT_LENGTH - n + 0.0) / b return impulse_response def lowpass(original, cutoff): coefs = lowpass_coefs(cutoff) return numpy.convolve(original, coefs) if __name__ == "__main__": import wave, struct original = wave.open("NubiaCantaDalva.wav", "r") filtered = wave.open("test_fir.wav", "w") filtered.setnchannels(1) filtered.setsampwidth(2) filtered.setframerate(SAMPLE_RATE) n = original.getnframes() original = struct.unpack('%dh' % n, original.readframes(n)) original = [s / 2.0**15 for s in original] result = lowpass(original, 1000) result = [ int(sample * 2.0**15) for sample in result ] filtered.writeframes(struct.pack('%dh' % len(result), *result))
A técnica de "girar a fase" para executar a modulação FM sugere que FM é basicamente uma versão analógica da modulação por fase (PM), bem conhecida no mundo digital. De fato, modos digitais baseados em fase ou freqüência (PM, FSK, MSK, GMSK) podem ser realizados via (de)modulação FM de um sinal digital (onda quadrada).
Naturalmente, rádios "de verdade" usem técnicas mais eficientes, mas funciona bem o suficiente para ouvir walkie-talkies digitais usando SDR combinado com o programa DSD.