Comunicación nRF24L01

Para explicar la comunicación inalámbrica haremos tres ejemplos, el primero será enviar un mensaje simple de «Hola mundo» de un Arduino a otro, en el segundo ejemplo tendremos una comunicación bidireccional entre las placas Arduino, donde usando el Joystick en el primer Arduino controlaremos el servomotor en el segundo Arduino, y viceversa, usando el botón pulsador en el segundo Arduino controlaremos el LED en el primer Arduino, y en un tercero veremos como enviar varias variables.

modulo-nrf24l01_bas

Módulo transceptor nRF24L01

Utiliza la banda de 2,4 GHz y puede operar con velocidades de transmisión desde 250 kbps hasta 2 Mbps. Si se usa en espacios abiertos y con una tasa de baudios más baja, su alcance puede alcanzar hasta 100 metros.

Especificaciones :

Frequency range 2.4 – 2.5GHz ISM band
Data rates 250Kbps / 1Mbps / 2Mbps
Max. output power 0dBm
Operating voltage 1.9 – 3.6V
Max. operating current 12.3mA
Standby current 22µA
Logic inputs 5V tolerant
Communication range 100m (open space)

Cómo funciona

El módulo puede usar 125 canales diferentes, lo que brinda la posibilidad de tener una red de 125 dispositivos que funcionan de forma independiente en un solo lugar. Cada canal puede tener hasta 6 direcciones, es decir, cada unidad puede comunicarse con hasta 6 otras unidades al mismo tiempo.

NRF24L01-Working-Principles-of-Channels-and-Addresses

El consumo de energía de este módulo es de alrededor de 12 mA durante la transmisión, que es incluso más bajo que un solo LED. El voltaje de operación del módulo es de 1.9 a 3.6V, pero lo bueno es que los otros pines toleran una lógica de 5V, por lo que podemos conectarlo fácilmente a un Arduino sin usar ningún convertidor de nivel lógico.

NRF24L01-Transceiver-Module-Pinouts-Connections

Tres de estos pines son para la comunicación SPI y deben conectarse a los pines SPI del Arduino, pero tenga en cuenta que cada placa Arduino tiene diferentes pines SPI. Los pines CSN y CE se pueden conectar a cualquier pin digital de la placa Arduino y se utilizan para configurar el módulo en modo de espera o activo, así como para cambiar entre modo de transmisión o comando. El último pin es un pin de interrupción que no tiene que usarse.

Various-modules-based-on-the-NRF24L01-chip-768x446

Hay varias versiones de los módulos NRF24L01. El más popular es el que tiene antena integrada. Esto hace que el módulo sea más compacto, pero por otro lado, reduce el rango de transmisión a una distancia de unos 100 metros.

La segunda variación, en lugar de antena integrada, tiene un conector SMA y al que podemos conectar una antena de pato para un mejor alcance de transmisión.

La tercera variación que se muestra aquí, además de la antena de pato, tiene un chip RFX2401C que incluye PA (amplificador de potencia) y LNA (amplificador de bajo ruido). Esto amplifica la señal NRF24L01 y permite un rango de transmisión aún mejor de hasta 1000 metros en espacios abiertos.

Cómo conectar el nRF24L01 a Arduino

Así es como necesitamos conectar los módulos NRF24L01 a las placas Arduino.

NRF24L01-and-Arduino-Tutorial-Circuit-Schematic-768x430

Como ya mencioné, cada placa Arduino tiene diferentes pines SPI, así que tenlo en cuenta cuando conectes los módulos a tu placa Arduino.

  • Ardunio UNO    SCK MISO MOSI SS 13 12 11 10
  • Ardunio NANO SCK MISO MOSI SS 13 12 11 10
  • Ardunio MEGA  SCK MISO MOSI SS 52 50 51 53

Código Arduino y nRF24L01

Una vez que conectamos los módulos NRF24L01 a las placas Arduino, estamos listos para hacer los códigos tanto para el transmisor como para el receptor.

Primero necesitamos descargar e instalar la librería RF24 que hace que la programación sea menos difícil. También podemos instalar esta librería directamente desde Arduino IDE Library Manager. Simplemente busca «rf24» y busca e instala el de «TMRh20, Avamander«.

Aquí están los dos códigos para la comunicación inalámbrica y debajo está la descripción de ellos.

Código del transmisor

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN
const byte direcccion[5] = "00001";

void setup() {
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
}

void loop() {
  const char text[] = "Hello World";
  radio.write(&text, sizeof(text));
  delay(1000);
}

Código del receptor

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN
const byte address[5] = "00001";

void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
  radio.startListening();
}

void loop() {
  if (radio.available()) {
    char text[32] = "";
    radio.read(&text, sizeof(text));
    Serial.println(text);
  }
}

Código Descripción

Debemos incluir el SPI básico y las bibliotecas RF24 recién instaladas y crear un objeto RF24. Los dos argumentos aquí son los pines CSN y CE.

RF24 radio(7, 8); // CE, CSN

A continuación, debemos crear un vector de bytes que representará la dirección, o el llamado conducto a través del cual se comunicarán los dos módulos.

const byte address[5] = «00001»;

Podemos cambiar el valor de esta dirección a cualquier cadena de 5 letras y esto permite elegir a qué receptor hablaremos, por lo que en nuestro caso tendremos la misma dirección tanto en el receptor como en el transmisor.

En la sección setup(), debemos inicializar el objeto de radio y, mediante la función radio.openWritingPipe(), configuramos la dirección del receptor al que enviaremos los datos, la cadena de 5 letras que configuramos previamente.

radio.begin();
radio.openReadingPipe(0, address);

Luego, usando la función radio.setPALevel(), establecemos el nivel del amplificador de potencia, en nuestro caso lo configuraré al mínimo ya que mis módulos están muy cerca uno del otro.

radio.setPALevel(RF24_PA_MIN);

IMPORTANTE: Ten en cuenta que si usa un nivel más alto, se recomienda usar condensadores de derivación a través de GND y 3,3 V de los módulos para que tengan un voltaje más estable durante el funcionamiento.

A continuación tenemos la función radio.stopListening() que configura el módulo como transmisor y, por otro lado, tenemos la función radio.startListening() que configura el módulo como receptor.

// at the Transmitter
radio.stopListening();

// at the Receiver
radio.startListening();

En la sección de loop(), en el transmisor, creamos String de caracteres a los que asignamos el mensaje «Hola mundo». Usando la función radio.write() enviaremos ese mensaje al receptor. El primer argumento aquí es la variable que queremos que se envíe.

void loop() {
    const char text[] = «Hello World»;
    radio.write(&text, sizeof(text));
    delay(1000);
}

Al usar el “&” antes del nombre de la variable, en realidad establecemos una indicación de la variable que almacena los datos que queremos enviar y, al usar el segundo argumento, establecemos la cantidad de bytes que queremos tomar de esa variable. En este caso, la función sizeof() obtiene todos los bytes de las cadenas «text». Al final del programa añadiremos 1 segundo de retraso.

Usando la función radio.write() podemos enviar un máximo de 32 bytes a la vez.

Por otro lado, en el receptor, en la sección loop() y usando la función radio.available() verificamos si hay datos para recibir. Si eso es cierto, primero creamos una cadena de 32 elementos, llamada «text», en la que guardaremos los datos entrantes.

void loop() {
    if (radio.available()) {
        char text[32] = «»;
        radio.read(&text, sizeof(text));
        Serial.println(text);
    }
}

Usando la función read() leemos y almacenamos los datos en la variable «text».

Al final solo imprimimos texto en el monitor serie. Una vez que cargamos ambos programas, podemos ejecutar el monitor en serie en el receptor y notaremos que el mensaje «Hello World» se imprime cada segundo.

Solución de problemas

Vale la pena señalar que el ruido de la fuente de alimentación es uno de los problemas más comunes que experimentan las personas cuando intentan establecer una comunicación exitosa con los módulos NRF24L01. En general, los circuitos de RF o las señales de radiofrecuencia son sensibles al ruido de la fuente de alimentación. Por lo tanto, siempre es una buena idea incluir un condensador de desacoplamiento en la línea de alimentación. El capacitor puede ser de 10uF a 100uF.

NRF24L01-Troubleshooting-decoupling-capacitor-and-external-power-supply

Otro problema común es que el pin de 3,3 V de las placas Arduino no siempre puede suministrar suficiente energía al módulo NRF24L01. Por lo tanto, alimentar el módulo con una fuente de alimentación externa también es una buena idea.

Comunicación inalámbrica bidireccional con dos NRF24L01 y Arduino

Veamos el segundo ejemplo, una comunicación inalámbrica bidireccional entre dos placas Arduino. Aquí está el esquema del circuito:

Arduino-Wireless-Communication-NRF24L01-Circuit-Schematic-Tutorial-768x458

Componentes :

  • NRF24L01 Transceiver Module
  • Arduino Board
  • Joystick Module
  • Servo Motor
  • Pushbutton
  • LED

Aquí están los dos códigos y su descripción a continuación.

Código del transmisor

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#define led 12

RF24 radio(7, 8); // CE, CSN
const byte addresses[][5] = {"00001", "00002"};
boolean buttonState = 0;

void setup() {
  pinMode(12, OUTPUT);
  radio.begin();
  radio.openWritingPipe(addresses[1]); // 00002
  radio.openReadingPipe(1, addresses[0]); // 00001
  radio.setPALevel(RF24_PA_MIN);
}

void loop() {
  delay(5);
  radio.stopListening();
  int potValue = analogRead(A0);
  int angleValue = map(potValue, 0, 1023, 0, 180);
  radio.write(&angleValue, sizeof(angleValue));

  delay(5);
  radio.startListening();
  while (!radio.available());
  radio.read(&buttonState, sizeof(buttonState));
  if (buttonState == HIGH) {
    digitalWrite(led, HIGH);
  }
  else {
    digitalWrite(led, LOW);
  }
}

Código del receptor

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

#define button 4

RF24 radio(7, 8); // CE, CSN
const byte addresses[][5] = {"00001", "00002"};
Servo myServo;
boolean buttonState = 0;

void setup() {
  pinMode(button, INPUT);
  myServo.attach(5);
  radio.begin();
  radio.openWritingPipe(addresses[0]); // 00001
  radio.openReadingPipe(1, addresses[1]); // 00002
  radio.setPALevel(RF24_PA_MIN);
}

void loop() {
  delay(5);
  radio.startListening();
  if ( radio.available()) {
    while (radio.available()) {
      int angleV = 0;
      radio.read(&angleV, sizeof(angleV));
      myServo.write(angleV);
    }
    delay(5);
    radio.stopListening();
    buttonState = digitalRead(button);
    radio.write(&buttonState, sizeof(buttonState));
  }
}

Lo que es diferente aquí del ejemplo anterior es que necesitamos crear dos conductos o direcciones para la comunicación bidireccional.

const byte addresses[][5] = {«00001», «00002»};

En la sección de setup(), debemos definir ambas tuberías y recordar que la dirección de escritura en el primer Arduino debe ser la dirección de lectura en el segundo Arduino, y viceversa.

// at the Transmitter
radio.openWritingPipe(addresses[1]); // 00001
radio.openReadingPipe(1, addresses[0]); // 00002

// at the Receiver
radio.openWritingPipe(addresses[0]); // 00002
radio.openReadingPipe(1, addresses[1]); // 00001

En la sección de loop() usando la función radio.stopListening() configuramos el primer Arduino como transmisor, leemos y mapeamos el valor del Joystick de 0 a 180, y usando la función radio.write() enviamos los datos al receptor.

radio.stopListening();
int potValue = analogRead(A0);
int angleValue = map(potValue, 0, 1023, 0, 180);
radio.write(&angleValue, sizeof(angleValue));

Por otro lado, usando la función radio.startListening() configuramos el segundo Arduino como receptor y verificamos si hay datos disponibles. Mientras haya datos disponibles, los leeremos, los guardaremos en la variable «angleV» y luego usaremos ese valor para rotar el servomotor.

radio.startListening();
if ( radio.available()) {
    while (radio.available()) {
        int angleV = 0;
        radio.read(&angleV, sizeof(angleV));
        myServo.write(angleV);
}

A continuación, en el transmisor, configuramos el primer Arduino como receptor y con un bucle «while» esperamos que el segundo Arduino envíe los datos,

delay(5);
radio.startListening();
while (!radio.available());
radio.read(&buttonState, sizeof(buttonState));
if (buttonState == HIGH) {
    digitalWrite(led, HIGH);
}
else {
    digitalWrite(led, LOW);
}

son los datos para el estado del botón, ya sea que esté presionado o no.

delay(5);
radio.stopListening();
buttonState = digitalRead(button);
radio.write(&buttonState, sizeof(buttonState));

Si se presiona el botón, el LED se encenderá.

Entonces, este proceso se repite constantemente y ambas placas Arduino envían y reciben datos constantemente.

Ejemplo 3 – Envío de múltiples variables en un solo paquete

Todo será igual que en los ejemplos anteriores, excepto por la forma en que estructuramos y enviamos la fecha.

Código del transmisor

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN

const byte address[5] = "00001";

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte a = 0;
  byte b = 125;
  byte c = 255;
  int d = 1024;
  float e = 3.141592;
  String f = "Test";
};

Data_Package data; // Create a variable with the above structure

void setup() {
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
}

void loop() {
  // Send the whole data from the structure to the receiver
  radio.write(&data, sizeof(Data_Package));
  delay(500);
}

Podemos crear una estructura que en realidad es una colección de varios tipos de variables.

// Max size of this struct is 32 bytes – NRF24L01 buffer limit
struct Data_Package {
byte a = 0;
byte b = 125;
byte c = 255;
int d = 1024;
float e = 3.141592;
String f = «Test»;
};

Data_Package data; // Create a variable with the above structure

Debemos tener en cuenta que el tamaño máximo de esta estructura de datos puede ser de 32 bytes. Aquí podemos ver que incluí tres variables tipo byte, una variable int (4 bytes), una variable float (4 bytes) y una cadena que contiene cuatro caracteres (4 bytes). Eso es un total de 15 bytes.

Código del receptor

Ejemplo 3 – Envío de múltiples variables en un solo paquete

Todo será igual que en los ejemplos anteriores, excepto por la forma en que estructuramos y enviamos la fecha.

Código del transmisor

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN

const byte address[5] = "00001";

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte a = 0;
  byte b = 125;
  byte c = 255;
  int d = 1024;
  float e = 3.141592;
  String f = "Test";
};

Data_Package data; // Create a variable with the above structure

void setup(){
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
}

void loop(){
  // Send the whole data from the structure to the receiver
  radio.write(&data, sizeof(Data_Package));
  delay(500);
}

Podemos crear una estructura que en realidad es una colección de varios tipos de variables.

// Max size of this struct is 32 bytes – NRF24L01 buffer limit
struct Data_Package {
byte a = 0;
byte b = 125;
byte c = 255;
int d = 1024;
float e = 3.141592;
String f = «Test»;
};

Data_Package data; // Create a variable with the above structure

Debemos tener en cuenta que el tamaño máximo de esta estructura de datos puede ser de 32 bytes. Aquí podemos ver que incluí tres variables tipo byte, una variable int (4 bytes), una variable float (4 bytes) y una cadena que contiene cuatro caracteres (4 bytes). Eso es un total de 15 bytes.

Código del receptor

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); // CE, CSN

const byte address[5] = "00001";

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte a = 0;
  byte b = 125;
  byte c = 255;
  int d = 1024;
  float e = 3.141592;
  String f = "Test";
};

Data_Package data; //Create a variable with the above structure

void setup(){
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
  radio.startListening();
}

void loop(){
  // Check whether there is data to be received
  if (radio.available()) {
    radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
  }
  Serial.print("a: ");
  Serial.print(data.a);
  Serial.print(" b: ");
  Serial.print(data.b);
  Serial.print(" c: ");
  Serial.print(data.c);
  Serial.print(" d: ");
  Serial.print(data.d);
  Serial.print(" e: ");
  Serial.print(data.e);
  Serial.print(" f: ");
  Serial.println(data.f);
}

En el lado del receptor, tenemos que definir los mismos datos de estructura para poder recibir los datos entrantes. Para probar si la comunicación inalámbrica funciona correctamente, imprimí cada variable en el monitor serial.

Sending-multiple-data-as-a-single-package-NRF24L01-and-Arduino-Wireless-Communication-768x452