Comunicaciones en serie

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

4.0 Introducción

Las comunicaciones serie proporcionan una forma fácil y flexible de que tu placa Arduino interactúe con tu ordenador y otros dispositivos. Este capítulo explica cómo enviar y recibir información utilizando esta capacidad.

Enel Capítulo 1 se describe cómo conectar el puerto serie USB de Arduino a tu ordenador para cargar bocetos. El proceso de carga envía datos desde tu ordenador a Arduino, y Arduino envía mensajes de estado de vuelta al ordenador para confirmar que la transferencia funciona. Estas recetas muestran cómo puedes utilizar ese mismo enlace de comunicación para enviar y recibir cualquier información entre Arduino y tu ordenador u otro dispositivo serie.

Las comunicaciones serie también son una herramienta útil para depurar. Puedes enviar mensajes de depuración desde Arduino al ordenador y mostrarlos en la pantalla del ordenador o enviarlos a otro dispositivo, como una Raspberry Pi u otro Arduino. También puedes utilizar una pantalla LCD externa para mostrar estos mensajes, pero lo más probable es que utilices I2C o SPI para comunicarte con ese tipo de pantalla (consulta el Capítulo 13).

El IDE de Arduino (descrito en la Receta 1.3) proporciona un Monitor serie (mostrado en la Figura 4-1) para mostrar los datos serie enviados desde Arduino. También puedes enviar datos desde el Monitor serie a Arduino introduciendo texto en el cuadro de texto situado a la izquierda del botón Enviar. Arduino también incluye un Trazador serie que puede representar gráficamente los datos serie enviados desde Arduino (ver Receta 4.1).

Pantalla del monitor serie Arduino

Puedes establecer la velocidad a la que se transmiten los datos (la tasa de baudios, medida en bits por segundo) utilizando el cuadro desplegable de la parte inferior derecha. Asegúrate de ajustarla al valor que utilices con Serial.begin(). La velocidad por defecto de 9.600 bits por segundo está bien para muchos casos, pero si trabajas con un dispositivo que necesita una velocidad mayor, puedes pasar un número superior a 9.600 a Serial.begin().

Puedes utilizar el desplegable situado a la izquierda de la velocidad de transmisión para enviar automáticamente una nueva línea (carácter ASCII 10), retorno de carro (carácter ASCII 13), una combinación de nueva línea y retorno de carro ("Tanto NL como CR"), o sin terminador ("Sin final de línea") al final de cada mensaje.

Tu boceto Arduino puede utilizar el puerto serie para acceder indirectamente (normalmente a través de un programa proxy escrito en un lenguaje como Processing o Python) a todos los recursos (memoria, pantalla, teclado, ratón, conectividad de red, etc.) que tiene tu ordenador. Tu ordenador también puede utilizar el enlace serie para interactuar con determinados sensores u otros dispositivos conectados a Arduino. Si quieres hablar con varios dispositivos mediante comunicaciones serie, o bien necesitas más de un puerto serie o tendrás que utilizar software serie para emular un puerto serie utilizando los pines de Arduino (consulta "Emular hardware serie con pines digitales").

Nota

Muchos sensores y dispositivos de salida que admiten comunicaciones serie también admiten SPI o I2C (consulta el Capítulo 13). Aunque las comunicaciones serie se entienden bien y son en cierto modo universales, considera la posibilidad de utilizar SPI o I2C si el sensor o dispositivo de salida que quieres conectar admite uno u otro. Ambos protocolos ofrecen más flexibilidad a la hora de comunicarse con varios dispositivos.

Implementar comunicaciones serie implica hardware y software. El hardware proporciona la señalización eléctrica entre Arduino y el dispositivo con el que está hablando. El software utiliza el hardware para enviar bytes o bits que el hardware conectado entiende. Las bibliotecas serie de Arduino te aíslan de la mayor parte de la complejidad del hardware, pero es útil que entiendas lo básico, especialmente si necesitas solucionar cualquier dificultad con las comunicaciones serie en tus proyectos.

Hardware serie

El hardware serie envía y recibe datos como impulsos eléctricos que representan bits secuenciales. Los ceros y unos que transportan la información que compone un byte pueden representarse de varias formas. El esquema utilizado por Arduino es 0 voltios para representar un valor de bit de 0, y 5 voltios (o 3,3 voltios) para representar un valor de bit de 1.

Nota

Utilizar la tensión baja de un dispositivo (generalmente 0 voltios) para significar 0 y una tensión alta (3,3 o 5 voltios en el caso de Arduino) para significar 1 es muy común. Esto se denomina nivel TTL porque así era como se representaban las señales en una de las primeras implementaciones de la lógica digital, llamada Lógica de Transistor-Transistor (TTL). En la mayoría de las implementaciones, se puede señalar un 1 utilizando menos de la alta tensión del dispositivo, y 3,3 voltios suele ser más que suficiente para señalar un 1. Esto significa que puedes transmitir desde una placa de 3,3 V y recibir la señal en una placa de 5 V en la mayoría de los casos. Sin embargo, si quieres transmitir datos en serie desde una placa de 5 V a una de 3,3 V, tendrás que utilizar un desplazador de nivel o un divisor de tensión para evitar dañar la placa de 3,3 V. Consulta las Recetas 4.13 y 5.11 para ver ejemplos de divisores de tensión.

Algunas placas, como la Modern Device Bare Bones Board y las (ya descatalogadas) Adafruit Boarduino y Arduino Pro, Mini y Pro Mini, no tienen soporte USB y necesitan un adaptador para conectarse al ordenador que convierta TTL a USB. El Amigo CP2104 de Adafruit (número de pieza 3309 de Adafruit), la placa USB BUB de Modern Device (pieza MD022X de Modern Device), y FTDI USB TTL Adapter funcionan bien.

Algunos dispositivos serie utilizan el estándar RS-232 para la conexión serie. Suelen tener un conector de nueve pines, y se necesita un adaptador para utilizarlos con el Arduino. RS-232 utiliza niveles de voltaje que dañarán los pines digitales de Arduino, por lo que necesitarás obtener un adaptador de RS-232 a TTL para utilizarlo. Arduino tiene un tutorial de Arduino RS-232, y hay mucha información y enlaces disponibles en el sitio web Serial Port Central.

Un Arduino Uno tiene un único puerto serie de hardware, pero la comunicación serie también es posible utilizando bibliotecas de software para emular puertos adicionales (canales de comunicación) y proporcionar conectividad a más de un dispositivo. La comunicación serie por software requiere mucha ayuda del controlador Arduino para enviar y recibir datos, por lo que no es tan rápida ni eficiente como la comunicación serie por hardware.

El Leonardo y muchas placas de 32 bits (como el Arduino Zero, Adafruit Metro M0, y SparkFun RedBoard Turbo) tienen un segundo puerto serie de hardware además del USB serie. La placa Teensy 3 de PJRC tiene tres puertos serie además del USB serie. La placa Teensy 4.0 tiene siete puertos serie (además del USB serie).

El Arduino Mega tiene cuatro puertos serie de hardware que pueden comunicarse con hasta cuatro dispositivos serie diferentes. Sólo uno de ellos tiene un adaptador USB incorporado (podrías conectar un adaptador USB-TTL a cualquiera de los otros puertos serie si quieres más de una conexión USB).

La Tabla 4-1 muestra los pines utilizados para los puertos serie de varias placas Arduino y compatibles con Arduino. Los números de pin mostrados son para pines digitales, no analógicos.

Pines serie (digitales) para las placas seleccionadas
Junta RX/TX serie Serial1 RX/TX Serial2 RX/TX Serial3 RX/TX

Arduino MKR 1010

Sólo USB

13/14

ninguno

ninguno

Arduino Uno WiFi Rev2

Sólo USB

0/1

Conectado al módulo WiFi

ninguno

Arduino Nano Cada

Sólo USB

0/1

ninguno

ninguno

Arduino Nano 33 Sentido BLE

Sólo USB

0/1

ninguno

ninguno

Arduino Uno Rev3

0/1 (También USB)

ninguno

ninguno

ninguno

Metro Express Adafruit (M0)

Sólo USB

0/1

ninguno

ninguno

Arduino Zero/SparkFun RedBoard Turbo

Sólo USBa

0/1

ninguno

ninguno

Adafruit Itsy Bitsy M4 Express

Sólo USB

0/1

ninguno

ninguno

PJRC Teensy 3.2

Sólo USB

0/1

9/10

7/8

PJRC Teensy 4.0

Sólo USB

0/1

7/8

15/14

Arduino Due

0/1 (También USB)

19/18

17/16

15/14

Arduino Mega 2560 Rev2

0/1 (También USB)

19/18

17/16

15/14

Arduino Leonardo

Sólo USB

0/1

ninguno

ninguno

a Utiliza SerialUSB en lugar de Serial.

Nota

Algunas placas Teensy admiten más de tres puertos serie de hardware, y algunas te permiten modificar qué pines se utilizan para las comunicaciones serie. Consulta PJRC para más detalles.

Comportamiento del hardware serie

El número de puertos serie no es la única variable entre placas. También hay algunas diferencias fundamentales en el comportamiento. La mayoría de las placas basadas en los chips AVR ATmega, incluidos el Uno, original Nano, y Mega, tienen un chip para convertir el puerto serie del hardware del chip Arduino en USB para conectarse al puerto serie del hardware. En estas placas, cuando abras una conexión al puerto serie (por ejemplo, abriendo el monitor serie o accediendo al puerto serie desde un programa que se ejecute en un ordenador conectado a la placa por USB), la placa se reiniciará automáticamente, haciendo que el boceto comience desde el principio.

En algunas placas de 8 bits (Leonardo y compatibles) y en la mayoría de las placas de 32 bits, el puerto serie USB lo proporciona el mismo procesador en el que ejecutas tus bocetos. Debido a su diseño, abrir el puerto serie USB no reinicia estas placas. Como resultado, tu sketch empezará a enviar datos al puerto serie USB más rápido de lo que puedas abrir el puerto serie. Esto significa que si tienes algún comando de salida serie (Serial.print o Serial.println) en tu función setup(), no lo verás en el monitor serie porque no puedes abrir el monitor serie lo suficientemente rápido. (Podrías poner un delay en tu función setup, pero hay otra forma).

Además, el Leonardo y compatibles tienen otro comportamiento que hace que trabajar con el puerto serie sea complicado: cuando lo enciendas por primera vez, parpadeará un LED durante varios segundos para indicarte que está en un modo especial en el que te permite cargar un sketch por USB. Así que no podrás abrir el puerto serie para enviar o recibir datos hasta que termine de esperar.

En las placas que no se reinician automáticamente al abrir el puerto serie, puedes añadir el siguiente código a tu función setup (justo después de la llamada a Serial.begin()). Esto detendrá la ejecución hasta que se haya abierto el puerto serie para que puedas ver la salida serie que envías en setup:

while(!Serial) {
  ; // wait for serial port to connect
}

Puedes omitir las llaves y reducirlo a while(!Serial);, pero esto puede resultar confuso para los programadores novatos que lean tu código.

Dado que el comando while(!Serial); pausará la ejecución del boceto hasta que abras el puerto serie, este enfoque no debe utilizarse en entornos en los que se espera que tu solución basada en Arduino funcione de forma independiente; por ejemplo, cuando se ejecuta con baterías sin conexión USB. La Tabla 4-2 muestra el comportamiento del puerto serie USB para varias placas.

Comportamiento serie USB para varias placas
Junta while(!Serial); ¿se necesita? ¿Se reinicia cuando se accede a la serie?

Arduino MKR 1010

No

Arduino Uno WiFi Rev2

No

Arduino Nano Cada

No; Requiere un delay(800); después de Serial.begin() y debes abrir el Monitor serie antes de cargar para poder ver toda la salida serie.

No

Arduino Nano 33 Sentido BLE

No

Arduino Uno Rev3

No

Metro Express Adafruit (M0)

No

Adafruit Itsy Bitsy M4 Express

No

PJRC Teensy 3.2

No

PJRC Teensy 4.0

No

Arduino Mega 2560 Rev3

No

Arduino Leonardo

No

Emular hardware serie con pines digitales

Normalmente utilizarás la biblioteca serie incorporada de Arduino para comunicarte con los puertos serie del hardware. Las bibliotecas serie simplifican el uso de los puertos serie aislándote de las complejidades del hardware.

A veces necesitas más puertos serie que el número de puertos serie hardware disponibles. Si éste es el caso, puedes utilizar una biblioteca serie de software adicional que utilice software para emular el hardware serie. Las Recetas 4.11 y 4.12 muestran cómo utilizar una biblioteca serie de software para comunicarse con varios dispositivos.

Protocolos de mensajes

Las bibliotecas serie de hardware o software se encargan de enviar y recibir información. Esta información suele consistir en grupos de variables que deben enviarse juntas. Para que la información se interprete correctamente, la parte receptora necesita reconocer dónde empieza y acaba cada mensaje. La comunicación en serie significativa, o cualquier tipo de comunicación de máquina a máquina, sólo puede lograrse si las partes emisora y receptora están totalmente de acuerdo en cómo se organiza la información en el mensaje. La organización formal de la información en un mensaje y la gama de respuestas adecuadas a las peticiones se denomina protocolo de comunicaciones. Puedes establecer un protocolo sobre cualquier sistema de transferencia de datos subyacente, como las comunicaciones serie, pero estos mismos principios se aplican a otros medios de transferencia de datos, como las redes Ethernet o WiFi.

Los mensajes pueden contener uno o varios caracteres especiales que identifiquen el inicio del mensaje, lo que se denomina cabecera. También se pueden utilizar uno o varios caracteres para identificar el final del mensaje, lo que se denomina pie de página. Las recetas de este capítulo muestran ejemplos de mensajes en los que los valores que componen el cuerpo de un mensaje pueden enviarse en formato de texto o binario.

Enviar y recibir mensajes en formato de texto implica enviar comandos y valores numéricos como letras y palabras legibles por humanos. Los números se envían como la cadena de dígitos que representan el valor. Por ejemplo, si el valor es 1234, los caracteres 1, 2, 3 y 4 se envían como caracteres individuales.

Los mensajes binarios comprenden los bytes que el ordenador utiliza para representar valores. Los datos binarios suelen ser más eficientes (requieren el envío de menos bytes), pero los datos no son tan legibles para el ser humano como el texto, lo que dificulta su depuración. Por ejemplo, Arduino representa 1234 como los bytes 4 y 210 (4 * 256 + 210 = 1234). Si miraras estos caracteres en el Monitor Serial, no serían legibles porque el carácter ASCII 4 es un carácter de control y el carácter ASCII 210 está en el rango extendido de caracteres ASCII, por lo que probablemente mostrará un carácter acentuado u otra cosa dependiendo de tu configuración. Si el dispositivo al que te conectas sólo envía o recibe datos binarios, eso es lo que tendrás que utilizar, pero si puedes elegir, los mensajes textuales son más fáciles de implementar y depurar.

Hay muchas formas de abordar los problemas de software, y algunas de las recetas de este capítulo muestran dos o tres formas distintas de conseguir un resultado similar. Las diferencias (por ejemplo, enviar texto en lugar de datos binarios sin procesar) pueden ofrecer un equilibrio entre sencillez y eficacia. Cuando se ofrezcan opciones, elige la solución que te resulte más fácil de entender y adaptar: probablemente será la primera solución que se trate. Las alternativas pueden ser un poco más eficientes, o pueden ser más apropiadas para un protocolo concreto al que quieras conectarte, pero la "forma correcta" es la que te resulte más fácil de poner en funcionamiento en tu proyecto.

Notas de serie de Arduino

He aquí algunas cosas que debes tener en cuenta cuando trabajes con datos en serie en Arduino:

  • Serial.flush espera a que se envíen todos los datos salientes en lugar de descartar los datos recibidos (que era el comportamiento en versiones anteriores de Arduino). Puedes utilizar la siguiente sentencia para descartar todos los datos del búfer de recepción: . while(Serial.read() >= 0) ; // flush the receive buffer

  • Serial.write y no se bloquean. Las versiones antiguas de Arduino esperaban hasta que se enviaban todos los caracteres antes de volver. En cambio, los caracteres que envías utilizando o (y ) se transmiten en segundo plano (desde un manejador de interrupciones), permitiendo que el código de tu sketch reanude inmediatamente el procesamiento. Esto suele ser bueno (puede hacer que el sketch responda mejor), pero a veces quieres esperar hasta que se envíen todos los caracteres. Puedes conseguirlo llamando a inmediatamente después de o / . Serial.print Serial.write Serial.print println Serial.flush() Serial.write() Serial.print()println()

  • Las funciones de impresión en serie devuelven el número de caracteres impresos. Esto es útil cuando hay que alinear la salida de texto o para aplicaciones que envían datos que incluyen el número total de caracteres enviados.

  • Hay una capacidad de análisis incorporada para flujos como serie que permite extraer fácilmente números y encontrar texto. Consulta la sección Discusión de la Receta 4.5 para saber más sobre el uso de esta capacidad con serial.

  • La biblioteca SoftwareSerial incluida en Arduino puede ser muy útil; consulta las Recetas 4.11 y 4.12.

  • La función Serial.peek te permite "echar un vistazo" al siguiente carácter del búfer de recepción. A diferencia de Serial.read, el carácter no se elimina del búfer con Serial.peek.

4.1 Enviar información desde Arduino al ordenador

Problema

Quieres enviar texto y datos para que se muestren en tu PC, Mac u otro dispositivo (como una Raspberry Pi) utilizando el IDE de Arduino o el programa de terminal serie que elijas.

Solución

Este esquema imprime números secuenciales en el monitor serie:

/*
 * SerialOutput sketch
 * Print numbers to the serial port
*/
void setup()
{
  Serial.begin(9600); // send and receive at 9600 baud
}

int number = 0;

void loop()
{
  Serial.print("The number is ");
  Serial.println(number);    // print the number

  delay(500); // delay half second between numbers
  number++; // to the next number
}

Conecta Arduino a tu ordenador tal y como hiciste en el Capítulo 1 y carga este boceto. Haz clic en el icono Monitor serie del IDE y deberías ver la salida que se muestra a continuación:

The number is 0
The number is 1
The number is 2

Debate

Para mostrar el texto y los números de tu sketch en un ordenador a través de un enlace serie, pon la sentencia Serial.begin(9600) en setup(), y luego utiliza las sentencias Serial.print() para imprimir el texto y los valores que quieras ver. A continuación, puedes ver la salida en el Monitor Serie como se muestra en la Figura 4-2.

Pantalla del monitor serie Arduino

Para obtener una visualización gráfica del número que se envía de vuelta, cierra la ventana Monitor serie y selecciona Herramientas→Plotterserie. Se abrirá una ventana que dibujará un gráfico de los valores a medida que se reciben de la placa. El trazador puede aislar los números del texto, e identificar varios números separados por caracteres alfa y trazarlos por separado utilizando trazos de distinto color. La Figura 4-3 muestra el Trazador Serial.

Plotter serie

Tu boceto debe llamar a la función Serial.begin() antes de poder utilizar la entrada o salida serie. La función toma un único parámetro: la velocidad de comunicación deseada. Debes utilizar la misma velocidad para el lado emisor y el lado receptor, o verás un galimatías (o nada en absoluto) en la pantalla. Este ejemplo y la mayoría de los demás de este libro utilizan una velocidad de 9.600 baudios(los baudios son una medida del número de bits transmitidos por segundo). La velocidad de 9.600 baudios equivale aproximadamente a 1.000 caracteres por segundo. Puedes enviar a velocidades más bajas o más altas (el rango es de 300 a 115.200 o más dependiendo de las capacidades de tu placa), pero asegúrate de que ambos lados utilizan la misma velocidad. El Monitor Serial establece la velocidad utilizando el desplegable de velocidad en baudios (en la parte inferior derecha de la ventana del Monitor Serial en la Figura 4-2). Si tu salida tiene este aspecto

    `3??f<ÌxÌ▯▯▯ü`³??f<

debes comprobar que la velocidad en baudios seleccionada en el monitor serie de tu ordenador coincide con la velocidad establecida por Serial.begin() en tu esquema.

Consejo

Si tus velocidades serie de envío y recepción están configuradas correctamente pero sigues obteniendo texto ilegible, comprueba que tienes seleccionada la placa correcta en el menú Herramientas→Placadel IDE. Hay variantes de velocidad de chip en algunas placas, así que si has seleccionado la incorrecta, cámbiala por la correcta y vuelve a cargar en la placa.

Puedes transmitir texto utilizando la función Serial.print(). Las cadenas (texto entre comillas dobles) se imprimirán tal cual (pero sin las comillas). Por ejemplo, el siguiente código:

Serial.print("The number is ");

imprime esto:

The number is

Los valores (números) que imprimas dependerán del tipo de variable; para más información, consulta la Receta 4.2. Por ejemplo, al imprimir un número entero se imprimirá su valor numérico, por lo que si la variable number es 1, el código siguiente:

Serial.println(number);

imprimirá el valor actual de number:

1

En el esquema de ejemplo, el número impreso será 0 cuando se inicie el bucle y aumentará en uno cada vez que se pase por el bucle. El ln al final de println hace que la siguiente sentencia de impresión comience en una nueva línea.

Nota

Ten en cuenta que hay dos comportamientos diferentes del puerto serie que encontrarás con Arduino y las placas compatibles con Arduino: el Uno y la mayoría de las demás placas basadas en AVR ATmega se reiniciarán cuando abras el puerto serie. Esto significa que siempre verás que el recuento comienza en cero en el monitor o trazador serie. El Arduino Leonardo, así como las placas basadas en ARM, no se reinician automáticamente cuando abres el puerto serie. Esto significa que el boceto empezará a contar en cuanto se encienda. Como resultado, el valor que veas cuando abras por primera vez el monitor o el trazador serie dependerá de cuándo abras la conexión serie a la placa. Consulta "Comportamiento del hardware serie" para más detalles.

Con esto deberías empezar a imprimir texto y el valor decimal de los números enteros. Consulta la Receta 4.2 para más detalles sobre las opciones de formato de impresión.

Puede que quieras considerar un programa de terminal de terceros que tenga más funciones que Monitor Serial. Mostrar datos en formato de texto o binario (o ambos), mostrar caracteres de control y registrar en un archivo son sólo algunas de las capacidades adicionales disponibles en los muchos programas de terminal de terceros. Aquí tienes algunos recomendados por usuarios de Arduino:

CoolTerm

Un programa de terminal gratuito y fácil de usar para Windows, Mac y Linux

CuteCom

Un programa de terminal de código abierto para Linux

Terminal de Bray

Un ejecutable gratuito para el PC

Pantalla GNU

Un programa de gestión de pantallas virtuales de código abierto que admite comunicaciones serie; incluido con Linux y macOS

moserial

Otro programa de terminal de código abierto para Linux

PuTTY

Un programa SSH de código abierto para Windows y Linux que admite comunicaciones serie

RealTerm

Un programa de terminal de código abierto para el PC

ZTerm

Un programa shareware para el Mac

Puedes utilizar una pantalla de cristal líquido (LCD) como dispositivo de salida serie, aunque su funcionalidad será muy limitada. Consulta la documentación para ver cómo maneja tu pantalla los retornos de carro, ya que algunas pantallas pueden no avanzar automáticamente a una nueva línea después de las sentencias println. Además, cuando utilices una pantalla LCD, te conectarás a ella utilizando los pines serie TTL (digital 0 y 1) en lugar de una conexión USB. En la mayoría de las placas AVR ATmega como la Uno, estos pines corresponden al objeto Serial por lo que puedes utilizar el código mostrado en la Solución sin cambios. Sin embargo, en el Leonardo o en ciertas placas basadas en ARM (placas basadas en SAMD, por ejemplo), los pines 0 y 1 corresponden al objeto Serial1, por lo que tendrás que cambiar Serial por Serial1 para que funcione en esas placas. Consulta la Tabla 4-1 para ver una lista de las configuraciones de los pines del objeto Serie para diversas placas.

Ver también

La biblioteca Arduino LiquidCrystal para LCD de texto utiliza una funcionalidad de impresión subyacente similar a la de la biblioteca Serial, por lo que puedes utilizar muchas de las sugerencias tratadas en este capítulo con esa biblioteca (consulta el capítulo 11).

4.2 Enviar texto formateado y datos numéricos desde Arduino

Problema

Quieres enviar datos en serie desde Arduino mostrados como texto, valores decimales, hexadecimales o binarios.

Solución

Puedes imprimir datos en el puerto serie en muchos formatos diferentes; aquí tienes un esquema que demuestra todas las opciones de formato disponibles con las funciones de impresión serie print() y println :

/*
  SerialFormatting
  Print values in various formats to the serial port
*/
char chrValue  = 65;  // these are the starting values to print
byte byteValue = 65;
int intValue   = 65;
float floatValue = 65.0;

void setup()
{
  while(!Serial); // Wait until serial port's open on Leonardo and SAMD boards
  Serial.begin(9600);
}

void loop()
{
  Serial.print("chrValue:   ");
  Serial.print(chrValue); Serial.print(" ");
  Serial.write(chrValue); Serial.print(" ");
  Serial.print(chrValue, DEC);
  Serial.println();

  Serial.print("byteValue:  ");
  Serial.print(byteValue); Serial.print(" ");
  Serial.write(byteValue); Serial.print(" ");
  Serial.print(byteValue, DEC);
  Serial.println();

  Serial.print("intValue:   ");
  Serial.print(intValue); Serial.print(" ");
  Serial.print(intValue, DEC); Serial.print(" ");
  Serial.print(intValue, HEX); Serial.print(" ");
  Serial.print(intValue, OCT); Serial.print(" ");
  Serial.print(intValue, BIN);
  Serial.println();

  Serial.print("floatValue: ");
  Serial.println(floatValue);
  Serial.println();

  delay(1000); // delay a second between numbers
  chrValue++;  // to the next value
  byteValue++;
  intValue++;
  floatValue += 1;
}

El resultado es el siguiente:

chrValue:   A A 65
byteValue:  65 A 65
intValue:   65 65 41 101 1000001
floatValue: 65.00

chrValue:   B B 66
byteValue:  66 B 66
intValue:   66 66 42 102 1000010
floatValue: 66.00
...

Debate

Imprimir una cadena de texto es sencillo: Serial.print("hello world"); envía la cadena de texto "hola mundo" a un dispositivo situado al otro extremo del puerto serie. Si quieres que tu salida imprima una nueva línea después de la salida, utiliza Serial.println() en lugar de Serial.print().

Imprimir valores numéricos puede ser más complicado. La forma en que se imprimen los valores de bytes y enteros depende del tipo de variable y de un parámetro opcional de formato. El lenguaje Arduino es muy flexible en cuanto a la forma de referirse al valor de distintos tipos de datos (para más información sobre los tipos de datos, consulta la Receta 2.2 ). Pero esta flexibilidad puede resultar confusa, porque incluso cuando los valores numéricos son similares, el compilador los considera tipos distintos con comportamientos diferentes. Por ejemplo, imprimir un char, byte, y int del mismo valor no producirá necesariamente la misma salida.

Aquí tienes algunos ejemplos concretos; todos ellos crean variables que tienen valores similares:

char asciiValue  = 'A';   // ASCII A has a value of 65
char chrValue    = 65;    // an 8-bit signed character, this also is ASCII 'A'
byte byteValue   = 65;    // an 8-bit unsigned character, this also is ASCII 'A'
int intValue     = 65;    // a 16-bit signed integer set to a value of 65
float floatValue = 65.0;  // float with a value of 65

La Tabla 4-3 muestra lo que verás cuando imprimas variables utilizando rutinas de Arduino.

Formatos de salida con Serial.print
Tipo de datos imprimir (val) imprimir (val,DEC) escribir (val) imprimir (val,HEX) imprimir (val,OCT) imprimir (val,BIN)

char

A

65

A

41

101

1000001

byte

65

65

A

41

101

1000001

int

65

65

A

41

101

1000001

long

El formato de long es el mismo que int

float

65.00

Formato no admitido para valores de coma flotante

double

65.00

double en Uno es igual que float

Nota

La expresión Serial.print(val,BYTE); ya no es compatible con las versiones de Arduino a partir de la 1.0.

Si tu código espera que las variables byte se comporten igual que las variables char (es decir, que se impriman como ASCII), tendrás que cambiar esto por Serial.write(val);.

El esquema de esta receta utiliza una línea de código fuente distinta para cada sentencia print. Esto puede hacer que las sentencias de impresión complejas sean voluminosas. Por ejemplo, para imprimir la siguiente línea

At 5 seconds: speed = 17, distance = 120

normalmente tendrías que codificarlo así:

Serial.print("At ");
Serial.print(t);
Serial.print(" seconds: speed = ");
Serial.print(s);
Serial.print(", distance = ");
Serial.println(d);

Son muchas líneas de código para una sola línea de salida. Podrías combinarlas así

Serial.print("At "); Serial.print(t); Serial.print(" seconds, speed = ");
Serial.print(s); Serial.print(", distance = ");Serial.println(d);

O podrías utilizar la capacidad de inserción del compilador que utiliza Arduino para dar formato a tus declaraciones de impresión. Puedes aprovechar algunas capacidades avanzadas de C++ (sintaxis de inserción de flujo y plantillas) que puedes utilizar si declaras una plantilla de flujo en tu boceto. Esto se consigue más fácilmente incluyendo la biblioteca Streaming desarrollada por Mikal Hart. Puedes leer más sobre esta biblioteca en el sitio web de Mikal, y puedes instalarla utilizando el Gestor de Bibliotecas de Arduino (ver Receta 16.2).

Si utilizas la biblioteca Streaming, lo siguiente da la misma salida que las líneas mostradas anteriormente:

Serial << "At " << t << " seconds, speed=" << s << ", distance=" << d << endl;

Si eres un programador experimentado, te preguntarás por qué Arduino no es compatible con printf. En parte, esto se debe a que printfutiliza memoria dinámica y a la escasez de RAM en las placas de 8 bits. Las recientes placas de 32 bits tienen memoria de sobra, sin embargo, el equipo de Arduino se ha mostrado reacio a incluir la sintaxis lacónica y fácil de abusar como parte de las capacidades de salida en serie del lenguaje Arduino.

Aunque Arduino no incluye soporte para printf, puedes utilizar su hermano sprintf para almacenar texto formateado en un búfer de caracteres, y luego imprimir ese búfer utilizando Serial.print/println:

char buf[100];
sprintf(buf, "At %d seconds, speed = %d, distance = %d", t, s, d);
Serial.println(buf);

Pero sprintf puede ser peligroso. Si la cadena que estás escribiendo es mayor que tu búfer, se desbordará. Nadie sabe dónde se escribirán los caracteres desbordados, pero es casi seguro que harán que tu esbozo se bloquee o se comporte mal. La función snprintf te permite pasar un argumento que especifica el número máximo de caracteres (incluido el carácter nulo que termina todas las cadenas). Puedes especificar la misma longitud que utilizas para declarar la matriz (en este caso, 100), pero si lo haces, tendrás que acordarte de cambiar el argumento de longitud si cambias la longitud del búfer. En su lugar, puedes utilizar el operador sizeof para calcular la longitud del búfer. Aunque un char es 1 byte en todos los casos que se nos ocurran, es una buena práctica dividir el tamaño de la matriz por el tamaño del tipo de datos que contiene, por lo que sizeof (buf) / sizeof (buf[0]) te dará la longitud de la matriz:

snprintf(buf, sizeof (buf) / sizeof (buf[0]),
         "At %d seconds, speed = %d, distance = %d", t, s, d);
Serial.println(buf);
Nota

Aunque sepas que sizeof (buf[0]) tiene el valor 1 garantizado, es un buen hábito que debes adquirir. Considera el siguiente código, que imprime 400:

long buf2[100];
Serial.println(sizeof (buf2));

Puedes obtener el resultado correcto con sizeof (buf2) / sizeof (buf2[0]).

Utilizar sprintf o snprintf tiene un coste. En primer lugar, tienes la sobrecarga del búfer, 100 bytes en este caso. Además, está la sobrecarga de compilar la funcionalidad en tu boceto. En un Arduino Uno, añadir este código aumenta el uso de memoria en 1.648 bytes, que es el 5% de la memoria del Uno.

Ver también

El Capítulo 2 proporciona más información sobre los tipos de datos que utiliza Arduino. La referencia web de Arduino cubre los comandos serie, así como la salida en streaming (estilo inserción).

4.3 Recepción de datos serie en Arduino

Problema

Quieres recibir datos en Arduino desde un ordenador u otro dispositivo serie; por ejemplo, para que Arduino reaccione a órdenes o datos enviados desde tu ordenador.

Solución

Este sketch recibe un dígito (caracteres simples del 0 al 9) y hace parpadear el LED de la placa a una velocidad proporcional al valor del dígito recibido:

/*
 * SerialReceive sketch
 * Blink the LED at a rate proportional to the received digit value
*/
int blinkDelay = 0;  // blink delay stored in this variable

void setup()
{
  Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud
  pinMode(LED_BUILTIN, OUTPUT); // set this pin as output
}

void loop()
{
  if (Serial.available()) // Check to see if at least one character is available
  {
    char ch = (char) Serial.read();
    if( isDigit(ch) ) // is this an ASCII digit between 0 and 9?
    {
      blinkDelay = (ch - '0');       // ASCII value converted to numeric value
      blinkDelay = blinkDelay * 100; // actual delay is 100 ms *" received digit
    }
  }
  blink();
}

// blink the LED with the on and off times determined by blinkDelay
void blink()
{
  digitalWrite(LED_BUILTIN, HIGH);
  delay(blinkDelay); // delay depends on blinkDelay value
  digitalWrite(LED_BUILTIN, LOW);
  delay(blinkDelay);
}

Carga el boceto y envía mensajes utilizando el Monitor Serie. Abre el Monitor Serial haciendo clic en el icono Monitor (ver Receta 4.1) y escribe un dígito en el cuadro de texto de la parte superior de la ventana del Monitor Serial. Al hacer clic en el botón Enviar, se enviará el carácter escrito en el cuadro de texto; si escribes un dígito, deberías ver cómo cambia la velocidad de parpadeo.

Debate

La función Serial.read devuelve un valor int, por lo que debes convertirlo a un char para las comparaciones que siguen. Convertir los caracteres ASCII recibidos en valores numéricos puede no resultar obvio si no estás familiarizado con la forma en que ASCII representa los caracteres. A continuación se convierte el carácter ch a su valor numérico:

blinkDelay = (ch - '0');   // ASCII value converted to numeric value

Los caracteres ASCII '0' a '9' tienen un valor de 48 a 57 (ver Apéndice G). La conversión de '1' al valor numérico 1 se realiza restando '0' porque '1' tiene un valor ASCII de 49, por lo que hay que restar 48 (ASCII '0') para convertirlo en el número 1. Por ejemplo, si ch representa el carácter 1, su valor ASCII es 49. La expresión 49- '0' es igual a 49-48. Esto es igual a 1, que es el valor numérico del carácter 1.

En otras palabras, la expresión (ch - '0') es la misma que (ch - 48); esto convierte el valor ASCII de la variable ch en un valor numérico.

Puedes recibir números con más de un dígito utilizando los métodos parseInt y parseFloat , que simplifican la extracción de valores numéricos de Serial. (También funciona con Ethernet y otros objetos derivados de la clase Stream; consulta la introducción del capítulo 15 para obtener más información sobre el análisis de flujos con los objetos de red).

Serial.parseInt() y Serial.parseFloat() leen los caracteres de Serial y devuelven su representación numérica. Los caracteres no numéricos que preceden al número se ignoran y el número termina con el primer carácter que no sea un dígito numérico (o "." si se utiliza parseFloat). Si no hay caracteres numéricos en la entrada, las funciones devuelven 0, por lo que debes comprobar si hay valores cero y manejarlos adecuadamente. Si tienes configurado el monitor serie para que envíe una nueva línea o un retorno de carro (o ambos) cuando pulses Enviar, parseInt o parseFloat intentarán (y fallarán) interpretar la nueva línea o el retorno de carro como un número, y devolverán un cero. Esto daría lugar a que blinkDelay se pusiera a cero inmediatamente después de ajustarlo al valor deseado, lo que provocaría que no parpadeara:

/*
* SerialParsing sketch
* Blink the LED at a rate proportional to the received digit value
*/

int blinkDelay = 0;

void setup()
{
  Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud
  pinMode(LED_BUILTIN, OUTPUT); // set this pin as output
}

void loop()
{
  if ( Serial.available()) // Check to see if at least one character is available
  {
    int i = Serial.parseInt();
    if (i != 0) {
        blinkDelay = i;
    }
  }
  blink();
}

// blink the LED with the on and off times determined by blinkDelay
void blink()
{
  digitalWrite(LED_BUILTIN, HIGH);
  delay(blinkDelay); // delay depends on blinkDelay value
  digitalWrite(LED_BUILTIN, LOW);
  delay(blinkDelay);
}

Consulta la Discusión de la Receta 4.5 para ver otro ejemplo en el que se utiliza parseInt para encontrar y extraer números de datos en serie.

Otra forma de convertir cadenas de texto que representan números es utilizar la función de conversión del lenguaje C llamada atoi (para variables int ) o atol (para variables long ). Estas funciones de nombre oscuro convierten una cadena en enteros o enteros largos. Proporcionan más capacidad para manipular los datos entrantes a costa de una mayor complejidad del código. Para utilizar estas funciones, tienes que recibir y almacenar toda la cadena en una matriz de caracteres antes de poder llamar a la función de conversión.

Este fragmento de código termina los dígitos entrantes en cualquier carácter que no sea un dígito (o si la memoria intermedia está llena):

const int maxChars = 5;    // an int string contains up to 5 digits and
                           // is terminated by a 0 to indicate end of string
char strValue[maxChars+1]; // must be big enough for digits and terminating null
int idx = 0;               // index into the array storing the received digits

void loop()
{
  if( Serial.available())
  {
    char ch = (char) Serial.read();
    if( idx < maxChars && isDigit(ch) ){
      strValue[idx++] = ch; // add the ASCII character to the string;
    }
    else
    {
      // here when buffer full or on the first nondigit
      strValue[idx] = 0;           // terminate the string with a 0
      blinkDelay = atoi(strValue); // use atoi to convert the string to an int
      idx = 0;
    }
  }
  blink();
}

strValue es una cadena numérica construida a partir de los caracteres recibidos del puerto serie.

Consejo

Consulta la Receta 2.6 para obtener información sobre las cadenas de caracteres.

atoi (abreviatura de ASCII a entero) es una función que convierte una cadena de caracteres en un entero (atol se convierte en un entero long ).

Arduino también admite la función serialEvent que puedes utilizar para manejar los caracteres serie entrantes. Si tienes código dentro de una función serialEvent en tu sketch, éste será llamado una vez cada vez a través de la función loop. El siguiente sketch realiza la misma función que el primer sketch de esta receta, pero utiliza serialEvent para manejar los caracteres entrantes:

/*
 * SerialEvent Receive sketch
 * Blink the LED at a rate proportional to the received digit value
 */
int blinkDelay = 0;     // blink delay stored in this variable

void setup()
{
  Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud
  pinMode(LED_BUILTIN, OUTPUT); // set this pin as output
}

void loop()
{
  blink();
}

void serialEvent()
{
  while(Serial.available())
  {
    char ch = (char) Serial.read();
    Serial.write(ch);
    if( isDigit(ch) ) // is this an ASCII digit between 0 and 9?
    {
      blinkDelay = (ch - '0');       // ASCII value converted to numeric value
      blinkDelay = blinkDelay * 100; // actual delay is 100 ms times digit
    }
  }
}

// blink the LED with the on and off times determined by blinkDelay
void blink()
{
  digitalWrite(LED_BUILTIN, HIGH);
  delay(blinkDelay); // delay depends on blinkDelay value
  digitalWrite(LED_BUILTIN, LOW);
  delay(blinkDelay);
}

Ver también

Una búsqueda en Internet de "atoi" o "atol" proporciona muchas referencias a estas funciones (consulta la referencia de Wikipedia aquí).

4.4 Enviar varios campos de texto desde Arduino en un solo mensaje

Problema

Quieres enviar un mensaje que contenga más de un campo de información por mensaje. Por ejemplo, tu mensaje puede contener valores de dos o más sensores. Quieres utilizar estos valores en un programa como Processing, que se ejecuta en un ordenador o en un dispositivo como una Raspberry Pi.

Solución

Una forma fácil de hacerlo es enviar una cadena de texto con todos los campos separados por un carácter delimitador (separador), como una coma:

// CommaDelimitedOutput sketch

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int value1 = 10;    // some hardcoded values to send
  int value2 = 100;
  int value3 = 1000;

  Serial.print('H'); // unique header to identify start of message
  Serial.print(",");
  Serial.print(value1,DEC);
  Serial.print(",");
  Serial.print(value2,DEC);
  Serial.print(",");
  Serial.print(value3,DEC);
  Serial.println();  // send a carriage return and line feed
  delay(100);
}

Aquí tienes el esquema de Processing que lee estos datos del puerto serie:

// Processing Sketch to read comma delimited serial
// expects format: H,1,2,3

import processing.serial.*;

Serial myPort;        // Create object from Serial class
char HEADER = 'H';    // character to identify the start of a message
short LF = 10;        // ASCII linefeed

// WARNING!
// If necessary change the definition below to the correct port
short portIndex = 0;  // select the com port, 0 is the first port

void setup() {
  size(200, 200);
  println( (Object[]) Serial.list());
  println(" Connecting to -> " + Serial.list()[portIndex]);
  myPort = new Serial(this, Serial.list()[portIndex], 9600);
}

void draw() {
  if (myPort.available() > 0) {

    String message = myPort.readStringUntil(LF); // read serial data
    if (message != null)
    {
      message = message.trim(); // Remove whitespace from start/end of string
      println(message);
      String [] data = message.split(","); // Split the comma-separated message
      if (data[0].charAt(0) == HEADER && data.length == 4) // check validity
      {
        for (int i = 1; i < data.length; i++) // skip header (start at 1, not 0)
        {
          println("Value " +  i + " = " + data[i]);  // Print the field values
        }
        println();
      }
    }
  }
}

Debate

El código Arduino de la Solución de esta receta enviará la siguiente cadena de texto al puerto serie (\r indica un retorno de carro y \n indica un salto de línea):

H,10,100,1000\r\n

Debes elegir un carácter separador que nunca aparezca dentro de los datos reales; por ejemplo, si tus datos constan sólo de valores numéricos, una coma es una buena elección como delimitador. También puedes querer asegurarte de que la parte receptora pueda determinar el inicio de un mensaje para asegurarse de que tiene todos los datos de todos los campos. Para ello, envía un carácter de cabecera que indique el inicio del mensaje. El carácter de cabecera también debe ser único; no debe aparecer dentro de ninguno de los campos de datos y también debe ser diferente del carácter separador. En este ejemplo se utiliza una H mayúscula para indicar el inicio del mensaje. El mensaje consta de la cabecera, tres valores numéricos separados por comas como cadenas ASCII, y un retorno de carro y un salto de línea.

Los caracteres de retorno de carro y avance de línea se envían cada vez que Arduino imprime utilizando la función println() , y esto se utiliza para ayudar a la parte receptora a saber que se ha recibido la cadena de mensajes completa. Dado que el código de Procesamiento myPort.readStringUntil(LF) incluirá el retorno de carro ('\r') que aparece antes del salto de línea, este boceto utiliza trim() para eliminar cualquier espacio en blanco inicial o final, lo que incluye espacios, tabuladores, retornos de carro y saltos de línea.

El código de Procesamiento lee el mensaje como una cadena y utiliza el método Java split() para crear una matriz a partir de los campos separados por comas.

Nota

En la mayoría de los casos, el primer puerto serie será el que quieras cuando utilices un Mac (o un PC sin puerto físico RS-232) y el último puerto serie será el que quieras cuando utilices un PC que tenga un puerto físico RS-232. El sketch de Processing incluye código que muestra los puertos disponibles y el que está seleccionado en ese momento, por lo que deberás comprobar que éste es el puerto conectado a Arduino. Puede que tengas que ejecutar el sketch una vez, obtener un error y revisar la lista de puertos serie en la Consola de Procesamiento, en la parte inferior de la pantalla, para determinar qué valor debes utilizar para portIndex.

Utilizar Processing para mostrar los valores de los sensores puede ahorrarte horas de tiempo de depuración, ya que te ayuda a visualizar los datos. Aunque CSV es un formato común y útil, JSON (JavaScript Object Notation) es más expresivo y también es legible por humanos. JSON es un formato común de intercambio de datos que se utiliza para intercambiar mensajes a través de una red. El siguiente boceto lee el acelerómetro del Arduino WiFi Rev 2 o del Arduino Nano 33 BLE Sense (descomenta la línea include correspondiente) y lo envía al puerto serie utilizando JSON (por ejemplo: {'x': 0.66, 'y': 0.59, 'z': -0.49, }):

/*
   AccelerometerToJSON. Sends JSON-formatted representation of
   accelerometer readings.
*/

#include <Arduino_LSM6DS3.h>   // Arduino WiFi R2
//#include <Arduino_LSM9DS1.h> // Arduino BLE Sense

void setup() {
  Serial.begin(9600);
  while (!Serial);

  if (!IMU.begin()) {
    while (1) {
      Serial.println("Error: Failed to initialize IMU");
      delay(3000);
    }
  }
}

void loop() {
  float x, y, z;
  if (IMU.accelerationAvailable()) {
    IMU.readAcceleration(x, y, z);
    Serial.print("{");
    Serial.print("'x': "); Serial.print(x); Serial.print(", ");
    Serial.print("'y': "); Serial.print(y); Serial.print(", ");
    Serial.print("'z': "); Serial.print(z); Serial.print(", ");
    Serial.println("}");
    delay(200);
  }
}

El siguiente sketch de Processing añade una visualización en tiempo real de hasta 12 valores enviados desde Arduino. Muestra valores de coma flotante en un rango de -5 a +5:

/*
 * ShowSensorData.
 *
 * Displays bar graph of JSON sensor data ranging from -127 to 127
 * expects format as: "{'label1': value, 'label2': value,}\n"
 * for example:
 * {'x': 1.0, 'y': -1.0, 'z': 2.1,}
 */

import processing.serial.*;
import java.util.Set;

Serial myPort;  // Create object from Serial class
PFont fontA;    // font to display text
int fontSize = 12;
short LF = 10;        // ASCII linefeed

int rectMargin  = 40;
int windowWidth = 600;
int maxLabelCount = 12; // Increase this if you need to support more labels
int windowHeight  = rectMargin + (maxLabelCount + 1) * (fontSize *2);
int rectWidth  = windowWidth - rectMargin*2;
int rectHeight = windowHeight - rectMargin;
int rectCenter = rectMargin + rectWidth / 2;

int origin = rectCenter;
int minValue = -5;
int maxValue = 5;

float scale = float(rectWidth) / (maxValue - minValue);

// WARNING!
// If necessary change the definition below to the correct port
short portIndex = 0;  // select the com port, 0 is the first port

void settings() {
  size(windowWidth, windowHeight);
}

void setup() {
  println( (Object[]) Serial.list());
  println(" Connecting to -> " + Serial.list()[portIndex]);
  myPort = new Serial(this, Serial.list()[portIndex], 9600);
  fontA = createFont("Arial.normal", fontSize);
  textFont(fontA);
}

void draw() {

  if (myPort.available () > 0) {
    String message = myPort.readStringUntil(LF);
    if (message != null) {

      // Load the JSON data from the message
      JSONObject json = new JSONObject();
      try {
        json = parseJSONObject(message);
      }
      catch(Exception e) {
        println("Could not parse [" + message + "]");
      }

      // Copy the JSON labels and values into separate arrays.
      ArrayList<String> labels = new ArrayList<String>();
      ArrayList<Float> values = new ArrayList<Float>();
      for (String key : (Set<String>) json.keys()) {
        labels.add(key);
        values.add(json.getFloat(key));
      }

      // Draw the grid and chart the values
      background(255);
      drawGrid(labels);
      fill(204);
      for (int i = 0; i < values.size(); i++) {
        drawBar(i, values.get(i));
      }
    }
  }
}

// Draw a bar to represent the current sensor reading
void drawBar(int yIndex, float value) {
  rect(origin, yPos(yIndex)-fontSize, value * scale, fontSize);
}

void drawGrid(ArrayList<String> sensorLabels) {
  fill(0);

  // Draw the minimum value label and a line for it
  text(minValue, xPos(minValue), rectMargin-fontSize);
  line(xPos(minValue), rectMargin, xPos(minValue), rectHeight + fontSize);

  // Draw the center value label and a line for it
  text((minValue+maxValue)/2, rectCenter, rectMargin-fontSize);
  line(rectCenter, rectMargin, rectCenter, rectHeight + fontSize);

  // Draw the maximum value label and a line for it
  text(maxValue, xPos(maxValue), rectMargin-fontSize);
  line(
  xPos(maxValue), rectMargin, xPos(maxValue), rectHeight + fontSize);

  // Print each sensor label
  for (int i=0; i < sensorLabels.size(); i++) {
    text(sensorLabels.get(i), fontSize, yPos(i));
    text(sensorLabels.get(i), xPos(maxValue) + fontSize, yPos(i));
  }
}

// Calculate a y position, taking into account margins and font sizes
int yPos(int index) {
  return rectMargin + fontSize + (index * fontSize*2);
}

// Calculate a y position, taking into account the scale and origin
int xPos(int value) {
  return origin  + int(scale * value);
}

La Figura 4-4 muestra cómo se muestran los valores del acelerómetro (x, y, z). Aparecerán barras a medida que agites el dispositivo.

Pantalla de procesamiento que muestra los datos del sensor

El rango de valores y el origen del gráfico pueden cambiarse fácilmente si se desea. Por ejemplo, para mostrar barras con origen en el eje izquierdo con valores de 0 a 1.024, utiliza lo siguiente:

int origin = rectMargin; // rectMargin is the left edge of the graphing area
int minValue = 0;
int maxValue = 1024;

Si no tienes un acelerómetro, puedes generar valores con el siguiente sencillo sketch que muestra valores de entrada analógicos. Si no tienes ningún sensor que conectar, pasar los dedos por la parte inferior de las patillas analógicas producirá niveles que se pueden ver en el sketch de Processing. Los valores van de 0 a 1.023, así que cambia el origen y los valores mínimo y máximo en el sketch de Processing, como se describe en el párrafo anterior:

/*
   AnalogToJSON. Sends JSON-formatted representation of
   analog readings.
*/

void setup() {
  Serial.begin(9600);
  while (!Serial);
}

void loop() {
  Serial.print("{");
  Serial.print("'A0': "); Serial.print(analogRead(A0)); Serial.print(", ");
  Serial.print("'A1': "); Serial.print(analogRead(A1)); Serial.print(", ");
  Serial.print("'A2': "); Serial.print(analogRead(A2)); Serial.print(", ");
  Serial.print("'A3': "); Serial.print(analogRead(A3)); Serial.print(", ");
  Serial.print("'A4': "); Serial.print(analogRead(A4)); Serial.print(", ");
  Serial.print("'A5': "); Serial.print(analogRead(A5)); Serial.print(", ");
  Serial.println("}");
}

Ver también

El sitio web de Processing proporciona más información sobre la instalación y el uso de este entorno de programación.

También hay disponibles varios libros sobre Procesamiento:

  • Primeros pasos con Processing, segunda edición, por Casey Reas y Ben Fry (Make)

  • Processing: Manual de programación para diseñadores visuales y artistas, de Casey Reas y Ben Fry (MIT Press).

  • Visualizar datos por Ben Fry (O'Reilly)

  • Procesamiento: Codificación creativa y arte computacional de Ira Greenberg (Apress)

  • Making Things Talk por Tom Igoe (Make Community) (Este libro cubre Processing y Arduino y proporciona muchos ejemplos de código de comunicación).

4.5 Recibir varios campos de texto en un solo mensaje en Arduino

Problema

Quieres recibir un mensaje que contenga más de un campo. Por ejemplo, tu mensaje puede contener un identificador para indicar un dispositivo concreto (como un motor u otro actuador) y a qué valor (como la velocidad) hay que ajustarlo.

Solución

Arduino no tiene la función split() utilizada en el código de Procesamiento de la Receta 4.4, pero se puede implementar una funcionalidad similar como se muestra en esta receta. El siguiente código recibe un mensaje con un único carácter H como cabecera, seguido de tres campos numéricos separados por comas y terminados por el carácter de nueva línea:

/*
 * SerialReceiveMultipleFields sketch
 * This code expects a message in the format: H,12,345,678
 * This code requires a newline character to indicate the end of the data
 * Set the serial monitor to send newline characters
 */

const int NUMBER_OF_FIELDS = 3; // how many comma separated fields we expect
int values[NUMBER_OF_FIELDS];   // array holding values for all the fields

void setup()
{
  Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud
}

void loop()
{
  if ( Serial.available())
  {
    if (Serial.read() == 'H') {

      // Read the values
      for (int i = 0; i < NUMBER_OF_FIELDS; i++)
      {
        values[i] = Serial.parseInt();
      }

      // Display the values in comma-separated format
      Serial.print(values[0]); // First value

      // Print the rest of the values with a leading comma
      for (int i = 1; i < NUMBER_OF_FIELDS; i++)
      {
        Serial.print(","); Serial.print(values[i]);
      }
      Serial.println();
    }
  }
}

Debate

Este sketch utiliza el método parseInt que facilita la extracción de valores numéricos de flujos serie y web. Este es un ejemplo de cómo utilizar esta capacidad(el Capítulo 15 contiene más ejemplos de análisis sintáctico de flujos). Puedes probar este sketch abriendo el monitor serie y enviando un mensaje separado por comas como H1,2,3. parseInt ignora todo lo que no sea un signo menos y un dígito, por lo que no tiene que estar separado por comas. Puedes utilizar otro delimitador como H1/2/3. El boceto almacena los números en una matriz y luego los imprime, separados por comas.

Nota

Este esquema muestra una lista separada por comas de una forma que puede parecer inusual al principio. Imprime el primer número recibido (por ejemplo, 1), y luego imprime los números restantes, cada uno precedido por una coma (por ejemplo, ,2 y luego ,3). Podrías utilizar menos líneas de código e imprimir una coma después de cada número, pero acabarías con 1,2,3, en lugar de 1,2,3. Muchos otros lenguajes de programación, incluido Processing, proporcionan una función join que combinará una matriz en una cadena delimitada, pero Arduino no lo hace.

Las funciones de análisis de flujos agotarán el tiempo de espera a la espera de un carácter; el valor predeterminado es de un segundo. Si no se ha recibido ningún carácter y parseInt agota el tiempo de espera, devolverá 0. Puedes cambiar el tiempo de espera llamando a Stream.setTimeout(timeoutPeriod). El parámetro de tiempo de espera es un entero long que indica el número de milisegundos, por lo que el rango de tiempo de espera es de 1 ms a 2.147.483.647 ms.

Stream.setTimeout(2147483647); cambiará el intervalo de tiempo de espera a algo menos de 25 días.

A continuación se presenta un resumen de los métodos de análisis de flujos admitidos por Arduino (no todos se utilizan en el ejemplo anterior):

bool find(char *target);

Lee del flujo hasta encontrar el objetivo dado. Devuelve true si se encuentra la cadena objetivo. Un resultado de false significa que no se han encontrado los datos en ninguna parte del flujo y que no hay más datos disponibles. Ten en cuenta que el análisis Stream realiza una única pasada por el flujo; no hay forma de volver atrás para intentar encontrar u obtener algo más (véase el método findUntil ).

bool findUntil(char *target, char *terminate);

Similar al método find, pero la búsqueda se detendrá si se encuentra la cadena terminate. Devuelve true sólo si se encuentra el objetivo. Esto es útil para detener una búsqueda en una palabra clave o terminador. Por ejemplo:

    finder.findUntil("target", "\n");

intentará buscar la cadena "value", pero se detendrá en un carácter de nueva línea para que tu esbozo pueda hacer otra cosa si no encuentra el objetivo.

long parseInt();

Devuelve el primer valor entero válido (long). Se omiten los caracteres iniciales que no sean dígitos o un signo menos. El número entero termina con el primer carácter no numérico que sigue al número. Si no se encuentra ningún dígito, la función devuelve 0.

long parseInt(char skipChar);

Igual que parseInt, pero se ignora el skipChar dado dentro del valor numérico. Esto puede ser útil al analizar un único valor numérico que utiliza una coma entre bloques de dígitos en números grandes, pero ten en cuenta que los valores de texto formateados con comas no pueden analizarse como una cadena separada por comas (por ejemplo, 32.767 se analizaría como 32767).

float parseFloat();

La versión float de parseInt. Se omiten todos los caracteres excepto los dígitos, un punto decimal o un signo menos inicial.

size_t readBytes(char *buffer, size_t length);

Coloca los caracteres entrantes en el búfer dado hasta que se agote el tiempo de espera o se hayan leído los caracteres de longitud. Devuelve el número de caracteres colocados en el búfer.

size_t readBytesUntil(char terminator, char *buf, size_t length);

Introduce los caracteres entrantes en el búfer dado hasta que se detecte el carácter terminator. Las cadenas más largas que length se truncan para que quepan. La función devuelve el número de caracteres introducidos en el búfer.

Ver también

El Capítulo 15 proporciona más ejemplos de análisis sintáctico de flujos utilizados para encontrar y extraer datos de un flujo. La biblioteca ArduinoJson te permite analizar mensajes con formato JSON (ver Receta 4.4) en Arduino.

4.6 Enviar datos binarios desde Arduino

Problema

Necesitas enviar datos en formato binario, porque quieres pasar información con el menor número de bytes o porque la aplicación a la que te conectas sólo maneja datos binarios.

Solución

Este sketch envía una cabecera seguida de dos valores enteros (de dos bytes) como datos binarios. El sketch utiliza short porque serán dos bytes independientemente de si tienes una placa de 8 o 32 bits (ver Receta 2.2). Los valores se generan utilizando la función Arduino random (ver Receta 3.11). Aunque random devuelve un valor de long, el argumento de 599 significa que nunca devolverá un valor superior a ese número, que es lo suficientemente pequeño como para caber en short:

/*
 * SendBinary sketch
 * Sends a header followed by two random integer values as binary data.
*/

short intValue; // short integer value (two bytes on all boards)

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Serial.print('H'); // send a header character

  // send a random integer
  intValue = random(599); // generate a random number between 0 and 599
  // send the two bytes that comprise that integer
  Serial.write(lowByte(intValue));  // send the low byte
  Serial.write(highByte(intValue)); // send the high byte

  // send another random integer
  intValue = random(599); // generate a random number between 0 and 599
  // send the two bytes that comprise that integer
  Serial.write(lowByte(intValue));  // send the low byte
  Serial.write(highByte(intValue)); // send the high byte

  delay(1000);
}

Debate

El envío de datos binarios requiere una planificación cuidadosa, porque obtendrás un galimatías a menos que el lado emisor y el lado receptor comprendan y acuerden exactamente cómo se enviarán los datos. A diferencia de los datos de texto, en los que el final de un mensaje puede determinarse por la presencia del carácter de terminación retorno de carro (u otro carácter único que tú elijas), puede que no sea posible saber cuándo empieza o termina un mensaje binario observando sólo los datos: unos datos que pueden tener cualquier valor pueden, por tanto, tener el valor de un carácter de encabezamiento o de terminación.

Esto puede superarse diseñando tus mensajes de modo que los lados emisor y receptor sepan exactamente cuántos bytes se esperan. El final de un mensaje viene determinado por el número de bytes enviados, en lugar de por la detección de un carácter concreto. Esto puede implementarse enviando un valor inicial que diga cuántos bytes seguirán. O puedes fijar el tamaño del mensaje para que sea lo suficientemente grande como para contener los datos que quieres enviar. Hacer cualquiera de estas dos cosas no siempre es fácil, ya que las distintas plataformas y lenguajes pueden utilizar tamaños diferentes para los tipos de datos binarios: tanto el número de bytes como su orden pueden ser diferentes de Arduino. Por ejemplo, Arduino define un int como dos bytes (16 bits) en plataformas de 8 bits, cuatro bytes (32 bits) en una plataforma de 32 bits, y Processing (Java) define un int como cuatro bytes (short es el tipo Java para un entero de dos bytes). Enviar un valor int como texto (como se ha visto en recetas de texto anteriores) simplifica este problema porque cada dígito individual se envía como un dígito secuencial (tal como se escribe el número). La parte receptora reconoce cuando el valor se ha recibido completamente mediante un retorno de carro u otro delimitador de no dígitos. Las transferencias binarias sólo pueden conocer la composición de un mensaje si está definida de antemano o especificada en el mensaje.

La solución de esta receta requiere conocer los tipos de datos de las plataformas emisora y receptora y cierta planificación cuidadosa. La receta 4.7 muestra un código de ejemplo que utiliza el lenguaje Processing para recibir estos mensajes.

Enviar bytes sueltos es fácil; utiliza Serial.write(byteVal). Para enviar un entero desde Arduino necesitas enviar los bytes bajo y alto que componen el entero (consulta la Receta 2.2 para saber más sobre los tipos de datos). Para ello, utiliza las funciones lowByte y highByte (ver Receta 3.14):

Serial.write(lowByte(intValue));
Serial.write(highByte(intValue));

El envío de un entero long se realiza descomponiendo los cuatro bytes que componen un long en dos pasos. El long se descompone primero en dos enteros de 16 bits; cada uno de ellos se envía utilizando el método de envío de enteros descrito anteriormente:

long longValue = 2147483648;
int intValue;

Primero envías el valor entero inferior de 16 bits:

intValue = longValue & 0xFFFF;  // get the value of the lower 16 bits
Serial.write(lowByte(intValue));
Serial.write(highByte(intValue));

Luego envías el valor entero de 16 bits superior:

intValue = longValue >> 16;  // get the value of the higher 16 bits
Serial.write(lowByte(intValue));
Serial.write(highByte(intValue));

Puede que te resulte conveniente crear funciones para enviar los datos. Aquí tienes una función que utiliza el código mostrado anteriormente para imprimir un número entero de 16 bits en el puerto serie:

// function to send the given integer value to the serial port
void sendBinary(int value)
{
  // send the two bytes that comprise a two-byte (16-bit) integer
  Serial.write(lowByte(value));  // send the low byte
  Serial.write(highByte(value)); // send the high byte
}

La siguiente función envía el valor de un entero long (de cuatro bytes) enviando primero los dos bytes bajos (más a la derecha), seguidos de los bytes altos (más a la izquierda):

// function to send the given long integer value to the serial port
void sendBinary(long value)
{
  // first send the low 16-bit integer value
  int temp = value & 0xFFFF;  // get the value of the lower 16 bits
  sendBinary(temp);
  // then send the higher 16-bit integer value:
  temp = value >> 16;  // get the value of the higher 16 bits
  sendBinary(temp);
}

Estas funciones para enviar valores binarios int y long tienen el mismo nombre: sendBinary. El compilador las distingue por el tipo de valor que utilizas para el parámetro. Si tu código llama a sendBinary con un valor de dos bytes, se llamará a la versión declarada como void sendBinary(int value). Si el parámetro es un valor long, se llamará a la versión declarada como void sendBinary(long value). Este comportamiento se denomina sobrecarga de funciones. La receta 4.2 proporciona otra ilustración de esto; la diferente funcionalidad que viste en Serial.print se debe a que el compilador distingue los diferentes tipos de variable utilizados.

También puedes enviar datos binarios utilizando estructuras. Las estructuras son un mecanismo para organizar datos, y si aún no estás familiarizado con su uso puede que sea mejor que te ciñas a las soluciones descritas anteriormente. Para los que se sientan cómodos con el concepto de punteros a estructuras, lo siguiente enviará los bytes dentro de una estructura al puerto serie como datos binarios. Incluye el carácter de cabecera en la estructura, por lo que envía los mismos mensajes que la Solución:

/*
   SendBinaryStruct sketch
   Sends a struct as binary data.
*/

typedef struct {
  char padding; // ensure same alignment on 8-bit and 32-bit
  char header;
  short intValue1;
  short intValue2;
} shortMsg;

void setup()
{
  Serial.begin(9600);
}

void loop()
{

  shortMsg myStruct = { 0, 'H', (short) random(599), (short) random(599) };
  sendStructure((byte *)&myStruct, sizeof(myStruct));

  delay(1000);
}

void sendStructure(byte *structurePointer, int structureLength)
{
  int i;
  for (i = 0 ; i < structureLength ; i++) {
    Serial.write(structurePointer[i]);
  }
}
Nota

Si declararas la estructura shortMsg sin el miembro padding, podrías encontrarte con que la longitud de la estructura es de cinco bytes en una placa y de seis bytes en otra. Esto se debe a que el compilador de una arquitectura podría permitir perfectamente una estructura de cinco bytes, pero otra podría insertar uno o más bytes extra para garantizar que el tamaño de la estructura es un múltiplo del tamaño de datos natural de la placa. Al poner el relleno al principio, te aseguras de que el char aparezca en un límite par (el segundo de dos bytes), por lo que es poco probable que el compilador inserte relleno entre los valores char y short. Pero no siempre está garantizado que este truco funcione, así que puede que tengas que experimentar. Otra ventaja de poner el relleno antes del carácter de cabecera es que el código de la Receta 4.7 ignorará la entrada hasta que vea un carácter H.

Enviar datos como bytes binarios es más eficaz que enviarlos como texto, pero sólo funcionará de forma fiable si el lado emisor y el receptor están de acuerdo exactamente en la composición de los datos. He aquí un resumen de las cosas importantes que debes comprobar al escribir tu código:

Tamaño variable

Asegúrate de que el tamaño de los datos que se envían es el mismo en ambos lados. Un número entero es de dos bytes en Arduino Uno y otras placas de 8 bits, y de cuatro bytes en placas de 32 bits y en la mayoría de las demás plataformas. Consulta siempre la documentación de tu lenguaje de programación sobre el tamaño de los tipos de datos para asegurarte de que coinciden. No hay ningún problema en recibir un entero Arduino de dos bytes como un entero de cuatro bytes en Processing, siempre que Processing espere recibir sólo dos bytes. Pero asegúrate de que el lado emisor no utiliza valores que desborden el tipo utilizado por el lado receptor.

Orden de bytes

Asegúrate de que los bytes dentro de un int o long se envían en el orden esperado por el lado receptor. La solución utiliza el mismo orden de bytes que las placas Arduino utilizan internamente, llamado little endian. Esto se refiere al orden de los bytes, en el que el byte menos significativo aparece primero. Técnicamente, las placas Arduino compatibles con ARM son bi-endian, lo que significa que pueden configurarse para utilizar el modo big-endian o little-endian, pero en la práctica es poco probable que encuentres una placa Arduino que no sea little endian. Cuando utilizas lowByte y highByte para descifrar un entero, controlas el orden en que se envían los bytes. Pero cuando envías un struct en formato binario, éste utilizará la representación interna del struct, que se ve afectada por la endianidad de tu placa. Así que, si ejecutas el código de la estructura con el código de Procesamiento de la Receta 4.7 y no ves el valor deseado (16.384), puede que tu estructura esté invertida.

Sincronización

Asegúrate de que tu lado receptor puede reconocer el principio y el final de un mensaje. Si empiezas a escuchar en medio de un flujo de transmisión, no obtendrás datos válidos. Esto se puede conseguir enviando una secuencia de bytes que no se produzcan en el cuerpo de un mensaje. Por ejemplo, si envías valores binarios desde analogRead, éstos sólo pueden ir de 0 a 1.023, por lo que el byte más significativo debe ser menor que 4 (el valor int de 1.023 se almacena como los bytes 3 y 255); por lo tanto, nunca habrá datos con dos bytes consecutivos mayores que 3. Así pues, enviar dos bytes de 4 (o cualquier valor superior a 3) no puede ser un dato válido y puede utilizarse para indicar el inicio o el final de un mensaje.

Control de caudal

Elige una velocidad de transmisión que garantice que el lado receptor pueda seguir el ritmo del lado emisor, o utiliza algún tipo de control de flujo. El control de flujo es un apretón de manos que indica a la parte emisora que el receptor está preparado para recibir más datos.

Ver también

Enel Capítulo 2 encontrarás más información sobre los tipos de variables que se utilizan en los bocetos de Arduino.

Consulta la Receta 3.15 para saber más sobre el manejo de bytes altos y bajos. Además, consulta las referencias de Arduino para lowByte y highByte.

Para más información sobre el control de flujo, consulta la referencia de Wikipedia.

4.7 Recibir datos binarios de Arduino en un ordenador

Problema

Quieres responder a los datos binarios enviados desde Arduino en un lenguaje de programación como Processing. Por ejemplo, quieres responder a los mensajes de Arduino enviados en la Receta 4.6.

Solución

La solución de esta receta depende del entorno de programación que utilices en tu PC o Mac. Si aún no tienes una herramienta de programación favorita y quieres una que sea fácil de aprender y que funcione bien con Arduino, Processing es una opción excelente.

Aquí tienes las dos líneas de código Processing para leer un byte, tomadas del ejemplo Processing SimpleRead (consulta la introducción de este capítulo):

  if ( myPort.available() > 0) {  // If data is available,
    val = myPort.read();          // read it and store it in val

Como puedes ver, esto es muy parecido al código Arduino que has visto en recetas anteriores.

A continuación se muestra un esquema de Processing que establece el tamaño de un rectángulo proporcional a los valores enteros recibidos del esquema de Arduino en la Receta 4.6:

/*
 * ReceiveBinaryData_P
 *
 * portIndex must be set to the port connected to the Arduino
 */
import processing.serial.*;

Serial myPort;        // Create object from Serial class

// WARNING!
// If necessary change the definition below to the correct port
short portIndex = 0;  // select the com port, 0 is the first port

char HEADER = 'H';
int value1, value2;         // Data received from the serial port

void setup()
{
  size(600, 600);
  // Open whatever serial port is connected to Arduino.
  String portName = Serial.list()[portIndex];
  println((Object[]) Serial.list());
  println(" Connecting to -> " + portName);
  myPort = new Serial(this, portName, 9600);
}

void draw()
{
  // read the header and two binary *(16-bit) integers:
  if ( myPort.available() >= 5)  // If at least 5 bytes are available,
  {
    if( myPort.read() == HEADER) // is this the header
    {
      value1 = myPort.read();                 // read the least significant byte
      value1 =  myPort.read() * 256 + value1; // add the most significant byte

      value2 = myPort.read();                 // read the least significant byte
      value2 =  myPort.read() * 256 + value2; // add the most significant byte

      println("Message received: " + value1 + "," + value2);
    }
  }
  background(255);             // Set background to white
  fill(0);                     // set fill to black

  // draw rectangle with coordinates based on the integers received from Arduino
  rect(0, 0, value1,value2);
}
Consejo

Asegúrate de que configuras portIndex para que se corresponda con el puerto serie al que está conectado Arduino. Puede que tengas que ejecutar el boceto una vez, obtener un error y revisar la lista de puertos serie en la consola de Procesamiento, en la parte inferior de la pantalla, para determinar qué valor debes utilizar para portIndex.

Debate

El lenguaje Processing influyó en Arduino, y ambos son intencionadamente similares. La función setup de Processing se utiliza para gestionar la inicialización única, igual que en Arduino. Processing tiene una ventana de visualización, y setup establece su tamaño en 600 × 600 píxeles con la llamada a size(600,600).

La línea String portName = Serial.list()[portIndex]; selecciona el puerto serie -en Processing, todos los puertos serie disponibles están contenidos en el objeto Serial.list, y este ejemplo utiliza el valor de una variable llamada portIndex. println((Object[]) Serial.list()) imprime todos los puertos disponibles, y la línea myPort = new Serial(this, portName, 9600); abre el puerto seleccionado como portName. Asegúrate de que estableces portIndex en el puerto serie que está conectado a tu Arduino. Arduino suele ser el primer puerto en un Mac; en un PC, suele ser el último puerto si el PC tiene un puerto físico RS-232, de lo contrario puede ser el primer puerto. También puedes consultar la lista de puertos en el IDE de Arduino, que puede mostrar los puertos serie en el mismo orden en que Processing los enumera.

La función draw en Processing funciona como loop en Arduino; se llama repetidamente. El código en draw comprueba si hay datos disponibles en el puerto serie; si es así, se leen los bytes y se convierten al valor entero representado por los bytes. Se dibuja un rectángulo en función de los valores enteros recibidos.

Ver también

Puedes leer más sobre el Procesamiento en el sitio web de Procesamiento.

4.8 Envío de valores binarios desde Processing a Arduino

Problema

Quieres enviar bytes binarios, enteros o valores largos desde Processing a Arduino. Por ejemplo, quieres enviar un mensaje formado por un identificador de mensaje "tag" y dos valores de 16 bits.

Solución

Utiliza este código:

// Processing Sketch

/* SendingBinaryToArduino
 * Language: Processing
 */
import processing.serial.*;

Serial myPort;  // Create object from Serial class

// WARNING!
// If necessary change the definition below to the correct port
short portIndex = 0;  // select the com port, 0 is the first port

public static final char HEADER    = 'H';
public static final char MOUSE_TAG = 'M';

void setup()
{
  size(512, 512);
  String portName = Serial.list()[portIndex];
  println((Object[]) Serial.list());
  myPort = new Serial(this, portName, 9600);
}

void draw(){
}

void serialEvent(Serial p) {
  // handle incoming serial data
  String inString = myPort.readStringUntil('\n');
  if(inString != null) {
    print( inString );   // print text string from Arduino
  }
}

void mousePressed() {
  sendMessage(MOUSE_TAG, mouseX, mouseY);
}

void sendMessage(char tag, int x, int y){
  // send the given index and value to the serial port
  myPort.write(HEADER);
  myPort.write(tag);
  myPort.write((char)(x / 256)); // msb
  myPort.write(x & 0xff);  //lsb
  myPort.write((char)(y / 256)); // msb
  myPort.write(y & 0xff);  //lsb
}
Consejo

Asegúrate de que configuras portIndex para que se corresponda con el puerto serie al que está conectado Arduino. Puede que tengas que ejecutar el boceto una vez, obtener un error y revisar la lista de puertos serie en la consola de Procesamiento, en la parte inferior de la pantalla, para determinar qué valor debes utilizar para portIndex.

Cuando se pulse el ratón en la ventana de Procesamiento, se llamará a sendMessage con la etiqueta de 8 bits que indica que se trata de un mensaje de ratón y las dos coordenadas de ratón de 16 bits x y y. La función sendMessage envía los valores de 16 bits x y y como dos bytes, con el byte más significativo en primer lugar.

Este es el código de Arduino para recibir estos mensajes y enviar los resultados a Processing:

// BinaryDataFromProcessing
// These defines must mirror the sending program:
const char HEADER       = 'H';
const char MOUSE_TAG    = 'M';
const int  TOTAL_BYTES  = 6  ; // the total bytes in a message

void setup()
{
  Serial.begin(9600);
}

void loop(){
  if ( Serial.available() >= TOTAL_BYTES)
  {
    if( Serial.read() == HEADER)
    {
      char tag = Serial.read();
      if(tag == MOUSE_TAG)
      {
        int x = Serial.read() * 256;
        x = x + Serial.read();
        int y = Serial.read() * 256;
        y = y + Serial.read();
        Serial.println("Got mouse msg:");
        Serial.print("x=");    Serial.print(x);
        Serial.print(", y="); Serial.println(y);
      }
      else
      {
        Serial.print("Unknown tag: ");
        Serial.write(tag); Serial.println();
      }
    }
  }
}

Debate

El código de Procesamiento envía un byte de cabecera para indicar que sigue un mensaje válido. Esto es necesario para que Arduino pueda sincronizarse si se inicia en medio de un mensaje o si la conexión serie puede perder datos, como ocurre con un enlace inalámbrico. La etiqueta proporciona una comprobación adicional de la validez del mensaje y permite gestionar individualmente cualquier otro tipo de mensaje que quieras enviar. En este ejemplo, se llama a la función con tres parámetros: una etiqueta y las coordenadas del ratón de 16 bits x y y.

El código Arduino comprueba que se han recibido al menos TOTAL_BYTES, asegurándose de que el mensaje no se procesa hasta que se dispone de todos los datos necesarios. Tras comprobar la cabecera y la etiqueta, los valores de 16 bits se leen como dos bytes, y el primero se multiplica por 256 para devolver al byte más significativo su valor original.

Si quisieras enviar la salida serie de Arduino a otro dispositivo, como una pantalla LCD de caracteres serie, podrías utilizar un puerto SoftwareSerial o uno de los puertos serie adicionales de tu placa, como se muestra en la Receta 4.11. Tendrías que inicializar el puerto serie en setup, y cambiar todas las sentencias Serial.write y Serial.print/println para utilizar ese puerto serie. Por ejemplo, los siguientes cambios enviarían datos serie al pin 1 TX Serial1 del Arduino WiFi Rev 2, Leonardo y la mayoría de los compatibles Arduino basados en ARM. Primero añadirías esto a setup:

Serial1.begin(9600);

Y cambia el código print/println/write al final de loop como se muestra:

Serial1.println();
Serial1.println("Got mouse msg:");
Serial1.print("x=");   Serial1.print(x);
Serial1.print(", y="); Serial1.print(y);

y:

Serial1.println();
Serial1.print("Unknown tag: ");
Serial1.write(tag); Serial1.println();
Advertencia

El lado emisor y el lado receptor deben utilizar el mismo tamaño de mensaje para que los mensajes binarios se manejen correctamente. Si quieres aumentar o disminuir el número de bytes a enviar, cambia TOTAL_BYTES en el código Arduino para que coincida.

4.9 Enviar los valores de varios pines de Arduino

Problema

Quieres enviar grupos de bytes binarios, enteros o valores largos desde Arduino. Por ejemplo, puedes querer enviar los valores de los pines digitales y analógicos a Processing.

Solución

Esta receta envía una cabecera seguida de un entero que contiene los valores en bits de los pines digitales 2 a 13. Le siguen seis enteros que contienen los valores de los pines analógicos del 0 al 5. El Capítulo 5 tiene muchas recetas que establecen valores en los pines analógicos y digitales que puedes utilizar para probar este boceto:

/*
 * SendBinaryFields
 * Sends digital and analog pin values as binary data
 */

const char HEADER = 'H';  // a single character header to indicate
                          // the start of a message

void setup()
{
  Serial.begin(9600);
  for(int i=2; i <= 13; i++)
  {
    pinMode(i, INPUT_PULLUP);  // set pins 2 through 13 to inputs (with pullups)
  }
}

void loop()
{
  Serial.write(HEADER); // send the header
  // put the bit values of the pins into an integer
  int values = 0;
  int bit = 0;

  for(int i=2; i <= 13; i++)
  {
    bitWrite(values, bit, digitalRead(i));  // set the bit to 0 or 1 depending
                                            // on value of the given pin
    bit = bit + 1;                          // increment to the next bit
  }
  sendBinary(values); // send the integer

  for(int i=0; i < 6; i++)
  {
    values = analogRead(i);
    sendBinary(values); // send the integer
  }
  delay(1000); //send every second
}

// function to send the given integer value to the serial port
void sendBinary(int value)
{
  // send the two bytes that comprise an integer
  Serial.write(lowByte(value));  // send the low byte
  Serial.write(highByte(value)); // send the high byte
}

Debate

El código envía una cabecera (el carácter H), seguido de un entero que contiene los valores de los pines digitales, utilizando la función bitRead para establecer un único bit en el entero que corresponda al valor del pin (ver Capítulo 3). A continuación, envía seis enteros que contengan los valores leídos de los seis puertos analógicos (para más información, consulta el Capítulo 5 ). Todos los valores enteros se envían utilizando sendBinary, introducido en la Receta 4.6. El mensaje tiene una longitud de 15 bytes: un byte para la cabecera, dos bytes para los valores de las patillas digitales y 12 bytes para los seis enteros analógicos. El código para las entradas digitales y analógicas se explica en el Capítulo 5.

Suponiendo que los pines analógicos tienen valores de 0 en el pin 0, 100 en el pin 1, y 200 en el pin 2 hasta 500 en el pin 5, y que los pines digitales del 2 al 7 son altos y del 8 al 13 son bajos, éste es el valor decimal de cada byte que se envía:

72   // the character 'H' - this is the header
     // two bytes in low high order containing bits representing pins 2-13
63   // binary 00111111 :  this indicates that pins 2-7 are high
0    // this indicates that  8-13 are low

     // two bytes for each pin representing the analog value
0    // pin 0's analog value is 0 and this is sent as two bytes
0

100  // pin 1 has a value of 100, sent as a byte of 100 and a byte of 0
0
...
     // pin 5 has a value of 500
244  // the remainder when dividing 500 by 256
1    //  the number of times 500 can be divided by 256

Este código de Processing lee el mensaje e imprime los valores en la consola de Processing :

// Processing Sketch

/*
 * ReceiveMultipleFieldsBinary_P
 *
 * portIndex must be set to the port connected to the Arduino
*/

import processing.serial.*;

Serial myPort;        // Create object from Serial class
short portIndex = 0;  // select the com port, 0 is the first port

char HEADER = 'H';

void setup()
{
  size(200, 200);
  // Open whatever serial port is connected to Arduino.
  String portName = Serial.list()[portIndex];
  println((Object[]) Serial.list());
  println(" Connecting to -> " + portName);
  myPort = new Serial(this, portName, 9600);
}

void draw()
{
int val;

  if ( myPort.available() >= 15)  // wait for the entire message to arrive
  {
    if( myPort.read() == HEADER) // is this the header
    {
      println("Message received:");
      // header found
      // get the integer containing the bit values
      val = readArduinoInt();
      // print the value of each bit
      for(int pin=2, bit=1; pin <= 13; pin++){
        print("digital pin " + pin + " = " );
        int isSet = (val & bit);
        if( isSet == 0) {
          println("0");
        }
        else{
          println("1");
        }
        bit = bit * 2; //shift the bit to the next higher binary place
      }
      println();
      // print the six analog values
      for(int i=0; i < 6; i ++){
        val = readArduinoInt();
        println("analog port " + i + "=" + val);
      }
      println("----");
    }
  }
}

// return integer value from bytes received from serial port (in low,high order)
int readArduinoInt()
{
  int val;      // Data received from the serial port

  val = myPort.read();              // read the least significant byte
  val = myPort.read() * 256 + val;  // add the most significant byte
  return val;
}
Consejo

Asegúrate de que configuras portIndex para que se corresponda con el puerto serie al que está conectado Arduino. Puede que tengas que ejecutar el boceto una vez, obtener un error y revisar la lista de puertos serie en la consola de Procesamiento, en la parte inferior de la pantalla, para determinar qué valor debes utilizar para portIndex.

El código de Procesamiento espera a que lleguen 15 caracteres. Si el primer carácter es la cabecera, entonces llama a la función llamada readArduinoInt para leer dos bytes y transformarlos de nuevo en un entero haciendo la operación matemática complementaria que realizaba Arduino para obtener los bits individuales que representan los pines digitales. Los seis enteros representan entonces los valores analógicos. Ten en cuenta que todos los pines digitales tendrán por defecto el valor 1 (HIGH). Esto se debe a que se han activado los pull-ups en ellos mediante INPUT_PULLUP. Esto significa que si tuvieras un botón conectado a ellos, un valor de 1 indica que el botón no está pulsado, mientras que 0 indica que está pulsado. La receta 2.4 contiene una discusión sobre este modo.

Ver también

Para enviar valores de Arduino al ordenador o manejar los pines desde el ordenador (sin tomar decisiones en la placa), considera la posibilidad de utilizar Firmata. La biblioteca Firmata y los bocetos de ejemplo (Archivo→Ejemplos→Firmata) se incluyen en la distribución del software Arduino, y hay una biblioteca disponible para utilizar en Processing. Cargas el código Firmata en Arduino, controlas si los pines son entradas o salidas del ordenador, y luego configuras o lees esos pines.

4.10 Registrar los datos de Arduino en un archivo de tu ordenador

Problema

Quieres crear un archivo que contenga la información recibida a través del puerto serie desde Arduino. Por ejemplo, quieres guardar los valores de los pines digitales y analógicos a intervalos regulares en un archivo de registro.

Solución

Ya hemos tratado el envío de información desde Arduino a tu ordenador en recetas anteriores. Esta solución utiliza el mismo código de Arduino explicado en la Receta 4.9. El sketch de Processing que gestiona el registro de archivos se basa en el sketch de Processing descrito también en esa receta.

Este sketch de Processing crea un archivo (utilizando la fecha y hora actuales como nombre de archivo) en el mismo directorio que el sketch de Processing. Los mensajes recibidos de Arduino se añaden al archivo. Pulsando cualquier tecla se guarda el archivo y se sale del programa:

/*
 * ReceiveMultipleFieldsBinaryToFile_P
 *
 * portIndex must be set to the port connected to the Arduino
 * based on ReceiveMultipleFieldsBinary, this version saves data to file
 * Press any key to stop logging and save file
 */

import processing.serial.*;
import java.util.*;
import java.text.*;

PrintWriter output;
DateFormat fnameFormat = new SimpleDateFormat("yyMMdd_HHmm");
DateFormat  timeFormat = new SimpleDateFormat("hh:mm:ss");
String fileName;

Serial myPort;        // Create object from Serial class
short portIndex = 0;  // select the com port, 0 is the first port
char HEADER = 'H';

void setup()
{
  size(200, 200);
  // Open whatever serial port is connected to Arduino.
  String portName = Serial.list()[portIndex];
  println((Object[]) Serial.list());
  println(" Connecting to -> " + portName);
  myPort = new Serial(this, portName, 9600);
  Date now = new Date();
  fileName = fnameFormat.format(now);
  output = createWriter(fileName + ".txt"); // save the file in the sketch folder
}

void draw()
{
  int val;

  if ( myPort.available() >= 15)  // wait for the entire message to arrive
  {
    if( myPort.read() == HEADER) // is this the header
    {
      String timeString = timeFormat.format(new Date());
      println("Message received at " + timeString);
      output.println(timeString);

      // get the integer containing the bit values
      val = readArduinoInt();
      // print the value of each bit
      for (int pin=2, bit=1; pin <= 13; pin++){
        print("digital pin " + pin + " = " );
        output.print("digital pin " + pin + " = " );
        int isSet = (val & bit);
        if (isSet == 0){
           println("0");
           output.println("0");
        }
        else
        {
          println("1");
          output.println("1");
        }
        bit = bit * 2; // shift the bit
      }
      // print the six analog values
      for (int i=0; i < 6; i ++){
        val = readArduinoInt();
        println("analog port " + i + "=" + val);
        output.println("analog port " + i + "=" + val);
      }
      println("----");
      output.println("----");
    }
  }
}

void keyPressed() {
  output.flush(); // Writes the remaining data to the file
  output.close(); // Finishes the file
  exit(); // Stops the program
}

// return the integer value from bytes received on the serial port
// (in low,high order)
int readArduinoInt()
{
  int val;      // Data received from the serial port

  val = myPort.read();             // read the least significant byte
  val = myPort.read() * 256 + val; // add the most significant byte
  return val;
}

No olvides que tienes que establecer portIndex al puerto serie conectado a Arduino. Si eliges un valor incorrecto para portIndex, revisa la salida inicial del boceto de Processing donde imprime la lista de puertos serie disponibles y elige el correcto.

Debate

El nombre base del archivo de registro se forma utilizando la función DateFormat de Processing:

DateFormat fnameFormat= new SimpleDateFormat("yyMMdd_HHmm");

El nombre completo del archivo se crea con un código que añade un directorio y una extensión de archivo:

output = createWriter(fileName + ".txt");

Para crear el archivo y salir del esbozo, puedes pulsar cualquier tecla mientras esté activa la ventana principal del esbozo de Processing. No pulses la tecla Escape porque terminará el esbozo sin guardar el archivo. El archivo se creará en el mismo directorio que el sketch de Processing (es necesario guardar el sketch al menos una vez para asegurarse de que el directorio existe). Para encontrar este directorio, elige Sketch→ShowSketch Folder en Processing.

createWriter es la función de Processing que abre el archivo; ésta crea un objeto (una unidad de funcionalidad en tiempo de ejecución) llamado que se encarga de la salida real del archivo. El texto que se escribe en el archivo es el mismo que se imprime en la consola en output la Receta 4.9, pero puedes formatear el contenido del archivo como desees utilizando las capacidades estándar de tratamiento de cadenas de Processing. Por ejemplo, la siguiente variación de la rutina produce un archivo separado por comas que puede ser leído por una hoja de cálculo o una base de datos. El resto del esquema de Processing puede ser el mismo, aunque quizá quieras cambiar la extensión de draw .txt a .csv:

void draw()
{
  int val;

  if (myPort.available() >= 15)  // wait for the entire message to arrive
  {
    if (myPort.read() == HEADER) // is this the header
    {
      String timeString = timeFormat.format(new Date());
      output.print(timeString);

      val = readArduinoInt();
      // print the value of each bit
      for (int pin=2, bit=1; pin <= 13; pin++){
        int isSet = (val & bit);
        if (isSet == 0){
           output.print(",0");
        }
        else
        {
          output.print(",1");
        }
        bit = bit * 2; // shift the bit
      }

      // output the six analog values delimited by a comma
      for (int i=0; i < 6; i ++){
        val = readArduinoInt();
        output.print("," + val);
      }
      output.println();
    }
  }
}

Ver también

Para más información sobre createWriter, consulta la página de Processing. Processing también incluye el objetoTable para crear, manipular y guardar archivos CSV.

4.11 Enviar datos a más de un dispositivo serie

Problema

Quieres enviar datos a un dispositivo serie, como una pantalla LCD serie, pero ya estás utilizando el puerto serie a USB incorporado para comunicarte con tu ordenador.

Solución

En una placa con más de un puerto serie (consulta la introducción para ver algunas sugerencias) esto no es un problema; en primer lugar, tendrás que conectar la placa al dispositivo serie como se muestra en la Figura 4-5. A continuación, puedes inicializar dos puertos serie y utilizar Serial para la conexión con tu ordenador, y el otro (normalmente Serial1) para el dispositivo:

void setup() {
  // initialize two serial ports on a board that supports this
  Serial.begin(9600);  // primary serial port
  Serial1.begin(9600); // Some boards have even more serial ports
}
Conectar un dispositivo serie al pin de transmisión de un puerto serie integrado
Advertencia

Si utilizas un Arduino o una placa compatible con Arduino que funcione a 3,3 V (como una placa basada en SAMD), puedes transmitir con seguridad a un dispositivo que utilice 5 V. Pero si utilizas una placa que funciona a 5V (como una Uno) con un dispositivo que utiliza 3,3V, acabarás dañando el dispositivo a menos que incorpores un divisor de tensión en el circuito para bajar la tensión. Consulta las Recetas 4.13 y 5.11 para ver ejemplos de divisores de tensión.

En una placa Arduino basada en ATmega328 (o similar), como la Uno, que sólo tiene un puerto serie hardware, tendrás que crear un puerto serie emulado o "blando" utilizando la biblioteca SoftwareSerial.

Selecciona dos patillas digitales disponibles, una para transmisión y otra para recepción, y conecta a ellas tu dispositivo serie. Conecta la línea de transmisión del dispositivo a la patilla de recepción y la línea de recepción a la patilla de transmisión. Para situaciones en las que sólo envías datos, como cuando visualizas caracteres en una pantalla LCD serie, sólo tienes que conectar la patilla de transmisión (TX) a la patilla de recepción (RX) del dispositivo, como se muestra en la Figura 4-6, donde hemos seleccionado la patilla 3 como patilla de transmisión.

Conectar un dispositivo serie a un puerto serie "blando

En tu sketch, crea un objeto SoftwareSerial e indícale qué pines has elegido como puerto serie emulado. En este ejemplo, estamos creando un objeto llamado serial_lcd, al que le indicamos que utilice los pines 2 y 3. Aunque no vamos a recibir ningún dato de esta conexión serie, necesitamos especificar un pin de recepción, por lo que no debes utilizar el pin 2 para otra cosa cuando utilices el puerto SoftwareSerial:

/*
 * SoftwareSerialOutput sketch
 * Output data to a software serial port
 */

#include <SoftwareSerial.h>

const int rxpin = 2;           // pin used to receive (not used in this version)
const int txpin = 3;           // pin used to send to LCD
SoftwareSerial serial_lcd(rxpin, txpin); // new serial port on pins 2 and 3

void setup()
{
  Serial.begin(9600); // 9600 baud for the built-in serial port
  serial_lcd.begin(9600); //initialize the software serial port also for 9600
}

int number = 0;

void loop()
{
  serial_lcd.print("Number: ");  // send text to the LCD
  serial_lcd.println(number);    // print the number on the LCD
  Serial.print("Number: ");
  Serial.println(number);        // print the number on the PC console

  delay(500); // delay half second between numbers
  number++;   // to the next number
}

Para utilizar el sketch con un puerto serie hardware incorporado, conecta los pines como se muestra en la Figura 4-5, y luego elimina estas líneas:

#include <SoftwareSerial.h>
const int rxpin = 2;
const int txpin = 3;
SoftwareSerial serial_lcd(rxpin, txpin);

Por último, añade esta línea en su lugar: #define serial_gps Serial1 (cambia Serial1 según sea necesario si utilizas un puerto diferente).

Nota

Algunas de las placas que admiten varios puertos serie de hardware, como Leonardo, Mega y Mega 2560, tienen restricciones sobre los pines que puedes utilizar para la recepción SoftwareSerial (RX). Aunque aquí no estamos utilizando la capacidad de recepción, y aunque lo más probable es que utilices los pines serie de hardware para Serial1 en esas placas (consulta la Tabla 4-1), debes tener en cuenta que esas placas no admiten la capacidad RX en el pin 2, por lo que si intentaras leer desde una conexión serie de software en una de esas placas, tendrías que utilizar un pin admitido. La receta 4.12 utiliza patillas RX que funcionarán en una amplia variedad de placas.

Este esquema supone que se ha conectado una pantalla LCD serie a la patilla 3, como se muestra en la Figura 4-6, y que se ha conectado una consola serie al puerto incorporado. El bucle mostrará repetidamente el mismo mensaje en cada una de ellas:

Number: 0
Number: 1
...

Debate

El microcontrolador Arduino contiene al menos un puerto serie hardware integrado. En el Arduino Uno, este puerto está conectado a la conexión serie USB, y también está conectado a los pines 0 (recepción) y 1 (transmisión), lo que te permite conectar al Arduino un dispositivo como una pantalla serie LCD. Los caracteres que transmitas a través del objeto Serial se mostrarán en la pantalla LCD.

Nota

Aunque puedes utilizar una fuente de alimentación independiente para el dispositivo serie, debes conectar la patilla de masa del Arduino a la patilla del dispositivo, dando así al Arduino y al dispositivo serie una masa común. En la Solución, hicimos esto, pero también utilizamos la salida de 5 V del Arduino para alimentar el dispositivo.

Además de la conexión serie USB integrada, algunas placas admiten una o más conexiones serie directas. En estas placas, los pines 0 y 1 suelen estar vinculados al objeto Serial1, lo que te permite mantener una conexión serie USB con tu ordenador mientras intercambias datos con el dispositivo en los pines 0 y 1. Algunas placas admiten puertos serie adicionales en un conjunto diferente de pines (consulta la Tabla 4-1 para ver una tabla de los puertos serie disponibles en varias placas). Todos los pines que admiten entrada y salida serie, además de ser pines digitales de uso general, están respaldados por el hardware receptor-transmisor asíncrono universal (UART) que lleva incorporado el chip. Esta pieza especial de hardware es responsable de generar la serie de pulsos sincronizados con precisión que su dispositivo asociado ve como datos y de interpretar el flujo similar que recibe a cambio.

Aunque las placas basadas en ARM SAMD (placas M0 y M4) tienen dos puertos serie soportados por hardware y la Mega tiene cuatro puertos de este tipo, el Arduino Uno y la mayoría de las placas similares basadas en el ATmega328 sólo tienen uno. En el Uno y placas similares, necesitarás una biblioteca de software que emule los puertos adicionales para proyectos que requieran conexiones a dos o más dispositivos serie. Una biblioteca "software serie" convierte de hecho un par arbitrario de patillas digitales de E/S en un nuevo puerto serie.

Para construir tu puerto serie software, selecciona un par de pines que actuarán como líneas de transmisión y recepción del puerto, de forma muy parecida a como un puerto serie hardware utiliza sus pines asignados. En la Figura 4-6, se muestran los pines 3 y 2, pero se puede utilizar cualquier pin digital disponible, con algunas excepciones para determinadas placas. Es aconsejable evitar el uso de 0 y 1, porque ya están siendo controlados por el puerto integrado.

La sintaxis para escribir en el puerto blando es idéntica a la del puerto hardware. En el sketch de ejemplo, los datos se envían tanto al puerto "real" como al emulado utilizando print() y println():

serial_lcd.print("Number: ");  // send text to the LCD
serial_lcd.println(number);    // print the number on the LCD
Serial.print("Number: ");
Serial.println(number);        // print the number on the PC console
Consejo

Si el texto combinado ("Number: ") y el número en sí son más largos que la anchura de tu pantalla LCD serie, la salida puede truncarse o salirse de la pantalla. Muchos visualizadores LCD de caracteres tienen dos filas de 20 caracteres cada una.

Ver también

Nick Gammon mantiene una versión de SoftwareSerial sólo para transmisión que te permite evitar la necesidad de asignar un pin para recibir datos cuando no lo necesitas.

4.12 Recibir datos serie de más de un dispositivo serie

Problema

Quieres recibir datos de un dispositivo serie, como un GPS serie, pero ya estás utilizando el puerto serie a USB incorporado para comunicarte con tu ordenador.

Solución

En una placa con más de un puerto serie (consulta la introducción para ver algunas sugerencias) esto no es un problema; primero, tendrás que conectar la placa al dispositivo serie como se muestra en la Figura 4-7. A continuación, puedes inicializar dos puertos serie y utilizar Serial para la conexión con tu ordenador, y el otro (normalmente Serial1) para el dispositivo:

void setup() {
  // initialize two serial ports on a board that supports this
  Serial.begin(9600);  // primary serial port
  Serial1.begin(9600); // Some boards have even more serial ports
}
Conectar un dispositivo serie al pin de recepción de un puerto serie integrado
Advertencia

Si utilizas un Arduino o una placa compatible con Arduino que funcione a 5 V (como un Uno), puedes recibir con seguridad datos de un dispositivo que utilice 3,3 V. Pero si utilizas una placa que funciona a 3,3V (la mayoría de las placas basadas en ARM) con un dispositivo que utiliza niveles lógicos de 5V, acabarás dañando tu placa a menos que incorpores un divisor de tensión al circuito para reducir la tensión. Consulta las Recetas 4.13 y 5.11 para ver ejemplos de divisores de tensión.

En el Arduino Uno y otras placas basadas en el ATmega328, que sólo tiene un puerto serie por hardware, tendrás que crear un puerto serie emulado o "blando" utilizando la biblioteca SoftwareSerial. Estarás limitado a velocidades de transferencia más lentas que con un puerto serie hardware integrado.

Este problema es similar al de la Receta 4.11, y de hecho la solución es prácticamente la misma. Si el puerto serie de tu Arduino está conectado a la consola y quieres conectar un segundo dispositivo serie, debes crear un puerto emulado utilizando una biblioteca serie de software. En este caso, recibiremos datos del puerto emulado en lugar de escribir en él, pero la solución básica es muy similar.

Selecciona dos pines para utilizarlos como líneas de transmisión y recepción. Esta Solución utiliza los pines 8 y 9 porque algunas placas (como la Arduino Leonardo) sólo admiten la recepción SoftwareSerial en los pines 8, 9, 10, 11 y 14. También son los pines que admiten el Arduino Mega y el Mega 2560 para la recepción SoftwareSerial. En la práctica, probablemente utilizarías el hardware serie disponible en los pines 0 y 1 (Serial1) de estas placas (las placas Mega tienen pines que admiten hardware serie como Serial2 y Serial3). Pero hemos elegido los pines SoftwareSerial que funcionarían en la gama más amplia posible de placas, por si decides probar este código en alguna de ellas.

Conecta tu GPS como se muestra en la Figura 4-8.

Conectar un dispositivo GPS serie a un puerto serie "blando

Como hiciste en la Receta 4.11, crea un objeto SoftwareSerial en tu sketch y dile qué pines debe controlar. En el siguiente ejemplo, definimos un puerto serie blando llamado serial_gps, utilizando los pines 8 y 9 para recibir y transmitir, respectivamente. Aunque no vayamos a enviar ningún dato a este dispositivo serie, necesitamos especificar un pin de transmisión, por lo que no debes utilizar el pin 9 para otra cosa cuando utilices el puerto serie software:

Consejo

Para utilizar el siguiente código con un puerto serie hardware incorporado, conecta las patillas como se muestra en la Figura 4-7, y luego elimina estas líneas:

#include <SoftwareSerial.h>
const int rxpin = 8;
const int txpin = 9;
SoftwareSerial serial_gps(rxpin, txpin);

Por último, añade esta línea en su lugar (cambia Serial1 si utilizas un puerto diferente):

#define serial_gps Serial1
/*
 * SoftwareSerialInput sketch
 * Read data from a software serial port
 */

#include <SoftwareSerial.h>
const int rxpin = 8;                     // pin used to receive from GPS
const int txpin = 9;                     // pin used to send to GPS
SoftwareSerial serial_gps(rxpin, txpin); // new serial port on these pins

void setup()
{
  Serial.begin(9600); // 9600 baud for the built-in serial port
  serial_gps.begin(9600); // initialize the port, most GPS devices
                          // use 9600 bits per second
}

void loop()
{
  if (serial_gps.available() > 0) // any character arrived yet?
  {
    char c = serial_gps.read();   // if so, read it from the GPS
    Serial.write(c);              // and echo it to the serial console
  }
}

Este breve esbozo simplemente envía todos los datos entrantes del GPS al monitor serie del Arduino. Si el GPS funciona y el cableado es correcto, deberías ver los datos del GPS en el monitor serie.

Debate

Inicializa un puerto SoftwareSerial emulado proporcionando los números de pin para transmitir y recibir. El siguiente código configurará el puerto para recibir en la patilla 8 y enviar en la patilla 9:

const int rxpin = 8;                     // pin used to receive from GPS
const int txpin = 9;                     // pin used to send to GPS
SoftwareSerial serial_gps(rxpin, txpin); // new serial port on these pins

La sintaxis para leer un puerto emulado es muy similar a la que se utiliza para leer de un puerto incorporado. Primero comprueba que ha llegado un carácter desde el GPS con available(), y luego léelo con read().

Es importante recordar que los puertos serie de software consumen tiempo y recursos. Un puerto serie emulado debe hacer todo lo que hace un puerto hardware, utilizando el mismo procesador con el que tu boceto está intentando hacer "trabajo real". Cada vez que llega un nuevo carácter, el procesador debe interrumpir lo que esté haciendo para manejarlo. Esto puede llevar mucho tiempo. A 4.800 baudios, por ejemplo, el Arduino tarda unos 2 ms en procesar un solo carácter. Aunque 2 ms puede no parecer mucho, ten en cuenta que si tu dispositivo conectado transmite de 200 a 250 caracteres por segundo, tu boceto está empleando entre el 40 y el 50% de su tiempo intentando seguir el ritmo de la entrada serie. Esto deja muy poco tiempo para procesar realmente todos esos datos. La lección es que si tienes dos dispositivos serie, cuando sea posible conecta el que consuma más ancho de banda al puerto integrado (hardware). Si debes conectar un dispositivo de gran ancho de banda a un puerto serie de software, asegúrate de que el resto del bucle de tu boceto es muy eficiente.

Recibir datos de varios puertos SoftwareSerial

Con la biblioteca SoftwareSerial incluida en Arduino, es posible crear varios puertos serie "blandos" en el mismo boceto. Es una forma útil de controlar, por ejemplo, varias radios XBee (ver Receta 14.2) o pantallas serie en el mismo proyecto. La advertencia es que, en un momento dado, sólo uno de estos puertos puede recibir datos activamente. Una comunicación fiable en un puerto de software requiere toda la atención del procesador. Por eso SoftwareSerial sólo puede comunicarse activamente con un puerto en un momento dado.

Es posible recibir en dos puertos SoftwareSerial diferentes en el mismo sketch. Sólo tienes que tener cuidado de no intentar recibir de ambos simultáneamente. Hay muchos diseños exitosos que, por ejemplo, monitorean un dispositivo GPS serie durante un tiempo, y luego aceptan la entrada de un XBee. La clave está en alternar lentamente entre ellos, cambiando a un segundo dispositivo sólo cuando finalice una transmisión del primero.

Por ejemplo, en el esquema siguiente, hay un módulo XBee conectado al Arduino. El módulo recibe órdenes de un dispositivo remoto conectado a otro módulo XBee. El sketch escucha el flujo de comandos a través del puerto "xbee" hasta que recibe la señal para empezar a recopilar datos de un módulo GPS conectado a un segundo puerto SoftwareSerial. A continuación, el sketch monitorea el GPS durante 10 segundos -esperemos que el tiempo suficiente para establecer un "punto fijo"- antes de volver al XBee.

En un sistema con varios puertos "blandos", sólo uno recibe datos activamente. Por defecto, el puerto "activo" es aquel para el que se ha llamado más recientemente a begin(). Sin embargo, puedes cambiar qué puerto está activo llamando a su método listen() . listen() indica al sistema SoftwareSerial que deje de recibir datos en un puerto y comience a escuchar datos en otro.

Nota

Como el siguiente ejemplo sólo recibe datos, puedes elegir cualquier pin para txpin1 y txpin2. Si necesitas utilizar los pines 9 y 11 para otra cosa, puedes cambiar txpin1/2 a otro pin. Te recomendamos que no lo cambies por un número de pin inexistente, porque podría provocar algún comportamiento inusual.

El siguiente fragmento de código ilustra cómo podrías diseñar un boceto para leer primero de un puerto y luego de otro:

/*
 * MultiRX sketch
 * Receive data from two software serial ports
 */
#include <SoftwareSerial.h>
const int rxpin1 = 8;
const int txpin1 = 9;
const int rxpin2 = 10;
const int txpin2 = 11;

SoftwareSerial gps(rxpin1, txpin1);  // gps TX pin connected to Arduino pin 9
SoftwareSerial xbee(rxpin2, txpin2); // xbee TX pin connected to Arduino pin 10

void setup()
{
  Serial.begin(9600);
  xbee.begin(9600);
  gps.begin(9600);
  xbee.listen(); // Set “xbee” to be the active device
}

void loop()
{
  if (xbee.available() > 0) // xbee is active. Any characters available?
  {
    if (xbee.read() == 'y') // if xbee received a 'y' character?
    {
      gps.listen(); // now start listening to the gps device

      unsigned long start = millis(); // begin listening to the GPS
      while (start + 100000 > millis()) // listen for 10 seconds
      {
        if (gps.available() > 0) // now gps device is active
        {
          char c = gps.read();
          Serial.write(c);              // echo it to the serial console
        }
      }
      xbee.listen(); // After 10 seconds, go back to listening to the xbee
    }
  }
}

Este sketch está diseñado para tratar la radio XBee como el puerto activo hasta que recibe un caráctery , momento en el que el GPS se convierte en el dispositivo de escucha activo. Tras procesar los datos del GPS durante 10 segundos, el sketch reanuda la escucha en el puerto XBee. Los datos que llegan a un puerto inactivo simplemente se descartan.

Ten en cuenta que la restricción de "puerto activo" sólo se aplica a múltiples puertos blandos. Si tu diseño realmente debe recibir datos de más de un dispositivo serie simultáneamente, considera la posibilidad de utilizar una placa, como la Teensy , que admita varios puertos serie (la Teensy 4.0 admite siete en total). La Tabla 4-1 muestra los pines utilizados para los puertos serie en varias placas Arduino y compatibles con Arduino.

Ver también

Si quieres ir más allá con un GPS y analizar los mensajes que recibas, consulta la Receta 6.14. Si quieres una alternativa a SoftwareSerial que admita varios dispositivos de forma más robusta, consulta la biblioteca AltSoftSerial.

4.13 Utilizar Arduino con la Raspberry Pi

Problema

Quieres utilizar las capacidades de Arduino junto con la potencia de procesamiento de un ordenador Linux monoplaca como la Raspberry Pi. Por ejemplo, quieres enviar comandos a Arduino desde un script que se ejecuta en el Pi.

Solución

Arduino puede monitorizar y responder a comandos serie de la Raspberry Pi. Este código controla los LED de Arduino desde scripts de Python que se ejecutan en la Pi.

Nota

También es posible conectar Arduino a una Raspberry Pi utilizando uno de los puertos USB de la Raspberry Pi. De hecho, incluso puedes ejecutar el IDE de Arduino en la Raspberry Pi. Descarga una de las versiones ARM. En el momento de escribir esto, el sistema operativo de la Raspberry Pi, Raspbian, funcionaba en modo de 32 bits, por lo que deberías elegir la versión de 32 bits a menos que estés ejecutando un sistema operativo de 64 bits.

Conecta la patilla de recepción serie del Arduino (patilla 0 marcada como RX en la placa) a la patilla 8 de la cabecera de la Pi. Conecta el pin 1 TX del Arduino al pin 10 GPIO de la Pi. La patilla de tierra (GND) del Arduino se conecta a cualquiera de las patillas de tierra de la Pi (en la Figura 4-9 se utiliza la patilla 14).

Placa Arduino conectada a Raspberry Pi
Nota

Lo siguiente es para el Arduino Uno y cualquier Arduino compatible que tenga un único puerto serie compartido entre la conexión serie USB y los pines RX/TX. Si utilizas una placa con un puerto serie de hardware adicional, como la Leonardo, la WiFi Rev2, la Nano Every o cualquier placa basada en ARM, cambia #define mySerial Serial por #define mySerial Serial1, y si tu placa no utiliza los pines 0 y 1 para RX y TX, utiliza los pines adecuados para Serial1 (consulta la Tabla 4-1).

Aquí tienes un esquema de Arduino que monitoriza los mensajes serie de la Pi. Cárgalo en la placa Arduino:

/*
 * ArduinoPi sketch
 * Pi control Arduino pins using serial messages
 * format is: Pn=state
 * where 'P' is header character, n is pin number, state is 0 or 1
 * example: P13=1  turns on pin 13
 */

// Replace Serial with Serial1 on boards with an additional serial port
#define mySerial Serial

void setup()
{
  mySerial.begin(9600); // Initialize serial port to send/receive at 9600 baud
}

void loop()
{
  if (mySerial.available()) // Check whether at least one character is available
  {
    char ch = mySerial.read();
    int pin = -1;
    if( ch == 'P') // is this the beginning of a message to set a pin?
    {
      pin = mySerial.parseInt();   // get the pin number
    }
    else if (ch == 'B') // Message to set LED_BUILTIN
    {
      pin = LED_BUILTIN;
    }

    if( pin > 1) { // 0 and 1 are usually serial pins (leave them alone)
      int state = mySerial.parseInt(); // 0 is off, 1 is on
      pinMode(pin, OUTPUT);
      digitalWrite(pin, state);
    }

  }
}

Guarda el siguiente script de Python como blinkArduino.py en el Pi, y ejecútalo con python blinkArduino.py. El script hará parpadear el LED integrado en la placa Arduino. Debes tener instalada la biblioteca python-serial antes de ejecutarlo. Puedes instalarla en la Raspberry Pi con sudo apt-get install python-serial:

#!/usr/bin/env python

import serial
from time import sleep

ser = serial.Serial('/dev/serial0', 9600)
ser.write('P13=1')
sleep(1)
ser.write('P13=0')

Cuando se ejecuta el script, debe encenderse un LED en la patilla 13 durante un segundo y luego apagarse.

Debate

El sketch de Arduino detecta el inicio de un mensaje cuando se recibe el carácter P. La función Arduino parseInt se utiliza para extraer el número de pin y el estado de pin deseados. Al enviar P13=1 se encenderá el LED de la patilla 13. P13=0 apagará el LED. En el capítulo 4 encontrarás más información sobre los mensajes serie y parseInt. Muchas placas Arduino y compatibles con Arduino utilizan algún pin distinto del 13, así que para ahorrarte la molestia de tener que buscarlo, el boceto de Arduino utilizará la constante LED_BUILTIN como número de pin cuando le envíes un mensaje como B=1 (sin necesidad de número de pin).

El script de Python envía los mensajes adecuados para encender y apagar el LED.

Si el LED integrado de tu placa no está en la patilla 13, utiliza esta versión, que utiliza el comando B para conmutar el LED que esté asignado a LED_BUILTIN:

#!/usr/bin/env python

import serial
from time import sleep

ser = serial.Serial('/dev/serial0', 9600)
ser.write('B=1')
sleep(1)
ser.write('B=0')

Los pines de la Raspberry Pi no son tolerantes a 5 V, por lo que debes utilizar el divisor de tensión que se muestra en el diagrama si vas a conectar una placa compatible con Arduino de 5 V a la Pi. Si utilizas un Arduino de 3,3 V, generalmente es seguro omitir el divisor de tensión, pero el divisor de tensión no le hará daño. Consulta la Receta 5.11 para más detalles sobre los divisores de tensión.

Los mensajes enviados en esta receta son muy sencillos, pero pueden ampliarse para que la Pi controle casi cualquier función de Arduino y para que Arduino envíe información de vuelta a la Pi. Consulta la Receta 4.0 para saber más sobre cómo hacer que Arduino funcione con un ordenador a través del enlace serie.

Puedes encontrar información detallada sobre Python y la Pi en Internet y en libros como Raspberry Pi Cookbook, Third Edition, de Simon Monk.

Get Libro de cocina Arduino, 3ª edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.