📜 ⬆️ ⬇️

Efficient interoperability between native Arduino and Linux processes

Using Arduino sketches for working with Intel Galileo and Intel Edison boards, you may encounter a situation where you need to add additional functionality by using the Yocto kit to develop embedded systems based on Linux OS. And here we have to solve the problem, which we have already mentioned in the title of our post: how to establish effective “communication” between these two worlds.



Let's define some criteria that we need to take into account:

Criteria


1. No disk sharing (SD card, eMMC) to reduce disk wear and increase performance.
2. Communication is caused solely by any events (in particular, we do not need to periodically check the status, but we want to receive notification of a particular event, otherwise - not to activate the data exchange).
')

Interprocess Communication (IPC) on Linux


A program created in the Arduino environment (sketch) and run on Intel Galileo or Intel Edison is a Linux process running in parallel with other similar processes. Since we are dealing with a full-fledged Linux system running on these boards, it is also possible to use standard means for interprocess communication (IPC) between Arduino processes and native processes. For Linux, there are various IPC methods. One of them is the “mapped IPC”. In essence, this means that IPC processes share the same memory area. In turn, this means that any modifications made by one process that uses a specific area of ​​memory immediately become visible to all other processes. This behavior satisfies our first criterion: during data transfer, we work with the latter exclusively in memory, they are not written to disk.

Mutexes and condition variables


The use of shared memory immediately raises a couple of questions, such as:

1. How to make sure that in a shared data at any particular time only one process is performed (synchronization)?

2. How to instantly notify another process (or processes) of data changes (notification)?

Below we consider these two questions in turn. We will make it possible to use "mutexes" and "condition variables" that are included in the POSIX thread library (Pthreads) available for the Linux system.

Sync - Mutex


Mutual exclusion, or mutex (mutex) - is a standard principle that is implemented, perhaps, in any modern multitasking OS. In this post, we will not describe all the principles and details, but only share specific information regarding the mutexes from the POSIX (Pthreads) thread set. For more information, refer to paper editions (for example, Tanenbaum, Woodhull: Operating Systems 3rd ed. Pearson 2006) or use Internet search.

As the name of the library suggests, the Pthreads suite is mainly intended for streaming programming. At the same time, it also offers powerful tools applicable to process management. Moreover, the Arduino IDE development environment for Intel Galileo and Intel Edison boards supports the Pthreads library (i.e., links to this library) out of the box, thus offering easy-to-implement integration. Therefore, using Pthreads for our purposes looks like a logical decision.

As a rule, a mutex ensures that only one thread receives access to a certain critical section of code. Since we are dealing with processes here, let's use mutexes so that only one process can access the pthread_mutex_lock request in the code. All other processes will be put into sleep mode by the OS itself - just until the mutex receives the pthread_mutex_unlock command, after which the OS will wake up all other processes, requesting pthread_mutex_lock.

Pseudocode:

pthread_mutex_lock(&mutex); // read / write shared memory data here pthread_mutex_unlock(&mutex); 

This must be done in the same way, both for blocking the record and blocking access to reading. Otherwise, in the process of reading can be accessed to partially updated data. In the next section, we will talk about the principles of operation of the Pthreads elements, which allow you to receive a notification in case of data changes.

Notification - Conditional Variable


Similar to the principle that when a process tries to access a locked mutex, the Pthreads library also includes condition variables. A condition variable allows the thread or (in our case) process to request permission to sleep until it is awakened. This is implemented through the pthread_cond_wait function.

The mutex and condition variable in combination give the following pseudocode:

 pthread_mutex_lock(&mutex); pthread_cond_wait(&cond_variable, &mutex); // read shared memory data here pthread_mutex_unlock(&mutex); 

Another process needs to unlock the mutex and send a change signal by calling pthread_cond_signal. This will wake the process from sleep mode.

More information can be found in specialized publications or on the relevant online resources. The following section presents an example code implementation.

Implementation


Some explanations:


Note:


Sample code below is supplied under the MIT License. Below you will find three files:


That is, on the Arduino side, you should have a folder that contains the mmap.ino and mmap.h files in the Arduino sketches directory. In Linux, you should use the folder containing the files mmap.cpp and mmap.r.

To run the sketch, open the mmap program in the Arduino IDE development environment and load it into the appropriate board (Intel Galileo Gen 1, Intel Galileo Gen 2 or Intel Edison). The Intel IoT Developer Package comes with a cross-compiler. In addition, the Yocto platform, which comes bundled with Intel Edison, as well as the Yocto image for an SD card, or the Intel Galileo board, comes with a pre-installed C ++ compiler. To run the pre-installed compiler, you need to run:

 g++ mmap.cpp -lpthread -o mmap 

The folder should be placed files mmap.cpp and mmap.h. This will generate a binary file that can be run as follows:

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

... where "{0,1}" appears for 0 or 1. For example, the expression "./mmap 00" turns off both indicators, while the command ./mmap 00 will turn them on. Some additional information is output to the serial monitor (Ctrl + Shift + M).

mmap.ino


 /* * 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, ie /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


 /* * 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> (eg ./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>"; eg "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


 /* * 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, ie 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 

Thanks for attention!

Source: https://habr.com/ru/post/368141/


All Articles