Llamar una función de la librería LiquidCrystal dentro de mi propia librería.

Buenas. Creo que el titulo lo dice todo. Estoy creando una librería para hacer una animación de texto en el LCD de forma automática para un proyecto y soy incapaz de llamar cualquier función del tipo lcd.clear (); lcd.print(); y demás funciones de la librería. Ahora mismo el código como esta compila correctamente y al menos la parte del Serial.print (_c) ; la hace sin problemas, como digo el lió es con la librería LiquidCrystal, desconozco como llamar funciones de esta librería desde la mía propia (se que hay partes de que momento no tienen sentido como el int Speed y es porque de momento no me he puesto con ello hasta que no sea capaz de usar el LCD). A ver si podéis ayudarme, es la primera vez que trabajo creando una librería.

Aquí os dejo mi código, espero que alguien pueda ayudarme. Estoy trabajando en Arduino UNO con un lcd de 16x2.

AnimatonText.h

#ifndef AnimationText_h
#define AnimationText_h
#include "Arduino.h"
#include <LiquidCrystal.h>


class AnimationText {
  private:
    char _c;
    char _i;
    LiquidCrystal*print();
  public:
	AnimationText (int Speed);
    void write (String (text));

 
};
#endif

AnimationText.cpp

#include "AnimationText.h"
#include "Arduino.h"
#include <LiquidCrystal.h>



AnimationText::AnimationText (int Speed){

}

void AnimationText::write (String (text)){
  for (char i=0; i< text.length() ; i++) {
    char _c = text.charAt (i);
   	Serial.print (_c);
 	LiquidCrystal*print(_c);
    delay (70);
  } 
}

AnimationText.ino

#include <AnimationText.h>
LiquidCrystal lcd(6, 7, 8, 9, 10, 11); // RS, E, D4, D5, D6, D7
AnimationText AnimationText(70);

void setup() {
  lcd.begin (16, 2);
  lcd.setCursor(0, 0);
  Serial.begin (9600);
}

void loop() {
  AnimationText.write("Hello World!");
  delay (1000);
  lcd.clear ();
  delay (1000);

}

I've deleted your other cross posts @Garal.

Cross posting is against the rules of the forum. The reason is that duplicate posts can waste the time of the people trying to help. Someone might spend 15 minutes writing a detailed answer on this thread, without knowing that someone else already did the same in the other thread.

Repeated cross posting will result in a suspension from the forum.

In the future, please take some time to pick the forum section that best suits the topic of your question and then only post once to that forum section. This is basic forum etiquette, as explained in the sticky "How to use this forum - please read." post you will find at the top of every forum section. It contains a lot of other useful information. Please read it.

Thanks in advance for your cooperation.

Prueba con algo tal que así:

AnimatonText.h

#ifndef AnimationText_h
#define AnimationText_h
#include "Arduino.h"
#include <LiquidCrystal.h>


class AnimationText {
  private:
    char _c;
    char _i;
    LiquidCrystal lcd; // <== línea modificada
  public:
 AnimationText (LiquidCrystal& lcd, int Speed); // <== línea modificada
    void write (String (text));

 
};
#endif

AnimationText.cpp

#include "AnimationText.h"
#include "Arduino.h"
#include <LiquidCrystal.h>



AnimationText::AnimationText (LiquidCrystal& lcd, int Speed) : lcd(lcd) { // <== línea modificada

}

void AnimationText::write (String (text)){
  for (char i=0; i< text.length() ; i++) {
    char _c = text.charAt (i);
   Serial.print (_c);
   lcd.print(_c); // <== línea modificada
    delay (70);
  } 
}

AnimationText.ino

#include <AnimationText.h>
LiquidCrystal lcd(6, 7, 8, 9, 10, 11); // RS, E, D4, D5, D6, D7
AnimationText AnimationText(lcd, 70); // <== línea modificada

void setup() {
  lcd.begin (16, 2);
  lcd.setCursor(0, 0);
  Serial.begin (9600);
}

void loop() {
  AnimationText.write("Hello World!");
  delay (1000);
  lcd.clear ();
  delay (1000);

}

Busca el comenteario // <== línea modificada en los tres códigos. Cambia las cinco líneas (2, 2 y 1) y prueba. Yo no lo he podido probar y tal vez se me escape algo, así que no garantizo ni que llegue a compilar ::slight_smile:

Si funciona, ya sólo tendrías que trabajar con el objeto lcd dentro de la clase igual que lo sueles hacer en el loop(): lcd.print(), lcd.clear(), etc.

Ya nos dirás si funciona. Si no, habría otra forma de hacerlo.

Nota: asegúrate que cambias las líneas enteras, poque en algunas hay más de un cambio (sobre todo la de la implementación del constructor).

Muchas gracias! Si que compila y hace lo que tienes que hacer. Por fin, no hacia nada mas que darme quebraderos de cabeza. He probado mas funciones como lcd.clear (), lcd.cursor () y lcd.blink () y me han funcionado sin problemas! Lo único que no funciona como debería es la función lcd.setCursor (x, y), la cual funciona solo en la primera linea, y cuando pongo lcd.setCursor (0, 1); no me hace caso. Cambiar de columna lo hace sin problemas.

Por cierto, podrías darme una explicación del código que me has escrito? No acabo de entenderlo.

Muchas gracias.

Perdona, creo que con las prisas me despisté y el primer cambio del fichero AnimationTex.h no es:

    LiquidCrystal lcd; // <== línea modificada

Debería de ser:

    LiquidCrystal &lcd; // <== línea modificada

Me faltó el &. Prueba a ponerlo y mira a ver si ahora te funciona bien el lcd.setCursor(0, 1); y me dices.

En cuanto pueda, con más tranquilidad, te explico lo que hace. Incluso el despiste del &.

Si! Ahora funciona correctamente, incluso el lcd.setCursor(). Muchas gracias, espero tu explicación!

Voy a seguir trabajando en la librería para hacerlo todo mas simple y comentarlo. Si me surge algun problema mas relacionado con esto escribiré por aquí.

Perdona Garal, pero no he podido disponer de todo el tiempo que quisiera para tratar de dar la explicación. Así que por lo pronto daré una explicación abreviada.

El Arduino no es más que un entorno de desarrollo de C++ con unas cuantas herramientas y librerías que hacen mucho más amigable el mundo de la programación de microcontroladores (y aún así sigue siendo más ardua de lo que se desea). A su vez el C++ es una evolución del C.

En C sólo se pueden pasar por copia los valores de los parámetros de las funciones. Esto significa que cuando se llama a una función indicando uno o varios valores, estos no se ven alterados por los cambios de valor que se le hagan a las variables que los reciben, ya que lo que reciben es una copia de esos datos que son alojadas en otra zona de memoria. Así que si se cambia los valores de las variables parámetros de la función, lo que se altera son esas copias, no los originales.

"¿Y los punteros?", preguntará más de uno. "Yo puedo pasar un puntero a la función y me modifica los datos de la variable en la llamada", dirán más de dos. Los punteros, como tal, no son alterados, y eso es lo que se pasa a la función, una copia del puntero. Se puede modificar esa copia y a la variable original no le pasa nada, no se ve modificada. Otra cosa distinta es que se modifique lo que es apuntado por el puntero. Eso es otra cosa, ahí sí que se puede estar modificando la variable original. De hecho, como en C siempre se pasan los valores de los parámetros por copia, cuando se quiere pasar una estructura de datos muy grande a una función, se suele pasar un puntero a ella, ya que no importa si la estructura ocupa 10 o 200 bytes porque lo que se copia no son esos 200 bytes (que lleva su tiempo copiarlos), lo que se copia es un puntero que apunta a la primera dirección de memoria en que está la estructura original y a partir de ahí se accede a los datos. El tamaño de ese puntero pueden ser de solo 2, 4 u 8 bytes, dependiendo del hardware en que se esté ejecutando el programa. En el Arduino UNO son 2 bytes. Para esto existen los punteros y la aritmética de punteros. Como curiosidad, en C un array no es más que un puntero al primer elemento del array. En Google se puede encontrar más información del tema.

¿Pero qué pasa con el C++, que es lo que en realidad es “el mundo Arduino”? Pues que en C++ se añadió otro tipo de variables y otro modo de pasar los parámetros. Las referencias y el paso de parámetros por referencia.

  int numero = 10;            // Variable de tipo entero que vale 10
  int otroNumero = numero;    // Variable de tipo entero que se inicializa con el valor de numero (lo mismo daría si lo inicializamos con un 10)
  int *puntero = &numero;     // Variable de tipo puntero a entero que “apunta” a la variable numero (posteriormente puede apuntar a otro entero cualquiera)
  int &referencia = numero;   // Variable de tipo referencia a entero que hace referencia a la variable numero. Tanto numero, como referencia son la misma cosa con distinto nombre. Ya no se puede cambiar referencia para que “señale” a otro entero.

En el ejemplo anterior, es indiferente usar la variable numero o la variable referencia, ya que ambas son la misma cosa con dos nombres distintos. Si se modifica la variable referencia también se modifica la variable numero.

En el siguiente ejemplo vemos dos funciones, a una se le pasa el parámetro por copia y a la otra por referencia. Observar que en la llamada el parámetro no tiene ningún & en ningún momento (como cuando se pasa un puntero en C para que la función pueda modificar la variable original).

void funcionCopia(int parametro) {
    parametro--;
}

void funcionReferencia(int &parametro) {
    parametro++;
}

  funcionCopia(numero);
  funcionReferencia(numero);

Al final de las dos llamada, el valor de numero será incrementado en una unidad ya que funcionCopia() no altera su valor mientras que funcionReferencia() sí que lo altera ya que durante su llamada las variables parametro y numero son la misma cosa.

Como ya he comentado, las referencias, una vez asignadas, no se pueden cambiar. En funcionReferencia() no se puede hacer que parametro haga referencia a otra variable una vez invocada la función, lo que sí que se puede hacer es invocar nuevamente la función con otra variable, pero sería otra llamada y otra ejecución diferente de la misma función.

Aunque parezca más práctico usar referencia antes que punteros, como ya he dicho muchas veces, las referencias una vez asignadas no se pueden cambiar. Mientras que los punteros sí se pueden modificar y hacer que apunte a otro elemento diferente cada vez que queramos. Así que cada tipo de variable tiene su utilidad.

(continúo en el siguiente post porque me ha salido un poquito grande la pequeña explicación)

Bueno, pues eso es lo que finalmente he hecho, en lugar de un puntero lo que he creado ha sido una referencia a un objeto tipo LiquidCrystal en la clase AnimationText. Sí, ya sé que originalmente me despisté y mi gozo en un pozo ya que se me olvidó el & en la declaración y en lugar de tener una referencia lo que tenía era una copia. Por eso cuando se llamaba a lcd.setCursor() fallaba. Ya que se le decía a uno de los objetos que posicionara el cursor en un lugar y se le pedía al otro que escribiese, y este otro no se había dado por enterado del cambio de posición del cursor. Ese fallo fue el que me dio a la pista de que se me había olvidado el &. Con la corrección ambos objetos son el mismo, y lo que se le dice a uno se le dice al otro.

¿Recuerdan que he dicho una y otra vez que una referencia, una vez creada no se puede cambiar? Pues ese es un pequeño gran detalle. Porque si habiendo declarado como referencia la variable lcd definimos el constructor así:

AnimationText::AnimationText (LiquidCrystal& _lcd, int Speed) {
  lcd = _lcd;
}

De nada habría servido definirla como referencia. Nos daría el mismo problema que antes con el lcd.setCursor(). (He llamado al parámetro _lcd para que se vea cual es el parámetro y cual es la variable de la clase, lcd. Y porque, si no, no estaría haciendo lo que se quiere que haga). Si bien _lcd sí es una referencia al lcd “original” con el que se invoca al constructor, lcd es una referencia que se habría creado sin hacer referencia a nadie (lo más probable es que el compilador nos de un aviso o fallo). Una vez creada se le asigna por copia el valor de _lcd. Así que volvemos a tener una copia. Ya sé que es un poco galimatías, pero lo que han de tener claro es que la variable lcd de la clase AnimationText “se crea” antes de que se ejecute el código que está definido entre las { } del constructor. Y una vez creada una referencia, esta se comporta como “la variable original”. Lo que pasa aquí es que no se le ha indicado en ningún momento cual es “la variable original”. Lo que se haga dentro de las { } ya “es demasiado tarde” para poder indicar a quién ha de hacer referencia. (Y lo más probable es que el compilador se nos queje diciendo que esa referencia no ha sido inicializada adecuadamente). Es por eso que se usa la sintaxis siguiente:

AnimationText::AnimationText (LiquidCrystal& _lcd, int Speed) : lcd(_lcd) {
}

Esto le indica al compilador que queremos que construya lcd con _lcd antes de que se ejecute el código del constructor (lo que está entre las { }). Esa forma de inicializar variables y objetos se puede usar para inicializar cualquier variable u objeto de la clase. Dependiendo de cómo esté configurado el compilador, algunos se quejan si no se ponen en el mismo orden en que se declaró en la clase. Por ejemplo, en lugar de poner:

ClaseA::ClaseA(int x, int y, String z) {
    a = x;
    b = y;
    c = z;
}

Podríamos poner:

ClaseA::ClaseA(int x, int y, String z) : a(x), b(y), c(z) {
}

Lo que sí que no es opcional es cuando, como en nuestro caso, necesitamos inicializar una referencia. Para ello sólo lo podemos hacer como en la segunda opción.

También es necesario cuando en nuestra clase definimos un objeto que no tiene constructor por defecto o queremos construirlo con un constructor con parámetros. Por ejemplo, si en lugar de querer trabajar con una referencia al lcd lo que queremos es trabajar con un objeto lcd propio de la clase y queremos que se construya llamando al constructor con parámetros.

Entonces al declarar la variable lcd en la clase lo haremos como lo hice al inicialmente, por error, sin el &. Y en constructor sería algo tal que así (he omitido la variable Speed porque no se usaba y por claridad):

AnimationText::AnimationText (int rs, int e, int d4, int d5, int d6, int d7) : lcd(rs, e, d4, d5, d6, d7) {
}

Y en lugar de tener esto:

#include <AnimationText.h>
LiquidCrystal lcd(6, 7, 8, 9, 10, 11); // RS, E, D4, D5, D6, D7
AnimationText animationText(lcd);

Tendríamos esto:

#include <AnimationText.h>
AnimationText animationText(6, 7, 8, 9, 10, 11);

Pero ahora ya no disponemos del objeto lcd en el loop() ya que lcd sólo existe dentro del objeto animationText.

Bueno, hasta aquí la “breve” explicación.