Recursividad en MikroC

Buenas, la pregunta es simple: ¿Se pueden desarrollar programas recursivos en MicroC?. Resulta que tengo la intención de ensamblar un pequeño robot con motores paso a paso que resuelva el problema de las torres de Hanoi, y el programa que pretendo grabar en el Microcontrolador PIC16F877A es recursivo, pero en MicroC me muestra el error "Recursion or cross-calling of mover". Mover esel nombre del programa recursivo. De antemano muchas gracias por sus aportes...
 
Buenas, la pregunta es simple: ¿Se pueden desarrollar programas recursivos en MicroC?. Resulta que tengo la intención de ensamblar un pequeño robot con motores paso a paso que resuelva el problema de las torres de Hanoi, y el programa que pretendo grabar en el Microcontrolador PIC16F877A es recursivo, pero en MicroC me muestra el error "Recursion or cross-calling of mover". Mover esel nombre del programa recursivo. De antemano muchas gracias por sus aportes...

Hasta donde sé el lenguaje C en general no soporta recursividad, puede pasar que deje compilar y ejecutar el programa pero lo más probable es que termine en desbordamiento de pila (stack overflow).
Esto pasa porque si tengo una función:
int FuncionRecursiva(int parametro1, int parametro2) {
//Instrucciones de la función
FuncionRecursiva(parametro1 - 1, parametro2-1);
//Más instrucciones
return AlgunaCosa;
}

parametro1 y parametro2 se guardan en la pila, además de la dirección de retorno, junto con registros del procesador...
Con cada llamada la pila continúa creciendo hasta que se termina la memoria, lo que en un microcontrolador pasa bien rápido. Por eso la advertencia de recursión al compilar.
Pero... hay una excepción y es que si la llamada recursiva se hace en la última instrucción de la función, entonces - si el compilador lo permite - se puede hacer "tail call optimization" = optimización de llamada de cola, que no suena muy bien... seguro hay una mejor traducción.
Es decir:
int FuncionRecursiva(int parametro1, int parametro2) {
//Instrucciones de la función
return FuncionRecursiva(parametro1 - 1, parametro2-1);
}
Notar que no hay NINGUNA instrucción después de la llamada a FunciónRecursiva. El compilador lo que hace es reutilizar el espacio de pila. Cuando se llama por primera vez a función recursiva se reserva espacio en pila para parametro1, parametro2, y el valor de retorno. Cuando se llama a FuncionRecursiva desde dentro de FuncionRecursiva el compilador guardar (parametro-1) en el mismo lugar de la pila donde estaba parametro1, y lo mismo para parametro2 que ahora tendrá el valor parametro2-1.
No cualquier función recursiva se puede programar de esa forma, así que es más una curiosidad que algo habitual.
Por último, tengo entendido que cualquier función recursiva se puede transformar en una iterativa (con un loop while/for/do-while); podrías buscar información sobre eso que seguro habrá algo escrito mejor que lo que yo pueda tipear en 5 minutos por acá.
Saludos.
 
Muchas gracias por los comentarios.

Adjunto el código que venia desarrollando, e indico que gracias a las observaciones, decidí buscar alguna solución iterativa, la encontré y es muy interesante.

Primeramente el código con recursividad es el clásico que conocemos sin embargo, para MikroC adjunté el envío de pulsos al puerto B, indicándome de que torre (ini) a que torre (fin) debo mover los discos.

PHP:
void mover(int n, int ini, int aux, int fin)
{
 if(n==1)
 {
  switch(ini)
  {
   case 1: portb.rb5=1, delay_ms(500), portb.rb5=0;break;
   case 2: portb.rb4=1, delay_ms(500), portb.rb4=0;break;
   default:portb.rb3=1, delay_ms(500), portb.rb3=0;
  }
  switch(fin)
  {
   case 1: portb.rb5=1, delay_ms(500), portb.rb5=0;break;
   case 2: portb.rb4=1, delay_ms(500), portb.rb4=0;break;
   default:portb.rb3=1, delay_ms(500), portb.rb3=0;
  }
 }
 else
 {
  mover(n-1, ini, fin, aux);
  switch(ini)
  {
   case 1: portb.rb5=1, delay_ms(500), portb.rb5=0;break;
   case 2: portb.rb4=1, delay_ms(500), portb.rb4=0;break;
   default:portb.rb3=1, delay_ms(500), portb.rb3=0;
  }
  switch(fin)
  {
   case 1: portb.rb5=1, delay_ms(500), portb.rb5=0;break;
   case 2: portb.rb4=1, delay_ms(500), portb.rb4=0;break;
   default:portb.rb3=1, delay_ms(500), portb.rb3=0;
  }
  mover(n-1, aux, ini, fin);
 }
}

void main()
{
 trisb=0;
 portb=0;
 while(1)
 {
  mover(3, 1, 2, 3);
 }
}
Definitivamente una solución recursiva no esta dando buenos resultados, el problema, como indicaban Ardogan, y Dr. Zoidberg, creo que tiene que ver con la memoria (RAM).

Como comenté anteriormente, la solución iterativa que encontré usa operadores de bits, realizando algunas mejoras y pruebas en C, queda el siguiente código:

PHP:
#include<stdio.h>
#include<stdlib.h>

main()
{
 short n, t, i;
 system("CLS");
 printf("Numero de discos: ");
 scanf("%d", &n);
 t=(1<<n)-1;
 printf("Numero de movimientos: %d\nMOVIMIENTOS:\n", t);
 for(i=1;i<=t;i++)
  printf("%d°:\tMover de la torre %d a la torre %d\n", i, ((i&i-1)%3)+1, (((i|i-1)+1)%3)+1);
 system("PAUSE");
}
Explicación:

Como indicaba, utiliza el manejo de bits, estos operadores convierten una cantidad a su equivalente en bits (número binario) y después realiza un desplazamiento, puede ser a la izquierda, a la derecha, según se indique. Entonces:

t=(1<<n)-1;

Quiere decir: “almacenar en “t” la diferencia entre: el desplazamiento a la izquierda de 1, n veces; y 1”.

El equivalente de 1 a bits es “…00000001”; el operador “<<” indica desplazar a la izquierda; por tanto, como n=3, desplazaremos “…00000001”, 3 veces a la izquierda.

Desplazar a la izquierda significa recorrer hacia la izquierda los bits e insertar nuevos bits ceros a derecha, es decir:

Bits a ser desplazados : 00000001

1° desplazamiento : 00000010
2° desplazamiento : 00000100
3° desplazamiento : 00001000

Se realizaron tres desplazamientos hacia la izquierda, el equivalente en base diez de “00001000” es “8”. Luego almacenamos en “t” la diferencia entre “8” y “1”

t = 8 – 1
t = 7

Esto quiere decir que para tres discos se realizarán siete movimientos.

((i&i-1)%3)+1

En este caso se emplea el operador AND para el manejo de bits.

(((i|i-1)+1)%3)+1)

En este caso se emplea el operador OR para el manejo de bits.

(Si se necesita la explicación de cual es el funcionamiento de los operadores AND y OR en el manejo de bits, estoy presto a hacerlo).

Realicé una prueba de escritorio y al ver que el programa responde correctamente, adapté el mismo a MikroC, ahora el nuevo código es el siguiente:

PHP:
#define T 250

void main()
{
 short n, i, ini, fin;
 trisb=0;
 portb=0;
 n=3;
 while(1)
 {
  if(portb.rb0==1)
  {
   for(i=1;i<(1<<n);i++)
   {
    ini=(i&i-1)%3;
    fin=((i|i-1)+1)%3;
    switch(ini)
    {
     case 0: portb.rb4=1, delay_ms(T), portb.rb4=0, delay_ms(T); break;
     case 1: portb.rb3=1, delay_ms(T), portb.rb3=0, delay_ms(T); break;
     case 2: portb.rb2=1, delay_ms(T), portb.rb2=0, delay_ms(T);
    }
    switch(fin)
    {
     case 0: portb.rb4=1, delay_ms(T), portb.rb4=0, delay_ms(T); break;
     case 1: portb.rb3=1, delay_ms(T), portb.rb3=0, delay_ms(T); break;
     case 2: portb.rb2=1, delay_ms(T), portb.rb2=0, delay_ms(T);
    }
   }
   portb.rb1=1, delay_ms(T), portb.rb1=0, delay_ms(T);
   portb=0;
  }
 }
}
Y la simulación en Proteus es el siguiente (estoy estudiando como adjuntar una imagen o un archivo adjunto a este mensaje)

Por ahora este avance nos muestra a través de los led’s, los movimientos que tenemos que realizar; es decir: “mover de la torre X a la torre Y”, el led “FIN” indica que ya se realizaron todos los movimientos.



Aquí adjunto el archivo Th2.rar, incluye:
-> thiter.cpp (programa en C)
-> ths2 (simulación en Ptoteus 8.4)
-> thp2 (código en MicroC).

Por favor, cualquier sugerencia será bienvenida.
 

Adjuntos

  • th2.rar
    195.5 KB · Visitas: 4
Última edición por un moderador:
ok TRILO-BYTE, en c tengo resuelto ese problema de los delay, sin embargo en MikroC no conozco aún otra alternativa, se maneja delay_ms(Tiempo) para milisegundos y delay_us(tiempo) para microsegundos, o son los que tuve oportunidad de manejar. En MikroC no se me presento problemas en manejarlo. Pero si hay otra alternativa me gustaría mucho hacer la prueba. Muchas gracias por la recomendación.
 
mm si hay varias alternativas

puedes usar un timer por ejemplo

un ejemplo ilustrativo que te puedo hacer es el siguiente:

se va a reescribir un mensaje en una LCD cada 1 segundo la manera de hacerlo con delay es:
while(1)
{
printf(put_lcd,"reescribir 1 segundo %d",i);
i++;
delay_ms(1000);

}

como se ve en el ejemplo reescribo cada 1 segundo un valor de incremento pero quiero no usar delay

para eso hay que hacer una interrupcion por timer digamos que hacemos una interrupciuon de timer cada 1ms

//interrupcion de timer
{
incremento++;
}


el incremento se incrementara cada 1ms

ahora en el main podemos redibujar la LCD cada 1 segundo asi:

while(1)
{
if(incremento>=1000)
{
incremento=0;
printf(put_lcd,"reescribir 1 segundo %d", i);
i++;
}

}

lo que hago con el if es preguntas si lla pasaron 1000 ms si es asi reseteo el contador y reescribo en la LCD

es el principio basico de un sistema multitarea y asi ya uno no recurre a codigos espagueti, codigos basura y uno se deja de ver obligado a usar RTOS
 
Interesante, voy a hacer pruebas por interrupción de timer. La verdad no había pensado en esta opción. Pero para eso están las pruebas. Nuevamente muchas gracias y ya te iré contando como me fue.
 
Atrás
Arriba