A modulação 16QAM (Quadrature Amplitude Modulation) é amplamente utilizada em sistemas de comunicações digitais. Ela combina modulação em amplitude e fase para transmitir 4 bits por símbolo. Construir um simulador em Python permite entender com mais profundidade os aspectos práticos da modulação, como o mapeamento dos bits para os pontos da constelação, o impacto do ruído na transmissão e a avaliação da taxa de erro de bits (BER) em diferentes relações sinal-ruído (SNR).
Neste projeto, vamos desenvolver um simulador completo de 16QAM, com geração de símbolos, conversão DA/AD modulação, adição de ruído e demodulação, com o objetivo de validar conceitos teóricos e apoiar experimentos futuros em sistemas de comunicação digital.
Inicialmente, é preciso entender o que é um sistema de comunicação baseado em 16QAM e como dividí-lo em blocos. Vamos compreender um pouco mais de cada um dos passos necessários para implementar esse sistema:
- Mensagem: O processo começa com a geração de uma sequência de bits binários que representam os dados a serem transmitidos. Esses bits serão agrupados em blocos de 4 bits, já que a modulação 16QAM possui 16 símbolos distintos, cada um representando exatamente 4 bits.
- Mapeamento para símbolos 16QAM: Em seguida, cada grupo de 4 bits é mapeado para um ponto específico na constelação 16QAM. Essa constelação é uma grade 4x4 no plano complexo, onde cada símbolo possui uma componente em fase (I) e outra em quadratura (Q), com diferentes amplitudes.
- Filtro de Nyquist: Esse filtro é utilizado para aplicar a interferência intersimbólica controlada no sinal, e reduzir a banda utilizada. Dessa forma, ao transmitir o sinal pelo canal, a banda já estará reduzida, o que evitará interferência
- Conversão Digital-Analógico: O resultado do mapeamento possui característica digital. É preciso converter essa saída digital para uma saída analógica e realizar a modulação.
- Transmissão pelo canal: Os símbolos modulados são então transmitidos por um canal que, para fins de simulação, é geralmente modelado como um canal AWGN (Additive White Gaussian Noise). Esse canal adiciona ruído gaussiano branco ao sinal, degradando-o e simulando as imperfeições de uma transmissão real.
- Demodulação: O sinal recebido, agora com ruído, precisa ser demodulado
- Coversão Analógico-Digital e amostragem: O sinal precisa retornar novamente ao domínio digital, e precisa ser amostrado nos instantes corretos para recuperar a mensagem
- Demapeamento e cálculo da BER: Os símbolos são convertidos de volta em bits. Esses bits são então comparados com os bits originais para calcular a taxa de erro de bits (BER), uma métrica essencial para avaliar o desempenho do sistema em diferentes níveis de ruído (SNR). Com as etapas explicadas, vamos ao código, para compreender as nuances da implementação desse sistema
1. Geração da Mensagem
A primeira etapa consiste em gerar uma sequência binária aleatória, que representa os dados brutos a serem transmitidos. Essa mensagem é formada por zeros e uns, simulando a informação digital original, que sempre será aleatória para o receptor.
import numpy as np
num_symbols = 1000
bits = np.random.randint(0, 2, num_symbols * 4)
2. Mapeamento 16QAM
Os bits gerados são agrupados de quatro em quatro e mapeados em coordenadas bidimensionais de uma constelação 16QAM. Cada grupo de 4 bits representa um símbolo, e cada símbolo é associado a um ponto fixo no plano I (in-phase) e Q (quadrature), com 16 combinações possíveis.
def bits_to_symbol(bits_group):
mapping_table = {
(0,0,0,0): (-3+3j), (0,0,0,1): (-3+1j), (0,0,1,1): (-3-1j), (0,0,1,0): (-3-3j),
(0,1,0,0): (-1+3j), (0,1,0,1): (-1+1j), (0,1,1,1): (-1-1j), (0,1,1,0): (-1-3j),
(1,1,0,0): (1+3j), (1,1,0,1): (1+1j), (1,1,1,1): (1-1j), (1,1,1,0): (1-3j),
(1,0,0,0): (3+3j), (1,0,0,1): (3+1j), (1,0,1,1): (3-1j), (1,0,1,0): (3-3j),
}
return np.array([mapping_table[tuple(bits_group[i:i+4])] for i in range(0, len(bits_group), 4)])
symbols = bits_to_symbol(bits)
3. Upsampling
Após o mapeamento, o sinal é preparado para transmissão contínua por meio de upsampling — processo em que inserimos amostras nulas entre os símbolos. Essa etapa é importante para a aplicação do filtro de Nyquist.
from scipy.signal import upfirdn
sps = 8 # samples per symbol
upsampled = upfirdn([1], symbols, sps)
4. Filtro de Nyquist
Aplicamos um filtro Raised Cosine para reduzir a interferência entre símbolos (ISI). Esse filtro suaviza as transições entre os valores amostrados e garante que os símbolos não interfiram significativamente uns nos outros ao longo do tempo, além de reduzir a banda. Foi utilizada a convolução no domínio do tempo para aplicar o filtro.
from scipy.signal import fir_filter_design, lfilter
def raised_cosine_filter(sps, alpha, num_taps):
t = np.arange(-num_taps//2, num_taps//2 + 1)
h = np.sinc(t / sps) * np.cos(np.pi * alpha * t / sps) / (1 - (2 * alpha * t / sps)**2)
h[t == sps/(2*alpha)] = np.pi/4 * np.sinc(1/(2*alpha)) # Handle division by zero
return h / np.sum(h)
alpha = 0.35
num_taps = 101
rc_filter = raised_cosine_filter(sps, alpha, num_taps)
tx_signal = np.convolve(upsampled, rc_filter, mode='same')
5. Canal AWGN
Para simular os efeitos do ambiente de transmissão, adicionamos ruído branco gaussiano aditivo (AWGN) ao sinal modulado. Esse ruído representa interferências inevitáveis do meio físico e permite testar a robustez do sistema.
def add_awgn_noise(signal, snr_db):
snr_linear = 10**(snr_db / 10)
power = np.mean(np.abs(signal)**2)
noise_power = power / snr_linear
noise = np.sqrt(noise_power / 2) * (np.random.randn(*signal.shape) + 1j*np.random.randn(*signal.shape))
return signal + noise
rx_signal = add_awgn_noise(tx_signal, snr_db=20)
6. Demodulação e Detecção
Na recepção, realizamos a demodulação recuperando os valores I/Q por meio de filtragem e downsampling. Depois, comparamos os símbolos recebidos com os da constelação original, selecionando o mais próximo para reconstruir os bits transmitidos.
rx_filtered = np.convolve(rx_signal, rc_filter, mode='same')
rx_downsampled = rx_filtered[num_taps//2::sps] # sincronização ideal
def symbol_to_bits(symbol):
demapping_table = {v: k for k, v in mapping_table.items()}
def closest(s):
return demapping_table[min(demapping_table, key=lambda x: abs(x - s))]
return np.concatenate([closest(s) for s in symbol])
received_bits = symbol_to_bits(rx_downsampled)
7. Cálculo da BER
Por fim, comparamos os bits recebidos com os bits transmitidos para calcular a taxa de erro de bits (BER), principal métrica de desempenho de sistemas de comunicação.
num_errors = np.sum(bits != received_bits[:len(bits)])
ber = num_errors / len(bits)
print(f"BER: {ber:.5f}")
Considerações Finais
Esse simulador cobre todos os principais blocos de um sistema de modulação digital 16QAM: desde a geração dos bits, passando pela modulação e filtragem, até a transmissão com ruído e demodulação na recepção. Ele serve como uma ferramenta prática para compreender como cada etapa afeta o desempenho do sistema e como avaliar a qualidade da transmissão por meio da BER.
Top comments (0)