Site menu Modulação FM
e-mail icon
Site menu

Modulação FM

e-mail icon

FM (Frequency Modulation ou Modulação em Freqüência) é a tecnologia mais empregada hoje em rádio analógico de alta fidelidade. 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:


(link to audio)

Agora farei a modulação FM, fazendo uso de uma portadora de 5512.5Hz. Este valor foi escolhido porque é 44100 dividido por 8, que fecha bem com a taxa de amostragem do arquivo WAV. A largura de banda do sinal original foi limitada a 1000Hz.

Por favor, não ouça o resultado em volume alto, sob pena de arrancar sua cabeça fora!


(link to audio)

Parece impossível recuperar voz dessa sopa, mas de fato ela pode ser recuperada. O código que gerou o áudio abaixo está no final do artigo:


(link to audio)

Agora, vamos ver como a salsicha é feita. Primeiro, um pouco de teoria. A modulação FM desvia a freqüência de uma portadora central, baseando-se no valor do sinal de entrada. A saída modulada tem potência constante. A fim de limitar a freqüência mínima e máxima da saída, e portanto respeitar a banda em que o rádio precisa caber, o valor do sinal de entrada deve ser limitado a um máximo absoluto (seja positivo ou negativo).

A potência constante de saída é a grande diferença entre FM e AM; neste último a potência de saída oscila conforme o valor do sinal de entrada. Esta característica torna FM muito resistente a ruídos e interferência, enquanto AM sofre interferência nos momentos em que a potência de saída está baixa.

Por outro lado, FM precisa mais largura de banda para transmitir o mesmo sinal. A banda ocupada pelo FM é função tanto da banda do sinal original, quanto do desvio de freqüência que o sinal pode imprimir à portadora. No caso extremo (desvio zero) a banda ocupada seria a mesma ocupada por AM, e como o desvio precisa ser diferente de zero, a banda é necessariamente maior. (Quanto maior o desvio, maior a qualidade e imunidade a ruídos.)

Segue uma implementação de modulador FM escrito em Python, de fato muito simples:

#!/usr/bin/env python

import wave, struct, math

baseband = wave.open("paralelepipedo_lopass.wav", "r")
FM_BAND = 1000.0
FM_CARRIER = 44100 / 8.0

fm = wave.open("fm.wav", "w")
fm.setnchannels(1)
fm.setsampwidth(2)
fm.setframerate(44100)

integ_base = 0
for n in range(0, baseband.getnframes()):
    base = struct.unpack('h', baseband.readframes(1))[0] / 32768.0
    # Base signal is integrated (not mandatory, but improves
    # volume at demodulation side)
    integ_base += base
    # The FM trick: time (n) is multiplied only by carrier freq;
    # the frequency deviation is added afterwards.
    signal_fm = math.cos(2 * math.pi * FM_CARRIER * (n / 44100.0) +
                         2 * math.pi * FM_BAND * integ_base / 44100.0)
    fm.writeframes(struct.pack('h', signal_fm * 32767))

Como o sinal de entrada influencia diretamente a freqüência da portadora, ele entra dentro de cos(), ou seja, o sinal é um argumento da própria função da portadora. O grande truque é que o sinal não é multiplicado pelo tempo (n). Tive algumas dificuldades para fazer o modulador funcionar, porque estava multiplicando tudo (portadora mais sinal) pelo tempo, e o resultado era uma saída com ruído branco. No final, fui salvo pela Wikipedia :)

A modulação FM parece mais simples que AM de radiodifusão (pelo menos tem menos linhas de código). E podemos considerar que isso também é verdadeiro no mundo real. Enquanto um transmissor AM precisa implementar uma cara multiplicação entre sinal e portadora (relativamente difícil de implementar num circuito analógico, sobretudo quando o resultado deve ser de boa qualidade), um transmissor FM pode ser construído com pouquíssimos componentes baratos.

Aqueles transmissores que vinham de brinde em revistas de eletrônica baseavam-se num diodo "varicap": a tensão do sinal de entrada altera levemente a capacitância do varicap; o varicap é parte do oscilador que gera a portadora; a pequena alteração de capacitância faz variar a freqüência da portadora, e aí temos um modulador FM funcional. É fato que a faixa de freqüência da rádio FM comercial (em torno de 100MHz) facilita muito as coisas, já que as bobinas do circuito podem ser minúsculas, etc.

Em contraste, a demodulação FM no receptor é bem mais complicada. Mesmo os receptores FM "para montar" costumam fazer uso de um circuito integrado dedicado à tarefa (no meu tempo era o TDA7000). Para fins deste artigo, "inventei" um demodulador ad-hoc em Python. É grosseiramente sub-ótimo, como eram os receptores FM "para montar" antes dos circuitos integrados, mas funciona:

1) passar o sinal FM através de um filtro passa-banda em rampa, de FM_CARRIER-FM_BAND até FM_CARRIER+FM_BAND. Sinais perto de -FM_BAND são enfraquecidos, e sinais perto de +FM_BAND são reforçados, de forma proporcional.

Isto converte o sinal FM numa espécie de sinal AM, porque a potência do sinal processado passa a ter relação com a força do sinal original.

2) detector e filtro passa-baixas, exatamente como num receptor AM.

Aqui está o código. É meio longo porque empreguei dois filtros FFT, um na conversão FM-AM, e outro no filtro passa-baixas de saída. A "tecnologia" dos filtros foi plagiada do artigo sobre FFT.

#!/usr/bin/env python

SAMPLE_RATE = 44100 # Hz
FM_BAND = 1000.0
FM_CARRIER = 44100 / 8.0
LOWPASS = FM_CARRIER - FM_BAND
HIGHPASS = FM_CARRIER + FM_BAND
HIGHPASS2 = FM_BAND # Hz

import wave, struct, math
from numpy import fft
FFT_LENGTH = 2048
OVERLAP = 512
FFT_SAMPLE = FFT_LENGTH - OVERLAP
NYQUIST_RATE = SAMPLE_RATE / 2.0

LOWPASS /= (NYQUIST_RATE / (FFT_LENGTH / 2.0))
HIGHPASS /= (NYQUIST_RATE / (FFT_LENGTH / 2.0))
HIGHPASS2 /= (NYQUIST_RATE / (FFT_LENGTH / 2.0))

zeros = [ 0 for x in range(0, OVERLAP) ]

# Make ramp filter for FM-AM conversion
mask = []
for f in range(0, FFT_LENGTH / 2 + 1):
    if f < LOWPASS or f > HIGHPASS:
        ramp = 0.0
    else:
        ramp = (f - LOWPASS) / (HIGHPASS - LOWPASS)
    mask.append(ramp)

# make lowpass filter
mask2 = []
for f in range(0, FFT_LENGTH / 2 + 1):
    if f > HIGHPASS2 or f == 0:
        ramp = 0.0
    else:
        ramp = 1.0
    mask2.append(ramp)

fm = wave.open("fm.wav", "r")
demodulated = wave.open("demod_fm.wav", "w")
demodulated.setnchannels(1)
demodulated.setsampwidth(2)
demodulated.setframerate(SAMPLE_RATE)

n = fm.getnframes()
fm = struct.unpack('%dh' % n, fm.readframes(n))
# scale from 16-bit signed WAV to float
fm = [s / 32768.0 for s in fm]

saved_td = zeros
intermediate = []

# Convert FM to AM

for pos in range(0, len(fm), FFT_SAMPLE):
    time_sample = fm[pos : pos + FFT_LENGTH]

    frequency_domain = fft.fft(time_sample, FFT_LENGTH)
    l = len(frequency_domain)

    for f in range(0, l/2+1):
        frequency_domain[f] *= mask[f]

    for f in range(l-1, l/2, -1):
        cf = l - f
        frequency_domain[f] *= mask[cf]

    time_domain = fft.ifft(frequency_domain)

    for i in range(0, OVERLAP):
        time_domain[i] *= (i + 0.0) / OVERLAP
        time_domain[i] += saved_td[i] * (1.0 - (i + 0.00) / OVERLAP)

    saved_td = time_domain[FFT_SAMPLE:]
    time_domain = time_domain[:FFT_SAMPLE]

    intermediate += time_domain.real.tolist()

# Detector "diode"

intermediate = [ abs(sample) for sample in intermediate ]

saved_td = zeros
output = []

del fm

# Filter the result, removing high frequencies

for pos in range(0, len(intermediate), FFT_SAMPLE):
    time_sample = intermediate[pos : pos + FFT_LENGTH]

    frequency_domain = fft.fft(time_sample, FFT_LENGTH)
    l = len(frequency_domain)

    for f in range(0, l/2+1):
        frequency_domain[f] *= mask2[f]

    for f in range(l-1, l/2, -1):
        cf = l - f
        frequency_domain[f] *= mask2[cf]

    time_domain = fft.ifft(frequency_domain)

    for i in range(0, OVERLAP):
        time_domain[i] *= (i + 0.0) / OVERLAP
        time_domain[i] += saved_td[i] * (1.0 - (i + 0.00) / OVERLAP)

    saved_td = time_domain[FFT_SAMPLE:]
    time_domain = time_domain[:FFT_SAMPLE]

    output += time_domain.real.tolist()

# Scale signal and write to WAV file

output = [ int(sample * 32767) for sample in output ]

demodulated.writeframes(struct.pack('%dh' % len(output), *output))
e-mail icon