sábado, 14 de mayo de 2016

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.

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

No hay comentarios.:

Publicar un comentario