Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Efficient communication between Arduino* and Linux native processes

0.00/5 (No votes)
17 Oct 2014CPOL6 min read 7K  
How to efficiently communicate between Intel® Galileo, or Intel® Edison and Yocto* Linux OS.

This article is for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers

Working with Arduino* sketches on Intel® Galileo, or Intel® Edison you might run into situations where you would want to add some functionality coming from the underlying Yocto* Linux OS running underneath. Which brings us directly to the subject of this blog: How to efficiently communicate between those two worlds.Let's start to define some criteria we will try to meet in the following

Criteria

  1. no communication on disk (SD card, eMMC) in order to reduce disk wearing and to improve performance
  2. communication triggered by events, i.e. in particular we don't want to periodically check a status but to get informed by an event and idle otherwise

Inter Process Communication (IPC) on Linux

An Arduino* sketch running on Intel® Galileo, or Intel® Edison is in fact just a Linux process running in parallel to other Linux processes. As there is a full grown Linux running on the boards we can also use standard means for inter process communication (IPC) between the Arduino* process and native process(es). There are various IPC methods on Linux. One out of those "memory mapped IPC". In essence it means that IPC processes share the same memory. Which means any modification from any process sharing that memory region will be at once visible to all the other processes. It also matches our first criteria to not write communication data on disk but operate only on memory.

Mutexes and condition variables

Having a shared memory leads to several questions like:

  1. how to make sure only one process is operating on the shared data at a given time? (synchronization)
  2. how to notify the other process(es) once data has been modified? (notification)

In the following we will look into those 2 questions one after the other. We will make use of "mutexes" and "condition variables" which are both contained in the POSIX threads (Pthreads) library available on Linux.

Synchronization - Mutex

A mutual exclusion (mutex) is a standard concept provided by probably any modern multitasking OS. In this blog we won't describe concepts and details here but only give high level information on POSIX threads (Pthreads) mutexes. For more information you should consult textbooks on operating systems (e.g. Tanenbaum, Woodhull: Operating Systems 3rd ed. Pearson 2006) or search online.

As the name says the Pthreads library focuses mainly on threads programming. However, it also offers powerful commands applicable to processes. Moreover the Arduino* IDE for Intel® Galileo, and Intel® Edison resp supports pthreads (i.e. links to the pthreads library) out of the box thus providing an easy integration. Hence, it seems a natural selection to use Pthreads for our purpose.

Typically a mutex makes sure a certain critical region of code is only accessed by one thread. As we are dealing with processes here we will use mutexes in a way that only one process can proceed a pthread_mutex_lock request in the code. Any other process will be set to sleep by the OS until pthread_mutex_unlock is called on the mutex after which the OS will wake all other processes requesting pthread_mutex_lock.

In pseudo code:

C++
pthread_mutex_lock(&mutex);

// read / write shared memory data here

pthread_mutex_unlock(&mutex);

This has to be done in the same way for locking write as well as for locking read accesses as otherwise a read could access "half updated" data. In the next section we come to a further Pthreads concept which provides notification on data changes

Notification - Condition variable

Similar to the mutex concept where a process trying to access an already locked mutex Pthreads provides the concept of condition variables. The condition variable allows a thread, or in our case a process to request to be put to sleep until waked up through the variable. This is done by the function pthread_cond_wait.

Mutex and condition variable combined yield following pseudo code:

C++
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond_variable, &mutex);

// read shared memory data here

pthread_mutex_unlock(&mutex);

The other process need to unlock the mutex and signal the change by calling pthread_cond_signal. This will wake up the sleeping process.

For further details pls check textbooks, or online tutorials resp on Pthreads. In the next section we will come to a sample implementation.

Implementation

some explanations:

  • For the mutex as well as for the condition variable we need to explicitly set attributes in order to allow the inter process use
  • We chose to utilize shared memory IPC via memory mapped files as the Arduino* IDE doesn't come with all libraries required for direct shared memory IPC. Putting the communication file in a filesystem mapped to main memory essentially provides identical functionality. The Yocto* Linux coming with Edison as well as the SD card Yocto* image on https://software.intel.com/iot have the temp folder /tmp mounted to tmpfs which lies in memory. I.e. any file within this folder would do. We chose the file "/tmp/arduino". It's meant to be used only for IPC.
  • We assume that the Arduino process is the process to initilize the mutex and the condition variable as the Arduino process would start on system boot.
  • we show only the situation where the Arduino* process waits for data from a Linux native process to operate on that data. For other purposes the code would have to be modified accordingly.
  • to show the concept we put as data to share two booleans in the memory mapped structure mmapData which define whether the built-in and an external LED on IO 8 is to be switched on or off:
    C++
    bool led8_on;   // led on IO8
    bool led13_on;  // built-in led
    
    Obviously any other data could have been put there. The two other variables in the mmapData struct are the mutex and the condition variable

     

Note

The sample code below is provided under the MIT license.

There are three files below:

  • mmap.ino: sketch to be put onto Arduino* IDE
  • mmap.cpp: native process sending data
  • mmap.h: header file - same file to be used on Arduino* IDE as well as Linux native

I.E. on the Arduino* side you should have a folder which contains "mmap.ino" and "mmap.h" in your Arduino* sketches directory. On the Linux native side you should have a folder containing "mmap.cpp", and "mmap.h".

In order to run the sketch just open the "mmap" sketch in the Arduino* IDE and upload it to the right board (Intel® Galileo Gen 1, Intel® Galileo Gen 2, or Intel® Edison respectively). On the native side the Intel IoT devkit on https://software.intel.com/iot comes with a cross compiler, or alternatively the Yocto* Linux coming with Intel® Edison as well as the SD card Yocto* image on https://software.intel.com/iot or Intel® Galiileo come with a C++ compiler pre-installed. Using the pre-installed compiler you would need to run

g++  mmap.cpp   -lpthread -o mmap

in the folder you put mmap.cpp and mmap.h. It will generate a binary you can execute as follows

./mmap {0,1}{0,1}

where "{0,1}" stands for either 0 or 1. I.E. "./mmap 00" would switch both LEDs off, whereas "./mmap 11" would switch both LEDs on. Some additional output to the serial monitor [CTRL+SHIFT+M] on the Arduino* IDE, and to the console you start the Linux native process show in addition the data which has been set.

mmap.ino

C++
/*
 * Author: Matthias Hahn <matthias.hahn@intel.com>
 * Copyright (C) 2014 Intel Corporation
 * This file is part of mmap IPC sample provided under the MIT license
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "mmap.h"

using namespace std;

/* assume /tmp mounted on /tmpfs -> all operation in memory */
/* we can use just any file in tmpfs. assert(file size not modified && file permissions left readable) */
struct mmapData* p_mmapData; // here our mmapped data will be accessed

int led8 = 8;
int led13 = 13;


void exitError(const char* errMsg) {
  /* print to the serial Arduino is attached to, i.e. /dev/ttyGS0 */
  string s_cmd("echo 'error: ");
  s_cmd = s_cmd + errMsg + " - exiting' > /dev/ttyGS0";
  system(s_cmd.c_str()); 
  exit(EXIT_FAILURE);
}  

void setup() {
  int fd_mmapFile; // file descriptor for memory mapped file
  /* open file and mmap mmapData*/
  fd_mmapFile = open(mmapFilePath, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  if (fd_mmapFile == -1) exitError("couldn't open mmap file"); 
  /* make the file the right size - exit if this fails*/
  if (ftruncate(fd_mmapFile, sizeof(struct mmapData)) == -1) exitError("couldn' modify mmap file");
  /* memory map the file to the data */
  /* assert(filesize not modified during execution) */
  p_mmapData = static_cast<struct mmapData*>(mmap(NULL, sizeof(struct mmapData), PROT_READ | PROT_WRITE, MAP_SHARED, fd_mmapFile, 0));  
  if (p_mmapData == MAP_FAILED) exitError("couldn't mmap"); 
  /* initialize mutex */
  pthread_mutexattr_t mutexattr; 
  if (pthread_mutexattr_init(&mutexattr) == -1) exitError("pthread_mutexattr_init");
  if (pthread_mutexattr_setrobust(&mutexattr, PTHREAD_MUTEX_ROBUST) == -1) exitError("pthread_mutexattr_setrobust");
  if (pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED) == -1) exitError("pthread_mutexattr_setpshared");
  if (pthread_mutex_init(&(p_mmapData->mutex), &mutexattr) == -1) exitError("pthread_mutex_init");

  /* initialize condition variable */
  pthread_condattr_t condattr;
  if (pthread_condattr_init(&condattr) == -1) exitError("pthread_condattr_init");
  if (pthread_condattr_setpshared(&condattr, PTHREAD_PROCESS_SHARED) == -1) exitError("pthread_condattr_setpshared");
  if (pthread_cond_init(&(p_mmapData->cond), &condattr) == -1) exitError("pthread_mutex_init");

  /* for this test we just use 2 LEDs */
  pinMode(led8, OUTPUT);
  pinMode(led13, OUTPUT);
}

void loop() {
  /* block until we are signalled from native code */
  if (pthread_mutex_lock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_lock");
  if (pthread_cond_wait(&(p_mmapData->cond), &(p_mmapData->mutex)) != 0) exitError("pthread_cond_wait");

  if (p_mmapData->led8_on) {
    system("echo 8:1 > /dev/ttyGS0");
    digitalWrite(led8, HIGH);
  }
  else {
    system("echo 8:0 > /dev/ttyGS0");
    digitalWrite(led8, LOW);
  }  
  if (p_mmapData->led13_on) {
    system("echo 13:1 > /dev/ttyGS0");
    digitalWrite(led13, HIGH);
  }
  else {
    system("echo 13:0 > /dev/ttyGS0");
    digitalWrite(led13, LOW);
  }  
  if (pthread_mutex_unlock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_unlock");
}

mmap.cpp

C++
/*
 * Author: Matthias Hahn <matthias.hahn@intel.com>
 * Copyright (C) 2014 Intel Corporation
 * This file is part of mmap IPC sample provided under the MIT license
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* mmap.cpp
   Linux native program communicating via memory mapped data with Arduino sketch.
   Compilation: g++  mmap.cpp   -lpthread -o mmap
   Run: ./mmap <LED8><LED13> (e.g. ./mmap 01 -> LED 8 off, LED 13 on)
   For "random" blink you may run following commands in the command line:
   while [ 1 ]; do ./mmap $(($RANDOM % 2))$(($RANDOM % 2)); done
*/

#include "mmap.h"

void exitError(const char* errMsg) {
  perror(errMsg);
  exit(EXIT_FAILURE);
}


using namespace std;


/**
 * @brief: for this example uses a binary string "<led8><led13>"; e.g. "11": both leds on
 * if no arg equals "00" 
 * For "random" blink you may run following commands in the command line:
 * while [ 1 ]; do ./mmap $(($RANDOM % 2))$(($RANDOM % 2)); done
 */
int main(int argc, char** argv) {
  struct mmapData* p_mmapData; // here our mmapped data will be accessed
  int fd_mmapFile; // file descriptor for memory mapped file

  /* Create shared memory object and set its size */
  fd_mmapFile = open(mmapFilePath, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  if (fd_mmapFile == -1) exitError("fd error; check errno for details");
  
  /* Map shared memory object read-writable */
  p_mmapData = static_cast<struct mmapData*>(mmap(NULL, sizeof(struct mmapData), PROT_READ | PROT_WRITE, MAP_SHARED, fd_mmapFile, 0));
  if (p_mmapData == MAP_FAILED) exitError("mmap error");
  /* the Arduino sketch might still be reading - by locking this program will be blocked until the mutex is unlocked from the reading sketch 
   * in order to prevent race conditions */
  if (pthread_mutex_lock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_lock");
  if (argc == 1) { 
    cout << "8:0" << endl;
    cout << "13:0" << endl;
    p_mmapData->led8_on = false;
    p_mmapData->led13_on = false;
  }
  else if (argc > 1) {
    // assert(correct string given)
    int binNr = atol(argv[1]);
    if (binNr >= 10) {
      cout << "8:1" << endl;
      p_mmapData->led8_on = true; 
    }
    else {
      cout << "8:0" << endl;
      p_mmapData->led8_on = false;
    }
    binNr %= 10;
    if (binNr == 1) {
      cout << "13:1" << endl;
      p_mmapData->led13_on = true; 
    }
    else {
      cout << "13:0" << endl;
      p_mmapData->led13_on = false;
    }
  }
  // signal to waiting thread
  if (pthread_mutex_unlock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_unlock");
  if (pthread_cond_signal(&(p_mmapData->cond)) != 0) exitError("pthread_cond_signal");
}

mmap.h

C++
/*
 * Author: Matthias Hahn <matthias.hahn@intel.com>
 * Copyright (C) 2014 Intel Corporation
 * This file is part of mmap IPC sample provided under the MIT license
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef MMAP_HPP
#define MMAP_HPP

#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <cstdio>
#include <pthread.h>

/* assert(/tmp mounted to tmpfs, i.e. resides in RAM) */
/* just use any file in /tmp */
static const char* mmapFilePath = "/tmp/arduino";


struct mmapData {
  bool led8_on;   // led on IO8
  bool led13_on;  // built-in led
  pthread_mutex_t mutex;
  pthread_cond_t cond;
};

#endif

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)