Transmitiendo Datos Analógicos con Módulos XBee Serie 1: De Potenciómetro a Dispositivo Final

La comunicación inalámbrica entre dispositivos se ha convertido en una piedra angular de la tecnología moderna, permitiendo la creación de sistemas de monitorización, control y automatización cada vez más sofisticados. Los módulos XBee, específicamente la Serie 1 con su protocolo 802.15.4, ofrecen una solución robusta y relativamente sencilla para establecer redes de comunicación inalámbrica. Este artículo explora en detalle cómo transmitir datos analógicos leídos por un potenciómetro en un módulo XBee actuando como coordinador, hacia un segundo módulo XBee configurado como dispositivo final. Abordaremos la configuración, el código necesario y la resolución de una problemática común en la recepción de datos.

Módulos XBee Serie 1 conectados a placas Arduino

Comprendiendo los Módulos XBee y su Configuración

Los módulos XBee son soluciones integradas que proporcionan un medio inalámbrico para la interconexión y comunicación entre dispositivos. Utilizan el protocolo de red IEEE 802.15.4 para crear redes punto a multipunto o punto a punto. Fueron diseñados para aplicaciones que requieren un alto tráfico de datos, baja latencia y una sincronización de comunicación predecible.

Tipos de Módulos XBee

Es fundamental entender las distintas series de módulos XBee para seleccionar la más adecuada para cada proyecto.

  • XBee Series 1 (XBee 802.15.4): Son la serie más fácil de usar, a menudo no requieren configuración avanzada para comunicaciones punto a punto y funcionan de manera eficiente. El hardware de las Series 1 y las Series 2/2.5/ZB NO son compatibles entre sí. Si un módulo no especifica su serie, es probable que sea Serie 1.

  • XBee Znet 2.5 (Retirado) y ZB (Actual Serie 2): Estos módulos requieren configuración previa y pueden operar en modo transparente o mediante comandos API. Son capaces de formar redes mesh, lo que permite que los datos salten por múltiples nodos para llegar a su destino, proporcionando mayor cobertura y confiabilidad. Los módulos Znet 2.5 han sido reemplazados por los ZB, que son más compatibles.

  • XBee 2B: Son módulos más recientes que los ZB, con mejoras de hardware pero que funcionan con el firmware ZB. No son compatibles con el firmware Znet 2.5.

  • XBee 900MHz: Operan en una banda de frecuencia diferente (900 MHz) y pueden tener mayor alcance y mejor penetración de señal, pero no están permitidos en todos los países.

Roles dentro de una Red XBee

Dentro de una red XBee, los módulos pueden desempeñar diferentes roles:

  • Coordinador XBee: Es el módulo que inicia la red. Se encarga de seleccionar el canal de transmisión y asignar direcciones a los módulos que se unen a la red.

  • Router XBee: Puede transmitir y recibir datos, y también retransmitir datos de otros módulos, extendiendo el alcance de la red.

  • End Device XBee: Se utiliza principalmente para transmitir datos, pero no puede retransmitir datos de otros módulos. Son ideales para aplicaciones donde el dispositivo solo necesita enviar información periódicamente y se prioriza el bajo consumo de energía.

Configuración con XCTU

Para configurar los módulos XBee, se utiliza el software XCTU de Digi. El proceso general incluye:

  1. Instalación de XCTU: Descargar e instalar el software en tu ordenador.
  2. Conexión del Módulo: Conectar el módulo XBee a tu PC mediante un adaptador USB XBee o una placa de desarrollo compatible.
  3. Detección de Módulos: Abrir XCTU, hacer clic en el icono de lupa para buscar y visualizar los puertos disponibles, y luego detectar los módulos XBee conectados.
  4. Lectura de Configuración: Seleccionar el módulo en XCTU y hacer clic en 'Read' para obtener su configuración actual.
  5. Configuración de Parámetros:
    • CE (Coordinator Enable): Configurar en 'Coordinator [1]' para el rol de coordinador.
    • ID (PAN ID): Establecer un identificador único para la red (ej. '2000'). Todos los módulos de la misma red deben compartir el mismo PAN ID.
    • DH (Destination Address High) y DL (Destination Address Low): Estas opciones definen la dirección del módulo al que se enviarán los datos. Para una comunicación punto a punto, deben coincidir con la dirección del módulo receptor.
    • AP (API Enable): Configurar el módulo en modo API. El modo API permite un control más avanzado sobre las capacidades del módulo que el modo AT (Transparente). Los comandos y respuestas en modo API se estructuran en tramas de datos con un formato específico.
    • JV (Channel Verification): Permite verificar la disponibilidad del canal.

Para una comunicación broadcast, donde un módulo envía un mensaje a todos los módulos dentro del mismo PAN ID, se configuran DH a 0x0000 y DL a 0xFFFF.

Interfaz del software XCTU mostrando la configuración de un módulo XBee

Implementación Práctica: Envío de Datos Analógicos

En el escenario presentado, se utilizan dos módulos XBee Serie 1, configurados como Coordinator y End Device respectivamente, junto con dos Arduino Nano. El objetivo es transmitir la lectura de un potenciómetro conectado al pin analógico A0 del Arduino del Coordinator al End Device.

Configuración de los Módulos XBee

Ambos módulos XBee Serie 1 se configuran en la plataforma XCTU:

  • CANAL CH: Se establece el mismo canal para ambos módulos.
  • Red PAN ID: Se define un PAN ID común para establecer la red.
  • Modo API 2: Se configura el modo API en su versión 2.

Código del Arduino para el XBee Coordinator

El sketch de Arduino para el módulo Coordinator se encarga de leer el potenciómetro y enviar los datos.

#include <SoftwareSerial.h> // Incluyo librería para la configuración Serial default de Arduino.#include <XBee.h> // Incluyo librería XBee de Arduino.SoftwareSerial myXBeeC(2, 3); // Configuro el Pin 2 como Rx, y el Pin 3 Tx del Arduino Nano.XBee myXBee = XBee(); // Declaro un objeto de tipo XBee.// Declaro las direcciones del módulo XBee End Device.// Estas direcciones (SH y SL) se obtienen de la configuración del módulo End Device en XCTU.XBeeAddress64 addr64 = XBeeAddress64(0x0013A200, 0x40D82C62); // Ejemplo de dirección. Debes reemplazarla por la real.int sensor = 0; // Variable para lectura del puerto analógico A0 del Arduino Nano.String valor; // Variable auxiliar para operación de conversión de tipo entero a cadena de caracteres.int longitud = 0; // Variable para determinar y conocer la longitud de la cadena de caracteres.void setup() { Serial.begin(9600); // Baudios para el puerto Serial de Arduino (para depuración). myXBeeC.begin(9600); // Baudios para el puerto Serial seleccionado para trabajar la información con XBee. myXBee.setSerial(myXBeeC); // Declaro el puerto Serial a trabajar con el objeto XBee.}void loop() { sensor = analogRead(A0); // Lectura del puerto analógico del Arduino Nano (0-1023). valor = String(sensor); // Conversión de tipo entero a tipo cadena. // La longitud de la cadena incluye el carácter nulo al final. longitud = valor.length() + 1; char bufer[longitud]; // Declaro una variable para convertir de tipo String a tipo Char Array. valor.toCharArray(bufer, longitud); // Conversión de tipo String a tipo Char Array. // Preparación del payload para el paquete XBee. // Se crea un buffer de bytes con la longitud de la cadena leída. uint8_t payload[longitud]; for (int i = 0; i < longitud; i++){ // Se copian los caracteres de la cadena al buffer de bytes. // La condición 'if (bufer[i] > 0x10 && bufer[i] < 0xE9)' parece ser una verificación de caracteres imprimibles // o un intento de filtrar ciertos valores, pero podría ser simplificada si se confía en la entrada del potenciómetro. // En modo API 2, los datos se envían como bytes. Si se envía la representación ASCII de los números, // cada dígito se envía como un byte. Por ejemplo, "534" se enviaría como los bytes ASCII de '5', '3', '4'. payload[i] = bufer[i]; } // Empaquetado de la información en una trama Tx64Request. // addr64: Dirección del dispositivo final. // payload: Los datos a enviar. // sizeof(payload): Tamaño del payload en bytes. Tx64Request tx = Tx64Request(addr64, payload, sizeof(payload)); myXBee.send(tx); // Envío de la información del módulo XBee Coordinador al módulo XBee Dispositivo Final. // Pequeña pausa para evitar saturar el envío. delay(100);}

Código del Arduino para el XBee End Device

El sketch del End Device se encarga de recibir los datos y mostrarlos.

#include <SoftwareSerial.h> // Incluyo librería para la configuración Serial default de Arduino.#include <XBee.h> // Incluyo librería XBee de Arduino.SoftwareSerial myXBeeE(2, 3); // Configuro el Pin 2 como Rx, y el Pin 3 Tx del Arduino Nano.XBee myXBee = XBee(); // Declaro un objeto de tipo XBee.XBeeResponse respuesta = XBeeResponse(); // Declaro un objeto de tipo XBee Response.Rx16Response rx16 = Rx16Response(); // Para respuestas de 16 bytes.Rx64Response rx64 = Rx64Response(); // Para respuestas de 64 bytes.// Variables para almacenar la opción y el primer byte de los datos recibidos.// El problema principal reside en cómo se interpretan y extraen los datos del paquete XBee.uint8_t option = 0;uint8_t data = 0;void setup() { Serial.begin(9600); // Baudios para el puerto Serial de Arduino (para depuración). myXBeeE.begin(9600); // Baudios para el puerto Serial seleccionado para trabajar la información con XBee. myXBee.setSerial(myXBeeE); // Declaro el puerto Serial a trabajar con el objeto XBee.}void loop() { myXBee.readPacket(); // Intenta leer un paquete de datos entrante. if (myXBee.getResponse().isAvailable()) { // Verifica si la respuesta contiene un paquete de recepción (RX_16_RESPONSE o RX_64_RESPONSE). if (myXBee.getResponse().getApiId() == RX_16_RESPONSE || myXBee.getResponse().getApiId() == RX_64_RESPONSE) { // Se procesa la respuesta según su tipo. if (myXBee.getResponse().getApiId() == RX_16_RESPONSE) { myXBee.getResponse().getRx16Response(rx16); option = rx16.getOption(); // El problema: rx64.getData(0) extrae SÓLO el primer byte del payload. // Si el valor del potenciómetro se envía como una cadena de caracteres ASCII (ej. "534"), // este código solo leerá el primer carácter ('5' en este caso). data = rx16.getData(0); } else { // RX_64_RESPONSE myXBee.getResponse().getRx64Response(rx64); option = rx64.getOption(); // El problema: rx64.getData(0) extrae SÓLO el primer byte del payload. data = rx64.getData(0); } // La impresión en hexadecimal es una consecuencia de cómo se interpreta el byte recibido. // Si se recibe el byte ASCII '5', su representación hexadecimal es 0x35. // Si se imprime como número decimal, sería 53. // El código original imprime en HEX, lo que puede confundir la interpretación. Serial.print("Dato recibido (primer byte): "); Serial.println(data, HEX); // Imprime el valor del primer byte en hexadecimal. // Para una visualización más clara, se puede imprimir el valor decimal o el carácter ASCII. // Serial.print("Dato recibido (decimal): "); // Serial.println(data); // Serial.print("Dato recibido (carácter ASCII): "); // Serial.println((char)data); } } else if (myXBee.getResponse().isError()) { // Manejo de errores en la recepción. Serial.println("Error en la recepción del paquete."); }}

Tutorial de XBee | Cómo usar XBee con Arduino para la comunicación ZigBee | Configuración del software XCTU

La Problemática: Recepción Incompleta de Datos

El error principal se manifiesta en la captación de información por parte del módulo XBee End Device. A pesar de que el módulo Coordinator envía la cadena completa de caracteres leídos del ADC, el End Device solo lee el primer byte de dicha cadena.

Ejemplos de resultados obtenidos:

  • Al enviar "1", se recibe "1" (en hexadecimal, esto sería 0x31).
  • Al enviar "10", se recibe "1" (el byte ASCII para '1').
  • Al enviar "25", se recibe "2" (el byte ASCII para '2').
  • Al enviar "47", se recibe "4" (el byte ASCII para '4').
  • Al enviar "534", se recibe "5" (el byte ASCII para '5').
  • Al enviar "899", se recibe "8" (el byte ASCII para '8').
  • Al enviar "1015", se recibe "1" (el byte ASCII para '1').

La suposición inicial de que la librería XBee.h determinaría automáticamente el inicio y final del paquete de información, eliminando los bytes de la estructura del modo API 2, es incorrecta en este contexto de extracción de datos. La función getData(index) de las respuestas Rx16Response y Rx64Response está diseñada para acceder a bytes específicos dentro del payload. Al usar getData(0), se está solicitando explícitamente el primer byte del payload, lo que explica por qué solo se recibe un único carácter.

Análisis de la Solución Propuesta y Corrección

La observación de que variable.getData(index) imprime el byte en la posición especificada por index es correcta. El problema radica en que, al enviar el valor numérico del potenciómetro como una cadena de texto (ej. "534"), cada dígito se convierte en un byte ASCII independiente dentro del payload.

Por ejemplo, el número 1023 se enviaría como los bytes:0x31 ('1'), 0x30 ('0'), 0x32 ('2'), 0x33 ('3').

El código actual en el End Device solo lee getData(0), que corresponde al primer byte ('1' en este caso). Para recibir la cadena completa, es necesario iterar sobre todos los bytes del payload o, idealmente, reconstruir el número a partir de los bytes recibidos.

Estrategia de Corrección

La forma más robusta de manejar esto es que el módulo Coordinator envíe los datos de una manera que el End Device pueda interpretar de forma inequívoca.

  1. Enviar el número como una cadena de caracteres ASCII: Esta es la aproximación utilizada actualmente. El problema no está en el envío, sino en la recepción. El End Device necesita leer todos los bytes del payload y luego reconstruir la cadena de texto para convertirla de nuevo a un número entero.

    Modificación en el End Device:

    // ... (código anterior) ...void loop() { myXBee.readPacket(); if (myXBee.getResponse().isAvailable()) { if (myXbbe.getResponse().getApiId() == RX_16_RESPONSE || myXBee.getResponse().getApiId() == RX_64_RESPONSE) { String receivedString = ""; // String para reconstruir el mensaje recibido.
     if (myXBee.getResponse().getApiId() == RX_16_RESPONSE) { myXBee.getResponse().getRx16Response(rx16); option = rx16.getOption(); // Iterar sobre todos los bytes del payload de rx16. for (int i = 0; i &lt; rx16.getDataLength(); i++) { receivedString += (char)rx16.getData(i); // Añadir cada byte como carácter a la cadena. } } else { // RX_64_RESPONSE myXBee.getResponse().getRx64Response(rx64); option = rx64.getOption(); // Iterar sobre todos los bytes del payload de rx64. for (int i = 0; i &lt; rx64.getDataLength(); i++) { receivedString += (char)rx64.getData(i); // Añadir cada byte como carácter a la cadena. } } Serial.print("Mensaje recibido como cadena: "); Serial.println(receivedString); // Convertir la cadena recibida de vuelta a un número entero. // Se puede usar atoi() o atol() dependiendo del rango esperado. int receivedValue = receivedString.toInt(); Serial.print("Valor numérico recibido: "); Serial.println(receivedValue);}
    } else if (myXBee.getResponse().isError()) { Serial.println("Error en la recepción del paquete."); }}
  2. Enviar el número como bytes que representan el valor entero: En lugar de enviar la representación ASCII de los dígitos, se pueden enviar los bytes que componen el número entero directamente. Por ejemplo, un número de 16 bits (como el resultado del analogRead) ocupa 2 bytes.

    Modificación en el Coordinator:

    // ... (código anterior) ...void loop() { sensor = analogRead(A0); // Lectura del puerto analógico (0-1023). // Preparar el payload con los bytes del número entero. // Asumiendo que sensor es un int (16 bits). uint8_t payload[2]; payload[0] = highByte(sensor); // Byte alto del entero. payload[1] = lowByte(sensor); // Byte bajo del entero. Tx64Request tx = Tx64Request(addr64, payload, sizeof(payload)); myXBee.send(tx); delay(100);}

    Modificación en el End Device:

    // ... (código anterior) ...void loop() { myXBee.readPacket(); if (myXBee.getResponse().isAvailable()) { if (myXBee.getResponse().getApiId() == RX_16_RESPONSE || myXBee.getResponse().getApiId() == RX_64_RESPONSE) { uint16_t receivedValue = 0; // Variable para almacenar el valor entero de 16 bits.
     if (myXBee.getResponse().getApiId() == RX_16_RESPONSE) { myXBee.getResponse().getRx16Response(rx16); option = rx16.getOption(); // Asegurarse de que el payload tiene al menos 2 bytes. if (rx16.getDataLength() &gt;= 2) { // Reconstruir el entero a partir de los dos bytes recibidos. receivedValue = word(rx16.getData(0), rx16.getData(1)); } } else { // RX_64_RESPONSE myXBee.getResponse().getRx64Response(rx64); option = rx64.getOption(); // Asegurarse de que el payload tiene al menos 2 bytes. if (rx64.getDataLength() &gt;= 2) { // Reconstruir el entero a partir de los dos bytes recibidos. receivedValue = word(rx64.getData(0), rx64.getData(1)); } } Serial.print("Valor numérico recibido (reconstruido): "); Serial.println(receivedValue);}
    } else if (myXBee.getResponse().isError()) { Serial.println("Error en la recepción del paquete."); }}

La primera estrategia (enviar como cadena ASCII y reconstruir) es más flexible si se planea enviar mensajes de texto o datos de longitud variable. La segunda estrategia (enviar como bytes de entero) es más eficiente y directa para enviar valores numéricos discretos.

Diagrama de flujo de datos desde el potenciómetro hasta el dispositivo final XBee

Consideraciones Adicionales y Mejora del Sistema

Modo API y Tramas de Datos

El modo API (Interfaz de Programación de Aplicaciones) es fundamental para una comunicación controlada con los módulos XBee. Las tramas de datos API tienen una estructura específica que incluye un delimitador de inicio (0x7E), la longitud del frame, el tipo de trama, los datos del payload y un checksum para la verificación de errores. La librería XBee.h para Arduino abstrae gran parte de esta complejidad, pero es crucial entender cómo se manejan los datos dentro de estas tramas.

Si se desea depurar a un nivel más bajo, se podría implementar código para detectar el delimitador 0x7E y procesar la trama byte a byte, calculando el checksum y extrayendo los datos de manera manual.

Flexibilidad y Escalabilidad

La capacidad de los módulos XBee para crear redes mesh (con módulos Serie 2/ZB/2B) ofrece una gran flexibilidad para expandir el sistema en el futuro. Si se requiere mayor alcance o cobertura, se pueden añadir módulos router.

Antenas y Alcance

La elección de la antena (Chip, Wire, u.FL, RPSMA) y el tipo de módulo (Regular vs. PRO) impactan directamente en el alcance de la comunicación. Los módulos PRO suelen ofrecer un mayor alcance a costa de un mayor consumo de energía y costo.

Regulaciones de Frecuencia

Es importante considerar las regulaciones gubernamentales sobre el uso de frecuencias de radio en la región donde se operarán los módulos XBee, especialmente para las versiones de 900 MHz.

Al implementar la solución corregida, se podrá visualizar el valor completo leído por el potenciómetro en el módulo XBee End Device, permitiendo así completar la tarea de transmisión inalámbrica de datos analógicos.

tags: #mostrar #por #serial #lo #que #recibo