Proyecto 19: Múltiples Funciones del Robot Tanque Ultrasónico

(1)Descripción:

El coche inteligente ha realizado una función individual en cada proyecto anterior.

¿Puede mostrar múltiples funciones a la vez? Sí.

En este último gran proyecto, pretendemos usar un código completo para controlar el coche inteligente y mostrar todas las funciones mencionadas en proyectos anteriores. Usamos las teclas de la APP Bluetooth para cambiar automáticamente entre varias funciones, bastante simple y conveniente.

(2)Diagrama de Flujo:

(3)Diagrama de Conexión:

1. GND, VCC, SDA y SCL de la placa 8x16 están conectados a G (GND), + (VCC), A4 y A5 de la placa de expansión.

2. VCC, Trig, Echo y Gnd del sensor ultrasónico están conectados a 5V (V), 12 (S), 13 (S) y Gnd (G).

3. El cable marrón, el cable rojo y el cable naranja del servo están conectados a Gnd (G), 5v (V) y D10.

4. RXD, TXD, GND y VCC del módulo BT están conectados a TX, RX, G (GND) y 5V (VCC). STATE y BRK no necesitan ser conectados.

5. Los pines “G”, “V” y S del módulo fotorresistor izquierdo están conectados a G (GND), V (VCC) y A1, respectivamente; El módulo fotorresistor derecho está conectado a G (GND), V (VCC) y A2, respectivamente.

6. Los puertos distales del sensor de seguimiento de línea son 11, 7 y 8.

(4)Código de Prueba:

(Nota: No conecte el módulo Bluetooth antes de cargar el código, porque la carga del código también usa comunicación serial, y puede haber conflictos con la comunicación serial Bluetooth, lo que puede causar que la carga falle.)

/*
  Keyestudio Mini Tank Robot V3 (Popular Edition)
  lesson 19
  Ultrasonic Tank Robot Multiple Functions
  http://www.keyestudio.com
*/
#include <IRremote.h>
IRrecv irrecv(3);  //
decode_results results;
long ir_rec;  //usado para guardar el valor IR

/***********/
#define USE_FAN_FUNCTION   0

//Arreglo, usado para guardar datos de imágenes, puede calcularse por uno mismo u obtenerse de la herramienta de módulo
unsigned char start01[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
unsigned char STOP01[] = {0x2E, 0x2A, 0x3A, 0x00, 0x02, 0x3E, 0x02, 0x00, 0x3E, 0x22, 0x3E, 0x00, 0x3E, 0x0A, 0x0E, 0x00};
unsigned char front[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x12, 0x09, 0x12, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char back[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x48, 0x90, 0x48, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char left[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x28, 0x10, 0x44, 0x28, 0x10, 0x44, 0x28, 0x10, 0x00};
unsigned char right[] = {0x00, 0x10, 0x28, 0x44, 0x10, 0x28, 0x44, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

unsigned char Smile[] = {0x00, 0x00, 0x1c, 0x02, 0x02, 0x02, 0x5c, 0x40, 0x40, 0x5c, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00};
unsigned char Disgust[] = {0x00, 0x00, 0x02, 0x02, 0x02, 0x12, 0x08, 0x04, 0x08, 0x12, 0x22, 0x02, 0x02, 0x00, 0x00, 0x00};
unsigned char Happy[] = {0x02, 0x02, 0x02, 0x02, 0x08, 0x18, 0x28, 0x48, 0x28, 0x18, 0x08, 0x02, 0x02, 0x02, 0x02, 0x00};
unsigned char Squint[] = {0x00, 0x00, 0x00, 0x41, 0x22, 0x14, 0x48, 0x40, 0x40, 0x48, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00};
unsigned char Despise[] = {0x00, 0x00, 0x06, 0x04, 0x04, 0x04, 0x24, 0x20, 0x20, 0x26, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00};
unsigned char Heart[] = {0x00, 0x00, 0x0C, 0x1E, 0x3F, 0x7F, 0xFE, 0xFC, 0xFE, 0x7F, 0x3F, 0x1E, 0x0C, 0x00, 0x00, 0x00};

unsigned char clear[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

#define SCL_Pin  A5  //establecer el pin del reloj en A5
#define SDA_Pin  A4  //establecer el pin de datos en A4

#define ML_Ctrl 4  //definir el pin de control de dirección del motor izquierdo como 4
#define ML_PWM 6   //definir el pin de control PWM del motor izquierdo
#define MR_Ctrl 2  //definir el pin de control de dirección del sensor derecho
#define MR_PWM 5   //definir el pin de control PWM del motor derecho
char ble_val;      //usado para guardar el valor Bluetooth
byte speeds_L = 200; //la velocidad inicial del motor izquierdo es 200
byte speeds_R = 200; //la velocidad inicial del motor derecho es 200
String speeds_l, speeds_r; //recibir caracteres PWM y convertirlos en valor PWM

//conectar el sensor de seguimiento de línea
#define L_pin  11  //izquierda
#define M_pin  7  //medio
#define R_pin  8  //derecha
int L_val, M_val, R_val;

#if USE_FAN_FUNCTION  /****usar ventilador*******/
int flame_L = A1; //definir el puerto analógico del sensor de llama izquierdo en A1
int flame_R = A2; //definir el puerto analógico del sensor de llama derecho en A2
int flame_valL, flame_valR;

//el pin del motor 130
int INA = 12;
int INB = 13;

#else /****usar el sensor ultrasónico*******/
#define servoPin    10  //pin del servo
#define light_L_Pin A1   //definir el pin del fotorresistor izquierdo
#define light_R_Pin A2   //definir el pin del fotorresistor derecho
int left_light;
int right_light;

#define Trig 12
#define Echo 13
float distance;//Almacenar los valores de distancia detectados por ultrasonido para seguimiento

//Almacenar los valores de distancia detectados por ultrasonido para evitar obstáculos
int a;
int a1;
int a2;

#endif

bool flag;  //variable flag, usada para entrar y salir de un modo

void setup() 
{
  Serial.begin(9600);
  irrecv.enableIRIn();  //Inicializar la librería del control remoto IR

  pinMode(SCL_Pin, OUTPUT);
  pinMode(SDA_Pin, OUTPUT);
  
  pinMode(ML_Ctrl, OUTPUT);
  pinMode(ML_PWM, OUTPUT);
  pinMode(MR_Ctrl, OUTPUT);
  pinMode(MR_PWM, OUTPUT);

  pinMode(L_pin, INPUT); //definir los pines de los sensores como ENTRADA
  pinMode(M_pin, INPUT);
  pinMode(R_pin, INPUT);

  matrix_display(clear);    //limpiar pantalla
  matrix_display(start01);  //mostrar inicio

#if USE_FAN_FUNCTION/****usar el ventilador*******/
  pinMode(INA, OUTPUT);//establecer INA como SALIDA
  pinMode(INB, OUTPUT);//establecer INB como SALIDA

  //definir entradas del sensor de llama
  pinMode(flame_L, INPUT);
  pinMode(flame_R, INPUT);
#else/****usar el sensor ultrasónico*******/
  pinMode(servoPin, OUTPUT);
  pinMode(light_L_Pin, INPUT);
  pinMode(light_R_Pin, INPUT);

  pinMode(Trig, OUTPUT);
  pinMode(Echo, INPUT);
  procedure(90); //establecer el ángulo del servo en 90°
#endif
}

void loop() 
{
  if (Serial.available()) //si hay datos en el buffer serial
  {
    ble_val = Serial.read();
    Serial.println(ble_val);
    switch (ble_val) 
    {
      case 'F': Car_front(); break; //el comando para ir hacia adelante

      case 'B': Car_back(); break;  //el comando para ir hacia atrás

      case 'L': Car_left(); break;  //el comando para girar a la izquierda

      case 'R': Car_right(); break; //el comando para girar a la derecha

      case 'S': Car_Stop();  break; //detener

      case 'e': Tracking();  break; //entrar en modo de seguimiento de línea

      case 'f': Confinement(); break;  //entrar en modo de confinamiento

#if USE_FAN_FUNCTION/****usar ventilador*******/
      case 'j': Fire(); break;  //activar modo de extinción de fuego

      case 'c': fan_begin(); break;  //activar el ventilador

      case 'd': fan_stop();  break;  //apagar el ventilador

#else/****usar el sensor ultrasónico*******/
      case 'g': Avoid(); break;  //entrar en modo de evasión de obstáculos

      case 'h': Follow(); break;  //entrar en modo de seguimiento

      case 'i': Light_following();  break;  //entrar en modo de seguimiento de luz
#endif
      case 'u': 
        speeds_l = Serial.readStringUntil('#'); 
        speeds_L = String(speeds_l).toInt(); 
        break; //comenzar recibiendo u, terminar recibiendo el carácter # y convertir en entero

      case 'v': 
        speeds_r = Serial.readStringUntil('#');
        speeds_R = String(speeds_r).toInt(); 
        break; //comenzar recibiendo u, terminar recibiendo el carácter # y convertir en entero

      case 'k': matrix_display(Smile);    break;  //mostrar cara de "sonrisa"
      case 'l': matrix_display(Disgust);  break;  //mostrar cara de "disgusto"
      case 'm': matrix_display(Happy);    break;  //mostrar cara de "feliz"
      case 'n': matrix_display(Squint);   break;  //mostrar cara de "tristeza"
      case 'o': matrix_display(Despise);  break;  //mostrar cara de "desprecio"
      case 'p': matrix_display(Heart);    break;  //mostrar imagen de corazón
      case 'z': matrix_display(clear);    break;  //limpiar imágenes

      default: break;
    }
  }

#if (USE_FAN_FUNCTION != 1)/****la función para no usar el ventilador*******/
  //Las siguientes tres señales se usan principalmente para impresión cíclica
  if (ble_val == 'x') 
  {
    distance = checkdistance(); Serial.println(distance);
    delay(50);
  } 
  else if (ble_val == 'w') 
  {
    left_light = analogRead(light_L_Pin);
    Serial.println(left_light);
    delay(50);
  } 
  else if (ble_val == 'y') 
  {
    right_light = analogRead(light_R_Pin);
    Serial.println(right_light);
    delay(50);
  }
#endif

  if (irrecv.decode(&results))  //Recibir valor del control remoto infrarrojo
  {
    ir_rec = results.value;
    Serial.println(ir_rec, HEX);
    switch (ir_rec) 
    {
      case 0xFF629D: Car_front();   break;   //ir hacia adelante
      case 0xFFA857: Car_back();    break;   //ir hacia atrás
      case 0xFF22DD: Car_left();    break;   //girar a la izquierda
      case 0xFFC23D: Car_right();   break;   //girar a la derecha
      case 0xFF02FD: Car_Stop();    break;   //detener
      default: break;
    }
    irrecv.resume();
  }
}

#if (USE_FAN_FUNCTION != 1)/****usar el sensor ultrasónico*******/

//Controlar el sensor ultrasónico
float checkdistance() 
{
  float distance;
  digitalWrite(Trig, LOW);
  delayMicroseconds(2);
  digitalWrite(Trig, HIGH);
  delayMicroseconds(10);
  digitalWrite(Trig, LOW);
  distance = pulseIn(Echo, HIGH) / 58.20;  //
  delay(10);
  return distance;
}


//la función para controlar el servo
void procedure(int myangle) 
{
  int pulsewidth;
  pulsewidth = map(myangle, 0, 180, 500, 2000);  //Calcular el valor del ancho de pulso, que debe ser el valor de mapeo de 500 a 2500. Considerando la influencia de la librería infrarroja, se usa 500~2000 aquí.
  for (int i = 0; i < 5; i++) 
  {
    digitalWrite(servoPin, HIGH);
    delayMicroseconds(pulsewidth);   //La duración del nivel alto es el ancho de pulso
    digitalWrite(servoPin, LOW);
    delay((20 - pulsewidth / 1000));  //El período es 20ms, por lo que el nivel bajo dura el resto del tiempo
  }
}

/*****************evasión de obstáculos******************/
void Avoid()
{
  flag = 0;
  while (flag == 0)
  {
    a = checkdistance();  //la distancia frontal se establece en a
    if (a < 20) //Cuando la distancia frontal es menor a 20cm
    {
      Car_Stop();  //detener
      delay(500); //retardo de 500ms
      procedure(180);  //el servo gira a la izquierda
      delay(500); //retardo de 500ms
      a1 = checkdistance();  //la distancia izquierda se establece en a1
      delay(100); //leer valor

      procedure(0); //el servo gira a la derecha
      delay(500); //retardo de 500ms
      a2 = checkdistance(); ///la distancia derecha se establece en a2
      delay(100); //leer valor

      procedure(90);  //volver a 90°
      delay(500);
      if (a1 > a2)  //Cuando la distancia a la izquierda es mayor que la distancia a la derecha
      {
        Car_left();  //el robot gira a la izquierda
        delay(700);  //girar a la izquierda 700ms
      } 
      else 
      {
        Car_right(); //girar a la derecha
        delay(700);
      }
    }
    else  //si la distancia frontal ≥20cm, el robot va hacia adelante
    {
      Car_front(); //ir hacia adelante
    }
    //recibir el valor Bluetooth para salir del bucle
    if (Serial.available())
    {
      ble_val = Serial.read();
      if (ble_val == 'S')  //recibir S
      {
        flag = 1;  //establecer flag en 1 para salir del bucle
        Car_Stop();
      }
    }
  }
}

/*******************seguimiento***************/
void Follow() 
{
  flag = 0;
  while (flag == 0) 
  {
    distance = checkdistance();  //establecer el valor de distancia en distance
    if (distance >= 20 && distance <= 60) //ir hacia adelante
    {
      Car_front();
    }
    else if (distance > 10 && distance < 20)  // detener
    {
      Car_Stop();
    }
    else if (distance <= 10)  //ir hacia atrás
    {
      Car_back();
    }
    else  //detener
    {
      Car_Stop();
    }
    if (Serial.available())
    {
      ble_val = Serial.read();
      if (ble_val == 'S')
      {
        flag = 1;  //salir del bucle
        Car_Stop();
      }
    }
  }
}

/****************seguimiento de luz******************/
void Light_following() 
{
  flag = 0;
  while (flag == 0) 
  {
    left_light = analogRead(light_L_Pin);
    right_light = analogRead(light_R_Pin);
    if (left_light > 650 && right_light > 650) //ir hacia adelante
    {
      Car_front();
    }
    else if (left_light > 650 && right_light <= 650)  //girar a la izquierda
    {
      Car_left();
    }
    else if (left_light <= 650 && right_light > 650) //girar a la derecha
    {
      Car_right();
    }
    else  //de lo contrario, detener
    {
      Car_Stop();
    }
    if (Serial.available())
    {
      ble_val = Serial.read();
      if (ble_val == 'S') 
      {
        flag = 1;
        Car_Stop();
      }
    }
  }
}

#else/****usar el ventilador*******/
/***************activar el ventilador*****************/
void fan_begin() 
{
  digitalWrite(INA, LOW);
  digitalWrite(INB, HIGH);
}

/***************detener el ventilador*****************/
void fan_stop() 
{
  digitalWrite(INA, LOW);
  digitalWrite(INB, LOW);
}

/***************extinguir fuego****************/
void Fire() 
{
  flag = 0;
  while (flag == 0) 
  {
    //Leer el valor analógico del sensor de llama
    flame_valL = analogRead(flame_L);
    flame_valR = analogRead(flame_R);
    if (flame_valL <= 700 || flame_valR <= 700) 
    {
      Car_Stop();
      fan_begin();
    } 
    else 
    {
      fan_stop();
      L_val = digitalRead(L_pin); //Leer el valor del sensor izquierdo
      M_val = digitalRead(M_pin); //Leer el valor del sensor izquierdo
      R_val = digitalRead(R_pin); //Leer el valor del sensor derecho
     if (M_val == 1)  //el del medio detecta líneas negras
      {
        if (L_val == 1 && R_val == 0)  //Si se detecta una línea negra a la izquierda, pero no a la derecha, girar a la izquierda
        {
          Car_left();
        }
        else if (L_val == 0 && R_val == 1)  //Si se detecta una línea negra a la derecha, no a la izquierda, girar a la derecha
        {
          Car_right();
        }
        else  //avanzar
        {
          Car_front();
        }
      }
      else  //el del medio detecta líneas negras
      {
        if (L_val == 1 && R_val == 0)  //Si se detecta una línea negra a la izquierda, pero no a la derecha, girar a la izquierda
        {
          Car_left();
        }
        else if (L_val == 0 && R_val == 1)  //Si se detecta una línea negra a la derecha, no a la izquierda, girar a la derecha
        {
          Car_right();
        }
        else  //de lo contrario detener
        {
          Car_Stop();
        }
      }
    }
    if (Serial.available())
    {
      ble_val = Serial.read();
      if (ble_val == 'S') 
      {
        flag = 1;
        Car_Stop();
      }
    }
  }
}

#endif

/***************seguimiento de línea*****************/
void Tracking() 
{
  flag = 0;
  while (flag == 0) 
  {
    L_val = digitalRead(L_pin); //Leer el valor del sensor izquierdo
    M_val = digitalRead(M_pin); //Leer el valor del sensor intermedio
    R_val = digitalRead(R_pin); //Leer el valor del sensor derecho
    if (M_val == 1)  //el del medio detecta líneas negras
    {
      if (L_val == 1 && R_val == 0)  //Si se detecta una línea negra a la izquierda, pero no a la derecha, girar a la izquierda
      {
        Car_left();
      }
      else if (L_val == 0 && R_val == 1)  //Si se detecta una línea negra a la derecha, no a la izquierda, girar a la derecha
      {
        Car_right();
      }
      else  //avanzar
      {
        Car_front();
      }
    }
    else  //el sensor del medio no detecta líneas negras
    {
      if (L_val == 1 && R_val == 0)  //Si se detecta una línea negra a la izquierda, pero no a la derecha, girar a la izquierda
      {
        Car_left();
      }
      else if (L_val == 0 && R_val == 1) //Si se detecta una línea negra a la derecha, no a la izquierda, girar a la derecha
      { 
        Car_right();
      }
      else //de lo contrario detener
      { 
        Car_Stop();
      }
    }
    if (Serial.available())
    {
      ble_val = Serial.read();
      if (ble_val == 'S') 
      {
        flag = 1;
        Car_Stop();
      }
    }
  }
}

/***************Confinamiento*****************/
void Confinement() 
{
  flag = 0;
  while (flag == 0) 
  {
    L_val = digitalRead(L_pin); //Leer el valor del sensor izquierdo
    M_val = digitalRead(M_pin); //Leer el valor del sensor intermedio
    R_val = digitalRead(R_pin); //Leer el valor del sensor derecho
    if ( L_val == 0 && M_val == 0 && R_val == 0 ) //Avanzar cuando no se detectan líneas negras 
    {      
        Car_front();
    }
    else 
    { 
      Car_back();
      delay(700);
      Car_left();
      delay(800);
    }
    if (Serial.available())
    {
      ble_val = Serial.read();
      if (ble_val == 'S') 
      {
        flag = 1;
        Car_Stop();
      }
    }
  }
}


/***************matriz de puntos******************/
//esta función se usa para mostrar la matriz de puntos 
void matrix_display(unsigned char matrix_value[])
{
  IIC_start();  //usar la función para comenzar a transmitir datos
  IIC_send(0xc0);  //seleccionar una dirección
  for (int i = 0; i < 16; i++) //los datos de imagen tienen 16 caracteres
  {
    IIC_send(matrix_value[i]); //datos para transmitir imágenes
  }
  IIC_end();   //finalizar la transmisión de datos de imágenes
  IIC_start();
  IIC_send(0x8A);  //mostrar control y seleccionar ancho de pulso 4/16
  IIC_end();
}

//la condición en que los datos comienzan a transmitirse
void IIC_start()
{
  digitalWrite(SDA_Pin, HIGH);
  digitalWrite(SCL_Pin, HIGH);
  delayMicroseconds(3);
  digitalWrite(SDA_Pin, LOW);
  delayMicroseconds(3);
  digitalWrite(SCL_Pin, LOW);
}

//transmitir datos
void IIC_send(unsigned char send_data)
{
  for (byte mask = 0x01; mask != 0; mask <<= 1) //cada carácter tiene 8 dígitos, que se detectan uno por uno
  {
    if (send_data & mask) //establecer niveles altos o bajos según cada bit (0 o 1)
    { 
      digitalWrite(SDA_Pin, HIGH);
    } 
    else 
    {
      digitalWrite(SDA_Pin, LOW);
    }
    delayMicroseconds(3);
    digitalWrite(SCL_Pin, HIGH); //subir el pin de reloj SCL_Pin para finalizar la transmisión de datos
    delayMicroseconds(3);
    digitalWrite(SCL_Pin, LOW); //bajar el pin de reloj SCL_Pin para cambiar las señales de SDA 
  }
}

//la señal de que la transmisión de datos finaliza
void IIC_end()
{
  digitalWrite(SCL_Pin, LOW);
  digitalWrite(SDA_Pin, LOW);
  delayMicroseconds(3);
  digitalWrite(SCL_Pin, HIGH);
  delayMicroseconds(3);
  digitalWrite(SDA_Pin, HIGH);
  delayMicroseconds(3);
}

/***************el motor funciona****************/
void Car_back() 
{
  digitalWrite(MR_Ctrl, LOW);
  analogWrite(MR_PWM, speeds_R);
  digitalWrite(ML_Ctrl, LOW);
  analogWrite(ML_PWM, speeds_L);
  matrix_display(back);  //mostrar la imagen de ir hacia atrás
}

void Car_front() 
{
  digitalWrite(MR_Ctrl, HIGH);
  analogWrite(MR_PWM, 255 - speeds_R);
  digitalWrite(ML_Ctrl, HIGH);
  analogWrite(ML_PWM, 255 - speeds_L);
  matrix_display(front);  //mostrar la imagen de ir hacia adelante
}

void Car_left() 
{
  digitalWrite(MR_Ctrl, HIGH);
  analogWrite(MR_PWM, 255 - speeds_R);
  digitalWrite(ML_Ctrl, LOW);
  analogWrite(ML_PWM, speeds_L);
  matrix_display(left);  //mostrar la imagen de girar a la izquierda
}

void Car_right() 
{
  digitalWrite(MR_Ctrl, LOW);
  analogWrite(MR_PWM, speeds_R);
  digitalWrite(ML_Ctrl, HIGH);
  analogWrite(ML_PWM, 255 - speeds_L);
  matrix_display(right);  //mostrar la imagen de girar a la derecha
}

void Car_Stop() 
{
  digitalWrite(MR_Ctrl, LOW);
  analogWrite(MR_PWM, 0);
  digitalWrite(ML_Ctrl, LOW);
  analogWrite(ML_PWM, 0);
  matrix_display(STOP01);  //mostrar la imagen de parada
}

(5)Resultado de la prueba:

Antes de cargar el código del programa, el módulo Bluetooth debe ser retirado; de lo contrario, la carga del código fallará.

Después de cargar el código exitosamente, activa los servicios de ubicación en tu dispositivo y luego conecta el módulo Bluetooth.

Una vez que el módulo Bluetooth esté conectado y encendido, y la APP móvil se haya conectado exitosamente al Bluetooth, podemos usar la APP móvil para controlar el robot tanque.

También puedes controlar el robot con el control remoto.