ArduinoでJavaを書く





この記事では、Arduino用のJavaでの記述方法を説明します。



なぜjava 要するに-ただの楽しみのために!



私はJavaプログラマーであり、暇なときにArduinoで遊んでいて、Javaの知識をマイクロコントローラーと組み込みデバイスの世界に伝えたいと思っていました。



現在、組み込みデバイスでJavaを実行するためのいくつかのオプションがあります。 この記事ではそれらをレビューします。



公式JVM



1つ目は、組み込み用の公式JVMです。

www.oracle.com/technetwork/java/embedded/embedded-se/overview/index.html

habrahabr.ru/post/243549 256KB RAMでJavaランタイムを実行



バイトコードを実行するほぼ実際のJVMがあります。 しかし、大きな欠点があります-これはRaspberry PiとFreescale K64Fでのみ機能します(もしそうなら、私は何かを逃しました-コメントに追加してください)。 Raspberry Piのサポートは間違いなく優れていますが、シングルボードではありますが、本質的にはコンピューターです。 その上で、単純なJVMを実行できます。 はい、それは3 trからかかります。 K64FはすでにCortex M4を搭載した開発ボードです。 しかし、それは3 trからもかかります。 これは一般的なArduino Unoよりもはるかに高価です。



バイトコードのコンパイルを備えたJVM



マイクロコントローラーでJavaを実行できるVMがいくつかあります-これらはLeJOSwww.lejos.org )とHaikuVM( haiku-vm.sourceforge.net )です

LeJOS-Lego MindStormでJavaアプリケーションを実行できます。 HaikuVM-AVRマイコン上。 現在、LeJOSは2つの部分に分かれています。

-後者のEV3には、Oracleの実際のJVMが使用されます( www.oracle.com/technetwork/java/embedded/downloads/javase/javaseemeddedev3-1982511.html )。 彼女については何も言えません-JVMだけです。

-以前のバージョンのNXJおよびRCXでは、TinyVMベースのJVM( tinyvm.sourceforge.net )が使用されます。 ここで、それについて詳しく説明する価値があります。



なぜなら マイクロコントローラのメモリはほとんどなく(Arduino Uno 28kB Flashおよび2kB SRAM)、クラスファイルを解釈する実際のJVMはそこで実行できません。 しかし、プログラムのバイトコードを変換してネイティブコードにコンパイルし、不要なものはすべて切り取り、そのランタイムはすべて使用しません。 コンパイルすると、Javaの機能の一部(リフレクションなど)が失われます。 しかし、プログラムは機能します!



HaikuVMも動作します-元のJVM(HotSpotのrt.jar)のJREの代わりに、Javaコードを受け取り、LeJOSのJREでコンパイルします(最適化には、String、StringBuilder、Integerなどの標準クラスの代替実装が必要です)。クラスファイルはC ++コードに変換され、HaikuVMからランタイムを追加し(ストリーム、GC、例外をサポート)、avr-gccを使用してこれらすべてをコンパイルします。 したがって、8kbのフラッシュメモリを備えたATMega8までのJavaプログラムを起動できます。



画像

HaikuVM操作アルゴリズム。 haiku-vm.sourceforge.netからの画像



コード変換の例


Javaコード:

public static void setup() { Serial.begin(57600); while (!Serial.isOpen()) { } }
      
      







バイトコード:

 public static setup()V L0 LINENUMBER 140 L0 GETSTATIC processing/hardware/arduino/cores/arduino/Arduino.Serial : Lprocessing/hardware/arduino/cores/arduino/HardwareSerial; LDC 57600 INVOKEVIRTUAL processing/hardware/arduino/cores/arduino/HardwareSerial.begin (J)V L1 LINENUMBER 141 L1 FRAME SAME GETSTATIC processing/hardware/arduino/cores/arduino/Arduino.Serial : Lprocessing/hardware/arduino/cores/arduino/HardwareSerial; INVOKEVIRTUAL processing/hardware/arduino/cores/arduino/HardwareSerial.isOpen ()Z IFNE L2 GOTO L1 L2 LINENUMBER 144 L2 FRAME SAME RETURN MAXSTACK = 3 MAXLOCALS = 0
      
      







生成されたCコード:

 /** public static void setup() Code(max_stack = 3, max_locals = 0, code_length = 22) */ #undef JMETHOD #define JMETHOD ru_timreset_IrTest_setup_V const ru_timreset_IrTest_setup_V_t JMETHOD PROGMEM ={ 0+(2)+3, 0, 0, // MaxLocals+(lsp+pc)+MaxStack, purLocals, purParams OP_GETSTATIC_L, SADR(processing_hardware_arduino_cores_arduino_Arduino_Serial), // 0: getstatic processing.hardware.arduino.cores.arduino.Arduino.Serial Lprocessing/hardware/arduino/cores/arduino/HardwareSerial; (16) OP_LDC2_W_L, CADR(Const0003), // 3: ldc2_w 57600 (35) OP_INVOKEVIRTUAL, B(2), LB(MSG_begin__J_V), // 6: invokevirtual processing.hardware.arduino.cores.arduino.HardwareSerial.begin (J)V (37) OP_GETSTATIC_L, SADR(processing_hardware_arduino_cores_arduino_Arduino_Serial), // 9: getstatic processing.hardware.arduino.cores.arduino.Arduino.Serial Lprocessing/hardware/arduino/cores/arduino/HardwareSerial; (16) OP_INVOKEVIRTUAL, B(0), LB(MSG_isOpen___Z), // 12: invokevirtual processing.hardware.arduino.cores.arduino.HardwareSerial.isOpen ()Z (38) OP_IFNE, TARGET(21), // 15: ifne #21 OP_GOTO, TARGET(9), // 18: goto #9 OP_RETURN, // 21: return };
      
      







上記の例からわかるように、HaikuVMはコードバイトを1つずつCに転送します。



Javaサポートに加えて、HaikuVMでは、C関数を直接呼び出すことができます-NativeCppFunction / NativeCFunctionアノテーションを使用して、メモリと割り込みを操作するためのメソッドが含まれています。



一般的に、私はこのプロジェクトが好きでした-Gradlegithub.com/TimReset/HaikuVMGradle )に翻訳しようとしましたが、HaikuVMにはbat / shファイルに非常に複雑なロジックが含まれているため、これを完全に行うことができませんでした。



しかし、欠点があります-マイクロコントローラーではメモリとプロセッサーの周波数が小さいため、GCの形の小さなオーバーヘッドでさえ(GCを無効にできますが、あまり役に立ちません)、バイトコードをCに変換すると、顕著な遅延が生じます。 これは、たとえば、高周波数(57600 kb / s以上)でシリアルを操作できないことで表され、データが失われ始めます。 それで、ArduinoでJavaを実行する独自の(テストおよびライブラリサポート付き)バージョンの開発を始めました。



JavaコードをWiringに変換する



GCとネイティブバイトコードインタープリターの形式のオーバーヘッドが何であれ、Javaコードを直接Wiring(Arduinoのプログラミング言語、同じC ++)に変換できます。 既製の実装が見つからなかったため、CのJava構文は非常に似ているため、独自の実装( github.com/TimReset/arduino-java )を作成することにしました。 このために、EclipseのAST分析を使用しました( help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FASTNode.html



変換アルゴリズム


抽象メソッドloop()およびsetup()およびユーティリティ定数およびメソッドdigitalRead(int)、analogRead(int)などを含む抽象クラスがあります。 強制的なオーバーライドには、抽象ループ/セットアップ方法が必要です。 ユーティリティメソッドと定数は、配線動作をエミュレートする必要があります-Arduinoのスケッチでは、これらのメソッド/定数にこの方法でアクセスできます。



スケッチはこの基本クラス(BaseArduinoと呼びます)を継承し、セットアップメソッドとループメソッドを実装します。



次に、ロジックを作成します。 メソッドを作成し、変数を使用できます。 サードパーティのライブラリを使用するには、これらのライブラリのメソッドを含むJavaスタブクラスを作成し、コードでこれらのクラスを使用する必要があります。 スタブクラスは、これらのクラスが実装するライブラリの名前を持つパッケージに含まれている必要があります。 ライブラリ自体は、ライブラリ名を持つフォルダー内のparser / src / main / cフォルダーに配置する必要があります。 既にWringコードをコンパイルする場合、これらのライブラリが使用されます。



最後に、Javaクラスは、org.eclipse.jdt.internal.core.dom.NaiveASTFlattenerクラスの子孫であるVisitorを使用して変換されます( www.cs.utep.edu/cheon/download/jml4c/javadocs/org/eclipse/jdt/internal /core/dom/NaiveASTFlattener.html )、いくつかのメソッドがオーバーライドされます:

boolean visit(VariableDeclarationStatement)、boolean visit(FieldDeclaration)、boolean visit(MethodDeclaration) -ライブラリからのクラスの使用を追跡し、すべての修飾子(最終、可視性修飾子、静的)を削除します。 おそらくこれは不要ですが、今のところ機能します。

また、オブジェクトの作成も置き換えます。

decode_results results = new decode_results(); decode_results結果に変換します();



boolean visit(MethodInvocation) -ライブラリクラスへのアクセスを追跡し、それらをメソッドに渡すときに、参照を(&を介して)渡します:

irrecv.decode(結果)irrecv.decode(&結果)に変換されます



C ++の専門家がいる場合、オブジェクトを転送することは常に必要ですか、または他のオプションがありますか?



6)これらはすべて、スケッチの検証とダウンロードを実行できるようにするGradleスクリプトにラップされています。



例:



スケッチ編集





スケッチのダウンロード



例として、スピーカーのIR信号を変換するプログラムを使用します(長い歴史があります-Microlab Speakers Solo 6Cスピーカーはリモコン付きで、数か月後にリモコンが動作しなくなりました。オリジナルが見つかりませんでした。 Arduinoの小さなコンソールchipster.ru/catalog/arduino-and-modules/control-modules/2077.htmlからスピーカーの信号まで)。



Javaコード:

 public class IrReceiverLib extends BaseArduino { public static final long REMOTE_CONTROL_POWER = 0xFF906F; public static final long REMOTE_CONTROL_VOL_UP = 0xFFA857; public static final long REMOTE_CONTROL_VOL_DOWN = 0xFFE01F; public static final long REMOTE_CONTROL_REPEAT = 0xFFFFFFFF; public static final long SPEAKER_IR_POWER = 2155823295L; public static final long SPEAKER_IR_VOL_DOWN = 2155809015L; public static final long SPEAKER_IR_VOL_UP = 2155841655L; public static final long SPEAKER_IR_BASS_UP = 2155843695L; public static final long SPEAKER_IR_BASS_DOWN = 2155851855L; public static final long SPEAKER_IR_TONE_UP = 2155827375L; public static final long SPEAKER_IR_TONE_DOWN = 2155835535L; public static final long SPEAKER_IR_AUX_PC = 2155815135L; public static final long SPEAKER_IR_REPEAT = 4294967295L; public static final int IR_PIN = A0; public final IRrecv irrecv = new IRrecv(IR_PIN); public final IRsend irsend = new IRsend(); long last_value = 0; @Override public void setup() { irrecv.enableIRIn(); } @Override public void loop() { decode_results results = new decode_results(); if (irrecv.decode(results) != 0) { final long value = results.value; if (value == REMOTE_CONTROL_POWER) { last_value = SPEAKER_IR_POWER; irsend.sendNEC(SPEAKER_IR_POWER, 32); irrecv.enableIRIn(); } else if (value == REMOTE_CONTROL_VOL_DOWN) { last_value = SPEAKER_IR_VOL_DOWN; irsend.sendNEC(SPEAKER_IR_VOL_DOWN, 32); irrecv.enableIRIn(); } else if (value == REMOTE_CONTROL_VOL_UP) { last_value = SPEAKER_IR_VOL_UP; irsend.sendNEC(SPEAKER_IR_VOL_UP, 32); irrecv.enableIRIn(); } else if (value == REMOTE_CONTROL_REPEAT) { if (last_value != 0) { irsend.sendNEC(last_value, 32); irrecv.enableIRIn(); } else { } } else { last_value = 0; } } } }
      
      







このコードに変換します:

 #include <IRremote.h> public static long REMOTE_CONTROL_POWER=0xFF906F; public static long REMOTE_CONTROL_VOL_UP=0xFFA857; public static long REMOTE_CONTROL_VOL_DOWN=0xFFE01F; public static long REMOTE_CONTROL_REPEAT=0xFFFFFFFF; public static long SPEAKER_IR_POWER=2155823295L; public static long SPEAKER_IR_VOL_DOWN=2155809015L; public static long SPEAKER_IR_VOL_UP=2155841655L; public static long SPEAKER_IR_BASS_UP=2155843695L; public static long SPEAKER_IR_BASS_DOWN=2155851855L; public static long SPEAKER_IR_TONE_UP=2155827375L; public static long SPEAKER_IR_TONE_DOWN=2155835535L; public static long SPEAKER_IR_AUX_PC=2155815135L; public static long SPEAKER_IR_REPEAT=4294967295L; public static int IR_PIN=A0; IRrecv irrecv(IR_PIN); IRsend irsend; long last_value=0; void setup(){ Serial.begin(256000); irrecv.enableIRIn(); } void loop(){ decode_results results; if (irrecv.decode(&results) != 0) { long value=results.value; if (value == REMOTE_CONTROL_POWER) { last_value=SPEAKER_IR_POWER; irsend.sendNEC(SPEAKER_IR_POWER,32); irrecv.enableIRIn(); } else if (value == REMOTE_CONTROL_VOL_DOWN) { last_value=SPEAKER_IR_VOL_DOWN; irsend.sendNEC(SPEAKER_IR_VOL_DOWN,32); irrecv.enableIRIn(); } else if (value == REMOTE_CONTROL_VOL_UP) { last_value=SPEAKER_IR_VOL_UP; irsend.sendNEC(SPEAKER_IR_VOL_UP,32); irrecv.enableIRIn(); } else if (value == REMOTE_CONTROL_REPEAT) { if (last_value != 0) { irsend.sendNEC(last_value,32); irrecv.enableIRIn(); } else { } } else { last_value=0; } } }
      
      







コードは単純です。信号を取得し、それがリモートコントロールからサポートされている信号である場合、スピーカーの対応する信号に変換します。



信号変換テスト:

 @RunWith(Parameterized.class) public class IRReceiverTest { @Parameterized.Parameters(name = "{index}: Type={0}") public static Iterable<Object[]> data() { return Arrays.asList(new Object[][]{ {"Power", IrReceiverLib.REMOTE_CONTROL_POWER, IrReceiverLib.SPEAKER_IR_POWER}, {"Vol down", IrReceiverLib.REMOTE_CONTROL_VOL_DOWN, IrReceiverLib.SPEAKER_IR_VOL_DOWN}, {"Vol up", IrReceiverLib.REMOTE_CONTROL_VOL_UP, IrReceiverLib.SPEAKER_IR_VOL_UP} }); } private final long remoteSignal; private final long speakerSignal; public IRReceiverTest(String type, long remoteSignal, long speakerSignal) { this.remoteSignal = remoteSignal; this.speakerSignal = speakerSignal; } @Test public void test() { IrReceiverLib irReceiverLib = new IrReceiverLib(); irReceiverLib.setup(); Assert.assertTrue(irReceiverLib.irrecv.isEnabled()); irReceiverLib.irrecv.receive(remoteSignal); irReceiverLib.loop(); Assert.assertEquals(speakerSignal, irReceiverLib.irsend.getLastSignal()); } }
      
      







テストでは、IRremoteライブラリのスタブクラスにメソッドを追加し、信号の受信と送信をエミュレートできるようにしました。 テストでは、信号を初期化してスケッチに転送し、スケッチから送信された信号が予期したものと一致することを確認します。



変換はまだ非常に粗雑ですが、今のところ必要な機能を実行します。 さらに、私はそこでTDDを使用し、すべての控えめな変換機能はテストでカバーされています。これにより、機能を失うことなく将来変更することができます(すでにテストされています-コードはライブラリサポートを追加するときにすでに書き直されています)。



一般的に、私自身はJavaをCに変換するバージョンに落ち着きました。



Javaコードを他の言語に変換する際の注意。 JavaコードはJSに変換できます。 現在、いくつかの作業オプションがあります:GWT( www.gwtproject.org )およびTeaVM( github.com/konsoletyper/teavm )。 また、2つの異なるアプローチを使用します-GWTはソースコードをJS TeaVM-バイトコードに変換します。



便利なリンク



Eclipse ASTを使用する方法を次に示します。habrahabr.ru / post / 269129 Javaプログラムを使用してJavaプログラムを解析する

Groovyコードをシェーダーに変換: habrahabr.ru/post/269591 Java + Groovyでシェーダーをデバッグする

AST分析: habrahabr.ru/post/270173パターンを使用したAST分析



All Articles