Si has llegado aquí desde fuera del foro " aquihayapuntes.com ", has de saber que el contenido de esta página forma parte de un foro externo a esta web. Para ver la información original haz clic en el siguiente enlace: http://aquihayapuntes.com/foro/viewtopic.php?f=23&t=215&hilit=nrf24l01&start=120
Una breve introducción:
Para comenzar avisar de que el código no es sencillo de entender, hay muchos conceptos con cierta complejidad de por medio. Como por ejemplo que uso un encoder absoluto, sin embargo, también lo uso como incremental, es decir, que cada 256 posiciones, si roto otra en sentido horario o anti-horario, hay un contador que incrementa o decrementa. Eso simplifica el control del motor de pasos y otras cuestiones para gestionar todo el programa.
El emisor envía un número muy grande (que representa las posibles vueltas que puede dar la veleta) y tengo puesto que el punto inicial sea la vuelta 5000. Por qué 5000? porque podrá ir la veleta 5000 vueltas hacia delante o 5000 hacia atrás. Tuve que hacer este truco para poder simplificar el tema de cuando la veleta pasa de 0º a 359º y viceversa sin que el motor de pasos diera toda la vuelta hacia atrás. Además, así es más sencillo trabajar con el motor de pasos. Qué ocurre si la veleta hipotéticamente da más de 5000 vueltas hacia delante o hacia atrás? Lo tengo programado para que tanto el emisor como el receptor se auto-resetee automáticamente, sólo dura 3 segundos como mucho y vuelve a la normalidad.
Tiempo después caí en la cuenta de un detalle muy importante y que hubiese sido un engorro no haberlo tenido presente. La veleta toma en el tiempo un número de vueltas hacia delante o hacia atrás. Como funcionará a pilas o batería tendría una alimentación independiente del receptor. Si por lo que sea el receptor se queda sin corriente, el emisor seguiría con un número de vueltas, pero el receptor, al volverlo a poner en marcha, entenderá que ha de dar todas las vueltas que le envía el emisor (la veleta). Entonces se pondría a dar vueltas y vueltas hasta alcanzar esa posición y podría pasar muchos minutos o quizás horas, hasta completarlas. Por ello hay en ciertos momentos comunicación bidireccional, para darse en ciertos casos la orden de resetarse mútuamente. Valor 170 para el emisor, y 127 para el receptor. Son valores arbitrarios, la cosa es ponerse de acuerdo.
Me encontré que no se puede declarar matrices (arrays) mayores de 15 niveles o posiciones, entonces tuve que declarar varias matrices independientes. Con ellas hago un registro de desplazamiento en el código del receptor. Cada valor que entra nuevo desplaza a los siguiente, eliminando el más antiguo. Después calcula la media de todos los valores, de las 3 matrices. No declaré más matrices porque no quedaba más memoria, así que usé el máximo posible.
El emisor envía información cada cierto tiempo (un tiempo muy corto).
El programa está reciente, sé que se puede optimizar más. Por ejemplo, uso variables de 32bits cuando podrían ser de 16, pero de momento queda así por cosas que quiero hacer en el futuro y necesitaré esa resolución.
Emisor:
#include <16F876A.h>
#FUSES NOWDT, XT, PUT, NOPROTECT, NODEBUG, NOBROWNOUT, NOLVP, NOCPD, NOWRT
#use delay(clock=4000000)
#zero_ram
#include "lib_rf2gh4_10.h" // Librería modificada para el hardware que uso.
#byte porta=0x05
#byte portb=0x06
#use fast_io(a)
#use fast_io(b)
#use fast_io(c)
int8 aux1, aux2, ret1, ret2, t, d[8];
int32 x=0, y=5000, temp=0;
#bit a0=porta.0 // Esto es un truco para poder usar la interrupción RB0/INT, y poder usar PORTB como entrada de datos.
#bit x0=aux1.0 // x0 será luego sustituido por el valor de a0.
#int_ext // Interrupción del módulo RF.
void int_externo()
{
ret1 = RF_RECEIVE();
if ( (ret1 == 0) || (ret1 == 1) ) // Tanto si es entrada simple o múltiple de datos, lee
los datos.
{
do
{
for (t=0; t<8; t++)
{
d[t]=RF_DATA[t];
}
ret1 = RF_RECEIVE();
} while ( (ret1 == 0) || (ret1 == 1) );
if (d[0]==170) { reset_cpu(); }
}
}
void main()
{
set_tris_a(0b111101);
set_tris_b(0b11111111);
RF_INT_EN(); // Habilitar interrupción RB0/INT.
RF_CONFIG_SPI(); // Configurar módulos SPI del PIC.
RF_CONFIG(0x40,0x01); // Configurar módulo RF (canal y dirección).
RF_ON(); // Activar el módulo RF.
while(true)
{
aux2=aux1;
aux1=portb; x0=a0;
if ( (aux1<64) && (aux2>200) ) {y++;} // Cada vuelta completa del encoder, es una vuelta
más para el contador incremental.
if ( (aux1>200) && (aux2<64) ) {y--;} // Uso un truco para 'darse cuenta' que ha pasado de
0 a 359 grados y viceversa con
// amplio margen.
if (temp>700) // Cada 700 bucles envía información del encoder.
{
temp=0;
RF_DATA[5] = 0;
if ( (y<2) || (y>10000) ) { RF_DATA[5] = 127; y=5000; } // Si supera 5000 posiciones a la
derecha o a la izquierza toma valores
// iniciales, y ordena al receptor
resetarse con el código 127.
x=(y<<8)+(int32)aux1; // Reconstruye la información absoluta e incremental
como un solo número (int32).
RF_DATA[0] = make8(x, 0); // carga el valor (int32, 4 bytes) que van a ser
enviados.
RF_DATA[1] = make8(x, 1);
RF_DATA[2] = make8(x, 2);
RF_DATA[3] = make8(x, 3);
RF_DATA[4] = 0;
RF_DATA[6] = 0;
RF_DATA[7] = 0;
RF_DIR=0x08;
ret2=RF_SEND(); // Envía los datos. No necesita comprobar si se ha
enviado o no.
}
temp++;
}
}
Receptor:
#include <16F876A.h>
#FUSES NOWDT, XT, PUT, NOPROTECT, NODEBUG, NOBROWNOUT, NOLVP, NOCPD, NOWRT
#use delay(clock=4000000)
#include "lib_rf2gh4_10.h" // Librería modificada para el hardware que uso.
#include "flex_lcd.c" // Librería especial y modificada para usar el LCD por el puerto RA del PIC.
#byte porta=0x05
#byte portb=0x06
#zero_ram
int32 nn[15], mm[15], oo[15], d[4], rad=0, tot=0, num=0, pos=1280000;
int16 med;
int8 ang=0, cc=0, cont=0, o=0, k=0, h, ret1, ret2;
int1 flag=0;
#int_ext // Interrupción del módulo RF.
void int_externo()
{
int8 t;
ret1 = RF_RECEIVE();
if ( (ret1 == 0) || (ret1 == 1) )
{
do
{
for (t=0; t<4; t++)
{
d[t]=RF_DATA[t];
}
ret1 = RF_RECEIVE();
} while ( (ret1 == 0) || (ret1 == 1) );
flag=1;
}
}
void paso() // Esta rutina se encarga del motor de pasos (Funciona con semi-paso, el doble de resolución).
{
if (k>8) { k=1; }
if (k==0) { k=8; }
if (k==1) { o=9; }
if (k==2) { o=1; }
if (k==3) { o=3; }
if (k==4) { o=2; }
if (k==5) { o=6; }
if (k==6) { o=4; }
if (k==7) { o=12;}
if (k==8) { o=8; }
portb=(o<<4); // Los bits b7, b6, b5 y b4 controlan el motor de pasos, por eso se desplaza 4 veces.
delay_ms(4); // Tiempo de pausa entre un paso y el siguiente.
}
void main() //Programa principal
{
set_tris_b(0b00001111); // Motor PaP y RB0/INT.
portb=0;
RF_INT_EN(); // Habilitar interrupción RB0/INT.
RF_CONFIG_SPI(); // Configurar módulos SPI del PIC.
RF_CONFIG(0x40,0x08); // Configurar módulo RF (canal y dirección).
RF_ON(); // Activar el módulo RF.
//delay_ms(10);
lcd_init();
lcd_putc("\f"); // Borra el contenido visual del LCD.
lcd_gotoxy(1,1); printf(lcd_putc, "Bienvenido."); // Al comienzo veremos un saludo.
delay_ms (1500);
lcd_putc("\f");
lcd_gotoxy(4,1); printf(lcd_putc, " Buscando ");
lcd_gotoxy(1,2); printf(lcd_putc, " posicion cero...");
#bit b3=portb.3 // RB3 es el bit del sensor Hall. Puede valer cualquier otro sensor que haga de interruptor digital.
while (b3==1) // Busca el punto cero.
{
k--;
paso();
}
while (b3==0) // Hago que el motor de pasos se desplace hacia atrás 3 veces seguidas para asegurarme la posición cero
{ // y no quede en un espacio intermedio. Dos veces no es suficiente porque existe un 'punto ciego'.
k--;
paso();
}
while (b3==1)
{
k--;
paso();
}
tot=pos;
for (h=0; h<15; h++) // Cargar todas las variables con el valor inicial.
{
nn[h]=pos;
mm[h]=pos;
oo[h]=pos;
}
lcd_putc("\f");
lcd_gotoxy(1,1); printf(lcd_putc, "Emisor fuera"); // Si hay emisor este mensaje no llega a verse.
lcd_gotoxy(1,2); printf(lcd_putc, "de servicio. "); // Pero si no lo hay, sí se lee por el 'While' de más abajo.
ret2=255;
while ( ret2>0 )
{
RF_DIR=0x01; // Mientras no haya emisor le envía el código de resetearse (el emisor) para cuando lo haya,
RF_DATA[0]=170; // con el código 170. Esto es un acuerdo, puede ser cualquier otro número.
ret2=RF_SEND();
}
lcd_putc("\f"); // Borra pantalla.
flag=0;
med=25000;
while(true) // Comienzo del programa principal.
{
if (flag==1) // Si ha llegados datos, entonces.... (La variable 'flag' se activa en la interrupción.)
{
flag=0;
num=((int32)d[0])+((int32)d[1]<<8)+((int32)d[2]<<16)+((int32)d[3]<<24); // reconstruye el valor de posición recibido.
for (cc=0; cc<14; cc++)
{
nn[cc]=nn[cc+1]; // Como no se puede crear un array de 45, he usado 3 arrays de 15 posiciones.
} // Cada vez que llega una posición nueva, se deplaza todos los valores una posición para
nn[14]=mm[0]; // luego hacer la media de todos ellos.
for (cc=0; cc<14; cc++)
{
mm[cc]=mm[cc+1];
}
mm[14]=oo[0];
for (cc=0; cc<14; cc++)
{
oo[cc]=oo[cc+1];
}
oo[14]=num;
tot=0;
for (cont=0; cont<15; cont++)
{
tot+=nn[cont]+mm[cont]+oo[cont]; // Suma las 45 posiciones.
}
tot/=45; // Aquí obtenemos el valor medio de todas las posiciones. ( tot=tot/45 )
}
else // Si no hay entrada nueva de datos, representar los datos en el display.
{
if (med>15000) // Se representa cada 15000 bucles que pase por este 'else').
{
med=0; // Cumplido el número de ciclo lo ponemos a cero.
rad=tot%256; // Un truco para extraer el ángulo como si fuese el propio valor del encoder absoluto (0..255).
rad=(rad*141)/100; // Hace la equivalencia de valor binario a ángulo (360º).
lcd_gotoxy(1,2);
if (rad>180) // Representa en la LCD el valor de 0 a 180 pudiendo ser del lado derecho o izquierdo de la veleta.
{
lcd_gotoxy(5,1);
printf(lcd_putc, " %3lu%c ", (360-rad), 223); // 223 es el código ASCII de º.
lcd_gotoxy(1,2);
printf(lcd_putc, " Babor. ");
}
else
{
lcd_gotoxy(5,1);
printf(lcd_putc, " %3lu%c ", rad, 223);
lcd_gotoxy(1,2);
printf(lcd_putc, " Estribor. ");
}
}
med++;
}
while (tot!=pos) // mueve el motor a la posición especificada de la media de las 45 posiciones.
{
if (pos>tot) { k--; pos--; } // 'pos' es la -posición actual- del motor y 'tot' a la que queremos que llegue.
if (pos<tot) { k++; pos++; } // 'k' ha de ir a la par de pos, pero es tratada en la sub-rutina paso().
paso(); // Cuando vaya a esa sub-rutina le dirá al motor que vaya un paso hacia delante o atrás
} // hasta completar el recorrido.
}
}