DEV Community

Cover image for Simulador de Modulação 16QAM em Python
Gabriel Dias
Gabriel Dias

Posted on

Simulador de Modulação 16QAM em Python

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode

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)