机器人与人工智能爱好者论坛

 找回密码
 立即注册
查看: 5231|回复: 0
打印 上一主题 下一主题

ARDUINO* 和 LINUX 本地进程实现高效通信

[复制链接]

196

主题

220

帖子

1615

积分

版主

Rank: 7Rank: 7Rank: 7

积分
1615
跳转到指定楼层
楼主
发表于 2016-5-14 10:02:05 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
ARDUINO* 和 LINUX 本地进程实现高效通信

Matthias H. (Intel), Hai Shen (Intel), 2016 年 4 月 17 日

在英特尔® Galileo 或英特尔® Edison 上处理 Arduino* sketch 时,大家可能会遇到希望添加来自底层 Yocto* Linux 操作系统的部分功能的情况。 因此我们本篇博客的主题就是: 如何实现这两个领域之间的高效通信。我们首先定义一些需要遵从的标准
标准
  • 磁盘(SD 卡,eMMC)上没有通信,目的是降低磁盘磨损和提升性能
  • 事件触发的通信,例如,尤其是我们不想定期检查状态,但希望在处于闲置状态时得到事件的通知
Linux 上的进程间通信 (IPC)
在英特尔® Galileo 或英特尔® Edison 上运行的 Arduino* sketch 实际上是 Linux 进程以并行的方式运行至其他 Linux 进程。 由于开发板上运行的 Linux 非常成熟,因此我们还可以使用标准方法实现 Arduino* 进程与本机进程之间的进程间通信 (IPC)。 Linux 提供多种 IPC 方法。 其中一种是 “内存映射 IPC”。 从本质上来说,它指的是 IPC 进程共享同一内存。 这意味着,只要共享该内存区域的任何一条进程进行任何更改,其他所有进程就会马上看到它所作出的更改。 它还符合我们第一条不在磁盘上写入通信数据的标准,但只在内存上操作。
互斥体和条件变量
共享内存会出现以下问题,比如:
  • 如何确保只有一条进程在特定时间运算该共享数据? (同步)
  • 如果数据发生变化,如何通知其他进程? (通知)
下面我们来回答这两个问题。 我们使用包含在 POSIX 线程 (Pthread) 库(支持 Linux)之中的 “互斥体” 和 “条件变量”。
同步 - 互斥体
斥 (mutex) 是所有多任务操作系统都会提供的一种标准概念。 在本博客中,我们不介绍概念和详情,只提供有关 POSIX 线程 (Pthread) 互斥体的高级信息。 如欲了解更多信息,请查阅有关操作系统的教材(比如 Tanenbaum, Woodhull: Operating Systems 3rd ed. Pearson 2006),或上网搜索。
顾名思义,Pthread 库主要专注于线程编程。 然而,它还提供适用于进程的强大指令。 而且,分别面向英特尔® Galileo 和英特尔® Edison 的 Arduino* IDE 也支持即购即用 pthread(比如,pthread 库链接),因此可轻松集成。 所以,使用 Pthread 来满足我们的要求似乎是自然而然的选择。
通常,互斥体可确保一条线程仅访问代码的某个关键区域。 此处在处理进程时,我们使用互斥体的方法是,只有一条进程可以在代码中继续 pthread_mutex_lock 请求。 操作系统将其他所有进程设置为睡眠状态,直到互斥体调用 pthread_mutex_unlock,之后操作系统将唤醒其他请求pthread_mutex_lock 的进程。
在伪代码中:
  1. pthread_mutex_lock(&mutex);

  2. // read / write shared memory data here

  3. pthread_mutex_unlock(&mutex);
复制代码

必须以同样的方式进行,以锁定写入和读取访问,否则,读取操作会访问“只更新了一半”的数据。 接下来我们将介绍 Pthread 的另一个概念,即如何通知数据变化。
通知 - 条件变量
与互斥体概念类似,如果进程尝试访问已被锁定的互斥体,Pthread 将提供条件变量概念。 条件变量允许线程或(本案例中的)进程请求进入睡眠状态,直到通过变量被唤醒。 为此,需要采用函数 pthread_cond_wait
互斥体和条件变量结合后,会产生以下伪代码:
  1. pthread_mutex_lock(&mutex);
  2. pthread_cond_wait(&cond_variable, &mutex);

  3. // read shared memory data here

  4. pthread_mutex_unlock(&mutex);
复制代码

其他进程需要解锁互斥体,并通过调用 pthread_cond_signal 发出数据变化信号。 这样就会唤醒睡眠的进程。
如欲了解更多详情,请查阅有关 Pthread 的教材或在线教程。 下一部分,我们将介绍示例实施。
实施
部分说明:
  • 就互斥体和条件变量而言,我们需要明确设置属性,以支持进程间的使用
  • 由于 Arduino* IDE 不附带直接共享内存 IPC 所需的所有库,因此我们选择通过 内存映射文件利用共享内存 IPC。 从本质上讲,将通信文件放入映射至主内存的文件系统,可以提供相同的功能。 Edison 附带的 Yocto* Linux 以及 SD 卡 Yocto* 映像 (https://software.intel.com/iot) 包含 temp folder /tmp mounted totmpfs(在内存中)。 例如,该文件夹中的所有文件都可以。我们选择文件 "/tmp/arduino"。 它仅适用于 IPC。
  • 由于 Arduino 进程会在系统启动时开始,因此我们假设 Arduino 进程是要初始化互斥体和条件变量的进程。
  • 我们仅展示 Arduino 进程等待运算 Linux 本机进程的数据的情况。 如要达到其他目的,代码必须进行相应的修改。
  • 为了说明这一概念,我们如此放置数据,以共享内存映射结构 mmapData(定义 IO 8 的内置 LED 和外置 LED 是开启还是关闭)中的两个布尔变量:
    1. bool led8_on;   // led on IO8 bool led13_on;  // built-in led
    复制代码

    显而易见,这里也可以加入其他任何数据。 mmapData 结构中的其他两个变量分别为互斥体和条件变量
注:
MIT 许可证下方提供以下示例代码。
包含以下三个文件:
  • mmap.ino:放在 Arduino* IDE 上的sketch
  • mmap.cpp:发送数据的本机进程
  • mmap.h:标头文件 - 用于 Arduino* IDE 和 Linux native 的同一个文件
例如,如果用于 Arduino*,Arduino* sketch 目录中应该有一个包含 "mmap.ino" 和 "mmap.h" 的文件夹。 如果用于 Linux native,应该有一个包含 "mmap.cpp" 和 "mmap.h" 的文件夹。
如需运行 sketch,只需打开 Arduino* IDE 中的 "mmap" sketch,并将其上传至相应的开发板(第一代英特尔® Galileo,第二代英特尔® Galileo,或英特尔® Edison)。 如果用于 native,英特尔物联网开发人员套件 (https://software.intel.com/iot) 附带了一个交叉编译器,英特尔® Edison 附带了 Yocto* Linux 和 SD 卡 Yocto* 映像 fon ( https://software.intel.com/iot),英特尔® Galileo 附带了一个预安装的 C++ 编译器。 使用可能需运行的预安装编译器
  1. g++  mmap.cpp   -lpthread -o mmap
复制代码

将 mmap.cpp 和 mmap.h 放在文件夹中, 这样会生成一个可以执行的二进制代码,如下所示:
  1. ./mmap {0,1}{0,1}
复制代码

其中,"{0,1}" 表示 0 或 1。 例如, "./mmap 00" 表示关闭两个 LED,"./mmap 11" 表示开启两个 LED。 其他针对 Arduino* IDE 上的 serial monitor 的输出,以及启动 Linux 本机进程的控制台的输出显示了另外已设置的数据。
mmap.ino
  1. /*
  2. * Author: Matthias Hahn <matthias.hahn@intel.com>
  3. * Copyright (C) 2014 Intel Corporation
  4. * This file is part of mmap IPC sample provided under the MIT license
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:

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

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

  22. #include "mmap.h"

  23. using namespace std;

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

  27. int led8 = 8;
  28. int led13 = 13;


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

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

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

  58.   /* for this test we just use 2 LEDs */
  59.   pinMode(led8, OUTPUT);
  60.   pinMode(led13, OUTPUT);
  61. }

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

  66.   if (p_mmapData->led8_on) {
  67.     system("echo 8:1 > /dev/ttyGS0");
  68.     digitalWrite(led8, HIGH);
  69.   }
  70.   else {
  71.     system("echo 8:0 > /dev/ttyGS0");
  72.     digitalWrite(led8, LOW);
  73.   }  
  74.   if (p_mmapData->led13_on) {
  75.     system("echo 13:1 > /dev/ttyGS0");
  76.     digitalWrite(led13, HIGH);
  77.   }
  78.   else {
  79.     system("echo 13:0 > /dev/ttyGS0");
  80.     digitalWrite(led13, LOW);
  81.   }  
  82.   if (pthread_mutex_unlock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_unlock");
  83. }
复制代码

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

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

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

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

  29. #include "mmap.h"

  30. void exitError(const char* errMsg) {
  31.   perror(errMsg);
  32.   exit(EXIT_FAILURE);
  33. }


  34. using namespace std;


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

  44.   /* Create shared memory object and set its size */
  45.   fd_mmapFile = open(mmapFilePath, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  46.   if (fd_mmapFile == -1) exitError("fd error; check errno for details");
  47.   
  48.   /* Map shared memory object read-writable */
  49.   p_mmapData = static_cast<struct mmapData*>(mmap(NULL, sizeof(struct mmapData), PROT_READ | PROT_WRITE, MAP_SHARED, fd_mmapFile, 0));
  50.   if (p_mmapData == MAP_FAILED) exitError("mmap error");
  51.   /* the Arduino sketch might still be reading - by locking this program will be blocked until the mutex is unlocked from the reading sketch
  52.    * in order to prevent race conditions */
  53.   if (pthread_mutex_lock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_lock");
  54.   if (argc == 1) {
  55.     cout << "8:0" << endl;
  56.     cout << "13:0" << endl;
  57.     p_mmapData->led8_on = false;
  58.     p_mmapData->led13_on = false;
  59.   }
  60.   else if (argc > 1) {
  61.     // assert(correct string given)
  62.     int binNr = atol(argv[1]);
  63.     if (binNr >= 10) {
  64.       cout << "8:1" << endl;
  65.       p_mmapData->led8_on = true;
  66.     }
  67.     else {
  68.       cout << "8:0" << endl;
  69.       p_mmapData->led8_on = false;
  70.     }
  71.     binNr %= 10;
  72.     if (binNr == 1) {
  73.       cout << "13:1" << endl;
  74.       p_mmapData->led13_on = true;
  75.     }
  76.     else {
  77.       cout << "13:0" << endl;
  78.       p_mmapData->led13_on = false;
  79.     }
  80.   }
  81.   // signal to waiting thread
  82.   if (pthread_mutex_unlock(&(p_mmapData->mutex)) != 0) exitError("pthread_mutex_unlock");
  83.   if (pthread_cond_signal(&(p_mmapData->cond)) != 0) exitError("pthread_cond_signal");
  84. }
复制代码




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|Archiver|手机版|小黑屋|陕ICP备15012670号-1    

GMT+8, 2024-4-26 08:48 , Processed in 0.059139 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表