### 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.)
```C
/*
Keyestudio Mini Tank Robot V3 (Popular Edition)
lesson 19
Ultrasonic Tank Robot Multiple Functions
http://www.keyestudio.com
*/
#include
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
```
```cpp
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.