Archivo de la categoría: Clases y Objetos

Tratamiento Avanzado de Strings

Como ya se ha visto anteriormente el tratamiento de strings es un parte muy importante en Arduino puesto que se usa muy frecuentemente y principalmente usamos en las comunicaciones, ya sea puerto serie, bluetooth, XBee, http, etc…

El uso de strings hace un uso intensivo de memoria lo que hace que podamos tener comportamientos extraños en los sketchs o incluso tengamos desbordamiento de memoria.

A la hora de usar strings en Arduino, podemos hacer uso de la clase String https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/ que nos ofrece unos métodos y es muy sencilla de usar, a cambio de ser poco eficiente a nivel de SRAM o usar los stringshttps://arduino.cc/reference/en/language/variables/data-types/string/ como arrays de chars https://arduino.cc/reference/en/language/variables/data-types/char/ que es más complejo de manejar pero más potente y tenemos más control del uso de memoria y pueden usarse muchas de las funciones estandard de C++.

String (Objeto)

Arduino nos ofrece una clase llamada String que facilita el uso de de las cadenas de caracteres con unos métodos muy sencillos de usar y poder usar los operadores que conocemos para los Strings.

Se trata de una clase que permite usar y manipular cadenas de texto de una forma más sencilla que los strings. Puedes concatenar, añadir, buscar, etc… usando los métodos/funciones que ofrece esta clase.

Los Strings tienen un uso intensivo de memoria, pero son muy útiles y se van a utilizar mucho en el apartado de comunicación, por ese motivo es importante aprender a manejar los Strings.

Tener en cuenta que al no ser un tipo de dato propiamente dicho sino una clase, tienes unas funciones asociadas (métodos), operadores y unas propiedades. Es una abstracción del dato y para aprender a usarlo hay que leerse la documentación correspondiente.

Toda la información de la clase String está en: https://arduino.cc/reference/en/language/variables/data-types/stringobject/

Ver documentación de Arduino sobre la clase String:

Tutoriales de uso de String:https://arduino.cc/en/Tutorial/BuiltInExamples#strings

Prácticas Manejo de Strings

Ejecutar el ejemplo https://arduino.cc/en/Tutorial/StringLengthTrim donde se hace uso de las funciones length() y trim().

Ejecutar el ejemplo https://arduino.cc/en/Tutorial/StringStartsWithEndsWith donde se hace uso de las funciones StartsWith() y EndsWith().

Ejecutar el ejemplo https://arduino.cc/en/Tutorial/StringSubstring donde se hace uso de la función substring().

Ejecutar el ejemplo https://arduino.cc/en/Tutorial/StringToInt donde se hace uso de la función toInt()

Otra de las funciones más útiles de String es IndexOf() con ejemplos en https://www.arduino.cc/en/Tutorial/StringIndexOf

Ejercicio: Conectarse a www.aprendiendoarduino.com y analizar el texto descargado. El texto descargado es una página web que contiene datos de una música a tocar por el buzzer

Hacerlo usando la clase String.

Obtener y sacar por el puerto serie:

  • Tamaño de la web (número de caracteres)
  • Localizar las cadenas: “Inicio Notas”, “Fin Notas”, “Inicio Duración” y “Fin Duración”
  • Obtener las notas (frecuencia) y la duración de las notas. Luego reproducirlo.

Solución: https://github.com/jecrespo/aprendiendoarduino-Curso_Programacion_Arduino/tree/master/Ejercicio16-Strings_Avanzado

string (Array de chars)

Un string es un array de chars. Cuando se trabaja con grandes cantidades de texto, es conveniente usar un array de strings. Puesto que los strings son en si mismo arrays. En el reference de Arduino https://arduino.cc/reference/en/language/variables/data-types/string/

La notación de un string como array de chars es char*

El core de Arduino me ofrece varias funciones de análisis de caracteres: https://arduino.cc/reference/en/language/variables/data-types/string/

Una cadena en C++ es un conjunto de caracteres, o valores de tipo char, terminados con el carácter nulo, es decir el valor numérico 0 (\0). Internamente, en el ordenador, se almacenan en posiciones consecutivas de memoria. Este tipo de estructuras recibe un tratamiento muy especial, ya que es de gran utilidad y su uso es continuo. La manera de definir una cadena es la siguiente: char <identificador> [<longitud máxima>];

Cuando se declara una cadena hay que tener en cuenta que tendremos que reservar una posición para almacenar el carácter nulo terminador, de modo que si queremos almacenar la cadena “HOLA”, tendremos que declarar la cadena como: char Saludo[5]; Las cuatro primeras posiciones se usan para almacenar los caracteres “HOLA” y la posición extra, para el carácter nulo.

También nos será posible hacer referencia a cada uno de los caracteres individuales que componen la cadena, simplemente indicando la posición. Por ejemplo el tercer carácter de nuestra cadena de ejemplo será la ‘L’, podemos hacer referencia a él como Saludo[2].

Se puede manipular las cadenas de caracteres de la misma manera en que manipula cualquier otro tipo de array, sin embargo, es preferible hacer uso de una librería estándar especialmente escrita para manipulación de cadenas de caracteres, se trata de la librería <string.h> y que viene incluida con todo compilador de C, C++.

Reference de C++ para la clase string http://cplusplus.com/reference/string/string/ y http://cplusplus.com/reference/cstring/ con funciones como strcpy para strings null-terminated.

Los compiladores de C, C++ dan soporte a la biblioteca de funciones <string.h>, a la que accede por medio de la directiva #include <string.h>. No veremos en detalle todas las funciones contenidas en dicha biblioteca, y nos limitaremos a mostrar algunos ejemplos de ciertas funciones importantes.

  • strlen(): Obtener longitud de cadenas. Sintaxis: size_t strlen(const char *s);
  • strcpy(): Copiar cadenas. Sintaxis: char *stpcpy(char *dest, const char *src);
  • strcat(): Concatenar cadenas. Sintaxis: char *strcat(char *dest, const char *src);
  • strlwr(): Convertir a minúsculas. Sintaxis: char *strlwr(char *dest);
  • strupr(): Convertir a mayúsculas. Sintaxis: char *strupr(char *dest);
  • strchr(): Buscar carácter (hacia adelante). Sintaxis: char *strchr(char *s, int c);
  • strrchr(): Buscar carácter (hacia atras). Sintaxis: char *strrchr(char *s, int c);
  • strstr(): Buscar subcadena. Sintaxis: char *strstr(const char *s1, char *s2);
  • memset(): Establece el primer num bytes del bloque de memoria apuntado por ptr al valor especificado en value (interpretado como un unsigned char).. Sintaxis: void * memset ( void * ptr, int value, size_t num );

Reference:

En C++ también tenemos soporte a la clase cstring, que no debe confundirse con la <string.h>. Una de las ventajas que ofrece la clase cstring es que, a diferencia de las cadenas estándar, ésta posee la capacidad de crecer o disminuir su tamaño en tiempo de ejecución. Además, entre otras características destacables, la clase string soporta operaciones de asignación tales como: =, +, +=, etc.; y de comparación tales como: ==, <=, etc.

Documentacíon de la librería <string.h>: http://www.cplusplus.com/reference/cstring/

Clase string de C++: http://www.cplusplus.com/reference/string/string/

Ejercicio Strings_vs_strings: Partiendo de la base del ejercicio StringsComparisonOperators intentar hacer las operaciones de comparación de igualdad y distinto de los StringOne y StringTwo con string en lugar de String. Ver como es más complicado y para iniciarse en la programación es mejor usar String (objeto) que string (char array).

Solución: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio18-strings/_5-String_vs_string

Más información:

Ejercicios Avanzados  Strings

Ejercicio: Mandar un SMS mediante un módulo SIM800L, donde pido por consola el número y el mensaje en formato” <numero_telefono>-<mensaje>!”. Analizar esta cadena y separar en teléfono y mensaje y mandar mediante la función bool sendSms(char* number,char* text);

El primer análisis hacerlo con la clase String y luego pasar las variables teléfono y mensaje a char* que es lo que pide la librería.

Librería disponible en el gestor de librerías: https://github.com/VittorioEsposito/Sim800L-Arduino-Library-revised

HW: https://es.aliexpress.com/item/SIM800L-V2-0-5V-Wireless-GSM-GPRS-MODULE-Quad-Band-W-Antenna-Cable-Cap/32465895576.html

Solución: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio68-toCharArray

Ejercicio: Manejo de JSON mediante la librería disponible en el gestor de librerías ArduinoJson https://arduinojson.org y repositorio https://github.com/bblanchon/ArduinoJson

JSON: https://es.wikipedia.org/wiki/JSON

Abrir el ejemplo JsonParserExample de la librería y probarlo.

PROGMEM

El uso de strings como cadena de caracteres en Arduino, en lugar de usar la clase String, nos permite también almacenar los strings en la memoria flash en lugar de la SRAM gracias a PROGMEM https://arduino.cc/reference/en/language/variables/utilities/progmem/

A menudo es conveniente cuando se trabaja con grandes cantidades de texto, como un proyecto con una pantalla LCD o en comunicaciones, usar PROGMEM con arrays de strings.  Estos tienden a ser grandes estructuras, así que ponerlas en memoria de programa (flash) es a menudo deseable. El código siguiente ilustra la idea.

 
#include <avr/pgmspace.h>
const char string_0[] PROGMEM = "String 0";   // "String 0" etc are strings to store - change to suit.
const char string_1[] PROGMEM = "String 1";
const char string_2[] PROGMEM = "String 2";
const char string_3[] PROGMEM = "String 3";
const char string_4[] PROGMEM = "String 4";
const char string_5[] PROGMEM = "String 5";

// Then set up a table to refer to your strings.

const char* const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

char buffer[30];    // make sure this is large enough for the largest string it must hold

void setup()
{
  Serial.begin(9600);
  while(!Serial);
  Serial.println("OK");
}

void loop()
{
   for (int i = 0; i < 6; i++)
  {
    strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
    Serial.println(buffer);
    delay( 500 );
  }
}

Utilidades de PROGMEM: https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html

Usar la tabla de cadenas en la memoria de programa (flash) requiere el uso de funciones especiales para recuperar los datos. La función strcpy_P copia un string desde el espacio del programa (flash) en un string en la memoria RAM (“buffer”). Debemos asegurarnos de que la cadena de recepción en la memoria RAM es lo suficientemente grande como para alojar cualquier string que está recuperando desde el espacio del programa

Las funciones para manejar PROGMEM están en http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html que podemos ver son iguales que las de <string.h>, pero seguidas de “_P”

Tener en cuenta que las variables deben ser definidas de forma global y como constantes, o definido con la palabra clave static, con el fin de trabajar con PROGMEM.

Recordar que podemos usar la macro F() junto con el métodos print y println de forma que todo lo que hay dentro de los métodos se guarda en la memoria flash.

Ejemplo: Serial.println(F(“This string will be stored in flash memory”));

Para saber como funciona PROGMEM ver  la pagina 347 y 354 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf y donde lo metes lo de MEMPROG

Anuncios

Clases y Objetos

Programación Orientada a Objetos

La programación orientada a objetos (POO, u OOP según sus siglas en inglés) es un paradigma de programación que viene a innovar la forma de obtener resultados. Los objetos manipulan los datos de entrada para la obtención de datos de salida específicos, donde cada objeto ofrece una funcionalidad especial.

La modularidad es la capacidad de dividir el problema en pequeñas partes independientes entre sí, esto va más allá de la encapsulación de un simple procedimiento de la programación estructurada que hemos visto hasta ahora. Aquí se encapsula un conjunto de operaciones y datos que tienen mucha relación entre sí formando un módulo.

Un lenguaje basado en objetos nos da la posibilidad de definir objetos y realizar operaciones sobre ellos y es similar al paradigma de abstracción de datos. La adición de de los conceptos de de clases y herencia nos hace hablar de lenguajes orientados a objetos. Un objeto es la instancia de una clase.

Mediante la herencia podemos reutilizar el comportamiento de una clase en la definición de nuevas clases.

Muchos de los objetos pre-diseñados de los lenguajes de programación actuales permiten la agrupación en bibliotecas o librerías, sin embargo, muchos de estos lenguajes permiten al usuario la creación de sus propias bibliotecas.

Está basada en varias técnicas, incluyendo herencia, cohesión, abstracción, polimorfismo, acoplamiento y encapsulamiento.

Los objetos son entidades que tienen un determinado “estado”, “comportamiento (método)” e “identidad”:

  • La identidad es una propiedad de un objeto que lo diferencia del resto; dicho con otras palabras, es su identificador (concepto análogo al de identificador de una variable o una constante).
  • Los métodos (comportamiento) y atributos (estado) están estrechamente relacionados por la propiedad de conjunto. Esta propiedad destaca que una clase requiere de métodos para poder tratar los atributos con los que cuenta.

Una clase es una plantilla para la creación de objetos de datos según un modelo predefinido. Las clases se utilizan para representar entidades o conceptos. Cada clase es un modelo que define un conjunto de variables (el estado), y métodos apropiados para operar con dichos datos (el comportamiento). Cada objeto creado a partir de la clase se denomina instancia de la clase.

Las clases son un pilar fundamental de la programación orientada a objetos. Permiten abstraer los datos y sus operaciones asociadas al modo de una caja negra. Los lenguajes de programación que soportan clases difieren sutilmente en su soporte para diversas características relacionadas con clases. La mayoría soportan diversas formas de herencia.

C++ está diseñado para la programación orientada a objetos (POO), y en ese paradigma, todas las entidades que podemos manejar son objetos. Los punteros en C++ sirven para señalar objetos, y también para manipularlos.

La OOP tiene tres características básicas que se suelen presentar como Encapsulación, Polimorfismo y Herencia.

  • La encapsulación es un procedimiento por el cual, los datos y las funciones se encierran en un contenedor llamado objeto, que usamos para aislar ambos elementos de la manipulación exterior y forzar a que esta se haga de un modo controlado y validado por nosotros mismos. El mecanismo que C++ utiliza para encapsular estos objetos se llaman clases.
  • Se suele definir el polimorfismo como: Un interface único, múltiples métodos. Y es algo que reduce la complejidad de los programas de una forma notable. A usar las funciones u operadores de diferentes maneras dependiendo del tipo de los datos, le llamamos polimorfismo y cuando redefinimos un operador o función para comportarse de forma diferente con esos datos, decimos que el operador esta sobrecargado (Overloaded)- Por ejemplo en C++ podemos sumar números enteros o números float con el mismo símbolo “+”, y nos parece tan normal, pero en realidad los procedimientos que se aplican son completamente distintos.
  • La herencia es un proceso por el cual un objeto hereda las propiedades y métodos de otro, sin necesidad de volverlas a definir desde el principio.

Ejemplo de uso de un objeto: Imaginemos que hemos hecho un coche coche controlado por bluetooth y tenemos 5 botones para las 4 direcciones y parar. Creamos una clase llamada ‘Coche’ que se inicializa con los pines donde conecto los dos motores del coche.

 
#include “Coche.h”

Coche MiCocheRC(6,7)

void setup() {
MiCoche.Arranca()
}

void loop() {
char valor = leeBluetooth();

switch (valor) {
    case ‘F’:
      MiCoche.Adelante();
      break;
    case ‘R’:
      MiCoche.Derecha();
      break;
    case ‘L’:
       MiCoche.Izquierda();
      break;
    case ‘B’:
       MiCoche.Atras();
      break;
    case ‘S’:
       MiCoche.Para();
      break;
  }
}

Dentro del fichero ‘Coche.h’ se define la clase y las variables y métodos. Dentro del fichero ‘Coche.cpp’ está el código para mover los motores según cada una de las 5 funciones que he definido.

Más información:

Clases y Objetos en Arduino

Antes de que empecemos a hablar sobre Clases y Objetos, es importante insistir en que, la OOP no es tanto un lenguaje de programación diferente, sino más bien, una manera diferente de organizar tus programas y tus ideas, de acuerdo con unos principios guía que nos permiten modelar nuestro código de un modo distinto a como lo hemos hecho hasta ahora.

Para definir las Clases, existen una serie de reglas y de nuevas instrucciones, pero por lo demás el lenguaje sigue siendo el de siempre.

Si queremos hacer un contador en Arduino, creamos una variable global llamada “contador”. Pero esto ilustra bastante bien el problema de que si quiero 6 contadores voy a necesitar 6 variables globales. La idea en OOP es crear una Clase que nos permita definir Objetos tipo Contador que se pueda reutilizar y que nos permita mezclar diferentes contadores en un mismo programa.

Una de las ideas básicas tras la OOP es encapsular los datos y las funciones (o propiedades y métodos) de nuestro programa en un contenedor común, y más importante aún, aplicamos el principio de: “Esconder los datos y mostrar los métodos o funciones”.

Cuando definimos Clases, veremos que hay partes que son públicas y otras que son privadas. La sintaxis para definir la Clase contador que nos ocupa:

 
class Contador{
  private:
    int N;

  public:
    void SetContador( int n){
      N = n;
    }

    void Incrementar(){
      N++;
    }

    int GetCont(){
      return (N);
    }
};

Debajo de la cláusula “private:” viene las variables y funciones ocultas al exterior. Solo pueden ser invocadas desde el interior de la clase, es decir no se pueden ejecutar por una llamada exterior. Y lo contrario ocurre con lo que definamos tras la cláusula “public:”

Vamos a definir una variable privada llamada N, que llevará la situación del contador, y después necesitaremos los métodos necesarios para trabajar con ella. En principio vamos a definir tres funciones públicas: Una que ponga el contador a un valor dado antes de nada, Otra que sirva para incrementar el contador, y otra tercera para que nos entregue el valor del contador en un momento dado.

La variable N se comporta como si fuera una variable global pero sólo dentro del ámbito de la Clase (Encapsulación), y como está definida como private es inaccesible desde el exterior (Cualquier intento de llegar a ella causará un error del compilador)

Para usar esta clase, primero se instancian tantas ocurrencias de la clase como queramos.

 
#include "Contador.h"

Contador C1,C2;

void setup()
   {
      C1.SetContador(10);
      C2.SetContador(100);
   }
void loop()
   { 
      C1.Incrementar() ;
      Serial.print("C1 = ") ; Serial.println(C1.GetCont());

      C2.Incrementar() ; C2.Incrementar() ; C2.Incrementar();
      Serial.print("C2 = ") ; Serial.println(C2.GetCont());
   }

Usamos el “.” para referir la función a la que queremos llamar, con el Objeto al que se le aplica, como hemos visto antes en otros programas aunque sin entrar en muchos detalles.

Podemos definir una función que se ejecuta siempre que se crea un objeto, y es tan habitual que tiene nombre. Se le llama Constructor de la Clase, y para ello basta con llamarla igual que la Clase (Sin tipo):

 
Class Contador{
  private:
    int N;   

  public:
    Contador( ){        // Constructor
      N = 0;
    }
 
    void SetContador(int n){
      N = n;
    }

    void Incrementar(){
      N++;
    }

    int GetCont(){
      return(N);
    }
};

Una peculiaridad de los constructores es que no tienen un tipo definido, otra de las razones por las que el compilador sabe que es un constructor.

En este caso cuando hago Contador C1,C2 ; ya está inicializado el contador a 0 que lo hace el propio constructor.

Ejercicio: Hacer un programa que cuente las pulsaciones de los botones A y B usando la clase contador guardada en un fichero “Contador.h”

Soluciónhttps://github.com/jecrespo/aprendiendoarduino-Curso_Programacion_Arduino/tree/master/Ejercicio11-Clase_Contador

Ver que necesito dos funciones detecta flanco y no puedo usar solo una porque si llamo a una y a otra simultáneamente el valor static se mantiene entre la llamada de una y otra lo que hace que falle. Para resolver este problema, hacer una clase DetectaFlanco y entonces puedo reutilizar el código ya que cada vez que instancio una nueva clase es como una función nueva.

Ejercicio: Hacer el programa anterior pero creando  una clase llamada DetectaFlanco en un fichero “DetectaFlanco.h”

Soluciónhttps://github.com/jecrespo/aprendiendoarduino-Curso_Programacion_Arduino/tree/master/Ejercicio12-Clase_Detecta_Flanco

Cuando las clases y las funciones miembro son tan pequeñas y sencillas como en este caso, la forma que hemos visto de definirlas puede valer, pero en seguida se quedará corta. Por eso podemos declarar las funciones y variables miembros en la declaración de Clase, y definirlas fuera para mayor comodidad y evitar errores de sintaxis complicados de detectar.

Podemos reescribir la clase Contador así:

Fichero “Contador.h”:

 
class Contador
   {  private:
         int N ;

      public:
         Contador( ) ;               	// Constructor
         void SetContador( int n) ;  	// Declaracion de funcion externa
         void Incrementar() ;        	// Declaracion de funcion externa
         int GetCont() ;             	// Declaracion de funcion externa
   } ;

Fichero “Contador.cpp”:

 
#include <Contador.h>
 
  void Contador::SetContador( int n)
        {  N = n ;	}

   void Contador::Incrementar()
        {  N++ ; }
 
   int Contador::GetCont()
        { return (N) ;}

Declaramos las funciones miembros dentro de la Clase (Para informar al compilador), pero no incluimos su código aquí, porque sería muy confuso en cuanto crezcan de tamaño (Pero fijaros que ahora hay un punto y coma al final de las declaraciones que antes no había).

En cualquier otro lugar podemos definir esas funciones sin más que hacer referencia a la Clase a la que pertenecen usando el operador ‘::’ (Scope Operator u Operador Ámbito) y el compilador entiende que son miembros de la clase que precede al operador. Este operador le indica al compilador, que estas funciones o variables son miembros de la clase, y sólo pueden invocarse de acuerdo a las condiciones que se especifican en la declaración de la Clase (Que debe coincidir con esta claro está).

Si editais cualquiera de las librerías de Arduino, encontrareis que ésta es la forma habitual de programar las clases y librerías.

Soluciónhttps://github.com/jecrespo/aprendiendoarduino-Curso_Programacion_Arduino/tree/master/Ejercicio12-Clase_Detecta_Flanco_cpp

Clase Serial Arduino

Un ejemplo de clase es una que usamos habitualmente, la clase Serial: https://www.arduino.cc/en/Reference/Serial que está definida en los ficheros:

Por lo tanto cuando estamos Serial.begin(9600) estamos llamando al método begin del objeto Serial. En este caso no hemos hecho un include del fichero donde está incluida esta clase porque el IDE lo incluye automáticamente en el proceso de compilación ni tampoco hacemos una declaración del objeto con el constructor, puesto que también lo hace el IDE de Arduino.