使用 Arduino 端口扩展你的 Raspberry Pi

在这个项目中,探索使用 Java、串行和 Arduino 扩展 Raspberry Pi 端口。
159 位读者喜欢这篇文章。
Parts, modules, containers for software

Opensource.com

作为创客社区的成员,我们一直在寻找创造性的方法来使用硬件和软件。这一次,Patrick Lima 和我决定使用 Arduino 板扩展 Raspberry Pi 的端口,以便我们可以访问更多的功能和端口,并为设备增加一层保护。这种设置有很多用途,例如构建一个跟踪太阳的太阳能电池板、一个家庭气象站、操纵杆交互等等。

我们决定从构建一个仪表板开始,该仪表板允许以下串行端口交互

  • 控制三个 LED 灯的开关
  • 控制三个 LED 灯以调整其光强度
  • 识别正在使用的端口
  • 显示操纵杆上的输入移动
  • 测量温度

我们还想在一个漂亮的用户界面 (UI) 中展示端口、硬件和传感器之间的所有交互,就像这样

你可以使用本文中的概念来构建许多不同的项目,这些项目使用许多不同的组件。你的想象力是唯一的限制!

1. 开始入门

第一步是扩展 Raspberry Pi 的端口,使其也可以使用 Arduino 端口。这可以通过 Linux ARM 的原生串行通信实现来实现,它使你能够使用 Arduino 的数字、模拟和脉冲宽度调制 (PWM) 端口在 Raspberry Pi 上运行应用程序。

本项目使用 TotalCross,一个用于为嵌入式设备构建 UI 的开源软件开发工具包,通过终端执行外部应用程序并使用原生串行通信。你可以使用两个类来实现此目的:Runtime.execPortConnector。它们代表了执行这些操作的不同方式,因此我们将在本教程中展示如何使用这两种方式,你可以决定哪种方式最适合你。

要开始这个项目,你需要

  • 1 个 Raspberry Pi 3
  • 1 个 Arduino Uno
  • 3 个 LED 灯
  • 2 个介于 1K 和 2.2K 欧姆之间的电阻
  • 1 个按钮
  • 1 个介于 1K 和 50K 欧姆之间的电位器
  • 1 个原型板(又名面包板)
  • 跳线

2. 设置 Arduino

创建一个通信协议来接收消息、处理消息、执行请求以及在 Raspberry Pi 和 Arduino 之间发送响应。这在 Arduino 上完成。

2.1 定义消息格式

接收到的每条消息都将具有以下格式

  • 调用的函数指示
  • 使用的端口
  • 字符分隔符(如果需要)
  • 要发送的值(如果需要)
  • 消息结束指示

下表列出了字符及其各自的功能、示例值以及示例的描述。此示例中使用的字符选择是任意的,可以随时更改。

字符 功能 示例 示例描述
* 指令结束 - -
, 分隔符 - -
# 设置模式 #8,0* 引脚 8 输入模式
< 设置数字值 <1,0* 设置引脚 1 低电平
> 获取数字值 >13* 获取引脚 13 的值
+ 获取 PWM 值 +6,250* 设置引脚 6 值为 250
- 获取模拟值 -14* 获取引脚 A0 的值

2.2 源代码

以下源代码实现了上述通信协议。它必须发送到 Arduino,以便它可以解释和执行消息命令

void setup() {
 Serial.begin(9600);
 Serial.println("Connected");
 Serial.println("Waiting command..."); 
}

void loop() {
String text="";
char character;
String pin="";
String value="0";
char separator='.';
char inst='.';

 while(Serial.available()){ // verify RX is getting data
   delay(10);
   character= Serial.read();
   if(character=='*'){
     action(inst,pin,value);
     break;
    }
    else {
     text.concat(character);}

   if(character==',') {
     separator=character;
   
   if(inst=='.'){
     inst = character;}
   else if(separator!=',' && character!=inst ){
     pin.concat(character);}
   else if (character!=separator && character!=inst ){
     value.concat(character);}
 }
}

void action(char instruction, String pin, String value){
 if (instruction=='#'){//pinMode
   pinMode(pin.toInt(),value.toInt());
 }

 if (instruction=='<'){//digitalWrite
   digitalWrite(pin.toInt(),value.toInt());
 }

 if (instruction=='>'){ //digitalRead
   String aux= pin+':'+String(digitalRead(pin.toInt()));
   Serial.println(aux);
 }

 if (instruction=='+'){ // analogWrite = PWM
   analogWrite(pin.toInt(),value.toInt());
 }

 if (instruction=='-'){ // analogRead
   String aux= pin+':'+String(analogRead(pin.toInt()));
   Serial.println(aux);
 }
}

2.3 构建电子元件

定义你需要测试的内容,以检查与 Arduino 的通信并确保输入和输出按预期响应

  • LED 灯以正逻辑连接。通过电阻连接到 GND 引脚,并使用数字端口 I/O 2 和 PWM 3 激活它。
  • 按钮具有连接到数字端口 I/O 4 的下拉电阻,如果不按下则发送 0 信号,如果按下则发送 1 信号。
  • 电位器与中心引脚连接到模拟输入 A0,一侧引脚连接到正极,另一侧引脚连接到负极。

2.4 测试通信

将第 2.2 节中的代码发送到 Arduino。打开串行监视器并通过发送以下命令来检查通信协议

#2,1*<2,1*>2*
#3,1*+3,10*
#4,0*>4*
#14,0*-14*

这应该是串行监视器中的结果

设备上的一个 LED 灯应以最大强度亮起,另一个 LED 灯应以较低强度亮起。

按下按钮并在发送读取命令时更改电位器的位置将显示不同的值。例如,将电位器转到正极侧并按下按钮。在按钮仍处于按下状态时,发送以下命令

>4*
-14*

应该出现两行

3. 设置 Raspberry Pi

使用 Raspberry Pi 通过终端访问串行端口,使用 cat 命令读取条目,使用 echo 命令发送消息。

3.1 进行串行测试

将 Arduino 连接到 Raspberry Pi 上的一个 USB 端口,打开终端,然后执行此命令

cat /dev/ttyUSB0 9600

这将启动与 Arduino 的连接并显示返回到串行的内容。

要测试发送命令,请打开一个新的终端窗口(保持前一个窗口打开),然后发送此命令

echo "command" > /dev/ttyUSB0 9600

你可以发送与第 2.4 节中使用的相同命令。

你应该在第一个终端中看到反馈,以及你在第 2.4 节中获得的相同结果

4. 创建图形用户界面

此项目的 UI 将很简单,因为目标只是展示使用串行端口进行端口扩展。另一篇文章将使用 TotalCross 为本项目创建一个高质量的 GUI,并启动应用程序后端(与传感器一起工作),如本文顶部的仪表板图像所示。

第一部分使用两个 UI 组件:Listbox 和 Edit。这些组件在 Raspberry Pi 和 Arduino 之间建立连接,并测试一切是否按预期工作。

模拟你在其中输入命令并查看答案的终端

  • Edit 用于发送消息。将其放置在底部,宽度设置为 FILL,以将组件扩展到屏幕的整个宽度。
  • Listbox 用于显示结果,例如,在终端中。将其添加到 TOP 位置,从 LEFT 侧开始,宽度与 Edit 相同,高度设置为 FIT 以垂直占据 Edit 未填充的所有空间。
package com.totalcross.sample.serial;

import totalcross.sys.Settings;
import totalcross.ui.Edit;
import totalcross.ui.ListBox;
import totalcross.ui.MainWindow;
import totalcross.ui.gfx.Color;

public class SerialSample extends MainWindow {
   ListBox Output;
   Edit Input;
   public SerialSample() {
       setUIStyle(Settings.MATERIAL_UI);
   }

   @Override
   public void initUI() {
       Input = new Edit();
       add(Input, LEFT, BOTTOM, FILL, PREFERRED);
       Output = new ListBox();
       Output.setBackForeColors(Color.BLACK, Color.WHITE);
       add(Output, LEFT, TOP, FILL, FIT);
   }
}

它应该看起来像这样

5. 设置串行通信

如上所述,有两种设置串行通信的方法:Runtime.exec 和 PortConnector。

5.1 选项 1:使用 Runtime.exec

java.lang.Runtime 类允许应用程序创建与运行环境的连接接口。它允许程序使用 Raspberry Pi 的原生串行通信。

使用你在第 3.1 节中使用的相同命令,但现在使用 UI 上的 Edit 组件将命令发送到设备。

读取串行

应用程序必须不断读取串行,如果返回一个值,则使用线程将其添加到 Listbox。线程是在后台处理进程而不阻塞用户交互的好方法。

以下代码在此线程上创建一个新进程,该进程执行 cat 命令,测试串行,并启动一个无限循环来检查是否收到新内容。如果收到内容,则将该值添加到 Listbox 组件的下一行。只要应用程序正在运行,此过程将继续运行

new Thread () {
   @Override
   public void run() {
       try {
           Process Runexec2 = Runtime.getRuntime().exec("cat /dev/ttyUSB0 9600\n");
           LineReader lineReader = new LineReader(Stream.asStream(Runexec2.getInputStream()));
           String input;
         
           while (true) {
               if ((input = lineReader.readLine()) != null) {
                   Output.add(input);
                   Output.selectLast();
                   Output.repaintNow();
               }
           }
         } catch (IOException ioe) {
            ioe.printStackTrace();
         }
       }
   }.start();
}

发送命令

发送命令是一个更简单的过程。它在你按下 Edit 组件上的 Enter 键时发生。

要将命令转发到设备,如第 3.1 节所示,你必须实例化一个新的终端。为此,Runtime 类必须在 Linux 上执行 sh 命令

try{
   Runexec = Runtime.getRuntime().exec("sh").getOutputStream()        }catch (IOException ioe) {
   ioe.printStackTrace();
}

在用户在 Edit 中写入命令并按下 Enter 键后,应用程序会触发一个事件,该事件使用 Edit 中指示的值执行 echo 命令

Input.addKeyListener(new KeyListener() {

   @Override
   public void specialkeyPressed(KeyEvent e) {
       if (e.key == SpecialKeys.ENTER) {
           String s = Input.getText();
           Input.clear();
           try {
               Runexec.write(("echo \"" + s + "\" > /dev/ttyUSB0 9600\n").getBytes());
           } catch (IOException ioe) {
           ioe.printStackTrace();
           }
       }
   }

   @Override
   public void keyPressed(KeyEvent e) {} //auto-generate code
   @Override
   public void actionkeyPressed(KeyEvent e) {} //auto-generate code
});

在连接了 Arduino 的 Raspberry Pi 上运行应用程序并发送命令进行测试。结果应该是

Runtime.exec 源代码

以下是包含所有解释部分的源代码。它包括将在第 31 行读取串行的线程和将在第 55 行发送命令的 KeyListener

package com.totalcross.sample.serial;
import totalcross.ui.MainWindow;
import totalcross.ui.event.KeyEvent;
import totalcross.ui.event.KeyListener;
import totalcross.ui.gfx.Color;
import totalcross.ui.Edit;
import totalcross.ui.ListBox;
import java.io.IOException;
import java.io.OutputStream;
import totalcross.io.LineReader;
import totalcross.io.Stream;
import totalcross.sys.Settings;
import totalcross.sys.SpecialKeys;

public class SerialSample extends MainWindow {
   OutputStream Runexec;
   ListBox Output;

   public SerialSample() {
       setUIStyle(Settings.MATERIAL_UI);
   }

   @Override
   public void initUI() {
       Edit Input = new Edit();
       add(Input, LEFT, BOTTOM, FILL, PREFERRED);
       Output = new ListBox();
       Output.setBackForeColors(Color.BLACK, Color.WHITE);
       add(Output, LEFT, TOP, FILL, FIT);
       new Thread() {
           @Override
           public void run() {
               try {
                   Process Runexec2 = Runtime.getRuntime().exec("cat /dev/ttyUSB0 9600\n");
                   LineReader lineReader = new
                   LineReader(Stream.asStream(Runexec2.getInputStream()));
                   String input;

                   while (true) {
                       if ((input = lineReader.readLine()) != null) {
                           Output.add(input);
                           Output.selectLast();
                           Output.repaintNow();
                       }
                   }

               } catch (IOException ioe) {
                   ioe.printStackTrace();
               }
           }
       }.start();

       try {
           Runexec = Runtime.getRuntime().exec("sh").getOutputStream();
       } catch (IOException ioe) {
           ioe.printStackTrace();
       }

       Input.addKeyListener(new KeyListener() {
           @Override
           public void specialkeyPressed(KeyEvent e) {
               if (e.key == SpecialKeys.ENTER) {
                   String s = Input.getText();
                   Input.clear();
                   try {
                       Runexec.write(("echo \"" + s + "\" > /dev/ttyUSB0 9600\n").getBytes());
                   } catch (IOException ioe) {
                       ioe.printStackTrace();
                   }
               }
           }

           @Override
           public void keyPressed(KeyEvent e) {
           }
           @Override
           public void actionkeyPressed(KeyEvent e) {
           }
      });
   }
}

5.2 选项 2:使用 PortConnector

PortConnector 专门用于处理串行通信。如果你想遵循原始示例,你可以跳过本节,因为这里的目的是展示另一种更简单的串行工作方式。

更改原始源代码以使用 PortConnector

package com.totalcross.sample.serial;
import totalcross.io.LineReader;
import totalcross.io.device.PortConnector;
import totalcross.sys.Settings;
import totalcross.sys.SpecialKeys;
import totalcross.ui.Edit;
import totalcross.ui.ListBox;
import totalcross.ui.MainWindow;
import totalcross.ui.event.KeyEvent;
import totalcross.ui.event.KeyListener;
import totalcross.ui.gfx.Color;

public class SerialSample extends MainWindow {
   PortConnector pc;
   ListBox Output;

   public SerialSample() {
       setUIStyle(Settings.MATERIAL_UI);
   }

   @Override
   public void initUI() {
       Edit Input = new Edit();
       add(Input, LEFT, BOTTOM, FILL, PREFERRED);
       Output = new ListBox();
       Output.setBackForeColors(Color.BLACK, Color.WHITE);
       add(Output, LEFT, TOP, FILL, FIT);
       new Thread() {
           @Override
           public void run() {
               try {
                   pc = new PortConnector(PortConnector.USB, 9600);
                   LineReader lineReader = new LineReader(pc);
                   String input;
                   while (true) {
                       if ((input = lineReader.readLine()) != null) {
                           Output.add(input);
                           Output.selectLast();
                           Output.repaintNow();
                       }
                   }
               } catch (totalcross.io.IOException ioe) {
                   ioe.printStackTrace();
               }
           }
       }.start();
       Input.addKeyListener(new KeyListener() {
           @Override
           public void specialkeyPressed(KeyEvent e) {
               if (e.key == SpecialKeys.ENTER) {
                   String s = Input.getText();
                   Input.clear();
                   try {
                       pc.writeBytes(s);
                   } catch (totalcross.io.IOException ioe) {
                       ioe.printStackTrace();
                   }
               }
           }

           @Override
           public void keyPressed(KeyEvent e) {
           }

           @Override
           public void actionkeyPressed(KeyEvent e) {
           }
      });
  }
}

你可以在项目仓库中找到所有代码。

6. 后续步骤

本文展示了如何通过使用 Runtime 或 PortConnector 类在 Java 中使用 Raspberry Pi 串行端口。你还可以调用其他语言的外部文件并创建无数其他项目——例如用于水族箱的水质监测系统,通过模拟输入测量温度;或者带有温度和湿度调节以及伺服电机旋转鸡蛋的育雏器。

未来的文章将使用 PortConnector 实现(因为它专注于串行连接)来完成与所有传感器的通信。它还将添加数字输入并完成 UI。

以下是一些参考资料,供进一步阅读

在你连接 Arduino 和 Raspberry Pi 后,请在下方留下评论并分享你的结果。我们很乐意阅读它们!

下一步阅读
User profile image.
创业者超过 12 年,创立了第四家公司,在软件开发领域拥有超过 15 年的经验,尤其是在移动应用程序方面。计算机科学硕士学位,也是创业公司的爱好者,在巴西的创业领域非常活跃。开源新手,但一直在学习 =P

1 条评论

这与使用 Firmata 可以做的事情有什么不同?顺便说一句,Firmata 除了串行接口和许多不同的语言外,还可以与 WiFi 和蓝牙一起使用。只是好奇。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.