Buenas a todos!
Quería compartir con ustedes la librería que he usado para mi último proyecto con nrf24L01.
Recientemente he acabado un proyecto de riego automático para un jardín. Este riego automático enciende y apaga una bomba de pozo a distancia, y para comunicar ambos pic he usado como transceptor el nrf24l01. A lo largo del tiempo he venido usando un par de librerías, las dos con sus ventajas y desventajas, pero nunca me convenció completamente ninguna de ellas. Adjunto ambas librerías para referencias. La librería "nrf24L01" la conseguí hace años en el foro de CCS, y la llamada "nrf24L01_bizintek" es una librería open source perteneciente a esa misma empresa. Para más referencias, consultad:
https://sites.google.com/site/proyectosroboticos/nrf24l01/16f876-nrf24l01Así que basándome en estas dos librerías he creado la mía propia. La que conseguí en el foro de CCS es muy simple pero muy configurable, aunque no incluye ACK. La de bizintek va muy bien, pero tiene demasiadas instrucciones, por lo que llena la RAM, tanto que se hace dificil de usar en un pic pequeño.
Aquí está mi aportación, nrf24L01_remix.c
/**************** Preprocessor Constants ***************************************/
#define CONFIG 0x20
#define EN_AA 0x21
#define EN_RXADDR 0x22
#define SETUP_AW 0x23
#define SETUP_RETR 0x24
#define RF_CH 0x25
#define RF_SETUP 0x26
#define STATUS 0x27
#define RX_ADDR_P0 0x2A
#define TX_ADDR 0x30
#define RX_PW_P0 0x31
#define RX_PW_P1 0x32
#define RX_PW_P2 0x33
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define TX_PAYLOAD 0xA0
#define RX_PAYLOAD 0x61
#define NO_OP 0xFF
#define ZERO 0x00
#define EOT 0x04
#define PAYLOAD_SIZE 0x20
//*****************
//* VARIABLES *
//*****************
#BYTE INTCON = 0x0B
//Variables internas
//static int INTCON=0x0B;
static int1 interRF;
static int16 noRF;
static int1 RCVNW=0;
static int8 DATA_N_SND=0;
static int8 DATA_N_RCV=0;
static int16 RF_CE,RF_CS,RF_IRQ;
//Variables configurables
static int8 RF_DATA[8];
static int8 RF_DIR;
//**************
//* CÓDIGO *
//**************
/**************** Write To NRF24L01 Configuration Register *********************/
void set_register(unsigned int command,unsigned int data)
{
output_low(RF_CS); // Engage SPI chip select
spi_write(command); // Write to register
if(data!=NO_OP){spi_write(data);} // Write data to register
output_high(RF_CS); // Dis-engage SPI chip select
delay_us(10);
}
//*****************************************************
//* RF_CONFIG_PINES() *
//*****************************************************
//*****************************************************
void RF_CONFIG_PINES(int16 CE, int16 CS, int16 IRQ)
{
RF_CE=CE;
RF_CS=CS;
RF_IRQ=IRQ;
}
//*****************************************************
//*****************************************************
//* RF_CONFIG(int canal,int dir,boolean Mbps,int dBm) *
//*****************************************************
//*Descripción:Esta función se encarga de configurar *
//*el transceptor habilitando su propia dirección de *
//*escucha y el canal entre otros parámetros. *
//*****************************************************
//*Variables de entrada:- Canal *
//* - Direccion *
//* - Velocidad de transmisión *
//* - Potencia *
//*Variables de salida: *
//*****************************************************
void RF_CONFIG(int canal, int dir, unsigned boolean Mbps, unsigned int dBm)
{
output_low(RF_CE);
// TX_ADDR (0xFF)
//Configuración de la dirección de envio aleatoria.
//En la función de enviar se configura la direccion
//deseada por el usuario.
output_low(RF_CS);
spi_write(0x30);
spi_write(0xFF);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
output_high(RF_CS);
// RX_ADDR_P0 (0xFF) ACK
//Configuración de la direccióndel Pipe0 para la
//recepción de ACK.
output_low(RF_CS);
spi_write(0x2A);
spi_write(0xFF);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
output_high(RF_CS);
// RX_ADDR_P1 (dir)
//Configuración de la direccióndel Pipe1 para la
//recepción de tramas.
output_low(RF_CS);
spi_write(0x2B);
spi_write(dir);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
output_high(RF_CS);
// RX_ADDR_P2 (0x00) BROADCAST
//Configuración de la direccióndel Pipe2 para la
//recepción de tramas
set_register(0x2C,0x00);
// EN_AA
//Habilitar AutoAck en los Pipe0,Pipe1 y Pipe2.
set_register(EN_AA,0x07);
// EN_RXADDR
//Habilitar los Pipe0,Pipe1 y Pipe2.
set_register(EN_RXADDR,0x07);
// SETUP_AW
//Configuración de la longitud de las direcciones.
//Direcciones de 5 bytes.
set_register(SETUP_AW,0x03);
//SETUP_RETR
//Configuración de las retrasmisiones en la transmisión.
//Diez retransmisiones cada 336us.
set_register(SETUP_RETR,0x0A);
//RF_CH
//Configuración del canal.
//Canal elegido por el usuario (0x01 - 0x7F).
set_register(RF_CH,canal);
//RF_SETUP
//Configuración aspectos RF.
//Ganancia máxima de LNA, 0dBm potencia de salida y 1Mbps de velocidad.
// set_register(RF_SETUP,0x07);
set_register(RF_SETUP,(Mbps<<3)|(dBm<<1)|0x01); // Setea velocidad y nivel de potencia. Velocidad puede ser 250kbs(0), 1mbps(1) o 2mbps(2). Potencia puede ser 0dbm(3, el más potente), -6dbm(2), -12dbm(1), -18dbm(0)
//STATUS
//Reseteo del registro STATUS
set_register(STATUS,0x70);
//RX_PW_P0
//Nº de bytes en Pipe0.
//1 byte (ACK).
set_register(RX_PW_P0,0x01);
//RX_PW_P1
//Nº de bytes en Pipe1.
//10 byte (Direccion emisor y trama).
set_register(RX_PW_P1,0x0A);
//RX_PW_P2
//Nº de bytes en Pipe2.
//10 byte (Direccion emisor y trama).
set_register(RX_PW_P2,0x0A);
}
//*****************************************************
//*****************************************************
//* RF_ON() *
//*****************************************************
//*Descripción:Esta rutina activa el módulo de *
//*radiofrecuencia en modo escucha para poder recibir *
//*datos enviados a su dirección. *
//*****************************************************
//*Variables de entrada: *
//*Variables de salida: *
//*****************************************************
void RF_ON()
{
output_low(RF_CE);
// CONFIG
//Se activa el modulo, se pone en recepción,
//se activa el CRC para que utilice 2 bytes.
set_register(CONFIG,0x0F);
delay_ms(2);
output_high(RF_CE);
delay_us(150);
}
//*****************************************************
//*****************************************************
//* RF_OFF() *
//*****************************************************
//*Descripción:Este procedimiento desactiva el módulo *
//*de radiofrecuencia. *
//*****************************************************
//*Variables de entrada: *
//*Variables de salida: *
//*****************************************************
void RF_OFF()
{
output_low(RF_CE);
// CONFIG
//Se desactiva el modulo
set_register(CONFIG,0x0C);
}
//*****************************************************
//*****************************************************
//* RF_SEND() *
//*****************************************************
//*Descripción:Esta función envía 8 Bytes de datos a *
//*la dirección indicada informando de la correcta *
//*recepción en el destinatario. *
//*****************************************************
//*Variables de entrada:- RF_DATA[] *
//* - RF_DIR
//*Variables de salida: - *
//*Salida: - 0: Envío correcto (ACK OK) *
//* - 1: No recepcibido (NO ACK) *
//* - 2: No enviado *
//*****************************************************
int RF_SEND()
{
int i;
int estado;
if(bit_test(INTCON,7))
interRF=1;
else
interRF=0;
// disable_interrupts(GLOBAL); //Deshabilitar las interrupciones arruina el funcionamiento del control de riego
// INICIO
output_low(RF_CE);
//STATUS
//Reseteo del registro STATUS
set_register(STATUS,0x70);
// EN_RXADDR
//Se habilita el Pipe0 para la recepción del ACK
set_register(EN_RXADDR,0x01);
// TX_ADDR
//Se configura la dirección de transmisión=RF_DIR
output_low(RF_CS);
spi_write(0x30);
spi_write(RF_DIR);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
output_high(RF_CS);
// RX_ADDR_P0
//Para la recepción del ACK se debe configurar el Pipe0 con
//la misma dirección a trasmitir.
output_low(RF_CS);
spi_write(0x2A);
spi_write(RF_DIR);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
spi_write(0xC2);
output_high(RF_CS);
// RX_ADDR_P1
//Se mete en RF_DIR la direccion propia.
//De esta manera el receptor sabe la dirección
//del transmisor.
output_low(RF_CS);
spi_write(0x0B);
RF_DIR=spi_read(0);
spi_read(0);
spi_read(0);
spi_read(0);
spi_read(0);
output_high(RF_CS);
// W_TX_PAYLOAD
//Se manda los datos al transductor
output_low(RF_CS);
spi_write(0xA0);
DATA_N_SND++;
spi_write(DATA_N_SND);
spi_write(RF_DIR);
for (i=0;i<8;i++)
spi_write(RF_DATA[i]);
output_high(RF_CS);
// CONFIG
//Se pasa a modo transmisión.
set_register(CONFIG,0x0E);
// Pulso de comienzo de envío
output_high(RF_CE);
delay_us(15);
output_low(RF_CE);
noRF=0;
while (input(RF_IRQ)==1) {
noRF++;
//Si no da respuesta en 7ms, no se ha enviado.
if(noRF==500){
break;
}
}
// STATUS
//Lectura del estado en el registro estatus.
output_low(RF_CS);
estado=spi_read(0x27);
spi_write(0x70);
output_high(RF_CS);
// EN_RXADDR
//Habilitar los Pipe0,Pipe1 y Pipe2.
set_register(EN_RXADDR,0x07);
// TX_FLUSH
//Limpieza de la FIFO de salida
output_low(RF_CS);
spi_write(0xE1);
output_high(RF_CS);
// CONFIG
//Paso a modo recepción
set_register(CONFIG,0x0F);
// FIN
output_high(RF_CE);
delay_us(150);
//Si no da respuesta en 7ms, no se ha enviado.
if(noRF==500){
if(interRF==1)
enable_interrupts(GLOBAL);
clear_interrupt(int_ext);
return(2);
}
//estado
//Chequeo de los bit del registro STATUS que indican si se ha recibido
//ACK y si se ha terminado las retrasmisiones sin ningun ACK.
if ((bit_test(estado,4)==0) && (bit_test(estado,5)==1)){
if(interRF==1)
enable_interrupts(GLOBAL);
clear_interrupt(int_ext);
return(0);
}
else{
if(interRF==1)
enable_interrupts(GLOBAL);
clear_interrupt(int_ext);
return(1);
}
}
//*****************************************************
//*****************************************************
//* RF_RECEIVE() *
//*****************************************************
//*Descripción: Esta rutina se encarga de comprobar si*
//*se ha producido una recepción y de ser así, *
//*devuelve la trama recibida. *
//*****************************************************
//*Variables de entrada:- *
//*Variables de salida: - RF_DATA[] *
//* - RF_DIR *
//*Salida: - 0: Recepción correcta y única *
//* - 1: Recepción correcta y múltiple *
//* - 2: No se ha producido recepción *
//* - 3: No se ha producido recepción *
//*****************************************************
int RF_RECEIVE()
{
int i;
int mas;
int estado;
if (input(RF_IRQ)==1 && RCVNW==0){
return (2);
}
//STATUS
//Lectura y reseteo del registro STATUS
output_low(RF_CS);
estado=spi_read(0x27);
spi_write(0x70);
output_high(RF_CS);
//estado
//Chequeo de la interrupción de recepción.
if (bit_test(estado,6)==0 && RCVNW==0){
return(3);
}
//R_RX_PAYLOAD
//Lectura de los datos recibidos.
output_low(RF_CS);
spi_write(0x61);
DATA_N_RCV=spi_read(0);
RF_DIR=spi_read(0);
for (i=0;i<8;i++)
{
RF_DATA[i]=spi_read(0);
}
output_high(RF_CS);
//FIFO_STATUS
//Comprobación del estado de la FIFO de
//recepción para comprobar si hay más datos
output_low(RF_CS);
spi_write(0x17);
mas=spi_read(0);
output_high(RF_CS);
if (bit_test(mas,0)==0){
RCVNW=1;
return(1);
}
RCVNW=0;
return(0);
}
//*****************************************************
También incluyo el código de un comprobador de RF, que lo que hace es mandar un byte cualquiera a ambos pic y comprueba si se recibe el ack, encendiendo unos led cuando existe conexión. Aquí podeis ver cómo configuro yo toda la vaina:
#include <16F88.h>
#device PASS_STRINGS=IN_RAM
#device ADC=10
#FUSES NOMCLR //Master Clear pin used for I/O
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOIESO //Internal External Switch Over mode disabled
#use delay(internal=4000000)
#include <nrf24L01_remix.c>
#define boton PIN_A6 //Boton para mandar ACK
#define led1 PIN_A1
#define led2 PIN_A0
#define testigo PIN_A2
//Variables RF
const int Mbps = 0; //Velocidad. 250kbs(0), 1mbps(1) o 2mbps(2)
const int dBm = 3; //Potencia. 0dbm(3, el más potente), -6dbm(2), -12dbm(1), -18dbm(0)
const int canal = 0x64; //Canal de comunicación (aquí, canal 100). Tiene que ser el mismo para todos los pic en comunicación. Canales posibles: 0-127 (0x00-7F)
const int direccionPropia=0x20; //Dirección de este PIC.
const int direccionBomba=0x10; //Dirección del PIC conectado a la bomba de agua.
const int direccionRiego=0x11; //Dirección del PIC conectado al riego
const int ack=0x0C; //Variable sin valor real, solo para probar ACK
unsigned int* entradaPaquete; //Paquete que entra, 8 bytes máximo.
int1 flagEntrada=0; //Flag que indica que hay un paquete listo para leer. No lo uso en este código
/*
Se envia la orden de on/off a la bomba de agua. Solo se envia un byte cada vez.
Salida: - 0: Envío correcto (ACK OK)
- 1: No recepcibido (NO ACK)
- 2: No enviado
*/
int enviarPaquete(int direccionReceptor,int mensaje) {
int ret2;
RF_DATA[0]=mensaje;
RF_DIR=direccionReceptor;
ret2=RF_SEND();
return(ret2);
}
#INT_TIMER0
void TIMER0_isr(void)
{
static int1 flagBoton=0; //Flag para evitar código redundante
int recepcion=5; //Reseteo la variable a un valor no usado
if(!input(boton)&&flagBoton==0) { //Pulso el botón
RF_ON();
delay_ms(3);
flagBoton=1;
}
if(!input(boton)) { //GND activa el boton
recepcion=enviarPaquete(direccionBomba,ack);
if(recepcion==0) //Recepción correcta
output_high(led1);
else
output_low(led1);
delay_ms(5);
recepcion=enviarPaquete(direccionRiego,ack);
if(recepcion==0)
output_high(led2);
else
output_low(led2);
}
if(input(boton)&&flagBoton==1) { //Dejo de pulsar el botón
RF_OFF(); //Apago transceptor
delay_ms(3);
output_low(led1);
output_low(led2);
flagBoton=0;
}
}
/*Interrupción usada para la recepción RF
ret1: - 0: Recepción correcta y única (una tabla, de 8 bytes)
- 1: Recepción correcta y múltiple (máximo 3 tablas seguidas), no lo uso
- 2: No se ha producido recepción
- 3: No se ha producido recepción
*/
#INT_EXT
void EXT_isr(void)
{
int8 ret1;
ret1 = RF_RECEIVE(); //Recibo la primera tabla. Sólo voy a recibir una, y con sólo un byte
if(ret1 == 0)
{
entradaPaquete=RF_DATA; //Sólo voy a usar RF_DATA[0], tenlo en cuenta. Por cierto, esto es un puntero
flagEntrada=1;
}
}
void main()
{
disable_interrupts(GLOBAL);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256|RTCC_8_bit); //65,5 ms overflow
enable_interrupts(INT_TIMER0);
//Configuración RF
enable_interrupts(INT_EXT);
ext_int_edge( H_TO_L );
RF_CONFIG_PINES(PIN_B6,PIN_B7,PIN_B0); //CE,CS,IRQ
setup_spi(SPI_MASTER|SPI_L_TO_H|SPI_XMIT_L_TO_H
|SPI_CLK_DIV_4|SPI_SAMPLE_AT_END);
delay_ms(100); //Voy a dejar tiempo de sobra para que el módulo RF se encienda y esté preparado
RF_CONFIG(canal,direccionPropia,Mbps,dBm);
RF_OFF(); //Apago el transceptor hasta que haga falta
delay_ms(3);
enable_interrupts(GLOBAL);
output_low(led1);
output_low(led2);
while(TRUE)
{
output_high(testigo);
delay_ms(500);
output_low(testigo);
delay_ms(500);
}
}
En el código de ejemplo podéis ver que uso RF_ON() y RF_OFF(), esto enciende y apaga el transceptor y ayuda a ahorrar batería, pero he notado que en algunos proyectos provoca comportamientos erráticos en el pic. Además, si teneis el pic en off no podreis recibir nada, claro está
. En caso de duda, usad RF_ON() y punto, no os compliquéis.
En definitiva, este sigue siendo un gran transceptor para mí, barato y potente. Espero os sirva. Un saludo!