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.