Site menu IoT: Controle remoto 433MHz (parte 6: RX com SDR)

IoT: Controle remoto 433MHz (parte 6: RX com SDR)

Este texto é uma continuação desta série, que explora o mundinho dos controles remotos 433MHz, comumente usados em portões eletrônicos.

Recepção com SDR

Os módulos de recepção que temos utilizado até aqui são muito práticos, pois fazem todo o tratamento de rádio, e da parte analógica da recepção OOK. Eles entregam ao Arduino a informação "presença ou ausência de sinal 433MHz". A decodificação digital OOK é uma tarefa que mesmo o microcontrolador mais fraco consegue realizar.

Mas, e se alguém quiser se arrumar pra cabeça, quiser entender como funciona a parte analógica? Aí pode utilizar um SDR — Rádio Definido por Software. É um receptor universal que delega toda, ou quase toda, a demodulação analógica ao software.

O modelo mais conhecido e acessível é o RTL-SDR. Existem SDRs bem melhores, mas o preço também sobe bem rápido. (Tudo que envolve rádio tende a custar os olhos da cara.)

TL;DR o primeiro resultado bem-sucedido de recepção de keyfobs usando RTL-SDR pode ser encontrado aqui e aqui. É melhor você clonar o repositório inteiro porque há nele uns módulos auxiliares que são referenciados pelo script principal.

Como a intenção aqui é fuçar sem preocupação, não vamos tentar fazer o negócio funcionar num microcontrolador. O script acima foi feito para rodar num PC ou Mac. E sim, eu sei que existe excelente software livre para receber controle remoto 433MHz via SDR, a ideia é fazer tudo nós mesmos.

Front-end

Para demodular digitalmente um sinal de 433MHz, precisamos em tese de uma taxa de amostragem 2× maior, idealmente 4× maior. Como essa taxa ainda é proibitiva para um computador comum, a maioria dos SDRs faz a demodulação analogicamente, e o software recebe uma taxa de amostragem muito menor, centrada na banda-base, ou seja, na banda de interesse.

A taxa de amostragem da banda-base, além de definir a carga de trabalho do software que vai processá-la em seguida, define a largura de banda passante. Pelo teorema de Nyquist, uma taxa de amostragem de 200kHz entrega uma banda de 100kHz. Se for um sinal complexo, entrega 200kHz (100kHz positivos e 100kHz negativos). O ideal é trabalhar com uma folga, ou seja, usar uma taxa de amostragem maior e passar um filtro passa-baixas, pois a digitalização distorce nos extremos da banda.

No nosso caso, estamos interessados numa banda de 200KHz em torno da frequência central 433.92MHz. Temos de "ouvir" essa banda toda pois os keyfobs não são muito precisos na frequência de transmissão. O RTL-SDR tem uma limitação de não aceitar taxas de amostragem entre 300kHz e 900kHz, então optamos por uma taxa de 960kHz para que o sinal caiba folgadamente. Além do que as transições OOK são da ordem de microssegundos, então uma taxa de amostragem alta garante a nitidez do sinal. Processar amostras nessa taxa não é problema para um PC.

Para não ter de estressar com a manipulação direta do RTL-SDR, lançamos mão de um utilitário de linha de comando para acionar a recepção, e recebemos as amostras via pipe:

rtl_sdr -f 433920000 -g $GAIN -s 960k - | ./ook_433.py

O processo de demodulação é explicado à exaustão neste artigo. O RTL-SDR implementa a chamada "conversão direta" onde o sinal original é transladado para a banda-base por um único mixer. Receptores mais avançados são heteródinos, ou seja, usam dois mixers, com filtragem adicional entre um e outro.

Amostras I/Q

Em geral os SDRs fazem demodulação complexa, e entregam um sinal de banda-base composto de números complexos. Uma explicação mais aprofundada pode ser encontrada aqui. A ideia geral é que o demodulador complexo, também chamado de I/Q, é um canivete suíço e serve como front-end de qualquer tipo de sinal de rádio.

A saída do utilitário rtl_sdr é uma sequência de amostras I/Q, ou seja, uma seqüência de números complexos. Cada número complexo é composto por 2 bytes, sendo 1 a parte imaginária e 1 a parte real, com bias de 127.4 e amplitude 128.0 (ou seja, uma amostra de valor 125 vale mesmo -0.01875).

Para converter bytes em números de forma minimamente eficiente, usamos os superpoderes do NumPy:

    # Parse RTL-SDR I/Q format
    iqdata = numpy.frombuffer(data, dtype=numpy.uint8)
    iqdata = iqdata - 127.4
    iqdata = iqdata / 128.0
    iqdata = iqdata.view(complex)

Nossa taxa de amostragem é 960kHz mas só desejamos sinais numa banda de 200kHz em torno da frequencia central, então filtramos digitalmente. Não alteramos a taxa de amostragem pois desejamos que o sinal tenha resolução de µs:

INPUT_RATE = 960000
BANDWIDTH = 200000
...
if_filter = filters.low_pass(INPUT_RATE, BANDWIDTH / 2, 48)
    ...
    iqdata = if_filter.feed(iqdata)

Dividimos BANDWIDTH por dois pois um sinal banda-base complexo tem a interessante característica de possuir frequências positivas e negativas, então filtrar por 100kHz mantém uma banda de -100kHz até +100kHz, total 200kHz conforme desejamos.

Detecção de envelope

Na modulação de rádio AM, quase extinta mas historicamente importante, o áudio pode ser extraído unicamente com base na amplitude do sinal, e isto pode ser feito por um "detector de envelope", que é um simples diodo retificador seguido de um filtro passa-baixas. (E o diodo ainda pode ser improvisado com uma gilete enferrujada. As pessoas faziam isso na guerra para conseguir ouvir rádio.)

Figura 1: Modulação AM: o sinal original é multiplicado por uma portadora, e somado a essa mesma portadora. Os picos do sinal de rádio seguem o contorno do sinal original.
Figura 2: Demodulação AM: para reconstituir o original a partir do contorno ou `envelope`, o sinal de rádio é retificado e filtrado.

A modulação ASK/OOK é conceitualmente semelhante, porém ainda mais simples: só queremos saber se existe recepção de rádio ou não.

Figura 3: Modulação ASK/OOK: o sinal digital original é multiplicado por uma portadora.
Figura 4: Demodulação ASK/OOK: o sinal de rádio é retificado e filtrado para tentar reconstituir o original.

Implementação digital do detector de envelope:

TRANS_MIN_US = 150
...
PREAMBLE_MIN_US = 5000
...
GLITCH_US = min(TRANS_MIN_US, PREAMBLE_MIN_US) / 4
...
envelope_filter = filters.low_pass(INPUT_RATE, 1000000 / GLITCH_US)
    ...
    iqdata = numpy.absolute(iqdata)
    # Detect envelope using a low-pass filter
    iqdata = envelope_filter.feed(iqdata)

A função numpy.absolute() faz o papel de diodo e converte o sinal complexo para valores reais positivos. Isto "destrói" o sinal, mas não importa, porque só queremos saber se há recepção ou não.

A freqüência de corte do filtro passa-baixas é um compromisso entre um envelope estável quando há recepção e uma transição rápida nas bordas, quando o sinal aparece ou cessa. Usamos o parâmetro GLITCH_US, um valor em µs; qualquer transição mais rápida que isso é um "soluço" que não nos interessa ver.

Outra opção seria filtrar por PREAMBLE_MIN_US que é a transição válida mais curta possível. Porém, conforme já foi abordado em capítulos anteriores, precisamos deixar passar um pouco de ruído de alta freqüência, pois esse ruído denuncia que não estamos recebendo um sinal válido.

Decodificação OOK

Neste ponto, o sinal obtido do SDR é quase o mesmo obtido de um módulo receptor, exceto que ele ainda é analógico. Precisamos estabelecer uma linha de corte para transformar o sinal em digital, com apenas dois níveis.

Em nosso código, temos a variável bgnoise que é uma média móvel do ruído de fundo. Presumimos que o sinal aparece de forma intermitente, e que na maior parte do tempo vamos estar ouvindo apenas ruído de fundo. O script parte de uma estimativa inicial, mas mesmo que ela esteja muito errada, com o tempo ela se conserta.

Nossa estratégia de digitalização é considerar que um sinal com força acima de "n" vezes o ruído de fundo é um nível "alto". Abaixo disso, é um nível "baixo". As constantes ook_threshold_db_up e ook_threshold_db_down determinam esse limiar alto/baixo. O sinal digitalizado vai para a variável samples.

Daí por diante, o decodificador OOK não é diferente dos demais que engendramos em capítulos anteriores. O código foi basicamente copiado da versão para microcontrolador.

Pode o SDR ser melhor que um módulo receptor?

Objetivamente, não. Os módulos para Arduino têm (ou alegam) sensibilidade entre -106dBm e -114dBm. A sensibilidade do RTL-SDR parece ser em torno de -95dBm. A diferença significa um alcance 8 vezes menor. Fora a necessidade de um PC para processar o sinal, que é algo como matar um mosquito com tiro de canhão.

A graça de usar um SDR é poder fuçar, mexer nos diversos parâmetros que podem afetar o funcionamento, para entender ou pelo menos estimar como um receptor "caixa-preta" funciona por dentro.

No momento, por comodidade, o RTL-SDR está conectado a uma antena VHF instalada no sótão, portanto inadequada para 433MHz. Mesmo assim, com todas as desvantagens, ele funciona, captando mais ou menos 50% dos keyfobs detectados pelo receptor "kosher".