sábado, 4 de junio de 2016

Instrumental económico.

Hoy terminé de armar y probar cierto instrumental de características básicas y destinados a educación adquiridos hace más de un mes ya, y recibido hace aproximadamente 10 días.

Se trata de un frecuencímetro, un generador de funciones y un osciloscopio, comprados vía Internet en la página http://www.gearbest.com/arduino-_gear/

Hay muchísimas páginas donde comprar componentes electrónicos, cosas para arduino, componentes para robótica, cuadricópteros, etc, etc, etc... uno puede pasarse días completos solo mirando.

Pero luego de mirar y comparar precios, y para lo que buscaba, la página referenciada fue la elegida por tener los precios más baratos y por tener envío gratis a Argentina.

De todos modos -hay que decirlo- entre los productos chinos hay "copias piratas" de entre ellos mismos, da la impresión que el espionaje empresarial en China está a la orden del día, los productos chinos pueden ser de tan buena o tan mala calidad cómo estemos dispuestos a investigar antes de comprar, no sea que les pase lo mismo que a mi, que el osciloscopio terminó siendo "trucho", detalle que descubrí amargamente al ir a la página del fabricante.

De todos modos funciona perfectamente.

Comparto entonces mis primeras experiencias con este tipo de material.

Generador de funciones:

Comprado en:

http://www.gearbest.com/kits/pp_229441.html


Algunos vídeos a consultar:

https://www.youtube.com/watch?v=s2pQJDy64l4


Principales características:
  • Salida de alta velocidad (HS) hasta 8 MHz
  • Señal DDS con amplitud variable y offset
  • Las señales DDS: sinusoidal, cuadrada, diente de sierra, triángular, ECG y generador de ruido
  • Menú en pantalla LCD de 2 x 16 e intuitivo teclado de 5 botones
  • Frecuencia de ajuste: 1, 10, 1000, 10000 Hz
  • El encenderlo restaura última configuración
  • Tiene dos salidas: una para la señal DDS y otro para la señal de alta velocidad (HS)
  • Frecuencia de la señal HS: 1, 2, 4, 8 MHz
Fabricante: 

Desconocido.

No trae manual, no hay más datos que la propia serigrafía de la placa, aunque es muy intuitivo para armarlo, y los componentes son estandars, las resistencias son todas al 1% de tolerancia.

Genera señales en las formas de onda indicadas desde 1 Hz hasta 65535 Hz en pasos programagles, se puede obtener cualquier frecuencia en ese rango en la salida ECG, En la otra salida solo se obtienen valores fijos de 1, 2 4 o 8 MHz.

Aún no he verificado las formas de onda con un osciloscopio profesional.

No saqué fotos mientras lo armaba, pero lo instalé en un gabinete junto al frecuencímetro (probablemente lo retire en un futuro a su propio gabinete) así cómo está no es cómodo acceder al teclado.



Hay que alimentar a la placa con -12 Vcc, +12 Vcc y +5 Vcc. Por lo que hay que desarrollar una fuente apropiada de no más de 500 mA. Yo ya tenía una pequeña fuente de +-12 Vcc y un viejo cargador de celulares por los 5 Vcc restantes.

Hay disponible en http://elecfreaks.com/estore/download/EF06127-DDSinstall.pdf un pseudo manual de usuario, donde se puede estudiar el circuito eléctrico del generador.

Frecuencímetro:

Comprado en:

http://www.gearbest.com/development-boards/pp_208063.html


Algunos vídeos a consultar:



Principales características:
  • Rango de frecuencia de medición: 1 Hz a 50 MHz
  • Puede probar cristales
  • Cinco dígitos de resolución
  • Cambio automático de rango
  • Ajustes programables, sumar y restar frecuencia, funciones comunes para medir transceptores y otros equipos
  • Modo de ahorro de energía, se activa si no hay cambios el la frecuencia de visualización dentro de los 15 segundos
  • Se puede energizar por puerto USB, también puede utilizar una fuente de alimentación externa o batería de 9V
  • Poco número de componentes, instalación sencilla
Fabricante: 

Desconocido.

No trae manual, no hay más datos que la propia serigrafía de la placa, aunque es muy intuitivo para armarlo, y los componentes son estandars, las resistencias son todas al 1% de tolerancia.

Algunas fotos:



el video de https://www.youtube.com/watch?v=KpE1cjbl6L8 explica bastante bien el funcionamiento del frecuencímetro, no hay nada que agregar.

El único problema de este instrumento desde el punto de vista hardware es que la entrada de señal no está protegida, la señal va directa al pin del microprocesador, cualquier error en la tensión aplicada en dicho pin (solo pueden ser señales digitales de hasta 5 Volts) destruye al micro irremediablemente, de echo en el vídeo sugerido comenta también este problema y propone una solución.

Por mi parte, en este momento solo he puesto un zener de 5,1 Volts y una resistencia limitadora de 10K, como simple protección, pero planeo construir el siguiente circuito:



Aún no no he probado, la idea es tener una protección un poco más segura en la entrada con la mayor impedancia de entrada posible, en el rango de frecuencias que puede medir el instrumento, así cómo un oscilador un poco más eficiente para probar cristales (no es que el que tiene no funcione, solo no me termina de convencer, aplicando la señal directamente a la entrada, de incorporar una compuerta seguro dejará de funcionar)

En cuanto al software (firmware), sería bueno tuviera la opción de medición de período, pero es lo que hay... y, salvo uno desarrollara su propio software (se podría tener un segundo pic16f628, resguardando así el original), tendrá que ser suficiente con la versión disponible.

En la página http://www.sainsmart.com/diy-1hz-50mhz-crystal-oscillator-frequency-counter-meter-kits-digital-led.html al final se encuentra un link de descarga de un pdf con algo de documentación referente al frecuencímetro.



Osciloscopio:

Comprado en:


Perfectamente documentado en la página oficial del fabricante:



No hay mucho que decir sobre este instrumento que no esté en la página del fabricante, salvo aclarar que hay dos modelos, uno que viene con todos los componentes superficiales ya soldados, otro que solo trae soldado al micro, lo que para quienes no tenemos muy buena vista es un problema.

Otro detalle a tener en cuenta, en la página del fabricante se advierte sobre el fraude cometido sobre el producto por parte de otra empresa, de lo que no tenía conocimiento antes de la compra, lamentablemente no compré el original, pero la copia es un clon 100% del original, a fines didácticos y de utilidad no hay nada que acotar.



Para señales de baja frecuencia es muy buen instrumento.

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

sábado, 14 de mayo de 2016

Juntando todo (2)

Continuando con el uso de la librería desarrollada para el robot, se recomienda la realización de pequeños programas de muestra del uso de sus funciones, y, si bien la librería es exclusiva para una aplicación en concreto y poca utilidad tendría en otros desarrollos, no está de más seguir con los consejos de la página oficial de Arduino en lo que a librerías concierne, aunque más no sea para recordar rápidamente que hace cada función.

Los siguientes cuatro programas están destinados a la verificación de las funciones de la librería.

En el código siguiente se crea el objeto Motores, pasando cómo parámetros los cuatro pines de conexión hacia el C.I. L298N, el objetivo del programa es el de mover a velocidad constante el robot en las ocho direcciones previstas durante un determinado tiempo, al finalizar el ciclo enciende y apaga el led del pin 13 cuatro veces, antes de repetir nuevamente el ciclo:

#include <MiselaneasRobot01.h>

MiselaneasRobot01 Motores(12, 13, 11, 7, 8, 6);

void setup() {
  Motores.Direccion(DetenerLOW);
  Motores.FijarVelocidades(200, 200);
}

void loop() {
  delay(2500);
  Motores.Direccion(Avanzar);    
delay(1000); 
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Retroceder); 
delay(1000); 
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_1);     
delay(500);
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_2);   
  delay(500);
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_3);   
  delay(750);  
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_4);     delay(750);
  Motores.Direccion(DetenerLOW);

  for (int i = 0; i < 5; i++) {
    Motores.Direccion(DetenerHIGH); delay(250);
    Motores.Direccion(DetenerLOW);  delay(250);
  }
}

El siguiente código hace lo mismo que el anterior, con la diferencia de que en lugar de probar las posibles direcciones durante un determinado tiempo, las verifica durante cierta cantidad de pulsos recibidos desde los encoders. Se crea el objeto Encoders, pasando cómo parámetros los pines donde se recibe la información, y un valor de 0.0 que anula la posibilidad de corregir velocidades:

#include <MiselaneasRobot01.h>

MiselaneasRobot01 Motores(12, 13, 11, 7, 8, 6);
MiselaneasRobot01 Encoders(2, 3, 0.0);

void setup() {
  Motores.Direccion(DetenerLOW);
  Motores.FijarVelocidades(200, 200);
}

void loop() {
  delay(2500);
  Motores.Direccion(Avanzar);  
  Encoders.ContarPulsos(100); 
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Retroceder); 
Encoders.ContarPulsos(100);
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_1);     
Encoders.ContarPulsos(20);
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_2);     
Encoders.ContarPulsos(20);
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_3);     
Encoders.ContarPulsos(40);
  Motores.Direccion(DetenerLOW);
  Motores.Direccion(Giro_4);     
Encoders.ContarPulsos(40);
  Motores.Direccion(DetenerLOW);

  for (int i = 0; i < 5; i++) {
    Motores.Direccion(DetenerHIGH);
    delay(250);
    Motores.Direccion(DetenerLOW);
    delay(250);
  }
  delay(5000);
}

El objetivo del siguiente programa es avanzar y retroceder en línea recta. Se aplica corrección de velocidades hasta 0.95 del valor de PWM de cada motor:

#include <MiselaneasRobot01.h>

MiselaneasRobot01 Motores(12, 13, 11, 7, 8, 6);
MiselaneasRobot01 Encoders(2, 3, 0.95);

int Vel_0, Vel_1;
byte aux = 0;

void setup() { }

void loop() {
  Vel_0 = 200;
  Vel_1 = 200;
  Motores.Direccion(DetenerLOW);
  delay(2500);

  Motores.CorregirVelocidades(Vel_0, Vel_1);
  Motores.FijarVelocidades(Vel_0, Vel_1);

  if (aux == LOW) {
    aux = HIGH;
    Motores.Direccion(Avanzar);
    Encoders.ContarPulsos(150);
  } else { 
    aux = LOW;
    Motores.Direccion(Retroceder);
    Encoders.ContarPulsos(150);
  }
}

Por último, un pequeño programa que sigue a un objeto que se encuentre delante del robot hasta una distancia de 100 cm. Se utiliza a la placa de ultrasonidos:

#include <MiselaneasRobot01.h>

MiselaneasRobot01 Ultrasonido(4, 5);
MiselaneasRobot01 Motores(12, 13, 11, 7, 8, 6);
MiselaneasRobot01 Encoders(2, 3, 0.95);

int Vel_0, Vel_1;
unsigned long D;

void setup() {
  Motores.Direccion(DetenerLOW);
  delay(2500);
}

void loop() {
  Vel_0 = 255;
  Vel_1 = 255;
  D = Ultrasonido.Distancia_cm();
 
  if (D > 10 && D < 100) {
    Motores.Direccion(Avanzar);
    Encoders.CorregirVelocidades(Vel_0, Vel_1);
    Motores.FijarVelocidades(Vel_0, Vel_1);
  } else { 
    Motores.Direccion(DetenerLOW);
    Encoders.DetenerInterrupciones();
  }
}

Verificado entonces la funcionalidad de la librería, queda disponible para ser usada ya en un proyecto final.

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

Juntando todo (1)

Hay muchas razones por las que es conveniente transferir una parte de nuestro código a una o varias librerías, y  varios criterios a seguir en la forma de organizar dichas librerías, qué compartir y qué no, etcétera.

Tal vez, en los entornos de programación de Arduino, la razón más altruísta para compartir rutinas a través de librerías sea la de facilitarle la vida a otros programadores, compartiendo aquel código de uso general para -por ejemplo- el uso de un sensor o un actuador fácilmente, sin necesidad de que quien decida usar nuestra librería tenga que pasar por el derrotero de conocer a fondo el hardware que pretende usar, basta saber que es lo que hace dicho sensor u actuador para aplicarlo en nuestros proyectos, desentendiendonos del como lo hace. Por supuesto que siempre es bueno conocer el "cómo" de lo que sea que estemos usando, pero puede ser que se requiera de un nivel de conocimientos de electrónica que no todo programador posee, lo que en realidad termina en complicarle la vida.

Lo mío no es tan altruista, apenas intento solo simplificar mis futuros códigos, por lo que he puesto aquellas rutinas de uso repetitivo en mis propios proyectos en una librería de nombre muy poco original (MiselaneasRobot01) con objeto de simplificarme la vida a mi mismo...

Cómo sea, en el siguiente link se explica muy bien cuales son los pasos a seguir para crear una librería para Arduino, y existen muchos otros tutoriales disponibles en la red:


Siguiendo entonces el tutorial recomendado en la página oficial de Arduno, construí los siguientes archivos: MiselaneasRobot01.h, MiselaneasRobot01.cpp y keywords.txt siendo el primero (.h) el encabezado de la librería, el segundo (.cpp) el código de la librería y el tercero (.txt) el de enlace de las nuevas funciones al entorno arduino, para que éstas se vean resaltadas en color al usarlas en mis programas. También los programas ejemplos de uso, pero los comentaré en la siguiente entrada.



El código del archivo de encabezado es el siguiente:

#ifndef MiselaneasRobot01_h
    #define MiselaneasRobot01_h
   
    //Constantes literales
    #define Retroceder  0
    #define Avanzar     1
    #define Giro_1      2
    #define Giro_2      3
    #define Giro_3      4
    #define Giro_4      5
    #define DetenerLOW  6
    #define DetenerHIGH 7
   
    class MiselaneasRobot01 
    {
      public:
        MiselaneasRobot01(int pinDISPR, int pinECO);
       
        MiselaneasRobot01(int pinMtrD1, int pinMtrD2, int pinPWM_D,
                          int pinMtrI1, int pinMtrI2, int pinPWM_I);
                          
        MiselaneasRobot01(int pinENCD0, int pinENCD1,
                          float LimCorrecn);
       
        unsigned long Distancia_cm();
        void Direccion(int direccion);
        void ContarPulsos(int p);
        void FijarVelocidades(int Vel0, int Vel1);
        void CorregirVelocidades(int &Vel0, int &Vel1);
        void DetenerInterrupciones();                                 
        void ActivarInterrupciones();
       
      private:
        int _pinDISPR;
        int _pinECO;
        int _pinMtrD1;
        int _pinMtrD2;
        int _pinPWM_D;
        int _pinMtrI1;
        int _pinMtrI2;
        int _pinPWM_I;
        int _pinENCD0;
        int _pinENCD1;
        float _LimCorrecn;
    };

#endif

Al inicio del archivo de encabezados, definí las constantes literales que estarán disponibles cómo las ocho posibles direcciones a tomar por los dos motores del robot, luego, en la parte pública de la clase, declaro tres objetos, el primero pensado en el uso de forma independiente del sensor de ultrasonidos, el segundo para el tratamiento de los motores y el tercero para encoders, continúo con la declaración (pública) de las funciones disponibles. Por último, en la zona de declaraciones privadas, las variables internas de trabajo de la librería, que se corresponden en cantidad y tipo con todos los parámetros de los tres objetos.

Este encabezado me permite usar los objetos de varias formas, es válido desde el entorno de Arduino declarar los objetos:

#include <MiselaneasRobot01.h>

MiselaneasRobot01 Ultrasonido(4, 5);
MiselaneasRobot01 Motores(12, 13, 11, 7, 8, 6);
MiselaneasRobot01 Encoders(2, 3, 0.95);

Y, al momento de usar las diversas funciones, será perfectamente posible hacer lo siguiente:

  D = Ultrasonido.Distancia_cm();

  if (D > 10 && D < 100) {
    Motores.Direccion(Avanzar);
    Encoders.CorregirVelocidades(Vel_0, Vel_1);
  ...

Pero, dado que todo es parte de la misma clase; QUALQUIER FUNCIÓN ESTÁ DISPONIBLE EN CUALQUIER OBJETO, o sea, con esa misma declaración de objetos, sería válido usar:

D = Ultrasonido.Distancia_cm();

  if (D > 10 && D < 100) {
   
Ultrasonido.Direccion(Avanzar);
   
Ultrasonido.CorregirVelocidades(Vel_0, Vel_1);
  ...


Lo que podría ser un poco confuso, o no, dependiendo ya de las declaraciones de los objetos y del programador usuario de la librería en sí; por ejemplo si uno de los tres objetos se llamara "robot" esto podría ser escrito de la siguiente forma:

D = robot.Distancia_cm();

  if (D > 10 && D < 100) {
   
robot.Direccion(Avanzar);
   
robot.CorregirVelocidades(Vel_0, Vel_1);
  ...


Que -por lo menos a mi juicio- hasta quedaría mucho más genérico y fácil de entender al momento de interpretar un programa realizado para el robot en cuestión.

En fin, queda entonces en manos de quien quiera usar esta librería la forma de cómo usarla, o corregirla y mejorarla, o agregarle más funciones... como siempre, el límite es nuestra imaginación.

La implementación del código es el siguiente:

#include "WProgram.h"
#include "MiselaneasRobot01.h"

volatile int _Pulsos0;
volatile int _Pulsos1;
unsigned long _msTranscurridos;

MiselaneasRobot01::MiselaneasRobot01(int pinDISPR, int pinECO) {
  pinMode(pinDISPR, OUTPUT);
  pinMode(pinECO, INPUT);
 
  _pinDISPR = pinDISPR;
  _pinECO   = pinECO;
}

MiselaneasRobot01::MiselaneasRobot01(int pinMtrD1, int pinMtrD2, int pinPWM_D,
                                     int pinMtrI1, int pinMtrI2, int pinPWM_I) {
  pinMode(pinMtrD1, OUTPUT);
  pinMode(pinMtrD2, OUTPUT);
  pinMode(pinPWM_D, OUTPUT);
  pinMode(pinMtrI1, OUTPUT);
  pinMode(pinMtrI2, OUTPUT);
  pinMode(pinPWM_I, OUTPUT);

  _pinMtrD1 = pinMtrD1;
  _pinMtrD2 = pinMtrD2;
  _pinPWM_D = pinPWM_D;
  _pinMtrI1 = pinMtrI1;
  _pinMtrI2 = pinMtrI2;
  _pinPWM_I = pinPWM_I;
}

MiselaneasRobot01::MiselaneasRobot01(int pinENCD0, int pinENCD1, 
                                     float LimCorrecn) {
  pinMode(pinENCD0, INPUT);
  pinMode(pinENCD1, INPUT);

  _pinENCD0   = pinENCD0;
  _pinENCD1   = pinENCD1;
  _LimCorrecn = LimCorrecn;
}
                                          
unsigned long MiselaneasRobot01::Distancia_cm() {
  digitalWrite(_pinDISPR, LOW ); delayMicroseconds(2);
  digitalWrite(_pinDISPR, HIGH); delayMicroseconds(10);
  digitalWrite(_pinDISPR, LOW );
  return int(0.017 * pulseIn(_pinECO, HIGH));
}

void MiselaneasRobot01::Direccion(int direccion) {
  switch (direccion) {
    case Retroceder:
            digitalWrite(_pinMtrD1, HIGH); digitalWrite(_pinMtrD2, LOW ); 
            digitalWrite(_pinMtrI1, LOW ); digitalWrite(_pinMtrI2, HIGH);
            break;
    case Avanzar:
            digitalWrite(_pinMtrD1, LOW ); digitalWrite(_pinMtrD2, HIGH);
            digitalWrite(_pinMtrI1, HIGH); digitalWrite(_pinMtrI2, LOW ); 
            break;
    case Giro_1: //ambos motores, uno al revés que el otro
            digitalWrite(_pinMtrD1, HIGH); digitalWrite(_pinMtrD2, LOW ); 
            digitalWrite(_pinMtrI1, HIGH); digitalWrite(_pinMtrI2, LOW );
            break;
    case Giro_2: //ambos motores, al revés que Giro 1
            digitalWrite(_pinMtrD1, LOW ); digitalWrite(_pinMtrD2, HIGH);
            digitalWrite(_pinMtrI1, LOW ); digitalWrite(_pinMtrI2, HIGH);
            break;
    case Giro_3: //un motor retrocede, el otro detenido
            digitalWrite(_pinMtrD1, HIGH); digitalWrite(_pinMtrD2, LOW ); 
            digitalWrite(_pinMtrI1, LOW ); digitalWrite(_pinMtrI2, LOW );
            break;
    case Giro_4: //al revés que Giro 3
            digitalWrite(_pinMtrD1, LOW ); digitalWrite(_pinMtrD2, LOW ); 
            digitalWrite(_pinMtrI1, LOW ); digitalWrite(_pinMtrI2, HIGH);
            break;
    case DetenerLOW:
            digitalWrite(_pinMtrD1, LOW ); digitalWrite(_pinMtrD2, LOW ); 
            digitalWrite(_pinMtrI1, LOW ); digitalWrite(_pinMtrI2, LOW ); 
            break;
    case DetenerHIGH:
            digitalWrite(_pinMtrD1, HIGH); digitalWrite(_pinMtrD2, HIGH);
            digitalWrite(_pinMtrI1, HIGH); digitalWrite(_pinMtrI2, HIGH);
            break;
  }
}

void Cuenta(int &ecd, int &pin, int &ecdant, int &pls) {
  ecd = digitalRead(pin);
  if (ecd != ecdant && ecd == HIGH) pls++;
  ecdant = ecd;
}

void MiselaneasRobot01::ContarPulsos(int p) {
  int Pulsos_0    = 0;
  int Pulsos_1    = 0;
  int E_Encdr0    = LOW;
  int E_Encdr1    = LOW;
  int E_Encdr0Ant = LOW;
  int E_Encdr1Ant = LOW;
 
  while (Pulsos_0 < p && Pulsos_1 < p) {
    Cuenta(E_Encdr0, _pinENCD0, E_Encdr0Ant, Pulsos_0);
    Cuenta(E_Encdr1, _pinENCD1, E_Encdr1Ant, Pulsos_1);
  } 
}

int AsegurarRango(int &V) {
  if (V < 0) V = 0; else if(V > 255) V = 255;
  return V;    
}
    
void MiselaneasRobot01::FijarVelocidades(int Vel0, int Vel1) {
  analogWrite(_pinPWM_I, AsegurarRango(Vel0));
  analogWrite(_pinPWM_D, AsegurarRango(Vel1));
}

void Correccion(volatile int &Pa, volatile int &Pb,
                         int &VS, int &Vx, float &LC) {
  if (Pa > Pb) VS = VS - (Pa - Pb);
  if (VS < (Vx * LC)) VS = Vx * LC;
}

void MiselaneasRobot01::CorregirVelocidades(int &Vel0, int &Vel1) {
  if (_LimCorrecn > 0.0) {
    if (millis() - _msTranscurridos >= 500) {
      DetenerInterrupciones(); 
      _msTranscurridos = millis();
      Correccion(_Pulsos0, _Pulsos1, Vel0, Vel1, _LimCorrecn);
      Correccion(_Pulsos1, _Pulsos0, Vel1, Vel0, _LimCorrecn);
      ActivarInterrupciones();
    }
  }
}

void MiselaneasRobot01::DetenerInterrupciones() {
  detachInterrupt(0);
  detachInterrupt(1);
}

void ContarPulsos0() { _Pulsos0++; }
void ContarPulsos1() { _Pulsos1++; }

void MiselaneasRobot01::ActivarInterrupciones() {
  _Pulsos0 = 0;
  _Pulsos1 = 0;
  attachInterrupt(0, ContarPulsos0, CHANGE);
  attachInterrupt(1, ContarPulsos1, CHANGE);
}

En principio, se trata de una transcripción de las funciones ya usadas y comentadas en entradas anteriores, adaptadas al formato de una librería como bien se explica en la página oficial de Arduino (ver link de más arriba). También traté de optimizar aquellas funciones que en mis originales tenían algún código repetido.

En el código de los tres objetos simplemente se configuran los pines digitales según correspondan en entradas o salidas y se transfieren el contenido de los parámetros de cada objeto a las correspondientes variables privadas de la librería, declaradas en el archivo de encabezados. E inmediatamente después ya está el desarrollo de cada función:

  • Distancia_cm: Función pública, usa la placa de ultrasonidos, mide distancias en centímetros.
  • Direccion: Función pública, setea los pines que hacen al sentido de giro de los motores.
  • Cuenta: Función interna, usada por la función ContarPulsos.
  • ContarPulsos: Función pública, usa a los dos encoders para contar pulsos directamente desde los motores.
  • AsegurarRango: Función interna, usada por la función FijarVelocidades.
  • FijarVelocidades: Función pública, setea el valor de los PWM de cada motor.
  • Correccion: Función interna, usada por la función CorregirVelocidades.
  • CorregirVelocidades: Función pública, cuando se alcanza un determinado tiempo, se aplica una corrección en las velocidades de ambos motores, se utilizan las interrupciones propias del ATMEGA para este fin.
  • DetenerInterrupciones: Función pública, permite detener las interrupciones del ATMEGA a voluntad del usuario.
  • ActivarInterrupciones: Función pública, permite activar las interrupciones del ATMEGA a voluntad del usuario. 
  • ContarPulsos0 y ContarPulsos1: Funciones internas, actualizan contadores relacionados con las interrupciones.

Por último, el archivo keywords.txt, que no merece más comentario del que se encuentra en el sitio oficial. Yo simplemente copié el contenido de un archivo disponible en otra de las librerías del entorno Arduino, para luego modificar las "palabras reservadas" que me interesan se "coloreen" al usar la librería en algún programita.

###########################################
# Syntax Coloring Map for MiselaneasRobot01
###########################################

###########################################
# Datatypes (KEYWORD1)
###########################################
MiselaneasRobot01    KEYWORD1

###########################################
# Methods and Functions (KEYWORD2)
###########################################
Distancia_cm    KEYWORD2
Direccion    KEYWORD2
ContarPulsos    KEYWORD2
FijarVelocidades    KEYWORD2
CorregirVelocidades    KEYWORD2
DetenerInterrupciones    KEYWORD2
ActivarInterrupciones    KEYWORD2

###########################################
# Instances (KEYWORD2)
###########################################

###########################################
# Constants (LITERAL1)
###########################################
Retroceder    LITERAL1
Avanzar    LITERAL1
Giro_1    LITERAL1
Giro_2    LITERAL1
Giro_3    LITERAL1
Giro_4    LITERAL1
DetenerLOW    LITERAL1
DetenerHIGH    LITERAL1


Nada más por el momento, gracias por tu tiempo leyendo esto y tu buena predisposición a tratar de entenderlo.

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

jueves, 5 de mayo de 2016

Buscando Obstáculos (2)

Se trata de hacer exactamente lo mismo que en la entrada anterior, pero evitando la librería "NewPing.h".

El programa es prácticamente el mismo, simplemente en lugar de usar el comando "Radar.ping_cm()" uso mi función para medir distancias, ya explicada, solo la renombré a "Distancia_cm()" que, creo, es un nombre un poquito más significativo.

Otra modificación (no necesaria en realidad) se encuentra dentro de la función "SonarAlarma()", donde en lugar de llamar a la función "Inicializar()" utilizo el comando "asm("jmp 0x0000")".

Este comando lo que hace es -desde asembler- saltar incondicionalmente a la primera línea del programa, es, por definirlo de algún modo, una especie de reset al programa en si, no al hardware. 

No existe ningún comando o directiva (al menos que yo conozca) que permita realizar un reset del ATMEGA, y, si bien podemos imaginarnos alguna simple electrónica para disparar el pin de reset del hardware, en la práctica no le encuentro necesidad, por lo menos en lo aquí descrito, tal vez algún día si.

A continuación el programa completo:

#define MaxDisObjt 150
#define MinDisObjt 10

#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;
int Vel_0, Vel_1;
byte aux, busquedas, girar;

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());
  Inicializar();
}

void Inicializar() {
  Sentido(DetineLOW);
  Pulsos_0        = 0;
  Pulsos_1        = 0;
  msTranscurridos = 0;
  Vel_0           = 0;
  Vel_1           = 0;
  aux             = 0;
  busquedas       = 0;
  girar = random(Giro_1, DetineLOW);
  delay(5000);
}

void loop() {
  if (aux > 4)
    BuscarObjetivo();
  else {
    Distancia_cm();
    aux++;
  }
}

void BuscarObjetivo() {
  Vel_0 = V_Crucero * LimCorrecn;
  Vel_1 = V_Crucero * LimCorrecn;
  
  if (Distancia_cm()!=0 && Distancia_cm()<MaxDisObjt) Avanzar();
  else
    if (busquedas < 14) {
      Sentido(girar);
      ContarPulsos(3);
      Sentido(DetineLOW);
      busquedas++;
      delay(250);
    } else busquedas = 0;
}

void Avanzar() { 
  if (Distancia_cm() < MinDisObjt) SonarAlarma();
  else {
    ActivarInterrupciones();
    Vel_0 = V_Crucero;
    Vel_1 = V_Crucero;
    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;
    } 
    Sentido(Avanza);
  }
} 

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);
}

void SonarAlarma() {
  for(int i=0; i<5; i++) {
    Sentido(DetineHIGH);
    delay(500);
    Sentido(DetineLOW);
    delay(250);
  }
  asm("jmp 0x0000"); //Inicializar(); 
}

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


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