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).
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.
Junta | RX/TX serie | Serial1 RX/TX | Serial2 RX/TX | Serial3 RX/TX |
---|---|---|---|---|
Sólo USB |
13/14 |
ninguno |
ninguno |
|
Sólo USB |
0/1 |
Conectado al módulo WiFi |
ninguno |
|
Sólo USB |
0/1 |
ninguno |
ninguno |
|
Sólo USB |
0/1 |
ninguno |
ninguno |
|
Arduino Uno Rev3 |
0/1 (También USB) |
ninguno |
ninguno |
ninguno |
Sólo USB |
0/1 |
ninguno |
ninguno |
|
Sólo USBa |
0/1 |
ninguno |
ninguno |
|
Sólo USB |
0/1 |
ninguno |
ninguno |
|
Sólo USB |
0/1 |
9/10 |
7/8 |
|
PJRC Teensy 4.0 |
Sólo USB |
0/1 |
7/8 |
15/14 |
0/1 (También USB) |
19/18 |
17/16 |
15/14 |
|
0/1 (También USB) |
19/18 |
17/16 |
15/14 |
|
Sólo USB |
0/1 |
ninguno |
ninguno |
|
a Utiliza |
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.
Junta | while(!Serial); ¿se necesita? | ¿Se reinicia cuando se accede a la serie? |
---|---|---|
Sí |
No |
|
No |
Sí |
|
No; Requiere un |
No |
|
Sí |
No |
|
Arduino Uno Rev3 |
No |
Sí |
Sí |
No |
|
Sí |
No |
|
Sí |
No |
|
PJRC Teensy 4.0 |
Sí |
No |
No |
Sí |
|
Sí |
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 deSerial.read
, el carácter no se elimina del búfer conSerial.peek
.
4.1 Enviar información desde Arduino al ordenador
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
.
(
"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.
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.
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
.
(
"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
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
.
(
"chrValue: "
);
Serial
.
(
chrValue
);
Serial
.
(
" "
);
Serial
.
write
(
chrValue
);
Serial
.
(
" "
);
Serial
.
(
chrValue
,
DEC
);
Serial
.
println
();
Serial
.
(
"byteValue: "
);
Serial
.
(
byteValue
);
Serial
.
(
" "
);
Serial
.
write
(
byteValue
);
Serial
.
(
" "
);
Serial
.
(
byteValue
,
DEC
);
Serial
.
println
();
Serial
.
(
"intValue: "
);
Serial
.
(
intValue
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
DEC
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
HEX
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
OCT
);
Serial
.
(
" "
);
Serial
.
(
intValue
,
BIN
);
Serial
.
println
();
Serial
.
(
"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.
Tipo de datos | imprimir (val) | imprimir (val,DEC) | escribir (val) | imprimir (val,HEX) | imprimir (val,OCT) | imprimir (val,BIN) |
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
El formato de |
||||||
|
|
|||||
|
|
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
.
(
"At "
);
Serial
.
(
t
);
Serial
.
(
" seconds: speed = "
);
Serial
.
(
s
);
Serial
.
(
", distance = "
);
Serial
.
println
(
d
);
Son muchas líneas de código para una sola línea de salida. Podrías combinarlas así
Serial
.
(
"At "
);
Serial
.
(
t
);
Serial
.
(
" seconds, speed = "
);
Serial
.
(
s
);
Serial
.
(
", 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 printf
utiliza 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
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
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
.
(
'H'
);
// unique header to identify start of message
Serial
.
(
","
);
Serial
.
(
value1
,
DEC
);
Serial
.
(
","
);
Serial
.
(
value2
,
DEC
);
Serial
.
(
","
);
Serial
.
(
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
.
(
"{"
);
Serial
.
(
"'x': "
);
Serial
.
(
x
);
Serial
.
(
", "
);
Serial
.
(
"'y': "
);
Serial
.
(
y
);
Serial
.
(
", "
);
Serial
.
(
"'z': "
);
Serial
.
(
z
);
Serial
.
(
", "
);
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.
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
.
(
"{"
);
Serial
.
(
"'A0': "
);
Serial
.
(
analogRead
(
A0
));
Serial
.
(
", "
);
Serial
.
(
"'A1': "
);
Serial
.
(
analogRead
(
A1
));
Serial
.
(
", "
);
Serial
.
(
"'A2': "
);
Serial
.
(
analogRead
(
A2
));
Serial
.
(
", "
);
Serial
.
(
"'A3': "
);
Serial
.
(
analogRead
(
A3
));
Serial
.
(
", "
);
Serial
.
(
"'A4': "
);
Serial
.
(
analogRead
(
A4
));
Serial
.
(
", "
);
Serial
.
(
"'A5': "
);
Serial
.
(
analogRead
(
A5
));
Serial
.
(
", "
);
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
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
.
(
values
[
0
]);
// First value
// Print the rest of the values with a leading comma
for
(
int
i
=
1
;
i
<
NUMBER_OF_FIELDS
;
i
++
)
{
Serial
.
(
","
);
Serial
.
(
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 defalse
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álisisStream
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étodofindUntil
). bool findUntil(char *target, char *terminate);
-
Similar al método
find
, pero la búsqueda se detendrá si se encuentra la cadenaterminate
. Devuelvetrue
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 devuelve0
. long parseInt(char skipChar);
-
Igual que
parseInt
, pero se ignora elskipChar
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
deparseInt
. 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 quelength
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
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
.
(
'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
olong
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 utilizaslowByte
yhighByte
para descifrar un entero, controlas el orden en que se envían los bytes. Pero cuando envías unstruct
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 valorint
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
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
)
{
(
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
.
(
"x="
);
Serial
.
(
x
);
Serial
.
(
", y="
);
Serial
.
println
(
y
);
}
else
{
Serial
.
(
"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
.
(
"x="
);
Serial1
.
(
x
);
Serial1
.
(
", y="
);
Serial1
.
(
y
);
y:
Serial1
.
println
();
Serial1
.
(
"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
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
++
){
(
"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
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
++
){
(
"digital pin "
+
pin
+
" = "
);
output
.
(
"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
.
(
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
.
(
",0"
);
}
else
{
output
.
(
",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
.
(
","
+
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
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
}
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.
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
.
(
"Number: "
);
// send text to the LCD
serial_lcd
.
println
(
number
);
// print the number on the LCD
Serial
.
(
"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
.
(
"Number: "
);
// send text to the LCD
serial_lcd
.
println
(
number
);
// print the number on the LCD
Serial
.
(
"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
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
}
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.
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
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).
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.