Aino

Java Bytecodes to Assembly Language Compiler for the PIC Processor Family.

Written in Java.




Aino triptych by Akseli Gallen-Kallela

Aino?

The first edition of the Kalevala appeared in 1835, compiled and edited by Elias Lönnrot on the basis of the epic folk poems he had collected in Finland and Karelia. This poetic song tradition, sung in an unusual, archaic trochaic tetrametre, had been part of the oral tradition among speakers of Balto-Finnic languages for two thousand years.

Aino is a tragical character in Kalevala, girl who drowns herself when forced to marry an old man. For furter information please visit http://www.finlit.fi/kalevala/indexeng.html




Copyright

Copyright © 2003 Hannu Jokinen, Finland. Aino is licensed free of charge. There is no warranty for the program. The program is provided "as is" without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the program is with you. Should the program prove defective, you assume the cost of all necessary servicing, repair or correction.


Overview

With most programming languages, you either compile or interpret a program so that you can run it on your computer. The Java programming language is unusual in that a program is both compiled and interpreted. With the compiler, first you translate a program into an intermediate language called Java bytecodes -the platform-independent codes interpreted by the interpreter on the Java platform. The interpreter parses and runs each Java bytecode instruction on the computer.

The Java Virtual Machine is an abstract computing machine. Like a real computing machine, it has an instruction set and uses various memory areas. The Java Virtual Machine knows nothing of the Java programming language, only of a particular file format, the class file format. A class file contains Java Virtual Machine instructions (or bytecodes) and a symbol table, as well as other ancillary information.

Java is well suited to code user interfaces and other not time critical applications. Interpreted language is not suitable for fast servers or other CPU intencive applications. Most certainly you would not expect to find anything Java related for microcontrollers.

Aino is a compiler that reads Java classes and outputs assembly code for the PIC processor family assemblers. Aino itself does not support any specific device but you have to give the required parameters in one of the class files.

Next we look how Aino copes with the standard Java. Many of the Java features are not supported or the support is restricted. Some things are made more microcontroller friendly. A short list of unsupported features:

At the moment an unknown set of the 14 bit PIC devices are supported (type number starts with 16, e.g. 16F84).

Usage

The following instructions are based on the assumption that you are using Linux and bash shell. Things look very much the same in the 32-bit DOS if you are using Cygwin.

Environment Setup

Install Java SDK (http://java.sun.com). Aino is not version critical.

Install assembler, gpasm will work. It is part of the gputils package (look at sourceforge).

Install gpsim simulator (http://www.dattalo.com/gnupic/gpsim.html). It is optional, but very usefull. Also lcd module is nice (use a search engine for the sources or package). Look at Notes for additional info.

Gunzip and untar the Aino package. You will find four directories:

No not change the name of the aino directory, since it is also the name of the aino package!

Create a new directory for your project:

cd aino-x.x/examples
mkdir my_project
cd my_project

Set environment variable

export CLASSPATH=~/aino-x.x:.:$CLASSPATH

Creating a Project

Required Files

Copy all the necessary source or class files in the same directory. You need:

The directory must not contain any unnecessary Java files, since Aino will compile everything it finds in the directory. Hint: do not copy the library files, but use links:

ln -s ../../lib/Math.class

Class Processor File

Processor.java file is used to make the change of the processor type easier. The whole source file follows:

public class Processor extends PIC16F84 {
}

Extend the processor type you are using. All the library classes extend the Processor class. Although not strictly necessary it is convenient if your source files also extend the Processor class:

public class MyProject extends Processor {...

Processor Type File

The processor type file has the following format (pic16f84 used as an example):

public class PIC16F84 {
    // processor's registers (optional)
    static final int _W = 0;
    static final int _F = 1;
    static final int _TMR0 = 1;
    static final int _PCL = 2;
    static final int _STATUS = 3;
    static final int _FSR = 4;
    static final int _PORTA = 5;
    static final int _PORTB = 6;
    static final int _EEDATA = 8;
    static final int _EEADR = 9;
    static final int _PCLATH = 10;
    static final int _INTCON = 11;
    static final int _OPTION = 0x81;
    static final int _TRISA = 0x85;
    static final int _TRISB = 0x86;
    static final int _EECON1 = 0x88;
    static final int _EECON2 = 0x89;

    static final int _C = 0;
    static final int _Z = 2;

    public PIC16F84() {
        _initline("\tlist\tp=pic16f84, r=dec");

        _initline("\torg\t0");
        _initline("\tgoto\t_start");
        _initline("\torg\t4");
        _initline("\tgoto\tinterrupt");

        _initline("_STATUS\tequ\t3");
        _initline("_C\tequ\t0");
        _initline("_Z\tequ\t2");
        _initline("_PCL\tequ\t2");
        _initline("_PCLATH\tequ\t10");
        _initline("_TMR0\tequ\t1");
        _initline("_INTCON\tequ\t11");
        _initline("_OPTION\tequ\t129");

        _memory(0x0c, 0x4f);
        _heap(0x4f);
        _banksize(128);
    }

    static void _setMemory(int address, int value) {
    }
    static int _getMemory(int address) {
        return 0;
    }

    static void _asm(String s) {
    }

    static void _initline(String line) {
    }
    static void _clock(int speed) {
    }
    static void _heap(int address) {
    }
    static void _memory(int start, int stop) {
    }
    static void _banksize(int len) {
    }
}

The static final declarations are not absolutely necessary, but usefull if you want to read or modify the memory. And that is exactly what you want to do.

There are a few predefined functions, which do not create any real code, but are used to describe the processor's properties:

static void _clock(int speed): define the processor's speed in Hz. Used by timers and delays.

static void _heap(int address): the last address of one of the memory banks. That bank is used for objects and arrays. Heap address must be less than 256.

static void _memory(int start, int stop): usable memory for the variables. You can have many of these depending on the number of banks in the processor or if you want to leave space for the heap in middle of the memory.

static void _banksize(int len): size of memory bank.

You have to call all the functions somewhere.

There are predefined functions which create code:

static void _asm(String line): creates one line of asm code.

static void _initline(String line): same as the _asm(), but the code is generated in the begining of the asm file. Used for initialisation.

static int _getMemory(int address): reads one byte from the memory. No unnecessary functions calls generated.

static void _setMemory(int address, int value): writes one byte to the memory. No unnecessary functions calls generated.

static void _setBit(int address, int bit): sets the bit number bit at the memory address address.

static void _clearBit(int address, int bit): clears the bit number bit at the memory address address.

static void _delay(int microseconds): generates quite accurate online delay code. Maximum delay depends on the clock speed.

You have to write the init code for the processor (or find a suitable file in the lib directory). At least the following lines must exists (for the pic16f84, other processors might have other requirements):

// Tell the processor type and the default number base.
// Number base must be dec!
_initline("\tlist\tp=pic16f84a, r=dec");
// Processor's init vectors
_initline("\torg\t0");
_initline("\tgoto\t_start");
_initline("\torg\t4");
_initline("\tgoto\tinterrupt");
// Important labels used by the compiler
_initline("_STATUS\tequ\t3");
_initline("_C\tequ\t0");
_initline("_Z\tequ\t2");
_initline("_PCL\tequ\t2");
_initline("_PCLATH\tequ\t10");
_initline("_TMR0\tequ\t1");
_initline("_INTCON\tequ\t11");
_initline("_OPTION\tequ\t129");

Compilation

Now you should have at least two source files in the work directory (MyProject.java, Processor.java) and the processor type class file (e.g. PIC16F84.class). If you need library classes copy (or make link) those also in the working directory.

Create class files:

$ javac *.java

Create asm files:

$ java aino.Aino

Aino reads all the class files and starts to compile. If you are using Java language inner classes there will be more class files that there were Java files.

Depending on your environment it should be possible to combine the two steps:

$ java aino.Aino *.java

Aino will generate an asm file which has the same name than the Java class file where the main() function was declared. If you need an asm file with DOS style newlines call Aino with an option -crlf:

$ java aino.Aino -crlf

Create the hex file:

$ gpasm MyProject.asm

It is a good idea to run the code in the simulator. Start it:

$ gpsim

You can use GUI together with a command line interface. Type the following lines in the text window.

Define the processor type:

gpsim> processor p16f84

Load the hex file or cod file (cod file contains symbolic information):

gpsim> load s MyProject.cod

Set a break point. If the test program ends to an eternal loop you can just run a big number of CPU cycles:

gpsim> break -c 10000

Run 10000 CPU cycles:

gpsim> run

Dump the pocessor's memory space:

gpsim> dump

gpsim documentation explains how to create a more compilacated simulation environment.


Compiler Implementation

Types and Values in the Standard Java

Java is a strongly typed language, which means that every variable and every expression has a type that is known at compile time. The types of the Java language are divided into two categories: primitive types and reference types. Corresponding to the primitive types and reference types, there are two categories of data values that can be stored in variables, passed as arguments, returned by methods, and operated upon: primitive values and reference values.

The primitive types are the boolean type, the integral types, and the floating-point types. The integral types are byte, short, int, and long, whose values are 8-bit, 16-bit, 32-bit, and 64-bit signed two's-complement integers, respectively, and char, whose values are 16-bit unsigned integers representing Unicode characters.

The floating-point types are float, whose values are 32-bit floating-point numbers, and double, whose values are 64-bit floating-point numbers.

The boolean type has the truth values true and false.

There are three kinds of reference types: the class types, the interface types, and the array types. An object is a dynamically created class instance or an array. The reference values are pointers to these objects and a special null reference, which refers to no object.

Primitive Types in Aino

The Java Virtual Machine defines an abstract notion of a word that has a platform-specific size. A word is large enough to hold a value of type byte, char, short, int, float, reference, or returnAddress, or to hold a native pointer. Two words are large enough to hold values of the larger types, long and double. In practise the word must be 32 bits long. Even a boolean value takes 32 bits in a typical virtual machine implementation.

It is not a very good idea to use at least 32 bits for every variable in the microcontroller environment. Thus Aino defines word differently: it is only 8 bits. Using a special trick the boolean value can use only one bit. The following table summarizes the types supported by Aino:

Type

Supported

Java size

VM size

Aino size

Values

boolean1

yes


32

8 or 1

0 or non-zero

byte2

yes

8

32

8

0...255

short

yes

16

32

8

0...255

int

yes

32

32

8

0...255

long

yes

64

64

16

0...65535

char3

yes

16 bit Unicode

32

8

0...255

float

no

32

32



double

no

64

64



1 Boolean arrays whose size is 8 use only one byte alltogether.
2 Byte arrays are equal to boolean arrays for the virtual machine. Thus byte arrays are not allowed. Use char arrays instead.
3 For Aino char is just a byte, no Unicode supported.

Although in the Java the integral types are signed Aino treats them unsigned.

Reference Types in Aino

Reference is a pointer to the location of an object instance. Since Aino word is 8 bits long only the first 256 bytes can hold an object. Locate the heap below address 256! Be careful with the objects: there are no garbage collection in Aino! All the objects hold the allocated memory for ever!

Variables

A variable is a storage location. It has an associated type that is either a primitive type or a reference type. A variable of a primitive type always holds a value of that exact primitive type. A variable of reference type can hold either a null reference or a reference to any object whose class is assignment compatible with the type of the variable.

There are seven kinds of variables:
1. A class variable is a field of a class type declared using the keyword static within a class declaration.
2. An instance variable is a field declared within a class declaration without using the keyword static.
3. Array components are unnamed variables that are created whenever a new object that is an array is created. Although Aino supports only single dimensional arrays, very complicated data structures are possible since the array components can be objects which themselves can hold arrays.
4. Method parameters name argument values passed to a method. For every parameter declared in a method declaration, the parameter's value is copied on the methods's local parameter variable each time that method is invoked.
5. Constructor parameters name argument values passed to a constructor. For every parameter declared in a constructor declaration, the parameter's value is copied on the constructor's local parameter variable each time a class instance creation expression or explicit constructor invocation is evaluated.
6. Exception-handler parameter variables are not supported by Aino.
7. Local variables are declared by local variable declaration statements.

Memory Usage of Aino

Memory is divided in the following parts (PIC16F84 as an example):




Memory Reserved by the Compiler

Aino needs six bytes of memory to run the code:

_ACC is the accumulator for the 16-bit variables upper byte.
_HEAP points to the last used byte of the heap.
_ACC_TEMP, _STATUS_TEMP, _FSR_TEMP, and _W_TEMP are used to save data during an interrupt.

Memory Reuse

Aino has no stack. All the variables are statically placed in the memory space. It does not mean the memory is waisted. Aino can share the same memory area between several functions if they do not call each others. Example code:

    void func1(int a) {
        int b;
        int c;
    }

    void func2(int d, int e) {
        int f, g;
    }

    public static void main() {
        Foo foo = new Foo();
        foo.func1(1);
        foo.func2(2, 3);
    }


We can see that main calls func1 and func2, but func1 does not call func2. Thus Aino arranges the local variables in the following way:


There are totally 8 (10) variables, which occupy only 6 bytes from the memory, since func1 and func2 can reuse the same space. Instance pointer (this) is used by all the non-static functions. It defines the object where the operations should apply.

Aino needs six bytes of memory for its internal use. Rest of the memory is divided between static variables and the heap. Heap contains all the objects and arrays. The following example illustrates the memory usage:

public class Foo extends Processor {
    static int staticVar1 = 0x77;
    static long staticVar2;
    int instanceVar1;
    long instanceVar2;

    public int increment(int i) {
        int a;
        a = i + 1;
        return a;
    }

    public static void main() {
        Foo foo1 = new Foo();
        Foo foo2 = new Foo();

        staticVar2 = 0x1234;
        foo1.instanceVar1 = foo1.increment(0x32);
        foo1.instanceVar2 = 0x5678;
        foo2.instanceVar1 = 0x44;
        foo2.instanceVar2 = 0x6789;

        for (;;) ;
    }
}

The memory map of the processor is illustrated in the following picture. It is a screen snapshot after simulation has finished in the gpsim. Memory locations starting from 0x0c are reserved for Aino's internal use. Class variables are in addresses 0x12- 0x14. Next there are five bytes for the method increment. Two of the bytes are for temporary variables. They are required for provisional results etc. Required extra space is estimated by the Java compiler and the information is included in the class file. Typically the temporary space is reused and thus it is not a big loss. Method main uses three bytes.

_HEAP is initialized to point to address 0x4e. After the first 'new' command the _HEAP points to address 0x4c and after the second class Foo instantiation it points to the address 0x49.





Coding Practices

Main

You need at least one method:

public static void main()

That is called after a few initializations.

Interrupts

You can have interrupt methods. Inside any class write a method

public void interrupt()

You may have several of those as far as they are in different classes. Aino will create code which calls all of those whenever an interrupt occurs. You have to check which interrupt was activated and reset the relevant bit youself. Be careful when calling other functions from the interrupt routine: some data of the called method may be corrupted! Aino reuses memory based on the visible method call chains, but calls from an interrupt fools the logic. To be on the safe side: do not call any methods which are called by any other method.

Integers > 255

Aino supports 8 bits long (int) and 16 bits long (long) unsigned integers. These are scaled down from the original 32 and 64 bits long Java types. It means that Java compiler is happy if you assign a value >255 to an integer variable:

int a = 1000; // ok by javac

Since Aino's integer is only 8 bits long this would not work. If you use values > 255 you have to explicitly make them long values. Do not forget the 'L' in the end of a long constant!

long a = 1000L; // ok by Aino, too

Boolean array

To save space it is possible to use only one bit for a boolean variable. The following code line declares a boolean array:

boolean[] boolArray;

Normally boolArray would be a pointer to the array allocated from the heap:

boolArray = new boolean[8];

Aino treats boolean arrays differently. boolArray itself contains the data bits. You can use boolArray without allocating any space for it, but the size of the array must be less or equal to eight! Also the index must be a constant integer.

static boolean[] boolArray;
final static int buttonPushed = 0;
final static int doorOpen = 1;
final static int lightsOn = 2;
public static void main() {
    if (button()) {
        boolArray[buttonPushed] = true;
        if (boolArray[doorOpen] && boolArray[lightsOn])
        ...



Problematic Program Structures

a = b>0 ? 1 : 2

How can such a simple line of code be so problematic? It is because Aino cannot see the original source code but the compiled classes. They have instructions for the virtual machine which executes the code online. Look at the pseudocode for the header example:

    if b > 0 goto label1
    push constant 2 to stack
    goto label2
label1: 
    push constant 1 to stack
label2:
    a := pop stack

For an interpreter the code is a piece of cake. For a compiler the situation is more difficult: at compile time it is not possible to know which constant will be pushed to the stack. Since Aino compiles instructions one at a time without seeing the big picture the result will not work. Use simpler code structures instead:

if (b > 0) a = 1;
else a = 2;




Libraries

Special Classes

Aino does not support the standard Java platform package classes (for obvious reasons). However, some classes are partly supported.

String

Only final (constant) Strings are supported, e.g.:

public class Test {
    static final String str = “Hello, world!”;
    void foo() {
        char ch = str.charAt(3);
        ...

or

char ch = “Hello, wordl!”.charAt(3);

The string is coded in the program memory. Equal strings are coded only once, even if they are defined in different files. toCharArray method generates always new code.

The following methods are supported:

int length() returns the length of this string. Value is known at the compile time and no real code is generated.

char charAt(int index) returns the character at the specified index. Generates “fool proof” code.

char[] toCharArray converts this string to a new character array.

Library Classes

There are a few library classes provided with the Aino compiler. The most important class is Task.

Task

Task is not an independent class but requires some built in support from the Aino compiler. It is a cooperative kernel, which can be used instead of threads. The constructor is:

public Task(String function_name)

The constructor creates a new task. The name must have the format “class.method”. The method must be of type static void.

The methods are:

public static void run()

Starts the kernel.

public static void sleep(int time)

Suspends the task for time milliseconds and starts the next task.

It is not possible to access the program counter stack of the PIC processor. Thus you can call the sleep() only on the main level of the task. Example:

Right

Wrong

public static void main() {
    new Task(“className.task1”);
    new Task(“className.task2”);
    Task.run();
}
void task1() {
    while (!button_pressed()) 
        sleep(10); // inside task1 body
    ledOn();
    ...
public static void main() {
    new Task(“className.task1”);
    new Task(“className.task2”);
    Task.run();
}
void wait_button() {
    while (!button_pressed()) 
        sleep(10); // NOT INSIDE TASK1
}
void task1() {
    wait_button();
    ledOn();
    ...



Math16

Math16 library contains routines for 16 bit multiplication and division. These routines are called automatically whenever 8 bit or 16 bit multiplication, division or modulus arithmetic is encountered. The functions are in the library so you can enhance the code if necessary.

Lcd

Lcd library writes text on the 'industrial standard' Hitachi LCD display. Constructor accepts the used port address and the associated control address. The constructor:

public Lcd(int port, int tris)

Port defines address of the used port. Tris is the associated control address.

The methods:

public void clear()

Clears the display.

public void line2()

Moves cursor to the beginning of the second line.

public void sendChar(int ch)

Sends one character to the display.

StringBuffer

Use StringBuffer instead of String. It is intended for composing a line of ASCII text. The constructor:

public StringBuffer(int length)

Length defines the maximum size of the buffer. The space is allocated from the heap.

The methods:

public int length()

Returns the allocated length of the buffer.

public int strlen()

Returns the current length of the buffer.

public void clear()

Resets current length to zero.

public int charAt(int position)

Returns character at the buffer position.

public void append(int i)

Converts integer value to ASCII string and appends it to the end of the buffer.

public void append(long ii)

Converts long value to ASCII string and appends it to the end of the buffer.

public void append(char c)

Converts character value to ASCII string and appends it to the end of the buffer.

public void append(String s)

Appends String to the end of the buffer.


Examples

A few examples are provided. To build an example run the 'make' command. To simulate the result run command 'make sim'.

Lcd

xxxx to be completed...

Math

Task

StringBuffer


Notes

gpsim

There might be compilation problems. If the compiler complains about 'string', 'list' or 'hex' add the following lines in the relevant files:

#include <string>
using std::string;
#include <list>
using std::list;
#include <iomanip>

Hint: Add lines to the end of the generated file 'config.h' between directives #if 1/#endif. Run make and when the compilation stops to an error replace #if 1 with #if 0 and continue the make process. You may need to enable the lines again.

There might be complaints like

14bit-tmrs.cc:936: default argument given for parameter 1 of `void TMR2::update(int = TMR2_DONTCARE_UPDATE)'
14bit-tmrs.h:552: after previous specification in `void TMR2::update(int = TMR2_DONTCARE_UPDATE)'

Remove the default value from the cc-file (result is void TMR2::update(int)) to make the compiler happy.

Linker may complain about -lfl. Remove the library from link line in the Makefile. Write the code

int yywrap() { return 1; }

in a proper place inside extern “C” { }.

The previous instructions may also help to compile the lcd simulation module.






History

Date

Aino version

Comment

February 9, 2003

0.1.1

Bugs corrected:
- shifting long int with 0
- unnecessary pagesel directive generation removed

Documentation update

February 1, 2003

0.1

Initial version.