viernes, 29 de abril de 2016

Evitando Obstáculos (4)

Cómo es de esperar, podemos incorporar la corrección de velocidad a efectos de igualar velocidades cuando el robot avanza sin obstáculos al frente, y de paso, aplicar algunas reglas de "buen programador"... o mi mejor intento en dicho sentido.

La nueva versión del programa completo:

#define Interrupn0 0
#define Interrupn1 1
#define LimCorrecn 0.75
#define T_Interrup 500
#define V_Crucero  200
#define Retrocede  0
#define Avanza     1
#define Giro_1     2
#define Giro_2     3
#define Giro_3     4
#define Giro_4     5
#define DetineLOW  6
#define DetineHIGH 7

const int Ecd_0   =  2;
const int Ecd_1   =  3;
const int DISPARO =  4;
const int ECO     =  5;
const int PWM_I   =  6;
const int MtrI1   =  7;
const int MtrI2   =  8;
const int PWM_D   = 11;
const int MtrD1   = 12;
const int MtrD2   = 13;

volatile byte Pulsos_0, Pulsos_1;
unsigned long msTranscurridos, D;
int Vel_0, Vel_1;
byte aux;

void ContarPulsos0() { Pulsos_0++; }
void ContarPulsos1() { Pulsos_1++; }

void setup() {
  pinMode(Ecd_0,   INPUT );
  pinMode(Ecd_1,   INPUT );
  pinMode(DISPARO, OUTPUT); 
  pinMode(ECO,     INPUT ); 
  pinMode(PWM_I,   OUTPUT);
  pinMode(MtrI1,   OUTPUT);
  pinMode(MtrI2,   OUTPUT);
  pinMode(PWM_D,   OUTPUT);
  pinMode(MtrD1,   OUTPUT);
  pinMode(MtrD2,   OUTPUT);
  
  randomSeed(millis());

  Pulsos_0        = 0;
  Pulsos_1        = 0;
  msTranscurridos = 0;
  Vel_0           = 0;
  Vel_1           = 0;
  D               = 0;
  aux             = 0;

  Sentido(DetineHIGH);
  delay(1500);
  ActivarInterrupciones();
}

void loop() {
  D     = Distancia();
  Vel_0 = V_Crucero;
  Vel_1 = V_Crucero;
  if (aux > 4) {
    (D < 6 && D > 0)? Esquivar(): Avanzar();
  } else aux++;
}

unsigned long Distancia() {
  digitalWrite(DISPARO, LOW);
  delayMicroseconds(2);
  digitalWrite(DISPARO, HIGH);
  delayMicroseconds(10);
  digitalWrite(DISPARO, LOW);
  return int(0.017 * pulseIn(ECO, HIGH)); 
}

void Avanzar() { 
  Sentido(Avanza); 
  if (millis() - msTranscurridos >= T_Interrup) {
    DetenerInterrupciones();
    msTranscurridos = millis();
    if(Pulsos_0 > Pulsos_1) Vel_0 = Vel_0 - ((Pulsos_0 - Pulsos_1));
    if(Pulsos_1 > Pulsos_0) Vel_1 = Vel_1 - ((Pulsos_1 - Pulsos_0));
    if (Vel_0 < (Vel_1 * LimCorrecn)) Vel_0 = Vel_1 * LimCorrecn;
    if (Vel_1 < (Vel_0 * LimCorrecn)) Vel_1 = Vel_0 * LimCorrecn;
    Pulsos_0 = 0;
    Pulsos_1 = 0;
    ActivarInterrupciones();
  }
}    

void Esquivar() {
  DetenerInterrupciones();
  Sentido(DetineLOW);
  delay(50);
  Sentido(Retrocede);
  ContarPulsos(20);
  Sentido(DetineLOW);
  delay(50);
  Sentido(random(Giro_1, DetineLOW)); //Gira
  ContarPulsos(random(10, 25));
  Sentido(DetineLOW);
  delay(50);
  ActivarInterrupciones();
}
  
void Sentido(byte d) { 
  switch (d) {
    case Retrocede: 
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case Avanza:
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW);  
            break;
    case Giro_1: //ambos motores, uno al revés que el otro
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW); 
            break;
    case Giro_2: //ambos motores, al revés que Giro 1
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case Giro_3: //un motor retrocede, el otro detenido
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, LOW); 
            break;
    case Giro_4: //al revés que Giro 3
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case DetineLOW: 
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, LOW);  
            break;
    case DetineHIGH:
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, HIGH); 
            break;
  }
  analogWrite(PWM_I, Vel_0); 
  analogWrite(PWM_D, Vel_1); 
}

void ContarPulsos(byte p) {
  byte Pulsos_0    = 0;
  byte Pulsos_1    = 0;
  byte E_Encdr0    = LOW;
  byte E_Encdr1    = LOW;
  byte E_Encdr0Ant = LOW;
  byte E_Encdr1Ant = LOW;
  
  while (Pulsos_0 < p && Pulsos_1 < p) {
    E_Encdr0 = digitalRead(Ecd_0);
    E_Encdr1 = digitalRead(Ecd_1);
    if (E_Encdr0 != E_Encdr0Ant && E_Encdr0 == HIGH) Pulsos_0++;
    if (E_Encdr1 != E_Encdr1Ant && E_Encdr1 == HIGH) Pulsos_1++;
    E_Encdr0Ant = E_Encdr0;
    E_Encdr1Ant = E_Encdr1;
  }  
}

void DetenerInterrupciones() {
  detachInterrupt(Interrupn0);
  detachInterrupt(Interrupn1);
}

void ActivarInterrupciones() {
  attachInterrupt(Interrupn0, ContarPulsos0, CHANGE);
  attachInterrupt(Interrupn1, ContarPulsos1, CHANGE);
}

Es el objetivo del buen programador que su código sea tan claro que se interprete fácilmente por cualquier programador... pero cómo no es mi caso, trataré de explicarlo lo mejor que pueda.

En primer lugar, se declaran las constantes, algunas usando la directiva de Pre-procesador #define para definir valores constantes, otras usando la palabra reservada const, también para el mismo objetivo, en ambos casos solo defino constantes, la finalidad de los valores constantes es la de proporcionar una mayor legibilidad al código fuente y ayudar a que el mantenimiento del programa sea más sencillo y cómodo. Las constantes mejoran la legibilidad del código al dar un nombre a un valor.

El criterio que usé: las constantes que se utilizan en el programa las declaré con #define, las constantes que hacen referencia a los pines del metaboar, con const.

Se puede leer un poco más sobre las distintas formas de declarar constantes en: 

Con respecto a la declaración de variables globales y las dos funciones asociadas a las interrupciones, no hay nada que decir, salvo que renombré algunas variables con nombres un poquito más significativos según sus usos en el programa.

En lo que respecta al la función setup(), es muy clara por si sola, su función es la de configurar los pines según su uso, así como los valores iniciales de las variables globales, y algunas acciones relacionadas con la inicialización del hardware propiamente dicho.

En loop(), el único cambio es que las variables que hacen al PWM correspondiente a cada motor, pueden verse alteradas, modificadas según los pulsos contados en las interrupciones, por ende, se fijan a un valor predeterminado en cada ciclo de ejecución de esta función.

La función Distancia() no tiene modificación, nada que comentar, solo recordar que debe medir la distancia al objeto que, eventualmente, se encuentre dentro del rango de acción del sensor.

En la función Avanzar() si hay cambios importantes:

void Avanzar() { 
  
  /*Configurar los pines de los motores que hacen a la dirección
    que se corresponda con lo que consideramos como "adelante"*/
  Sentido(Avanza); 

  /*Cada vez que se alcance un tiempo determinado...*/
  if (millis() - msTranscurridos >= T_Interrup) {

    /*se detienen las interrupciones, se deja de contar los pulsos
      de las ruedas*/
    DetenerInterrupciones();

    /*se actualiza la variable msTrancurridos, para referencia de 
      la siguiente detención de interrupciones*/
    msTranscurridos = millis();

    /*si los pulsos leídos de un motor son mayores que los del 
      otro motor, entonces se reduce la variable que hace a la 
      velocidad del motor que gira más rápido, proporcionalmente
      a la diferencia existente entre ambos motores*/
    if(Pulsos_0 > Pulsos_1) Vel_0 = Vel_0 - ((Pulsos_0 - Pulsos_1));
    if(Pulsos_1 > Pulsos_0) Vel_1 = Vel_1 - ((Pulsos_1 - Pulsos_0));

    /*a efectos de evitar reducir en demasía la velocidad de un
      motor con respecto al otro, se mantiene la diferencia
      dentro de cierto límite*/
    if (Vel_0 < (Vel_1 * LimCorrecn)) Vel_0 = Vel_1 * LimCorrecn;
    if (Vel_1 < (Vel_0 * LimCorrecn)) Vel_1 = Vel_0 * LimCorrecn;

    /*puesta a cero de las variables que cuentan los pulsos de
      ambos motores, para comenzar un nuevo conteo mientras no se
      cumpla otro ciclo de detención de interrupciones*/
    Pulsos_0 = 0;
    Pulsos_1 = 0;

    /*se activan las interrupciones, se inicia el conteo de los
      pulsos leídos desde las ruedas con los encoders*/
    ActivarInterrupciones();

  } //fin del condicional de medición de tiempo
} //fin de la función Avanzar   

Es aquí donde se realiza la corrección de las velocidades de los motores a efectos de lograr una dirección un poco más recta cuando así debería ser.

En cuanto a la función Esquivar(), es prácticamente igual a la usada en la entrada Evitando Obstáculos (3), salvo el echo de que se detienen las interrupciones durante el proceso para esquivar un obstáculo.

En la función Sentido() agregué algunas posibles direcciones más, así cómo la actualización de los pulsos PWM en los pines correspondientes, pero en esencia, sigue siendo lo mismo de otras entradas.

Por último, las funciones DetenerInterrupciones() y ActivarInterrupciones() solo hacen a las llamadas a las instrucciones que realizan las acciones correspondientes.

Y eso es todo para esta entrada.

--------------------------------------------------------------------------------------------------------------------------

viernes, 22 de abril de 2016

Evitando obstáculos (3)

En esta nueva versión del programa anterior, utilizo los dos encoders ya instalados, que permiten contar pulsos desde las ruedas directamente, y así modificar la forma en que se esquivan los objetos, en lugar de hacerlo por tiempo, hacerlo a partir de cierta cantidad de pulsos.

El programa completo:

const int Ecd_0   =  2;
const int Ecd_1   =  3;
const int DISPARO =  4;
const int ECO     =  5;
const int PWM_I   =  6;
const int MtrI1   =  7;
const int MtrI2   =  8;
const int PWM_D   = 11;
const int MtrD1   = 12;
const int MtrD2   = 13;

unsigned long D = 0;
byte aux = 0;

void setup() {
  pinMode(Ecd_0,   INPUT );
  pinMode(Ecd_1,   INPUT );
  pinMode(DISPARO, OUTPUT); 
  pinMode(ECO,     INPUT ); 
  pinMode(PWM_I,   OUTPUT);
  pinMode(MtrI1,   OUTPUT);
  pinMode(MtrI2,   OUTPUT);
  pinMode(PWM_D,   OUTPUT);
  pinMode(MtrD1,   OUTPUT);
  pinMode(MtrD2,   OUTPUT);
  analogWrite(PWM_I, 175); 
  analogWrite(PWM_D, 175); 
  randomSeed(millis());
  Sentido(6);
  delay(1500);
}

void loop(){
  D = Distancia();
  if (aux > 4) {
    (D > 0 && D < 6)? Esquiva(): Avanza();
  } else aux++;
}

unsigned long Distancia() {
  digitalWrite(DISPARO, LOW);
  delayMicroseconds(2);
  digitalWrite(DISPARO, HIGH);
  delayMicroseconds(10);
  digitalWrite(DISPARO, LOW);
  return int(0.017 * pulseIn(ECO, HIGH)); 
}

void Avanza() { Sentido(1); }    

void Esquiva() {
  Sentido(6); //Detiene
  delay(50);

  Sentido(0); //Retrocede
  ContarPulsos(20);

  Sentido(6); //Detiene
  delay(50);
  
  Sentido(random(2, 6)); //Gira
  ContarPulsos(random(10, 25));

  Sentido(6); //Detiene
  delay(50);
}
  
void Sentido(byte d) { 
  switch (d) {
    case 0: //Dirección 1
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case 1: //Dirección 2
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW);  
            break;
    case 2: //Giro 1 ambos motores, uno al revés que el otro
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW); 
            break;
    case 3: //Giro 2 ambos motores, al revés que Giro 1
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case 4: //Giro 3 un motor retrocede, el otro detenido
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, LOW); 
            break;
    case 5: //Giro 4 al revés que Giro 3
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case 6: //Detiene (LOW)
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, LOW);  
            break;
    case 7: //Detiene (HIGH)
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, HIGH); 
            break;
  }
}

void ContarPulsos(byte p) {
  byte Pulsos_0 = 0;
  byte Pulsos_1 = 0;
  byte E_Encdr0 = LOW;
  byte E_Encdr1 = LOW;
  byte E_Encdr0Ant = LOW;
  byte E_Encdr1Ant = LOW;
  
  while (Pulsos_0 < p && Pulsos_1 < p) {
    E_Encdr0 = digitalRead(Ecd_0);
    E_Encdr1 = digitalRead(Ecd_1);
    if (E_Encdr0 != E_Encdr0Ant && E_Encdr0 == HIGH) Pulsos_0++;
    if (E_Encdr1 != E_Encdr1Ant && E_Encdr1 == HIGH) Pulsos_1++;
    E_Encdr0Ant = E_Encdr0;
    E_Encdr1Ant = E_Encdr1;
  }  
}

El programa inicia con las declaraciones de pines, donde incorporé los pines donde tengo conectado ambos encoders (pines 2 y 3), las variables globales son las ya comentadas en la entrada anterior, y en la función setup() agregué la instrucción randomSeed(millis()); a efectos de cambiar la semilla random para generar números al azar, así cómo trasladé las instrucciones analogWrite(PWM_I, 175); y analogWrite(PWM_D, 175); de modo de fijar las velocidades de los motores solo una vez.

La función loop() tiene una mínima modificación en el condicional que determina cuando debe esquivar un obstáculo, si la distancia al obstáculo se encuentra entre 1 cm y 5 cm entonces se llama a la función "Esquiva()", de lo contrario se llama a la función "Avanza()". Esto lo modifiqué en función del echo que si la distancia es mayor que el límite del sensor (entre 400 a 500 cm), éste devuelve 0 cm, y en espacios abiertos esto confunde al robot-juguete-auto, o cómo prefieran llamarlo.

La funciones "Distancia()" y "Avanza()" no tienen ninguna modificación con respecto a la entrada anterior, si la función "Esquiva()", en particular al momento de retroceder, donde retrocede 20 pulsos -20 es la cantidad de ranuras que tienen los discos asociados a las ruedas- o sea, retrocede una vuelta completa de ruedas. También modifiqué el giro, donde al azar se escogen entre cuatro posibles formas de girar, para lograr un efecto más vistoso simplemente. 

La función "Sentido()" -si bien ha sido modificada- no requiere de mayor comentarios, se explica por si sola.

Por último, la función "ContarPulsos(byte p)", la cual tiene la tarea de contar los pulsos leídos por los encoders en un bucle, comparándolos contra el valor ingresado en su parámetro de entrada "p". Y su pseudocódigo es el siguiente:

MIENTRAS (cualquiera de los dos contadores sean menor que p) HACER
  almacenar el estado actual del primer encoder
  almacenar el estado actual del segundo encoder
  SI (estado actual es distinto al estado anterior del primer encoder) ENTONCES
    aumentar su correspondiente contador en uno
  FIN SI
  SI (estado actual es distinto al estado anterior del segundo encoder) ENTONCES
    aumentar su correspondiente contador en uno
  FIN SI
  actualizar el estado anterior al estado actual del primer encoder
  actualizar el estado anterior al estado actual del segundo encoder
FIN MIENTRAS

Se cuentan pulsos cada vez que un encoder cambia su estado de LOW a HIGH, y basta que cualquiera de los dos contadores llegue a la cantidad deseada para salir del bucle, continuando con el programa.

De más está que diga que esta función puede ser mejorada, simplificada aprovechando las interrupciones, usar la librería NewPing.h, etcétera. El límite es la imaginación de quien quiera modificarla a su gusto.

Hasta aquí entonces, mi experiencia contando pulsitos...

------------------------------------------------------------------------------------------------------------------


jueves, 21 de abril de 2016

Evitando obstáculos (2)

Continuando mis experiencias con el sensor de ultrasonidos, he realizado un sencillo programa que evita chocar contra obstáculos de forma autónoma.

El programa completo desarrollado para tal fin, es el siguiente:

const int DISPARO = 4;
const int ECO     = 5;
const int PWM_I   = 6;
const int MtrI1   = 7;
const int MtrI2   = 8;
const int PWM_D   =11;
const int MtrD1   =12;
const int MtrD2   =13;

byte aux = 0;
unsigned long D = 0;

void setup() {
  pinMode(DISPARO, OUTPUT); 
  pinMode(ECO,     INPUT ); 
  pinMode(PWM_I,   OUTPUT);
  pinMode(MtrI1,   OUTPUT);
  pinMode(MtrI2,   OUTPUT);
  pinMode(PWM_D,   OUTPUT);
  pinMode(MtrD1,   OUTPUT);
  pinMode(MtrD2,   OUTPUT);
  Sentido(4);
  delay(2000);
}

void loop() {
  D = Distancia();
  if (aux > 4) {
    (D>5)? Avanza(): Esquiva();
    analogWrite(PWM_I, 175); 
    analogWrite(PWM_D, 175); 
  } else aux++;
}

unsigned long Distancia() {
  digitalWrite(DISPARO, LOW);
  delayMicroseconds(2);
  digitalWrite(DISPARO, HIGH);
  delayMicroseconds(10);
  digitalWrite(DISPARO, LOW);
  return int(0.017 * pulseIn(ECO, HIGH)); 
}

void Avanza() {
  Sentido(1);
}    

void Esquiva() {

  //Detiene
  Sentido(4); 
  delay(50);
  
  //Retrocede
  Sentido(0);
  delay(300);
  
  //Detiene
  Sentido(4); 
  delay(50);

  //Gira
  Sentido(random(2, 4));
  delay(random(250, 500));
  
  //Detiene
  Sentido(4); 
  delay(50);
}
  
void Sentido(byte d) { 
  switch (d) {
    case 0: //Dirección 1
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case 1: //Dirección 2
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW);  
            break;
    case 2: //Giro 1
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW); 
            break;
    case 3: //Giro 2
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case 4: //Detiene (LOW)
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, LOW);  
            break;
    case 5: //Detiene (HIGH)
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, HIGH); 
            break;
  }
}

Las primeras líneas, corresponden a las definiciones de los pines utilizados y no hay nada nuevo:
const int DISPARO = 4;
const int ECO     = 5;
const int PWM_I   = 6;
const int MtrI1   = 7;
const int MtrI2   = 8;
const int PWM_D   =11;
const int MtrD1   =12;
const int MtrD2   =13;

Cómo es tradición, a continuación las variables:
byte aux = 0;
unsigned long D = 0;

Donde "aux" es una variable auxiliar y "D" la utilizo para almacenar la distancia medida.

La función de configuración es la siguiente:
void setup() {
  pinMode(DISPARO, OUTPUT); 
  pinMode(ECO,     INPUT ); 
  pinMode(PWM_I,   OUTPUT);
  pinMode(MtrI1,   OUTPUT);
  pinMode(MtrI2,   OUTPUT);
  pinMode(PWM_D,   OUTPUT);
  pinMode(MtrD1,   OUTPUT);
  pinMode(MtrD2,   OUTPUT);
  Sentido(4);
  delay(2000);
}

Donde se definen cómo se comportan los pines -recuerdo que no es necesario declarar las salidas, ya lo son por defecto, pero si queda más fácil de leer el programa- con la llamada a la función "Sentido(4)" seteo los valores LOW en los cuatro pines que hacen al sentido de giro de los dos motores, apagándolos de esta forma, por último retraso en 2 segundos la continuación del programa, por comodidad nomás.

Seguidamente, el cuerpo principal del programa, o bucle infinito;
void loop() {
  D = Distancia();
  if (aux > 4) {
    (D>5)? Avanza(): Esquiva();
    analogWrite(PWM_I, 175); 
    analogWrite(PWM_D, 175); 
  } else aux++;
}

Esto se podría reducir a:
void loop() {
  (Distancia()>5)? Avanza(): Esquiva();
  analogWrite(PWM_I, 175); 
  analogWrite(PWM_D, 175); 
}

Y por supuesto funciona, evitando usar las dos variables declaradas. Simplemente basta con un condicional (usé la forma abreviada) donde si la distancia a un objeto es mayor a 5 centímetros, se avanza, de lo contrario se intenta esquivar el objeto. Y por supuesto, darle algún valor a los pulsos PWM que hacen a la velocidad de los motores, lógicamente hay una relación entre el tiempo de detención de los motores y la distancia recorrida por inercia, el límite de 5 cm podría ser conveniente subirlo si se desea usar los motores a máxima velocidad (255 en lugar de 175).

La razón por la que incrusté este código dentro de un condicional que no permita se ejecute movimiento alguno del vehículo hasta no se hayan hecho 5 disparos del sensor de ultrasonidos es para evitar mediciones erróneas.

A continuación, las diversas funciones declaradas en el programa, siendo la primera la que se encarga de medir la distancia al objeto:

unsigned long Distancia() {
  digitalWrite(DISPARO, LOW);
  delayMicroseconds(2);
  digitalWrite(DISPARO, HIGH);
  delayMicroseconds(10);
  digitalWrite(DISPARO, LOW);
  return int(0.017 * pulseIn(ECO, HIGH)); 
}

Funciona de la forma ya descrita en la entrada anterior, de hecho es el mismo código adaptado a una función, recuerdo que se trata de generar un pulso de 10 us en el pin que dispara al sensor para realizar una medición, para luego medir el ancho del pulso recibido en el pin ECO, y en función de este tiempo, calcular la distancia.

void Avanza() {
  Sentido(1);
}    

La función "Avanza()" no es más que una llamada a la función "Sentido(1)", que configura los pines que hacen a la dirección de giro de los motores en una dirección, la que personalmente adopté como frente del dispositivo, donde he puesto el sensor de ultrasonidos. No es necesario declararla, bien podría usarse Sentido(1) en su lugar, pero solo la hice con objeto de que el código sea lo más legible posible.

void Esquiva() {

  //Detiene
  Sentido(4); 
  delay(50);
  
  //Retrocede
  Sentido(0);
  delay(300);
  
  //Detiene
  Sentido(4); 
  delay(50);

  //Gira
  Sentido(random(2, 4));
  delay(random(250, 500));
  
  //Detiene
  Sentido(4); 
  delay(50);
}

En cambio, la función "Esquiva()" es más interesante de comentar, su algoritmo es el siguiente:

1) detiene los motores por 50 ms.
2) retrocede cambiando de sentido de giro ambos motores durante 300 ms.
3) detiene los motores por 50 ms.
4) invierte la dirección de giro de los motores al azar, haciendo que gire sobre su eje a izquierda o
    derecha, durante un tiempo -también al azar- de entre 250 y 499 ms.
5) detiene los motores por 50 ms.

No se puede hablar de verdadero azar ya que la función random() genera siempre los mismos valores, repitiéndolos cada vez que se resetea al ATMEGA, o dicho de otra forma, la secuencia para esquivar objetos es repetitiva cada vez que se arranca el juguete.

Se puede usar la instrucción randomSeed(millis()); en la función setup(), para cambiar la semilla de random según el mili segundo en que se activa al ATMEGA, dando entonces valores más aleatorios, y evitando así se repita la secuencia, pero para este ejemplo no es significativo.

Por último, la función "Sentido()", que hace a algunas posibles direcciones:

void Sentido(byte d) { 
  switch (d) {
    case 0: //Dirección 1
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case 1: //Dirección 2
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW);  
            break;
    case 2: //Giro 1
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, LOW); 
            break;
    case 3: //Giro 2
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, HIGH); 
            break;
    case 4: //Detiene (LOW)
            digitalWrite(MtrD1, LOW);  digitalWrite(MtrD2, LOW);  
            digitalWrite(MtrI1, LOW);  digitalWrite(MtrI2, LOW);  
            break;
    case 5: //Detiene (HIGH)
            digitalWrite(MtrD1, HIGH); digitalWrite(MtrD2, HIGH); 
            digitalWrite(MtrI1, HIGH); digitalWrite(MtrI2, HIGH); 
            break;
  }
}

No hay mucho que decir en cuanto a esta función, simplemente se encarga de setear los pines de dirección de los motores, en mi caso y según cómo tengo conectado los motores, cada caso se corresponde a la siguiente dirección:

caso 0: ambos motores giran en sentido anti horario.
caso 1: ambos motores giran en sentido horario.
caso 2: un motor en sentido anti horario, el otro en sentido horario.
caso 3: al revés que el caso 2.
caso 4: ambos motores detenidos, los cuatro pines en LOW (0 volts)
caso 5: ambos motores detenidos, los cuatro pines en HIGH (5 volts)

No preveo la necesidad de un motor girando y el otro detenido, ya sea en sentido anti horario u horario, tanto del lado izquierdo como del lado derecho, por lo menos, hasta ahora, no he necesitado de esas direcciones...

Y eso es todo, por el momento.

------------------------------------------------------------------------------------------------------------