Emulador de radar com Arduino + Python

Tempo de leitura: 9 minutes

A palavra radar é uma combinação de ‘RAdio Detection And Ranging’ – que descreve a função dos primeiros sistemas de radar que foram desenvolvidos para detectar e alcançar aeronaves inimigas que se aproximam usando ondas de rádio. Normalmente, os sistemas de radar usam uma ferramenta de visualização chamada indicador de posição do plano (PPI), que coloca pontos em uma configuração polar para representar objetos que ocupam espaço na faixa do detector (leia mais sobre radar: Introdução aos Sistemas de Radar). Neste tutorial, um sensor ultrassônico (HC-SR04) será usado no lugar de um emissor de rádio; e um indicador de posição de plano será construído em Python registrando os movimentos angulares de um servo motor. Uma placa Arduino registrará os dados de alcance do sensor ultrassônico ao mesmo tempo em que controla e gera a posição angular do servo motor. Isso permitirá a criação de um PPI para visualizar a posição de vários objetos ao redor do sistema de radar.

 

Lista de peças e ligação para Arduino

A replicação de um sistema de radar envolve dois componentes essenciais: um dispositivo de alcance e um motor/detector angular. Como afirmado acima, o dispositivo de alcance pode ser qualquer dispositivo que detecta a distância de um ponto estacionário. O dispositivo ultrassônico HC-SR04 será usado, no entanto, o sensor de alcance VL53L0X (usa a técnica de tempo de voo com um laser de 940 nm) também foi usado e funciona bem com este tutorial. Um kit foi montado especificamente para replicar este tutorial e é recomendado para acompanhar este tutorial. Além do kit, a única coisa necessária é uma placa Arduino e um computador. Os componentes individuais também estão listados abaixo, caso o usuário queira montar os componentes independentemente:

 

Lista de componentes:

  • Placa Arduino Uno
  • MG90S Micro Servo Motor
  • Sensor Ultrasônico HC-SR04
  • Fios para Ligação – (12 unidades: 8 HxM, 4 HxH)
  • Mini ProtoBoard
  • Sensor de tempo de voo VL53L0X
  • Suporte para Sensor Ultrasônico com Micro Servo

O HC-SR04 e o MG90S podem ser conectados a uma placa Arduino Uno usando o seguinte diagrama:

O código do Arduino usa essa configuração de fiação específica, no entanto, os pinos podem ser facilmente alterados no código para representar ligação específica. O código Python fornecido posteriormente também descreverá como o Arduino está sendo lido pela porta serial e por que certos métodos Serial.print() são chamados.

 

Código e uso do Arduino

O código do Arduino usa a biblioteca servo para se comunicar via modulação por largura de pulso (PWM) em um de seus pinos. Um algoritmo personalizado é usado para recuperar dados de alcance do HC-SR04, usando o efeito de tempo de voo para ondas sonoras. Tanto o ângulo do servo motor MG90S (0° – 180°) quanto a distância aproximada do HC-SR04 (2cm – 400cm) são enviados para a porta serial para um programa Python ler (mais sobre isso posteriormente). O código do Arduino é fornecido abaixo:

#include <Servo.h>

Servo servo_1; // servo controlador (podem existir vários)

int trig = 4; // pino trigonométrico para HC-SR04
int echo = 5; // pino de eco para HC-SR04
int servo_pin = 3; // Pino PWM para servo controle

int pos = 0;    // posição inicial do servo
float duration,distance;

void setup() {
  Serial.begin(115200);
  Serial.println("Radar Start");
  servo_1.attach(servo_pin); // inicia o controle servo
  pinMode(trig,OUTPUT);
  pinMode(echo,INPUT);
}

void loop() {
  for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
    // em passos de 1 grau
    servo_1.write(pos);              // diga ao servo para ir para a posição na variável 'pos'
    delay(60); // demora para permitir que o servo alcance a posição desejada
    dist_calc(pos);
  }
 
  for (pos = 180; pos >= 0; pos -= 1) {// vai de 180 graus a 0 graus
    servo_1.write(pos);              // diga ao servo para ir para a posição na variável 'pos'
    delay(60);
    dist_calc(pos);
  }
}

float dist_calc(int pos){
  // aciona pulso de 40kHz para variação
  digitalWrite(trig,LOW);
  delayMicroseconds(2);
  digitalWrite(trig,HIGH);
  delayMicroseconds(10);
  digitalWrite(trig,LOW);
  // converte a duração do detector de pulso para alcançar (microssegundos) para a faixa (em cm)
  duration = pulseIn(echo,HIGH); // duração do pulso para alcançar o detector (em microssegundos)
  distance = 100.0*(343.0*(duration/2.0))/1000000.0; // 100,0 * (velocidade do som * duração / 2) / conversão de microseg

  Serial.print(pos); // posição do servo motor
  Serial.print(","); // variáveis separadas por vírgula
  Serial.println(distance); // imprimir distância em cm
}

A equação de tempo de voo, dada na função ‘dist_calc()’, usa o seguinte princípio:

onde d é a distância do sensor HC-SR04 ao objeto que está detectando, c é a velocidade do som no ar (~343m/s) e Δt é o tempo registrado que leva para o pulso atingir o alvo e chegar de volta ao receptor (detector).

Abrir a porta serial no Arduino deve ler o seguinte:

nota: verificar a impressão acima é essencial para continuar com este tutorial

Se a impressão não for semelhante à anterior, o código do leitor serial Python na seção a seguir não funcionará corretamente. A impressão ‘Radar Start’ informa ao código Python para iniciar sua análise de radar, e o formato de ‘ângulo, distância’ separado por vírgulas alimenta os dados exatamente como precisam ser lidos no código Python. Portanto, se a impressão não imitar o acima, o código Python retornará erros.

Código Python e demonstração

Em Python, esse projeto se torna exponencialmente mais complexo. A razão é, conforme declarado na introdução deste tutorial, um indicador de posição do plano (PPI) será usado para visualizar o mapa de pontos conforme o motor MG90S gira 180° para frente e para trás em torno de seu eixo. A razão pela qual isso se torna difícil é que agora precisamos pegar um gráfico polar e preenchê-lo com as saídas da placa Arduino. Portanto, nosso processo se torna o seguinte:

  1. Inicie a comunicação com a placa Arduino
  2. Crie plotagem polar para emulador de radar
  3. Comece o loop pelos dados de entrada do Arduino
  4. Espere o ‘Radar Start’ começar a traçar
  5. Atualizar pontos de dispersão e PPI

E se isso fosse feito exatamente como referenciado acima, seriam necessários alguns recursos para fazer em tempo real. Assim, algumas soluções alternativas são implementadas para garantir a eficiência na plotagem e na leitura dos dados. A seguir estão simplificações e implementações de métodos eficientes para atualizar e traçar o ângulo e os pontos de dispersão de alcance recebidos pelo Arduino:

  1. Atualizar apenas os dados, não o gráfico (trechos de código restore_region(), drawartist() e blit() abaixo)
  2. Plote apenas a cada 5 graus de rotação

Todas as rotinas e implementações acima são fornecidas abaixo no código, com comentários quando necessário:

# Python + Arduino-based Radar Plotter
#
# ** Funciona com qualquer motor que produz rotação angular
# ** e com qualquer sensor de distância (HC-SR04, VL53L0x, LIDAR)
#
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import serial,sys,glob
import serial.tools.list_ports as COMs
#
#
############################################
# Encontre as portas do Arduino, selecione uma e inicie a comunicação com ela
############################################
#
def port_search():
    if sys.platform.startswith('win'): # Windows
        ports = ['COM{0:1.0f}'.format(ii) for ii in range(1,256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'): # MAC
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Máquina Não Compatível com Pyserial')

    arduinos = []
    for port in ports: # loop through to determine if accessible
        if len(port.split('Bluetooth'))>1:
            continue
        try:
            ser = serial.Serial(port)
            ser.close()
            arduinos.append(port) # if we can open it, consider it an arduino
        except (OSError, serial.SerialException):
            pass
    return arduinos

arduino_ports = port_search()
ser = serial.Serial(arduino_ports[0],baudrate=115200) # match baud no Arduino
ser.flush() # clear the port
#
############################################
# Inicie a ferramenta de plotagem interativa e
# plotar 180 graus com dados fictícios para começar
############################################
#
fig = plt.figure(facecolor='k')
win = fig.canvas.manager.window # janela de figura
screen_res = win.wm_maxsize() # usado para formatação de janela posterior
dpi = 150.0 # figure resolution
fig.set_dpi(dpi) # set figure resolution

# atributos do gráfico polar e condições iniciais
ax = fig.add_subplot(111,polar=True,facecolor='#006d70')
ax.set_position([-0.05,-0.05,1.1,1.05])
r_max = 100.0 # pode mudar isso com base na faixa do sensor
ax.set_ylim([0.0,r_max]) # gama de distâncias para mostrar
ax.set_xlim([0.0,np.pi]) # limitado pela amplitude do servo (0-180 graus)
ax.tick_params(axis='both',colors='w')
ax.grid(color='w',alpha=0.5) # cor da grade
ax.set_rticks(np.linspace(0.0,r_max,5)) # show 5 different distances
ax.set_thetagrids(np.linspace(0.0,180.0,10)) # mostrar 10 ângulos
angles = np.arange(0,181,1) # 0 - 180 degrees
theta = angles*(np.pi/180.0) # para radianos
dists = np.ones((len(angles),)) # distâncias fictícias até que dados reais entrem
pols, = ax.plot([],linestyle='',marker='o',markerfacecolor = 'w',
                 markeredgecolor='#EFEFEF',markeredgewidth=1.0,
                 markersize=10.0,alpha=0.9) # pontos para pontos de radar
line1, = ax.plot([],color='w',
                  linewidth=4.0) # enredo de braço extenso

# ajustes de apresentação de figura
fig.set_size_inches(0.96*(screen_res[0]/dpi),0.96*(screen_res[1]/dpi))
plot_res = fig.get_window_extent().bounds # window extent for centering
win.wm_geometry('+{0:1.0f}+{1:1.0f}'.\
                format((screen_res[0]/2.0)-(plot_res[2]/2.0),
                       (screen_res[1]/2.0)-(plot_res[3]/2.0))) # plotagem de centralização
fig.canvas.toolbar.pack_forget() # remova a barra de ferramentas para uma apresentação limpa
fig.canvas.set_window_title('Arduino Radar')

fig.canvas.draw() # desenhar antes do laço
axbackground = fig.canvas.copy_from_bbox(ax.bbox) # background to keep during loop

############################################
# evento de botão para parar o programa
############################################

def stop_event(event):
    global stop_bool
    stop_bool = 1
prog_stop_ax = fig.add_axes([0.85,0.025,0.125,0.05])
pstop = Button(prog_stop_ax,'Stop Program',color='#FCFCFC',hovercolor='w')
pstop.on_clicked(stop_event)
# botão para fechar a janela
def close_event(event):
    global stop_bool,close_bool
    if stop_bool:
        plt.close('all')
    stop_bool = 1
    close_bool = 1
close_ax = fig.add_axes([0.025,0.025,0.125,0.05])
close_but = Button(close_ax,'Close Plot',color='#FCFCFC',hovercolor='w')
close_but.on_clicked(close_event)

fig.show()

############################################
# loop infinito, atualizando constantemente o
# 180deg radar com entrada de dados do Arduino
############################################
#
start_word,stop_bool,close_bool = False,False,False
while True:
    try:
        if stop_bool: # pára o programa
            fig.canvas.toolbar.pack_configure() # mostrar barra de ferramentas
            if close_bool: # fecha a janela do radar
                plt.close('all')
            break
        ser_bytes = ser.readline() # ler dados seriais do Arduino
        decoded_bytes = ser_bytes.decode('utf-8') # decodificar dados para utf-8
        data = (decoded_bytes.replace('\r','')).replace('\n','')
        if start_word:
            vals = [float(ii) for ii in data.split(',')]
            if len(vals)<2:
                continue
            angle,dist = vals # separar em ângulo e distância
            if dist>r_max:
                dist = 0.0 # medindo mais do que r_max, é provavelmente impreciso
            dists[int(angle)] = dist
            if angle % 5 ==0: # update every 5 degrees
                pols.set_data(theta,dists)
                fig.canvas.restore_region(axbackground)
                ax.draw_artist(pols)

                line1.set_data(np.repeat((angle*(np.pi/180.0)),2),
                   np.linspace(0.0,r_max,2))
                ax.draw_artist(line1)

                fig.canvas.blit(ax.bbox) # replantar apenas dados
                fig.canvas.flush_events() # flush for next plot
        else:
            if data=='Radar Start': # começar palavra no Arduino
                start_word = True # espere que o Arduino produza a palavra inicial
                print('Radar Starting...')
            else:
                continue
           
    except KeyboardInterrupt:
        plt.close('all')
        print('Keyboard Interrupt')
        break

O código foi testado para Linux (Raspberry Pi), Windows 10 e Catalina OS do Mac – todos com Python 3.6+

 

Depois de executar o código acima, o seguinte gráfico deve aparecer:

A interface gráfica do usuário (GUI) permite que os usuários parem o programa ou fechem o gráfico e saiam do programa. Enquanto isso, o gráfico deve ser atualizado a cada 5 graus (aproximadamente a cada 300 ms), com pontos de dispersão sendo colocados onde os objetos são detectados pelo HC-SR04. Há também um braço de varredura que faz parte do indicador de posição do plano, que notifica o usuário sobre a localização aproximada do motor ou área que está sendo varrida.

Uma demonstração de vídeo completa do radar de varredura Arduino usando o servo MG90S e o sensor HC-SR04 é fornecida abaixo, onde o código Python é implementado em um Raspberry Pi por meio de sua porta USB:

Uma última coisa a notar é que o HC-SR04 não produz pontos perfeitos no espaço. Seu cone de detecção é de aproximadamente 15° – o que significa que ele pode prever com precisão distâncias em curtas distâncias, mas em distâncias maiores ele tem dificuldade em discernir objetos de área pequena de objetos de área maior. O cone de direção de 15° equivale a aproximadamente uma área de objeto de 13% da distância que poderia estar. Como exemplo, um objeto que está a 1m de distância precisará ter 130 cm para que o HC-SR04 o detecte corretamente. Se a área for menor, ele pode interpretar mal o tamanho do objeto e, portanto, sua capacidade de reconhecê-lo. Se o objeto for maior que 130 cm, então ele pode ser registrado em vários ângulos até que esteja fora da visão principal do sensor. Se assumirmos que uma pessoa tem cerca de 50 cm de largura, isso significa que com cerca de 400 cm o HC-SR04 a reconhecerá corretamente. Se a pessoa estiver a mais de 400 cm, o sensor pode não registrar a pessoa, enquanto se a pessoa estiver dentro de 400 cm, ele a reconhecerá em vários ângulos.

Conclusão

Um projeto de radar baseado em Arduino foi implementado neste tutorial usando um Arduino, sensor de distância ultrassônico HC-SR04, micro servo motor MG90S e código Python executado em um Raspberry Pi. O objetivo deste projeto era apresentar um novo conceito relacionado à tecnologia do mundo real, mas implementado por meio de ferramentas baratas disponíveis para o fabricante e aspirante a engenheiro. O HC-SR04 usa ondas sonoras para aproximar a distância entre seu receptor e um objeto à distância, enquanto o servo MG90S gira de uma forma prescrita de acordo com os sinais de modulação de largura de pulso controlados pela placa Arduino. A fim de visualizar a posição angular emitida e o intervalo aproximado do HC-SR04 – o código Python foi implementado em um Raspberry Pi para criar um indicador de posição de plano em um gráfico polar. Este PPI dá ao usuário uma maneira de visualizar os objetos que circundam o motor e o sensor ultrassônico, de forma muito semelhante a um radar que aproxima os objetos ao redor de sua estação base. Diversas habilidades utilizadas neste tutorial podem ser aplicadas a aplicações do mundo real, seja por meio de detecção de obstáculos, controle motor, distanciamento e alcance, ou até mesmo uma nova ferramenta de visualização de dados.