RETO AL CÓDIGO

Para sustentar una pregunta que voy a hacer en breve en el foro, se me ocurrió primero este reto.

El reto es …

¿Por qué no se apaga el led?

// --- punteros a funciones (https://forum.arduino.cc/index.php?topic=84682.0)

#include <Arduino.h>


// --- Yo soy una función que enciendo el led del Arduino
void encenderLed() { digitalWrite(13, HIGH); }

// --- Yo soy una función que apago el led del Arduino
void apagarLed() { digitalWrite(13, LOW); }

// --- Yo soy una función que imprimo hola por el serial
void decirHola() { Serial.println("HOLA COCACOLA"); }

// --- Yo soy una función que imprimo presto por el serial
void decirPresto() { Serial.println("PRESTO PICCOLO BAMBINO"); }

// --- Yo soy una función que hago tonterías
void tonteria() { Serial.println("--  ESTOY HACIENDO TONTERIAS --"); }


// --- YO EJECUTO LO QUE ME ECHEN ENCIMA
void ejecutador( void(*queHago)() ){ (*queHago)(); }

// --- byte, que la memoria está muy cara
byte veces=0;

// --- Un puntero ABSTRACTO A UNA FUNCION, NI IDEA DE CUAL, Global
void (* noSeLoQueSoy_GLOBAL)();




// --- Preparemos el escenario
void setup() {
   pinMode(13, OUTPUT);
   Serial.begin(9600);

   // --- Si, ya sé que es una tontería
   veces = 0;
}


// --- ¡¡ VAMOS A VER QUE PASA !!

void loop() {

   // --- Vamos a contar las veces que damos vueltas
   veces ++;

   Serial.println();Serial.println();Serial.println();
   Serial.println( "HOLA ARDUINERO, soy el ciclo " + String(veces) );


   // --- Un puntero ABSTRACTO A UNA FUNCION, NI IDEA DE CUAL, local
   void (* noSeLoQueSoy_LOCAL)();

   /* --- Aquí ocurren un montón de cosas complicadas que no las entiende ni el
          que las programó, pero que funcionan
    */

   if ( veces == 1) {
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }

   /* --- Aquí ocurren un montón de cosas que pueden modificar el valor de veces
          no importa por qué, pero supongamos que ocurre, o incluso cambiar el
          puntero
    */

   if ( veces == 2) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }

   /* --- Aquí ocurren otro montón de cosas que pueden modificar el valor de
          veces o vete tu a saber
    */

   if ( veces == 3) {
      noSeLoQueSoy_GLOBAL = tonteria;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }

   /* --- Más cosas raras
    */

   if ( veces == 4) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }

   /* --- Más cosas raras
    */

   if ( veces == 5) {
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }

   /* --- Más cosas Más raras
    */

   if ( veces == 6) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }

   /* --- MUCHAS Más cosas Más raras
    */

   if ( veces == 7) {
      noSeLoQueSoy_GLOBAL = tonteria;
   }


   /* --- Por cierto ¿Piensas que cuando veces > 7 no pasará nada? JEJEJEJE
    */



   /* --- Pedazo de código más largo que el quijote
    */


   /* --- Y ahora se sabe qué se tendría que hacer de ese montón de cosas
          posibles. ¿Miles de if endif?
    */

   // --- YO EJECUTO LO QUE ME ECHEN ENCIMA
   ejecutador( noSeLoQueSoy_GLOBAL );
   ejecutador( noSeLoQueSoy_LOCAL );


   // --- Aquí pongo un delay de esos que tanto les gusta a la comunidad Arduino
   delay(3000);


   // --- PREMIO SI AVERIGUAS POR QUÉ DESPUÉS DEL CICLO 7 NO SE APAGA EL LED
}

Les insto a @surbyte o a @gatul a que nos expliquen por qué, yo creo que es porque se destruye el puntero, pero ¿Por qué cambia el estado del led?

Eeeeeehhhhh...
Perá, perá, no me apuré...

Bueno, el ejercicio me ha hecho trabajar y debo confesar que la resolucion no me satisface del todo pero la expondré como si estuviera en un exámen y no tengo mas ideas.

Al ser un puntero local parece ser que la primer definición queda como x defecto.

En mis pruebas alteré que ponía en el primer condicional

if ( veces == 1) {
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }

Si pones esto el valor que permanece luego del 7mo if es apagarLed, lo que prueba que esta primer asignación tiene cierto peso en el compilador y en como permanece el puntero asignado cuando no le digo a donde apuntar.

Lo detecté viendo la dirección a la que el puntero local apuntaba y descubrí que siempre arranca con la primer posición.

EDITO: solo corregí errores de redacción.

No se me había ocurrido, muy interesante

Por favor, Tony permite a otros dar su opinión. Lindo reto.

Creo tmb que este post podemos mantenerlo aqui o en donde tu lo pusiste, casi que me arrepiento de haberlo movido.
Es algo nuevo y aprecio tu idea para movilizar al foro!!
Gracias.

No voy a comentar más para que todos jueguen, donde lo pongas me parece bien.

A mi me ha pasado por encima como una ola pero sigo sorprendido por ese código, desde la madrugada sigo así :fearful:
Claramente no tengo explicación de por qué sucede lo que a todas luces no debería.
La explicación de @surbyte me ha dejado satisfecho por ahora.

Es que una parte de la pregunta es ¿es sano declarar punteros locales a funciones globales? o mejor preguntado ¿punteros a funciones en diferentes ámbitos?, de entrada parece ya un problema serio

Punteros locales yo uso todo el tiempo en cosas insignificantes como sprintf y su correspondiente buffer, pero punteros a funciones locales no he usado jamás.
Por eso me llamó la atención e investigué que pasaba y porque se daba la situación.
Estuve buen tiempo intentando mostrar la dirección hexa.
Esta fue mi código ayuda.

// --- punteros a funciones (https://forum.arduino.cc/index.php?topic=84682.0)

#include <Arduino.h>

// --- Yo soy una función que enciendo el led del Arduino
void encenderLed() { Serial.println("LED ON"); }

// --- Yo soy una función que apago el led del Arduino
void apagarLed() { Serial.println("LED OFF"); }

// --- Yo soy una función que imprimo hola por el serial
void decirHola() { Serial.println("HOLA COCACOLA"); }

// --- Yo soy una función que imprimo presto por el serial
void decirPresto() { Serial.println("PRESTO PICCOLO BAMBINO"); }

// --- Yo soy una función que hago tonterías
void tonteria() { Serial.println("--  ESTOY HACIENDO TONTERIAS --"); }


// --- YO EJECUTO LO QUE ME ECHEN ENCIMA
void ejecutador( void(*queHago)() ){ (*queHago)(); }

// --- byte, que la memoria está muy cara
byte veces=0;

// --- Un puntero ABSTRACTO A UNA FUNCION, NI IDEA DE CUAL, Global
void (* noSeLoQueSoy_GLOBAL)();

void imprimirPuntero(void(*queHago)() ){
  char buffer[30];
  sprintf(buffer, "%p\n", queHago);
  Serial.print(buffer);
}


// --- Preparemos el escenario
void setup() {
   pinMode(13, OUTPUT);
   Serial.begin(9600);

   // --- Si, ya sé que es una tontería
   veces = 0;
}


// --- ¡¡ VAMOS A VER QUE PASA !!

void loop() {
   // --- Vamos a contar las veces que damos vueltas
   veces ++;

   Serial.println();Serial.println();Serial.println();
   Serial.println( "HOLA ARDUINERO, soy el ciclo " + String(veces) );

   // --- Un puntero ABSTRACTO A UNA FUNCION, NI IDEA DE CUAL, local
   void (* noSeLoQueSoy_LOCAL)();

   /* --- Aquí ocurren un montón de cosas complicadas que no las entiende ni el
          que las programó, pero que funcionan
    */
   imprimirPuntero(noSeLoQueSoy_LOCAL);
   if ( veces == 1) {
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }


   /* --- Aquí ocurren un montón de cosas que pueden modificar el valor de veces
          no importa por qué, pero supongamos que ocurre, o incluso cambiar el
          puntero
    */

   if ( veces == 2) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }
   /* --- Aquí ocurren otro montón de cosas que pueden modificar el valor de
          veces o vete tu a saber
    */

   if ( veces == 3) {
      noSeLoQueSoy_GLOBAL = tonteria;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }
   imprimirPuntero(noSeLoQueSoy_LOCAL);
   /* --- Más cosas raras
    */

   if ( veces == 4) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }
   /* --- Más cosas raras
    */

   if ( veces == 5) {
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }
   /* --- Más cosas Más raras
    */

   if ( veces == 6) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }
    /* --- MUCHAS Más cosas Más raras
    */

   if ( veces == 7) {
      noSeLoQueSoy_GLOBAL = tonteria;
   }

   /* --- Por cierto ¿Piensas que cuando veces > 7 no pasará nada? JEJEJEJE
    */



   /* --- Pedazo de código más largo que el quijote
    */


   /* --- Y ahora se sabe qué se tendría que hacer de ese montón de cosas
          posibles. ¿Miles de if endif?
    */

   // --- YO EJECUTO LO QUE ME ECHEN ENCIMA
   ejecutador( noSeLoQueSoy_GLOBAL );
   ejecutador( noSeLoQueSoy_LOCAL );
   imprimirPuntero(noSeLoQueSoy_LOCAL);   
   
   // --- Aquí pongo un delay de esos que tanto les gusta a la comunidad Arduino
   delay(5000);


   // --- PREMIO SI AVERIGUAS POR QUÉ DESPUÉS DEL CICLO 7 NO SE APAGA EL LED
}

Igualmente creo que hay otra explicación.

¡¡ ESTOY ASOMBRADO !!

He modificado el sketch

He creado una función global

// --- Yo no hago nada
void nadaDeNada() { Serial.println("YO NO HAGO NADA"); }

Y luego he modificado el ciclo 1

   if (veces == 1) {
      noSeLoQueSoy_LOCAL  = nadaDeNada;
      ejecutador( noSeLoQueSoy_LOCAL );
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }

El resultado es que el LED no se apaga tampoco, así que empiezo a pensar que, cuando muere una función/puntero a un elemento Hardware, este regresa a su estado inicial o de reposo, que en el PIN 13 es encendido, lo cual tiene grandes implicaciones en el comportamiento del Arduino.
Empiezo a pensar que tener punteros locales a funciones que interactúan con el Hardware, es mala idea.

Pues más te va a sorprender esto:

// --- punteros a funciones (https://forum.arduino.cc/index.php?topic=84682.0)

#include <Arduino.h>


// --- Yo soy una función que enciendo el led del Arduino
void encenderLed() { digitalWrite(13, HIGH); }

// --- Yo soy una función que apago el led del Arduino
void apagarLed() { digitalWrite(13, LOW); }

// --- Yo soy una función que imprimo hola por el serial
void decirHola() { Serial.println("HOLA COCACOLA"); }

// --- Yo soy una función que imprimo presto por el serial
void decirPresto() { Serial.println("PRESTO PICCOLO BAMBINO"); }

// --- Yo soy una función que hago tonterías
void tonteria() { Serial.println("--  ESTOY HACIENDO TONTERIAS --"); }


// --- YO EJECUTO LO QUE ME ECHEN ENCIMA
void ejecutador( void(*queHago)() ){ (*queHago)(); }

// --- byte, que la memoria está muy cara
byte veces=0;

// --- Un puntero ABSTRACTO A UNA FUNCION, NI IDEA DE CUAL, Global
void (* noSeLoQueSoy_GLOBAL)();




// --- Preparemos el escenario
void setup() {
   pinMode(13, OUTPUT);
   Serial.begin(9600);
   Serial.println("Soy encenderLed y mi direccion es: " + String((uint32_t)encenderLed));
   // --- Si, ya sé que es una tontería
   veces = 0;
}


// --- ¡¡ VAMOS A VER QUE PASA !!

void loop() {

   // --- Vamos a contar las veces que damos vueltas
   veces ++;

   Serial.println();Serial.println();Serial.println();
   Serial.println( "HOLA ARDUINERO, soy el ciclo " + String(veces) );


   // --- Un puntero ABSTRACTO A UNA FUNCION, NI IDEA DE CUAL, local
   void (* noSeLoQueSoy_LOCAL)();

   // --- Y aquí solo un poco de información:
   Serial.println("Hola colega mi direccion es:" + String((uint32_t)&noSeLoQueSoy_LOCAL));
   Serial.println("y apunto a:" +String((uint32_t)*noSeLoQueSoy_LOCAL));
   

   /* --- Aquí ocurren un montón de cosas complicadas que no las entiende ni el
          que las programó, pero que funcionan
    */

   if ( veces == 1) {
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }

   /* --- Aquí ocurren un montón de cosas que pueden modificar el valor de veces
          no importa por qué, pero supongamos que ocurre, o incluso cambiar el
          puntero
    */

   if ( veces == 2) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }

   /* --- Aquí ocurren otro montón de cosas que pueden modificar el valor de
          veces o vete tu a saber
    */

   if ( veces == 3) {
      noSeLoQueSoy_GLOBAL = tonteria;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }

   /* --- Más cosas raras
    */

   if ( veces == 4) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }

   /* --- Más cosas raras
    */

   if ( veces == 5) {
      noSeLoQueSoy_GLOBAL = decirHola;
      noSeLoQueSoy_LOCAL  = encenderLed;
   }

   /* --- Más cosas Más raras
    */

   if ( veces == 6) {
      noSeLoQueSoy_GLOBAL = decirPresto;
      noSeLoQueSoy_LOCAL  = apagarLed;
   }

   /* --- MUCHAS Más cosas Más raras
    */

   if ( veces == 7) {
      noSeLoQueSoy_GLOBAL = tonteria;
   }


   /* --- Por cierto ¿Piensas que cuando veces > 7 no pasará nada? JEJEJEJE
    */



   /* --- Pedazo de código más largo que el quijote
    */


   /* --- Y ahora se sabe qué se tendría que hacer de ese montón de cosas
          posibles. ¿Miles de if endif?
    */

   // --- YO EJECUTO LO QUE ME ECHEN ENCIMA
   ejecutador( noSeLoQueSoy_GLOBAL );
   ejecutador( noSeLoQueSoy_LOCAL );


   // --- Aquí pongo un delay de esos que tanto les gusta a la comunidad Arduino
   delay(3000);


   // --- PREMIO SI AVERIGUAS POR QUÉ DESPUÉS DEL CICLO 7 NO SE APAGA EL LED
}

Básicamente he añadido estas dos líneas, justo despues de crear la funcion noSeLoQueSoy_LOCAL:

// --- Y aquí solo un poco de información:
   Serial.println("Hola colega mi direccion es:" + String((uint32_t)&noSeLoQueSoy_LOCAL));
   Serial.println("y apunto a:" +String((uint32_t)*noSeLoQueSoy_LOCAL));

E increiblemente el led se apaga…

Con esas dos líneas muestro la dirección de la variable noSeLoQueSoy_LOCAL y a donde apunta. En el setup del sketch empecé a escribir las direcciones de cada función.

En teoria, un puntero es una dirección de memoria. Lo que apunta puede ser cualquier cosa, en este caso es la dirección de una función. Cuando el compilador crea una variable, asigna una posicion de memoria. Y comprobando las direcciones y viendo a donde apunta me he quedado flipando al ver el comportamiento.

Desde que hacer mal un puntero significa reiniciar un ordenador tengo la mala costumbre de que cuando creo uno lo asigno a NULL. Y luego si o si, antes de usarlo compruebo que este no es NULL. En este código sería:

void ejecutador( void(*queHago)() ){ 
  if ( quehago!=NULL) quehago(); 
}

void loop() {
  ...
  void (* noSeLoQueSoy_LOCAL)()=NULL;
}

Con lo cual el problema también desaparece.

Ahora bien, sigo sin entender muy bien lo que está ocurriendo con tu código original. Pensaba
que al crearse la variable en el mismo lugar que ocupaba en el loop anterior y al no modificar
el valor al que apunta, entonces debería ejecutar la última que fue asignada
(veces==6, apagarLed), pero entonces va y enciende el led…

Gracias @victorjam, creo que le has dado en el clavo, creo que los punteros a funciones locales que van a ser destruidos hay que redireccionarlos a NULL, a fin de cuentas es un C++ y no hay recolector de basura como en C#,así que, sobre todo en punteros, hay que pensar muy bien que ocurren con ellos cuando cambiamos el ámbito o cuando mueren, gracias

 void static (* noSeLoQueSoy_LOCAL)();

Funciona bien, pero de hecho cuando vi el código dije: "El error es que no se declaro volatil" .

¡¡ ERES UN GENIO !!

Ahora os dejo el segundo reto

Porque a mí me ha superado

Una pregunta @PeterKantTropus ¿No querrás decir que el problema es que se declaró volátil? porque si no necesito que me expliques, ya que static = ‘no volatil’ o TonyDiana = ‘ni idea de esto del C++’

De hecho no tengo en claro porque funciono, creí que había declararla volatil, pero el hecho es que funciono lo contrario.

Pues eso es lo que creo, que los punteros a funciones, sobre todo en hardware, no deben ser declarados volátiles, así que declararlos en un ámbito en el que van a ser destruidos tienen consecuencias nefastas o no deseadas, por eso reapuntarlos a NULL, controlando la destrucción como haríamos con un objeto NEW en c++, o declararlos estáticos, tiene mucho que ver con el reto que he propuesto de crear una función genérica para PROGMEM y, para otras locuras que tengo en mente

volatile y static son dos cosas diferentes.

En el caso de volatile se le indica al compilador que esa posición de memoria podrá ser compartida por otro proceso/recurso. En el mundo de arduino las usamos en el entorno de las interrupciones generalmente: cuando una variable que vas a usar en el loop la usas también en la rutina de la interrupción, conviene que esta sea volatile, así el compilador la trata de manera especial y tiene en cuenta que puede ser cambiada por otro proceso.

static por lo contrario fija una variable. Es decir si declaras una variable static en una función está variable permanecerá en memoria, no importa las veces que llames a dicha función.

¿Por qué si pones la función con el modificador static funciona el código? En la primera llamada a loop se crea la variable y le asignas un valor (o varios por el código), pero cuando el loop termina y se vuelve a ejecutar esa variable static no se crea de nuevo, si no que permanece de la llamada anterior y en el estado que tuviera. Podriamos decir que es una variable local, pero tiene ambito global (vamos, como si la hubieras declarado global).

Un uso de static que también es interesante es en objetos. Si un miembro de una clase es declarado como static, significará que ese miembro es común a todos los objetos. Por ejemplo, clase MESA, con un miembro static “patas”, ahora creas dos objetos mesa, mesacomedor y mesacocina. Si dices que mesacomedor tiene cuatro patas, automaticamente mesacocina tendrá cuatro patas. Pero si a la mesacocina le dices que tiene tres, automaticamente la del comedor pasará a tener también tres, aunque previamente hayas puesto cuatro.

El mundo de C++ es demasiado amplio y hay que tener cuidado con los modificadores ya que pueden también causarte dolores de cabeza.

Si, es lo que me temía, es como el C, bien complicados y si los descuidas te matan, gracias a todos por ayudarme a entenderlo mejor