array-puerto-serie-arduino

Enviar o recibir un array por puerto serie en Arduino

Continuamos con esta serie de entradas destinadas a dominar el puerto serie de procesadores como Arduino. En la entrada anterior vimos cómo enviar y recibir una secuencia de bytes. Quedamos en profundizar en generar y procesar los bytes. Para ello, en esta entrada veremos cómo enviar y recibir un array por puerto serie directamente como bytes.

Al enviar o recibir un array por puerto serie estamos trabajando con una secuencia de datos del mismo tipo (int, float). En la siguiente entrada generalizaremos el proceso viendo como enviar o recibir una agrupación cualquiera de datos, en forma de objeto u estructura.

Pese a ser un caso particular de la siguiente entrada, el envío y recepción del array por puerto serie es interesante tanto porque es una necesidad habitual, y porque nos permite ilustrar y presentar la conversión a bytes que emplearemos en el caso del envío de estructuras.

Enviar el array por puerto serie directamente como bytes es mucho más eficiente que enviarlos como texto con un seperador, como vimos en la entrada enviar un array separado por comas. Se envía una cantidad de datos menos, es más eficiente porque evita tener que realizar conversiones, y el código es más sencillo.

Recordar lo mencionado en la entrada anterior relativo a las diferencias en la cantidad de bytes que emplean los distintos procesadores para almacenar los distintos tipos de variables.

Envío y recepción elementos individuales

El primer método vamos a realizar el envío de los elementos individuales. En el ejemplo, vamos a enviar un array de int. Por brevedad el array del ejemplo está definido pero vacío. En un caso real, lógicamente, este array contendría los datos que queremos enviar.

El código es bastante sencillo. El emisor recorre el array, y emplea una función para enviar los bytes que forman cada uno de los datos.

const size_t dataLength = 10;
int data[dataLength];
 
void setup()
{
   Serial.begin(9600);
  
   for(int n = 0; n < dataLength; n++)
   {
      sendBytes(data[n]);
   }
} 
 
void loop() 
{ 
}
 
void sendBytes(uint16_t value)
{
  Serial.write(highByte(value));
  Serial.write(lowByte(value));
}

Por otro lado, el receptor recibe los bytes, emplea otra función para recomponer el dato a partir de los bytes, y los almacena en el array.

const size_t dataLength = 10;
int data[dataLength];
int dataIndex = 0;
 
void setup()
{
  Serial.begin(9600);
} 
 
 
void loop()
{   
  if(Serial.available() >= sizeof(data[0])
  {
    byte byte1 = Serial.read();
    byte byte2 = Serial.read();
    data[dataIndex] = byteToInt(byte1, byte2);
 
    dataIndex++;
    if(dataIndex >= dataLength)
    {
      dataIndex = 0;
    }
  } 
} 

uint16_t byteToInt(byte byte1, byte byte2)
{
   return (uint16_t)byte1 << 8 | (uint16_t)byte2;
}

La mayor ventaja del método es que nos permite una gran flexibilidad en el proceso ya que lo controlamos manualmente. Por ejemplo, podemos emplear los valores para realizar acciones sin tener que esperar todo el array.

Sin embargo, en general, no es algo que vayamos a echar de menos. Normalmente queremos trabajar a trama fija, es decir, no queremos realizar acciones hasta que hayamos elegido el array completo.

Envío y recepción con cast directo

Ahora veamos cómo se realizaría el envío y la recepción trabajando directamente bytes. Para enviar el array simplemente realizamos un cast a byte* y enviamos el número de bytes del array.

const size_t dataLength = 10;
int data[dataLength];

void setup()
{
  Serial.begin(9600);
  Serial.write((byte*)data, dataLength * sizeof(data[0]));
} 

void loop()
{
}

La recepción es igual de simple, simplemente pasamos a la función read() el array de int como un byte*, y en la recepción el array se sobre escribirá con los valores recibidos.

const size_t dataLength = 10;
int data[dataLength];
 
void setup()
{
  Serial.begin(9600);
} 

void loop()
{   
  if(Serial.available() >= dataLength * sizeof(data[0]))
  {
    Serial.readBytes((byte*)data, dataLength * sizeof(data[0]));
  } 
} 

La ventaja del método es evidente. Más rápido, cómodo, sencillo (y más “pro”). Desventajas, como hemos comentado tenemos que esperar para recibir todos los bytes. Fijaros que la condición ha cambiado y la recepción no se realiza hasta que no se ha recibido todos los bytes de la trama.

Por ello, tenemos como condicionante el tamaño del buffer del puerto serie, que en el caso de Arduino Uno y Nano es de 64 bytes. Si se necesita una trama mayor podemos o cambiar el valor en la librería HardwareSerial.h o, más recomendable, dividir el envío en varios paquetes.

Por otro lado, nuevamente, tener muy presente las posibles diferencias en el tamaño en bytes de los distintos tipos de variables.

En la próxima entrada veremos una generalización del caso para el envío de agrupaciones arbitrarias de datos en forma de estructura u objetos.

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github. github-full