Labirinto com Arduino e Acelerômetro MPU6050 33

Neste post iremos mostrar como construímos um jogo de labirinto com Arduino e Acelerômetro MPU6050 que se move nos eixos X e Y utilizando um joystick para controle. Ao todo utilizamos um Arduino, dois servos, acelerômetro MPU6050 e algumas peças impressas em 3D.

Labirinto com Arduino

Materiais utilizados no projeto Labirinto com Arduino

Peças de impressão 3D

Um ponto interessante sobre impressão 3D é que muitos projetos estão disponíveis gratuitamente na internet e podem ser customizados de acordo com necessidades diversas. A estrutura do labirinto já é de um projeto existente que pode ser encontrado aqui.

No projeto original, o autor utiliza um Arduino com módulo bluetooth e o controle do labirinto é feito utilizando o acelerômetro de um celular. O autor ainda escreveu um Instructables documentando todo o processo de construção e montagem do labirinto.

Material utilizado

A FilipeFlop fez uma modificação para que o labirinto fosse controlado através de um “joystick”. Desenhamos no Fusion 360 uma peça 3D adicional que se assemelha um joystick. O MPU6050 é então encaixado nessa peça para facilitar o manuseio. O Projeto do Joystick pode ser encontrado no Thingiverse da FILIPEFLOP.

Modelagem 3D no Fusion 360

Joystick

Caso você não tenha acesso a uma impressora 3D, existem serviços on-line de impressão 3D. Ou você também pode optar por usar outra forma de construção utilizando madeira por exemplo.

Circuito eletrônico

Os servos são conectados nas portas digitais do Arduino e o acelerômetro na porta I2C como indicado no esquema abaixo.

Circuito Labirinto com Arduino

Programação Arduino

A programação basicamente consiste em ler os dados de ângulo do acelerômetro e mapear esses dados para movimentar os dois servos do eixo X e Y.

#include <VarSpeedServo.h>
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
//#include "MPU6050.h" 
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
    #include "Wire.h"
#endif

MPU6050 mpu;

#define OUTPUT_READABLE_YAWPITCHROLL

#define INTERRUPT_PIN 2
#define LED_PIN 13 // (Arduino is 13, Teensy is 11, Teensy++ is 6)
bool blinkState = false;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

VarSpeedServo servoX;
VarSpeedServo servoY;

int8_t adjustX = -7;
int8_t adjustY = -20;

int8_t angleX;
int8_t angleY;


// ================================================================
// ===               INTERRUPT DETECTION ROUTINE                ===
// ================================================================

volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
    mpuInterrupt = true;
}


void setup() {
  servoX.attach(5);
  servoY.attach(6);

  // configure LED for output
  pinMode(LED_PIN, OUTPUT);    
  
  // join I2C bus (I2Cdev library doesn't do this automatically)
  #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
      Wire.begin();
      Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
  #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
      Fastwire::setup(400, true);
  #endif

  // initialize serial communication
  // (115200 chosen because it is required for Teapot Demo output, but it's
  // really up to you depending on your project)
  Serial.begin(115200);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately

  // initialize device
  Serial.println(F("Initializing I2C devices..."));
  mpu.initialize();
  pinMode(INTERRUPT_PIN, INPUT);

  // verify connection
  Serial.println(F("Testing device connections..."));
  Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));

  delay(1000);

//  // wait for ready
//  Serial.println(F("\nSend any character to begin DMP programming and demo: "));
//  while (Serial.available() && Serial.read()); // empty buffer
//  while (!Serial.available());                 // wait for data
//  while (Serial.available() && Serial.read()); // empty buffer again

  // load and configure the DMP
  Serial.println(F("Initializing DMP..."));
  devStatus = mpu.dmpInitialize();

  // supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(220);
  mpu.setYGyroOffset(76);
  mpu.setZGyroOffset(-85);
  mpu.setZAccelOffset(1788); // 1688 factory default for my test chip

  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
      // turn on the DMP, now that it's ready
      Serial.println(F("Enabling DMP..."));
      mpu.setDMPEnabled(true);

      // enable Arduino interrupt detection
      Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
      attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
      mpuIntStatus = mpu.getIntStatus();

      // set our DMP Ready flag so the main loop() function knows it's okay to use it
      Serial.println(F("DMP ready! Waiting for first interrupt..."));
      dmpReady = true;

      // get expected DMP packet size for later comparison
      packetSize = mpu.dmpGetFIFOPacketSize();
  } else {
      // ERROR!
      // 1 = initial memory load failed
      // 2 = DMP configuration updates failed
      // (if it's going to break, usually the code will be 1)
      Serial.print(F("DMP Initialization failed (code "));
      Serial.print(devStatus);
      Serial.println(F(")"));
  }
}

void loop() {


  // if programming failed, don't try to do anything
  if (!dmpReady) return;

  // wait for MPU interrupt or extra packet(s) available
  while (!mpuInterrupt && fifoCount < packetSize) {
      // other program behavior stuff here
      // .
      // .
      // .
      // if you are really paranoid you can frequently test in between other
      // stuff to see if mpuInterrupt is true, and if so, "break;" from the
      // while() loop to immediately process the MPU data
      // .
      // .
      // .
  }

  // reset interrupt flag and get INT_STATUS byte
  mpuInterrupt = false;
  mpuIntStatus = mpu.getIntStatus();

  // get current FIFO count
  fifoCount = mpu.getFIFOCount();

  // check for overflow (this should never happen unless our code is too inefficient)
  if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
      // reset so we can continue cleanly
      mpu.resetFIFO();
      Serial.println(F("FIFO overflow!"));

  // otherwise, check for DMP data ready interrupt (this should happen frequently)
  } else if (mpuIntStatus & 0x02) {
      // wait for correct available data length, should be a VERY short wait
      while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();

      // read a packet from FIFO
      mpu.getFIFOBytes(fifoBuffer, packetSize);
      
      // track FIFO count here in case there is > 1 packet available
      // (this lets us immediately read more without waiting for an interrupt)
      fifoCount -= packetSize;

      #ifdef OUTPUT_READABLE_YAWPITCHROLL
        // display Euler angles in degrees
        mpu.dmpGetQuaternion(&q, fifoBuffer);
        mpu.dmpGetGravity(&gravity, &q);
        mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
        Serial.print("ypr\t");
        Serial.print(ypr[0] * 180/M_PI);
        Serial.print("\t");
        Serial.print(ypr[1] * 180/M_PI);
        Serial.print("\t");
        Serial.println(ypr[2] * 180/M_PI);

        angleX = map((ypr[1] * 180/M_PI), -70, 70, 80, 120);
        servoX.write(angleX+adjustX, 50, false);
        
        angleY = map((ypr[2] * 180/M_PI), -70, 70, 120, 80);
        servoY.write(angleY+adjustY, 50, false);   
 
      #endif
      
    // blink LED to indicate activity
    blinkState = !blinkState;
    digitalWrite(LED_PIN, blinkState);
  }
}

Labirinto em funcionamento

Para movimentar o labirinto basta mexer com o joystick. Utilizamos uma esfera de alumínio para movimentar. Veja abaixo o projeto em funcionamento.

Gostou do Labirinto com Arduino e Acelerômetro MPU6050? Ajude-nos a melhorar o blog comentando abaixo sobre este tutorial. Não se esqueça de visitar o fórum da FILIPEFLOP!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

33 Comentários

  1. Seria interessante implementar um código para o micro fazer o labirinto sozinho, independente do local em que você coloque a bolinha. 🙂

  2.  

    Arduino: 1.8.5 (Windows 7), Placa:”Arduino/Genuino Uno”

    Opções de compilação alteradas, recompilando tudo
    C:\Users\Micro01\Desktop\readme.txt\readme.txt.ino:2:20: fatal error: I2Cdev.h: No such file or directory

    #include “I2Cdev.h”

    ^

    compilation terminated.

    exit status 1
    Erro compilando para a placa Arduino/Genuino Uno

    Este relatório teria mais informações com
    “Mostrar a saida detalhada durante a compilação”
    opção pode ser ativada em “Arquivo -> Preferências”

    Olá, estou com este seguinte erro e não consigo solucionar, por favor poderia me ajudar?

      1. Obrigado.

      2. Boa tarde Erro ainda persiste, mesmo instalando a biblioteca como se deve, pelo link que você nos passou. Erro:

        Arduino: 1.6.5 (Windows 8.1), Placa:”Arduino/Genuino Uno”

        sketch_aug06a.ino:2:20: fatal error: I2Cdev.h: No such file or directory
        compilation terminated.
        Erro compilando.

        Este relatório deveria ter mais informações
        “Mostrar saída verbosa durante a compilação”
        habilitado em Arquivo > Preferências.

        Pedro Filipe de Souza Gonçalves
        1. Olá,

          Por favor, verifique se a biblioteca instalada possui o arquivo em questão. Para tal, basta abrir o .ZIP e analisar se tal arquivo existe. Se existir, efetue a instalação novamente. Em último caso, leve a sua dúvida para o nosso fórum, lá você pode enviar seu código para que possamos analisar mais acertadamente!

          Abraços!
          Diogo – Equipe FilipeFlop

          1. Olá! agradeço a resposta. Tudo está aparentemente correto, inclusive bibliotecas com links tiradas aqui. Acabo de abrir a dúvida no fórum.

            Obrigado!

  3. Boa noite, Giovanni! Tudo bom? Você sabe onde que eu posso comprar ou você vende está peça amarela, a base do Labirinto.

    Matheus Henrique Pretti Moreira Quintanilha
    1. Olá Matheus!

      A peça amarela foi impressa em 3D. Pronta, acho que você não vai encontrar. Você pode baixar todos os arquivos no thingiverse: https://www.thingiverse.com/thing:1451703

      Existem serviços de impressão 3D na internet, onde você pode encomendar peças.
      http://www.p1prototipos.com.br/
      http://www.impressao3dfacil.com.br/
      http://www.3dmachine.com.br/

      Abraço!

      1. Obrigado Giovanni, outra pergunta mesmo depois que eu baixei a biblioteca está aparecendo o seguinte erro, fiz o seguinte caminho sketch – incluir biblioteca e adicionar biblioteca.zip

        Arduino: 1.8.7 (Windows Store 1.8.15.0) (Windows 10), Placa:”Arduino/Genuino Uno”

        i2cdevlib-master.zip:1:27: error: VarSpeedServo.h: No such file or directory

        compilation terminated.

        exit status 1
        VarSpeedServo.h: No such file or directory

        A pasta/arquivo zip especificado não contém uma biblioteca válida

        Este relatório teria mais informações com
        “Mostrar a saida detalhada durante a compilação”
        opção pode ser ativada em “Arquivo -> Preferências”

        Matheus Henrique Pretti Moreira Quintanilha
        1. São duas bibliotecas a serem instaladas. i2cdevlib e varspeedservo.

          “VarSpeedServo.h: No such file or directory” significa que o arquivo da biblioteca não foi encontrado.

          https://github.com/jrowberg/i2cdevlib
          https://github.com/netlabtoolkit/VarSpeedServo

          Veja como instalar bibliotecas corretamnete:
          https://www.robocore.net/tutoriais/adicionando-bibliotecas-na-ide-arduino.html
          http://labdegaragem.com/profiles/blogs/tutorial-arduino-instalando-bibliotecas

          Abraço!

  4. Bom dia.
    Fiz esse projeto porém servo motor do eixo X não move quase nada. Já o servo motor do eixo Y move perfeitamente. Poderia me ajudar? Aonde eu teria que ajustar no codigo? pois sem o MPU6050 o servo motor X move perfeitamente.

    1. Olá Amanda!

      Você pode ajustar a posição central aqui:
      int8_t adjustX = -7;
      int8_t adjustY = -20;

      E a quantidade de movimento aqui:
      angleX = map((ypr[1] * 180/M_PI), -70, 70, 80, 120);
      servoX.write(angleX+adjustX, 50, false);

      angleY = map((ypr[2] * 180/M_PI), -70, 70, 120, 80);
      servoY.write(angleY+adjustY, 50, false);

      1. O que significa essas funções:

        angleX = map((ypr[1] * 180/M_PI), -70, 70, 80, 120);
        servoX.write(angleX+adjustX, 50, false);

        1. ypr[] é o vetor que contém os valores dos eixos. No caso estamos usando ypr[1] e ypr[2] que são pitch e row, ou X e Y.

          float ypr[3]; // [yaw, pitch, roll]

          Veja no google o que é Yaw Pitch e Row:
          https://www.google.com.br/search?q=yaw+pitch+roll&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjWkPe5w5reAhWjs1kKHavuBxMQ_AUIDigB&biw=1280&bih=654#imgrc=4R5jctF0uP_q5M:

          Abaixo é feita a conversão para ângulo de 0 a 180º
          ypr[1] * 180/M_PI

          Então é usada a função Map para mapear os valores de ângulo. Isso porque deseja-se limitar o movimento, ou seja o labirinto não pode girar de 0 a 180 graus. iremos ficar entre 80 e 120. E também os valores de ângulo do sensor(-70 até 70) não correspondem exatamente a posição física real do labirinto.

          angleX = map((ypr[1] * 180/M_PI), -70, 70, 80, 120);

          Função Map:
          https://www.arduino.cc/reference/pt/language/functions/math/map/

          enfim servoX.write “escreve” um valor de ângulo no servo. O servo irá movimentar-se para o ângulo.

          Documentação da biblioteca VarSpeedServo https://github.com/netlabtoolkit/VarSpeedServo

          Espero ter ajudado!

  5. Bem interessante o projeto. Vi que alguns componentes estão a venda, mas senti falta da venda de um kit completo do projeto (incluindo a base e o “joystick”).

    1. Olá Marcelo! Obrigado pela leitura

      Não temos um kit completo deste projeto. A ideia principal foi mostrar como utilizar um acelerometro junto com Arduino para controle de servo motores.

      Mas os arquivos de impressão 3D estão todos disponíveis e você pode utilizar um serviço de impressão 3d de sua preferência. Segue um exemplo:
      http://www.imprima3d.com/index.html

  6. Olá filipe!

    Fiz o projeto todo, a programação rodou certinho, no entanto não houve movimentação em X e nem em Y, o que poderia ser?

    1. Olá Rafael!

      Verifique se as conexões do acelerômetro estão bem conectadas e veja no Serial Monitor se as mensagens aparecem, incluindo os dados do acelerômetro.

      1. Muito obrigado, conseguir fazer ele funcionar. No entanto, ele funciona perfeitamente quando esta sendo alimentado pela USB do pc, quando passo pra uma fonte de 12V passa a ter problemas. Alguma dica quanto a isso?

        Obrigado pela disposição!

        1. Olá Rafael!

          Nós testamos aqui com uma fonte externa também mas era de 9V. No gif dá pra ver a fonte ligada.

          Veja se não há nenhum ruído na fonte.

  7. posso usar essa progamação diretamente no pic 18f4550?

    1. Olá Victor!

      Não pode. São microcontroladores diferentes com diferentes especificações. Para acionar um pino, por exemplo, um usa um tipo de instrução, outro usa instruções totalmente diferentes.

      O que você deve fazer é o seguinte:

      – Aprender a usar um acelerômetro MPU6050 com PIC
      – Aprender a usar um servo motor com PIC
      – Entender a programação e lógica deste tutorial
      – Juntar todos os conceitos e aplicar na sua programação customizada de labirinto com PIC.

      Espero ter ajudado e bons projetos!

  8. Olá meu amigo, como faz para limitar os ângulos dos servos motores? por exemplo limitar de 70° a 110°.

    1. Olá!

      Você deve colocar uma condição (if) antes da parte:
      angleX = map((ypr[1] * 180/M_PI), -70, 70, 80, 120);
      servoX.write(angleX+adjustX, 50, false);

      angleY = map((ypr[2] * 180/M_PI), -70, 70, 120, 80);
      servoY.write(angleY+adjustY, 50, false);

      Nela você verifica se os ângulos estão dentro do limite que você impôs e, se tiverem, você ‘escreve’ eles nos servos.

      Abraços!

      André – Equipe FilipeFlop

  9. Olá poderia ensinar a comandar o computador ou televisao com o sensor ultra 2 sensores sonico e com o Arduino

    1. Olá Gabriel,

      Ainda não temos nenhum tutorial sobre o assunto. Mas você pode começar com esse aqui: https://www.filipeflop.com/blog/sensor-ultrassonico-hc-sr04-ao-arduino/

      Abraço!
      Rosana – Equipe FilipeFlop

  10. Estou tentando reproduzir o labirinto,já instalei as bibliotecas, porem me é retornado o seguinte erro quando vou compilar:

    Arduino: 1.8.9 (Windows 7), Placa:”Arduino/Genuino Uno”

    Opções de compilação alteradas, recompilando tudo
    In file included from C:\Users\Eduardo\Documents\labirinto\labirinto.ino:3:0:

    C:\Users\Eduardo\Documents\Arduino\libraries\MPU6050/MPU6050_6Axis_MotionApps20.h: In member function ‘uint8_t MPU6050::dmpGetGravity(int16_t*, const uint8_t*)’:

    C:\Users\Eduardo\Documents\Arduino\libraries\MPU6050/MPU6050_6Axis_MotionApps20.h:522:65: warning: integer overflow in expression [-Woverflow]

    – (int32_t)qI[2] * qI[2] + (int32_t)qI[3] * qI[3]) / (2 * 16384);

    ^

    C:\Users\Eduardo\Documents\labirinto\labirinto.ino: In function ‘void loop()’:

    labirinto:188:47: error: no matching function for call to ‘VarSpeedServo::write(int, int, bool)’

    servoX.write(angleX+adjustX, 50, false);

    ^

    In file included from C:\Users\Eduardo\Documents\labirinto\labirinto.ino:1:0:

    C:\Users\Eduardo\Documents\Arduino\libraries\VarSpeedServo/VarSpeedServo.h:130:8: note: candidate: void VarSpeedServo::write(int)

    void write(int value); // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds

    ^

    C:\Users\Eduardo\Documents\Arduino\libraries\VarSpeedServo/VarSpeedServo.h:130:8: note: candidate expects 1 argument, 3 provided

    labirinto:191:47: error: no matching function for call to 'VarSpeedServo::write(int, int, bool)'

    servoY.write(angleY+adjustY, 50, false);

    ^

    In file included from C:\Users\Eduardo\Documents\labirinto\labirinto.ino:1:0:

    C:\Users\Eduardo\Documents\Arduino\libraries\VarSpeedServo/VarSpeedServo.h:130:8: note: candidate: void VarSpeedServo::write(int)

    void write(int value); // if value is Preferências”

    1. Eduardo, testei o código aqui agorinha e compilou tranquilo!

      Crie um arquivo novo e cole o código ali sem alterar nada para testar.

      Abraços!
      Diogo – Equipe FilipeFlop

      1. mesmo erro :/
        Arduino: 1.8.9 (Windows 7), Placa:”Arduino/Genuino Uno”

        Opções de compilação alteradas, recompilando tudo
        In file included from C:\Users\Eduardo\Documents\Labirinto_20-09\Labirinto_20-09.ino:3:0:

        C:\Users\Eduardo\Documents\Arduino\libraries\MPU6050/MPU6050_6Axis_MotionApps20.h: In member function ‘uint8_t MPU6050::dmpGetGravity(int16_t*, const uint8_t*)’:

        C:\Users\Eduardo\Documents\Arduino\libraries\MPU6050/MPU6050_6Axis_MotionApps20.h:522:65: warning: integer overflow in expression [-Woverflow]

        – (int32_t)qI[2] * qI[2] + (int32_t)qI[3] * qI[3]) / (2 * 16384);

        ^

        C:\Users\Eduardo\Documents\Labirinto_20-09\Labirinto_20-09.ino: In function ‘void loop()’:

        Labirinto_20-09:188:47: error: no matching function for call to ‘VarSpeedServo::write(int, int, bool)’

        servoX.write(angleX+adjustX, 50, false);

        ^

        In file included from C:\Users\Eduardo\Documents\Labirinto_20-09\Labirinto_20-09.ino:1:0:

        C:\Users\Eduardo\Documents\Arduino\libraries\VarSpeedServo/VarSpeedServo.h:130:8: note: candidate: void VarSpeedServo::write(int)

        void write(int value); // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds

        ^

        C:\Users\Eduardo\Documents\Arduino\libraries\VarSpeedServo/VarSpeedServo.h:130:8: note: candidate expects 1 argument, 3 provided

        Labirinto_20-09:191:47: error: no matching function for call to 'VarSpeedServo::write(int, int, bool)'

        servoY.write(angleY+adjustY, 50, false);

        ^

        In file included from C:\Users\Eduardo\Documents\Labirinto_20-09\Labirinto_20-09.ino:1:0:

        C:\Users\Eduardo\Documents\Arduino\libraries\VarSpeedServo/VarSpeedServo.h:130:8: note: candidate: void VarSpeedServo::write(int)

        void write(int value); // if value is Preferências”

        a linha destacada é essa
        ” servoY.write(angleY+adjustY, 50, false); “

        1. Eduardo,

          Já tentou reinstalar as bibliotecas? Ou uma outra versão da IDE?

          Abraços!
          Diogo – Equipe FilipeFlop

  11. O que significa essa parte do código:

    // supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(220);
    mpu.setYGyroOffset(76);
    mpu.setZGyroOffset(-85);
    mpu.setZAccelOffset(1788); // 1688 factory default for my test chip

    1. Joliver,

      É nesses métodos que você define os offsets do acelerômetro (os números entre parênteses).

      Abraços!
      Diogo – Equipe FilipeFlop