Developers Club geek daily blog

1 year, 1 month ago
We write on Java to Arduino

In article I will tell as it is possible to write on Java for Arduino.

Why Java? If it is short — just for fun!

I am Java the programmer and in free time I am played with Arduino and there were a wish to transfer the knowledge of Java to the world of microcontrollers and embedded of devices.

At the moment there are several opportunities to start Java on embedded devices. In this article I will consider them.

Official JVM


The first is official JVM for embedded:
www.oracle.com/technetwork/java/embedded/embedded-se/overview/index.html
habrahabr.ru/post/243549 We start Java Runtime on 256KB of random access memory

There is almost this JVM which performs byte-code. But there are big minuses — it works only for Raspberry Pi and Freescale K64F (I can that missed that, if so — add, please in comments). Support of Raspberry Pi it is definitely good, but it is in fact the computer, though single-board. On it it is possible to start simple JVM also. And there is it from 3 t.r. K64F is already dev board with Cortex M4 onboard. But costs from 3 t.r too. What is much more expensive than widespread Arduino Uno.

JVM with compilation of byte of a code


There are several VM which allow to start Java on microcontrollers is LeJOS (www.lejos.org) and HaikuVM (haiku-vm.sourceforge.net)
LeJOS — allows to start Java applications on Lego MindStorm. HaikuVM — on AVR microcomputers. Now LeJOS is separated into two parts:
— for the last, EV3, uses this JVM, from Oracle (www.oracle.com/technetwork/java/embedded/downloads/javase/javaseemeddedev3-1982511.html). I can tell nothing about it any more — simply JVM.
— for the previous versions, NXJ and RCX, JVM on the basis of TinyVM (tinyvm.sourceforge.net) is used. Here It is necessary to tell about it in more detail.

Since in microcontrollers there is not enough memory (in Arduino Uno 28kB Flash and 2kB SRAM) that this JVM with which would interpret class files not to start there. But it is possible to transform byte code of the program and to compile it in native a code, having cut at the same time all not necessary, all not used runtime. At compilation the part of functionality of Java is lost (for example, reflection). But the program will work!

HaikuVM works also — takes Java a code, compiles it with JRE from LeJOS (alternative implementation of some standard classes — String, StringBuilder, Integer, etc. — is necessary for optimization) instead of JRE from original JVM (rt.jar in HotSpot), the turned-out class files will be transformed to C ++ by a code, adds runtime from HaikuVM (in it support of flows, GC, exception) and compiles all this by means of avr-gcc. And thus it is possible to start Java the program up to ATMega8 with 8kB memory flash!

image
Algorithm of work of HaikuVM. The picture is taken from the website haiku-vm.sourceforge.net

Example of conversion of a code

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


Byte code:
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


The generated C code:
/**
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
};


Apparently from an example is higher — HaikuVM nearly one in one transfers byte code to C.

In addition to support of Java, HaikuVM allows to cause the C functions directly — by means of summaries of NativeCppFunction/NativeCFunction and contains methods on work with memory and interruptions.

In general the project was pleasant to me — I even tried to transfer it to Gradle (github.com/TimReset/HaikuVMGradle), but as HaikuVM comprises quite difficult logic in BAT files/sh, completely it was not succeeded to make it yet.

But there are minuses — as in microcontrollers of memory and frequency of the processor a little, even if small, overhead in the form of GC (though it is possible to disconnect GC, but it poorly helps) and the byte code conversion to C enters notable delays. It is expressed, for example, in impossibility to work with Serial at big frequencies (more than 57600 kb/s) — data begin to be lost. Therefore I began to develop the (with tests and support of libraries) option of start Java in Arduino.

Java conversions of a code to Wiring


What there would be no overhead in the form of GC and native of the byte code interpreter it is possible to transform Java a code directly to Wiring (a programming language in Arduino, the same C ++). I did not find ready implementations therefore decided to write (github.com/TimReset/arduino-java), the benefit syntax of Java is very similar to C. For this purpose used the analysis of AST from Eclipse (help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FASTNode.html)

Algorithm of conversion

There is an abstract class with the abstract loop methods () and setup () and with office constants and the methods digitalRead (int), analogRead (int), etc. The abstract loop/setup methods are necessary for obligatory redefinition. Office methods and constants have to emulate behavior of Wiring — in sketches for Arduino it is possible to address these methods / constants so.

The sketch inherits this base class (I it called BaseArduino) and implements the setup and loop methods.

Further just we write logic. It is possible to create methods, to use variables. For use of third-party libraries it is necessary to create stub classes on Java which would contain methods from these libraries and in a code to use these classes. Stub classes have to is in a packet with the name of library which these classes implement. Libraries have to be in the parser/src/main/c folder in the folder with the name of library. At compilation these libraries will be already used by code Wring.

And at last, the Java conversion of a class happens to the help of Visitor, the successor of the class org.eclipse.jdt.internal.core.dom.NaiveASTFlattener (www.cs.utep.edu/cheon/download/jml4c/javadocs/org/eclipse/jdt/internal/core/dom/NaiveASTFlattener.html) in which some methods are redefined:
boolean visit(VariableDeclarationStatement), boolean visit(FieldDeclaration), boolean visit(MethodDeclaration) — for tracking of use of classes from libraries and removal of all modifiers (final, modifiers of visibility and static). Possibly it excessively, but so far works so.
Also replaces creation of object:
decode_results results = new decode_results (); will transform to decode_results results ();

boolean visit(MethodInvocation) — for tracking of the appeal to classes of libraries and by transfer them transfers links to them to methods (through &):
irrecv.decode(results) will transform to irrecv.decode (&results;)

If there are experts on C ++, prompt, so it is always necessary to transfer objects or there are some else options?

6) All this is enveloped by Gradle a script which allows to start verification and loading of the sketch.

Example:
We write on Java to Arduino
Compilation of the sketch

We write on Java to Arduino
Loading of the sketch

As an example I will take the conversion program IR of signals for columns (there long history — the columns Microlab Speakers Solo 6C with the panel, the panel in several months ceased to work, the original did not find, it was necessary to replace with the universal panel, but it was the big size, as a result made the transformer of signals on Arduino of the small chipster.ru/catalog/arduino-and-modules/control-modules/2077.html panel in signals for columns).

Java code:
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;
            }
        }
    }

}


It will be transformed to this code:
#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;
    }
  }
}


The code is simple — we receive a signal and if it is the supported signal from the panel, then we will transform it to the corresponding signal for columns.

And test for conversion of signals:
@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());
    }
}


For the test I added methods in stub classes of IRremote library that it was possible to emulate acceptance and a signal transmission. In the test I initialize and I transmit a signal to the sketch, further I check that the signal sent from the sketch corresponds to expected.

Conversion is so far very crude, but executes functions, still necessary for me. I applied plus TDD and all modest opportunities of conversion there are covered with tests that will allow to change further it without functionality loss (it is already tested — a code already once was rewritten when added support of libraries).

Generally, so far for myself I stopped on the option of the Java conversion to C.

The note concerning the Java conversion of a code on other languages. Java a code can be converted into JS. Now there are several working options: GWT (www.gwtproject.org) and TeaVM (github.com/konsoletyper/teavm). And they also use two different approaches — GWT will transform the source code to JS, TeaVM — byte a code.

Useful links


Here it is described how to work with Eclipse AST: habrahabr.ru/post/269129 Analysis of Java of the program by means of program java
Groovy conversion of a code to shaders: habrahabr.ru/post/269591 Debugging of shaders on Java + Groovy
Analysis of AST: habrahabr.ru/post/270173 the Analysis of AST with the help of patterns

This article is a translation of the original post at habrahabr.ru/post/274571/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus