Emulador de radar com Arduino + Python
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.
Conteudo
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:
- Inicie a comunicação com a placa Arduino
- Crie plotagem polar para emulador de radar
- Comece o loop pelos dados de entrada do Arduino
- Espere o ‘Radar Start’ começar a traçar
- 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:
- Atualizar apenas os dados, não o gráfico (trechos de código restore_region(), drawartist() e blit() abaixo)
- 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.