lynxeyedの電音鍵盤

MBDとFPGAと車載で使うデバイスの備忘録

RunnableインターフェースでマルチスレッドLチカ

LPC800 JVMとRunnableクラスの実装

前回(LPC800にJVM載せてUARTループバック - Lynx-EyEDの電音鍵盤 新館)の続きです。

なお、JVMソースコード一式は以下を参照ください
lynxeyed-atsu/RaVem · GitHub

マルチスレッドで複数のLEDを別々の周期で点滅させようと思います。
そうなると、マイコンのIOポート番号や周期をThreadクラスに引数として持たせたい訳ですが、そうするにはJavaではRunnable実装クラスをインスタンス化したオブジェクト参照を、Threadクラスのコンストラクタ引数として渡す必要があります。

JVMレベルで考えると、オペランドスタックに引数をスタック → newされたオブジェクトのインスタンス初期化メソッドが呼び出された際にローカルレジスタにコピー → Public Field領域に引数を書き込みます。

このJVMの一連の動作をどのようにLPC812に実装するかが課題だったのですが、漸くできました。

とりあえず深い事は考えず、まず、以下のJavaのコードを SoftBlinkLEDTest.java という名前で保存します。

public class SoftBlinkLEDTest implements Runnable{
    private int x,y;
    
    public SoftBlinkLEDTest(int bit_num, int sleep_time_ms){
        
        x = bit_num;
        y = sleep_time_ms;
       
    }
    
    public static void main(String[] args){
        
        SoftBlinkLEDTest LED1 = new SoftBlinkLEDTest(7, 40);       // PIO0_7, half-cycle = 40msec
        SoftBlinkLEDTest LED2 = new SoftBlinkLEDTest(16, 1000);    // PIO0_16, half-cycle = 1000msec
        SoftBlinkLEDTest LED3 = new SoftBlinkLEDTest(17, 500);    // PIO0_17, half-cycle = 500msec
        
        
        Thread th1 = new Thread(LED1);
        Thread th2 = new Thread(LED2);
        Thread th3 = new Thread(LED3);
        
        th1.start();
        th2.start();
        th3.start();
    }
    
    public void run(){
        
        int port_bit = x, time = y;
        
        while(true){
            try{
                Thread.sleep(time);
            }catch(Exception e){}
            
            lpc800.portWrite(port_bit, 1);
            System.out.println("LED_on");
            
            try{
                Thread.sleep(time);
            }catch(Exception e){}
            
            lpc800.portWrite(port_bit, 0);
            System.out.println("LED_off");
        }
    }
}

ここで、GPIOにアクセスするためのportWrite(bit_num, value) というメソッドをlpc800クラスに用意しました。もちろんこれは独自のクラスです。そんなクラスもメソッドも無いとコンパイラに怒られてしまいますので、コンパイラを騙すフェイクを作ります。以下を lpc800.java という名前で保存します。

public class lpc800{
    
    public static void portWrite(int bit, int value){}
    
}

コレだけです。LPC812本体がこのクラスとメソッドを正しく認識すれば良く、Javacがこのクラスが何をするか知っていてもあまり意味はないので、何も実動作を記述する必要はありません。
二つのファイルを同一のフォルダに入れて、コンパイラバイトコードを生成してもらいます。

$ javac SoftBlinkLEDTest.java

2つのclassファイルができますが、必要なのは SoftBlinkLEDTest.class だけです。このクラスの実態バイトコードです。これをカンマ区切りデータで表現すると、以下の様になります。

0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x32, 0x00, 0x41, 0x0A, 0x00, 0x10, 
	0x00, 0x21, 0x09, 0x00, 0x04, 0x00, 0x22, 0x09, 0x00, 0x04, 0x00, 0x23, 0x07, 0x00, 0x24, 0x0A, 
	0x00, 0x04, 0x00, 0x25, 0x07, 0x00, 0x26, 0x0A, 0x00, 0x06, 0x00, 0x27, 0x0A, 0x00, 0x06, 0x00, 
	0x28, 0x0A, 0x00, 0x06, 0x00, 0x29, 0x07, 0x00, 0x2A, 0x0A, 0x00, 0x2B, 0x00, 0x2C, 0x09, 0x00, 
	0x2D, 0x00, 0x2E, 0x08, 0x00, 0x2F, 0x0A, 0x00, 0x30, 0x00, 0x31, 0x08, 0x00, 0x32, 0x07, 0x00, 
	0x33, 0x07, 0x00, 0x34, 0x01, 0x00, 0x01, 0x78, 0x01, 0x00, 0x01, 0x49, 0x01, 0x00, 0x01, 0x79, 
	0x01, 0x00, 0x06, 0x3C, 0x69, 0x6E, 0x69, 0x74, 0x3E, 0x01, 0x00, 0x05, 0x28, 0x49, 0x49, 0x29, 
	0x56, 0x01, 0x00, 0x04, 0x43, 0x6F, 0x64, 0x65, 0x01, 0x00, 0x0F, 0x4C, 0x69, 0x6E, 0x65, 0x4E, 
	0x75, 0x6D, 0x62, 0x65, 0x72, 0x54, 0x61, 0x62, 0x6C, 0x65, 0x01, 0x00, 0x04, 0x6D, 0x61, 0x69, 
	0x6E, 0x01, 0x00, 0x16, 0x28, 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 
	0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x29, 0x56, 0x01, 0x00, 0x03, 0x72, 0x75, 0x6E, 
	0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x01, 0x00, 0x0D, 0x53, 0x74, 0x61, 0x63, 0x6B, 0x4D, 0x61, 
	0x70, 0x54, 0x61, 0x62, 0x6C, 0x65, 0x07, 0x00, 0x2A, 0x01, 0x00, 0x0A, 0x53, 0x6F, 0x75, 0x72, 
	0x63, 0x65, 0x46, 0x69, 0x6C, 0x65, 0x01, 0x00, 0x15, 0x53, 0x6F, 0x66, 0x74, 0x42, 0x6C, 0x69, 
	0x6E, 0x6B, 0x4C, 0x45, 0x44, 0x54, 0x65, 0x73, 0x74, 0x2E, 0x6A, 0x61, 0x76, 0x61, 0x0C, 0x00, 
	0x15, 0x00, 0x1C, 0x0C, 0x00, 0x12, 0x00, 0x13, 0x0C, 0x00, 0x14, 0x00, 0x13, 0x01, 0x00, 0x10, 
	0x53, 0x6F, 0x66, 0x74, 0x42, 0x6C, 0x69, 0x6E, 0x6B, 0x4C, 0x45, 0x44, 0x54, 0x65, 0x73, 0x74, 
	0x0C, 0x00, 0x15, 0x00, 0x16, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 
	0x67, 0x2F, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x0C, 0x00, 0x15, 0x00, 0x35, 0x0C, 0x00, 0x36, 
	0x00, 0x1C, 0x0C, 0x00, 0x37, 0x00, 0x38, 0x01, 0x00, 0x13, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 
	0x61, 0x6E, 0x67, 0x2F, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x07, 0x00, 0x39, 
	0x0C, 0x00, 0x3A, 0x00, 0x16, 0x07, 0x00, 0x3B, 0x0C, 0x00, 0x3C, 0x00, 0x3D, 0x01, 0x00, 0x06, 
	0x4C, 0x45, 0x44, 0x5F, 0x6F, 0x6E, 0x07, 0x00, 0x3E, 0x0C, 0x00, 0x3F, 0x00, 0x40, 0x01, 0x00, 
	0x07, 0x4C, 0x45, 0x44, 0x5F, 0x6F, 0x66, 0x66, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 0x2F, 
	0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x01, 0x00, 0x12, 0x6A, 0x61, 
	0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x52, 0x75, 0x6E, 0x6E, 0x61, 0x62, 0x6C, 0x65, 
	0x01, 0x00, 0x17, 0x28, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x52, 
	0x75, 0x6E, 0x6E, 0x61, 0x62, 0x6C, 0x65, 0x3B, 0x29, 0x56, 0x01, 0x00, 0x05, 0x73, 0x74, 0x61, 
	0x72, 0x74, 0x01, 0x00, 0x05, 0x73, 0x6C, 0x65, 0x65, 0x70, 0x01, 0x00, 0x04, 0x28, 0x4A, 0x29, 
	0x56, 0x01, 0x00, 0x06, 0x6C, 0x70, 0x63, 0x38, 0x30, 0x30, 0x01, 0x00, 0x09, 0x70, 0x6F, 0x72, 
	0x74, 0x57, 0x72, 0x69, 0x74, 0x65, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 
	0x6E, 0x67, 0x2F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x01, 0x00, 0x03, 0x6F, 0x75, 0x74, 0x01, 
	0x00, 0x15, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 
	0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x3B, 0x01, 0x00, 0x13, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 
	0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x01, 0x00, 0x07, 
	0x70, 0x72, 0x69, 0x6E, 0x74, 0x6C, 0x6E, 0x01, 0x00, 0x15, 0x28, 0x4C, 0x6A, 0x61, 0x76, 0x61, 
	0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x29, 0x56, 0x00, 
	0x21, 0x00, 0x04, 0x00, 0x10, 0x00, 0x01, 0x00, 0x11, 0x00, 0x02, 0x00, 0x02, 0x00, 0x12, 0x00, 
	0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x14, 0x00, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 
	0x15, 0x00, 0x16, 0x00, 0x01, 0x00, 0x17, 0x00, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, 0x03, 0x00, 
	0x00, 0x00, 0x0F, 0x2A, 0xB7, 0x00, 0x01, 0x2A, 0x1B, 0xB5, 0x00, 0x02, 0x2A, 0x1C, 0xB5, 0x00, 
	0x03, 0xB1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x12, 0x00, 0x04, 0x00, 0x00, 
	0x00, 0x04, 0x00, 0x04, 0x00, 0x06, 0x00, 0x09, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x09, 0x00, 0x09, 
	0x00, 0x19, 0x00, 0x1A, 0x00, 0x01, 0x00, 0x17, 0x00, 0x00, 0x00, 0x90, 0x00, 0x04, 0x00, 0x07, 
	0x00, 0x00, 0x00, 0x54, 0xBB, 0x00, 0x04, 0x59, 0x10, 0x07, 0x10, 0x28, 0xB7, 0x00, 0x05, 0x4C, 
	0xBB, 0x00, 0x04, 0x59, 0x10, 0x10, 0x11, 0x03, 0xE8, 0xB7, 0x00, 0x05, 0x4D, 0xBB, 0x00, 0x04, 
	0x59, 0x10, 0x11, 0x11, 0x01, 0xF4, 0xB7, 0x00, 0x05, 0x4E, 0xBB, 0x00, 0x06, 0x59, 0x2B, 0xB7, 
	0x00, 0x07, 0x3A, 0x04, 0xBB, 0x00, 0x06, 0x59, 0x2C, 0xB7, 0x00, 0x07, 0x3A, 0x05, 0xBB, 0x00, 
	0x06, 0x59, 0x2D, 0xB7, 0x00, 0x07, 0x3A, 0x06, 0x19, 0x04, 0xB6, 0x00, 0x08, 0x19, 0x05, 0xB6, 
	0x00, 0x08, 0x19, 0x06, 0xB6, 0x00, 0x08, 0xB1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 
	0x00, 0x2A, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x19, 0x00, 0x0F, 
	0x00, 0x26, 0x00, 0x12, 0x00, 0x30, 0x00, 0x13, 0x00, 0x3A, 0x00, 0x14, 0x00, 0x44, 0x00, 0x16, 
	0x00, 0x49, 0x00, 0x17, 0x00, 0x4E, 0x00, 0x18, 0x00, 0x53, 0x00, 0x19, 0x00, 0x01, 0x00, 0x1B, 
	0x00, 0x1C, 0x00, 0x01, 0x00, 0x17, 0x00, 0x00, 0x00, 0x98, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 
	0x00, 0x39, 0x2A, 0xB4, 0x00, 0x02, 0x3C, 0x2A, 0xB4, 0x00, 0x03, 0x3D, 0x1C, 0x85, 0xB8, 0x00, 
	0x09, 0xA7, 0x00, 0x04, 0x4E, 0x1B, 0x04, 0xB8, 0x00, 0x0B, 0xB2, 0x00, 0x0C, 0x12, 0x0D, 0xB6, 
	0x00, 0x0E, 0x1C, 0x85, 0xB8, 0x00, 0x09, 0xA7, 0x00, 0x04, 0x4E, 0x1B, 0x03, 0xB8, 0x00, 0x0B, 
	0xB2, 0x00, 0x0C, 0x12, 0x0F, 0xB6, 0x00, 0x0E, 0xA7, 0xFF, 0xD4, 0x00, 0x02, 0x00, 0x0A, 0x00, 
	0x0F, 0x00, 0x12, 0x00, 0x0A, 0x00, 0x20, 0x00, 0x25, 0x00, 0x28, 0x00, 0x0A, 0x00, 0x02, 0x00, 
	0x18, 0x00, 0x00, 0x00, 0x26, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x0A, 0x00, 0x21, 0x00, 
	0x0F, 0x00, 0x22, 0x00, 0x13, 0x00, 0x24, 0x00, 0x18, 0x00, 0x25, 0x00, 0x20, 0x00, 0x28, 0x00, 
	0x25, 0x00, 0x29, 0x00, 0x29, 0x00, 0x2B, 0x00, 0x2E, 0x00, 0x2C, 0x00, 0x1D, 0x00, 0x00, 0x00, 
	0x11, 0x00, 0x05, 0xFD, 0x00, 0x0A, 0x01, 0x01, 0x47, 0x07, 0x00, 0x1E, 0x00, 0x54, 0x07, 0x00, 
	0x1E, 0x00, 0x00, 0x01, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00

上記バイトコードを、MDK-ARMプロジェクトの中のravem.cにvm_array[]という配列名で記述し、コンパイルします。

LPCXpressoだとPIO0_7(赤色LED)が80msec周期、PIO0_16(青色LED)が2000msec周期、PIO0_17(緑色LED)が1000msec周期で点滅するのが確認できると思います。

f:id:Lynx-EyED:20130823203623j:plain

LPC800にJVM載せてUARTループバック

Java VMのせてみた

f:id:Lynx-EyED:20130802144748p:plain
とある必要にかられて、Flash ~32kB/RAM ~4kBクラスの小規模コントローラ向けにJVMを実装しました。
Javaを使いたい場合Linuxを搭載した組み込み機器にJavaを載せるのが通常ですがそれが困難な容量の少ないデバイス向けです。

ここではCortex-M0+のLPC812にJVMの移植を試みました。
lynxeyed-atsu/RaVem · GitHub
解釈できるニーモニックは限られてます。
上記のリンクからダウンロードできるのはMDK-ARMプロジェクト一式です。開発環境はMDK-ARMです。32kBまでなら無償のMDK-ARM Liteが使用できます。(要登録)
https://www.keil.com/demo/eval/arm.htm

なお、LPCXpresso LPC812ボード単体だと書き込み、デバッグが出来ません。LPC-Link2をCMSIS-DAPにして使う必要があります。
(参考) LPCマイコン情報:LPC-Link2ノート1-LPC-Link2を各デバッガにしてデバッグする その1 〜Redlink & LPCXPresso, CMSIS-DAPデバッガ & Keil, SEGGER J-Link & Keil〜


Java中間言語生成にはjavac-1.6.0_51を使用しています。(なにか特殊なコードを吐く様にコンパイラを細工したりはしていません)

SDカードなどからバイトコードをブートしても良いですが、始めてやる作業なので不確定要素が多いと困ります。まず力弱くバイトコードを配列にしてソースコードに貼付け、まるごとMDK-ARMでコンパイルます。const char配列にするのでcsv形式で出力ができるバイナリエディタで、コンパイルした*.classファイルをリードすると良いでしょう。

つまり、javacでコンパイル → バイナリをCの配列にする → JVM本体ごとMDK-ARMでコンパイル → LPC812に書き込み
という流れです。

とりあえず、こんなJavaコードを書きました。UARTから1文字受信して、それを表示します。

import java.io.*;
class hoge1{
    public static void main(String[] args){
        int  i;
        System.out.println( "enter any key" );
        while(true){
            try{
                i = System.in.read();
                System.out.print((char)i);
            }
            catch(IOException e){
                System.err.println( "error" );
            }
        }
    }
}

上記をhoge1.javaとして保存。
コンパイルします。

javac hoge1.java

生成されたhoge1.classは下に示すバイトコードが保存されています。

0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x32, 0x00, 0x34, 0x0A, 0x00, 0x0C, 
0x00, 0x17, 0x09, 0x00, 0x18, 0x00, 0x19, 0x08, 0x00, 0x1A, 0x0A, 0x00, 0x1B, 0x00, 0x1C, 0x09, 
0x00, 0x18, 0x00, 0x1D, 0x0A, 0x00, 0x1E, 0x00, 0x1F, 0x0A, 0x00, 0x1B, 0x00, 0x20, 0x07, 0x00, 
0x21, 0x09, 0x00, 0x18, 0x00, 0x22, 0x08, 0x00, 0x23, 0x07, 0x00, 0x24, 0x07, 0x00, 0x25, 0x01, 
0x00, 0x06, 0x3C, 0x69, 0x6E, 0x69, 0x74, 0x3E, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x01, 0x00, 
0x04, 0x43, 0x6F, 0x64, 0x65, 0x01, 0x00, 0x0F, 0x4C, 0x69, 0x6E, 0x65, 0x4E, 0x75, 0x6D, 0x62, 
0x65, 0x72, 0x54, 0x61, 0x62, 0x6C, 0x65, 0x01, 0x00, 0x04, 0x6D, 0x61, 0x69, 0x6E, 0x01, 0x00, 
0x16, 0x28, 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 
0x72, 0x69, 0x6E, 0x67, 0x3B, 0x29, 0x56, 0x01, 0x00, 0x0D, 0x53, 0x74, 0x61, 0x63, 0x6B, 0x4D, 
0x61, 0x70, 0x54, 0x61, 0x62, 0x6C, 0x65, 0x07, 0x00, 0x21, 0x01, 0x00, 0x0A, 0x53, 0x6F, 0x75, 
0x72, 0x63, 0x65, 0x46, 0x69, 0x6C, 0x65, 0x01, 0x00, 0x0A, 0x68, 0x6F, 0x67, 0x65, 0x31, 0x2E, 
0x6A, 0x61, 0x76, 0x61, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x07, 0x00, 0x26, 0x0C, 0x00, 0x27, 0x00, 
0x28, 0x01, 0x00, 0x0D, 0x65, 0x6E, 0x74, 0x65, 0x72, 0x20, 0x61, 0x6E, 0x79, 0x20, 0x6B, 0x65, 
0x79, 0x07, 0x00, 0x29, 0x0C, 0x00, 0x2A, 0x00, 0x2B, 0x0C, 0x00, 0x2C, 0x00, 0x2D, 0x07, 0x00, 
0x2E, 0x0C, 0x00, 0x2F, 0x00, 0x30, 0x0C, 0x00, 0x31, 0x00, 0x32, 0x01, 0x00, 0x13, 0x6A, 0x61, 
0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x49, 0x4F, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 
0x6E, 0x0C, 0x00, 0x33, 0x00, 0x28, 0x01, 0x00, 0x05, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x01, 0x00, 
0x05, 0x68, 0x6F, 0x67, 0x65, 0x31, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 
0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 
0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x01, 0x00, 0x03, 0x6F, 
0x75, 0x74, 0x01, 0x00, 0x15, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 
0x69, 0x6E, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x3B, 0x01, 0x00, 0x13, 0x6A, 0x61, 0x76, 
0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 
0x01, 0x00, 0x07, 0x70, 0x72, 0x69, 0x6E, 0x74, 0x6C, 0x6E, 0x01, 0x00, 0x15, 0x28, 0x4C, 0x6A, 
0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 
0x29, 0x56, 0x01, 0x00, 0x02, 0x69, 0x6E, 0x01, 0x00, 0x15, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 
0x69, 0x6F, 0x2F, 0x49, 0x6E, 0x70, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x3B, 0x01, 
0x00, 0x13, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x49, 0x6E, 0x70, 0x75, 0x74, 0x53, 
0x74, 0x72, 0x65, 0x61, 0x6D, 0x01, 0x00, 0x04, 0x72, 0x65, 0x61, 0x64, 0x01, 0x00, 0x03, 0x28, 
0x29, 0x49, 0x01, 0x00, 0x05, 0x70, 0x72, 0x69, 0x6E, 0x74, 0x01, 0x00, 0x04, 0x28, 0x43, 0x29, 
0x56, 0x01, 0x00, 0x03, 0x65, 0x72, 0x72, 0x00, 0x20, 0x00, 0x0B, 0x00, 0x0C, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x01, 0x00, 0x0F, 0x00, 0x00, 0x00, 
0x1D, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x2A, 0xB7, 0x00, 0x01, 0xB1, 0x00, 0x00, 
0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x09, 
0x00, 0x11, 0x00, 0x12, 0x00, 0x01, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x02, 0x00, 0x03, 
0x00, 0x00, 0x00, 0x26, 0xB2, 0x00, 0x02, 0x12, 0x03, 0xB6, 0x00, 0x04, 0xB2, 0x00, 0x05, 0xB6, 
0x00, 0x06, 0x3C, 0xB2, 0x00, 0x02, 0x1B, 0x92, 0xB6, 0x00, 0x07, 0xA7, 0xFF, 0xF1, 0x4D, 0xB2, 
0x00, 0x09, 0x12, 0x0A, 0xB6, 0x00, 0x04, 0xA7, 0xFF, 0xE5, 0x00, 0x01, 0x00, 0x08, 0x00, 0x17, 
0x00, 0x1A, 0x00, 0x08, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x00, 
0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x0A, 0x00, 0x17, 0x00, 0x0E, 0x00, 0x1A, 
0x00, 0x0C, 0x00, 0x1B, 0x00, 0x0D, 0x00, 0x23, 0x00, 0x0E, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 
0x00, 0x02, 0x08, 0x51, 0x07, 0x00, 0x14, 0x00, 0x01, 0x00, 0x15, 0x00, 0x00, 0x00, 0x02, 0x00, 
0x16, 0x00, 0x00, 0x00

上記バイトコードを、MDK-ARMプロジェクトの中のravem.cにvm_array[]という配列名で記述し、コンパイル。
USB-UARTとLPC812を接続します。

PIO0_4 -> UART0_TXD
PIO0_0 -> UART0_RXD

無事動作すれば、下の様になります。
f:id:Lynx-EyED:20130802152912p:plain


開発風景(笑
f:id:Lynx-EyED:20130802154506j:plain

おまけ:マルチスレッドっぽい何かを実装してみた

ノンプリエンプティブ型でマルチスレッドを実装してみました。
このご時世にノンプリエンプティブとか何考えてるんですかwと言われかねませんが、、そもそも、すでにCortex-M0+にとってJVMが高負荷であり、メモリが限られていて、かつ10も20もスレッドを持つ意義が感じられない・持つことができない*1デバイスに複雑なTCBを持つプリエンプティブ型を採用するのは利点がないように思えたからです。このデバイスでマルチスレッドを持つとしてもメソッドの負荷によりますが2ないし3個が現実的でしょう。それならばノンプリエンプティブ型のほうが、トータルで見た開発時間が少なくてすみます。
Javacで以下のコードをコンパイルします

class hoge1 extends Thread{
    
        public void run() {
            while(true) {
                System.out.print("thread Number=");
                System.out.println(getName());
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                }
            }
        }
    
    
        public static void main(String[] args){
            System.out.println("Thread test");
            
            hoge1 thread1 = new hoge1();
            hoge1 thread2 = new hoge1();
            
            thread1.start();
            thread2.start();
        }
    
}

ここで、

System.out.println("thread Number=" + getName());

と記述した方が処理が高速になるよね?と考えた方、正解です。
すみません。StringBuilderを実装していないので、Stringの連結ができないんですよ。(ぉぃ
そのうちしますよそのうち
バイトコードは以下のようになります。

0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x32, 0x00, 0x35, 0x0A, 0x00, 0x0F, 
	0x00, 0x1B, 0x09, 0x00, 0x1C, 0x00, 0x1D, 0x08, 0x00, 0x1E, 0x0A, 0x00, 0x1F, 0x00, 0x20, 0x0A, 
	0x00, 0x0B, 0x00, 0x21, 0x0A, 0x00, 0x1F, 0x00, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x03, 0xE8, 0x0A, 0x00, 0x0B, 0x00, 0x23, 0x07, 0x00, 0x24, 0x07, 0x00, 0x25, 0x0A, 0x00, 0x0B, 
	0x00, 0x1B, 0x0A, 0x00, 0x0B, 0x00, 0x26, 0x08, 0x00, 0x27, 0x07, 0x00, 0x28, 0x01, 0x00, 0x06, 
	0x3C, 0x69, 0x6E, 0x69, 0x74, 0x3E, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x01, 0x00, 0x04, 0x43, 
	0x6F, 0x64, 0x65, 0x01, 0x00, 0x0F, 0x4C, 0x69, 0x6E, 0x65, 0x4E, 0x75, 0x6D, 0x62, 0x65, 0x72, 
	0x54, 0x61, 0x62, 0x6C, 0x65, 0x01, 0x00, 0x03, 0x72, 0x75, 0x6E, 0x01, 0x00, 0x0D, 0x53, 0x74, 
	0x61, 0x63, 0x6B, 0x4D, 0x61, 0x70, 0x54, 0x61, 0x62, 0x6C, 0x65, 0x07, 0x00, 0x24, 0x01, 0x00, 
	0x04, 0x6D, 0x61, 0x69, 0x6E, 0x01, 0x00, 0x16, 0x28, 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 
	0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x29, 0x56, 0x01, 0x00, 
	0x0A, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6C, 0x65, 0x01, 0x00, 0x0A, 0x68, 0x6F, 
	0x67, 0x65, 0x31, 0x2E, 0x6A, 0x61, 0x76, 0x61, 0x0C, 0x00, 0x10, 0x00, 0x11, 0x07, 0x00, 0x29, 
	0x0C, 0x00, 0x2A, 0x00, 0x2B, 0x01, 0x00, 0x0E, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x4E, 
	0x75, 0x6D, 0x62, 0x65, 0x72, 0x3D, 0x07, 0x00, 0x2C, 0x0C, 0x00, 0x2D, 0x00, 0x2E, 0x0C, 0x00, 
	0x2F, 0x00, 0x30, 0x0C, 0x00, 0x31, 0x00, 0x2E, 0x0C, 0x00, 0x32, 0x00, 0x33, 0x01, 0x00, 0x1E, 
	0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x49, 0x6E, 0x74, 0x65, 0x72, 0x72, 
	0x75, 0x70, 0x74, 0x65, 0x64, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x01, 0x00, 
	0x05, 0x68, 0x6F, 0x67, 0x65, 0x31, 0x0C, 0x00, 0x34, 0x00, 0x11, 0x01, 0x00, 0x0B, 0x54, 0x68, 
	0x72, 0x65, 0x61, 0x64, 0x20, 0x74, 0x65, 0x73, 0x74, 0x01, 0x00, 0x10, 0x6A, 0x61, 0x76, 0x61, 
	0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x01, 0x00, 0x10, 0x6A, 
	0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x01, 
	0x00, 0x03, 0x6F, 0x75, 0x74, 0x01, 0x00, 0x15, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 
	0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x3B, 0x01, 0x00, 0x13, 
	0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x53, 0x74, 0x72, 
	0x65, 0x61, 0x6D, 0x01, 0x00, 0x05, 0x70, 0x72, 0x69, 0x6E, 0x74, 0x01, 0x00, 0x15, 0x28, 0x4C, 
	0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 
	0x3B, 0x29, 0x56, 0x01, 0x00, 0x07, 0x67, 0x65, 0x74, 0x4E, 0x61, 0x6D, 0x65, 0x01, 0x00, 0x14, 
	0x28, 0x29, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 
	0x69, 0x6E, 0x67, 0x3B, 0x01, 0x00, 0x07, 0x70, 0x72, 0x69, 0x6E, 0x74, 0x6C, 0x6E, 0x01, 0x00, 
	0x05, 0x73, 0x6C, 0x65, 0x65, 0x70, 0x01, 0x00, 0x04, 0x28, 0x4A, 0x29, 0x56, 0x01, 0x00, 0x05, 
	0x73, 0x74, 0x61, 0x72, 0x74, 0x00, 0x20, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x11, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1D, 0x00, 
	0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x2A, 0xB7, 0x00, 0x01, 0xB1, 0x00, 0x00, 0x00, 0x01, 
	0x00, 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x14, 
	0x00, 0x11, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x60, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 
	0x00, 0x1F, 0xB2, 0x00, 0x02, 0x12, 0x03, 0xB6, 0x00, 0x04, 0xB2, 0x00, 0x02, 0x2A, 0xB6, 0x00, 
	0x05, 0xB6, 0x00, 0x06, 0x14, 0x00, 0x07, 0xB8, 0x00, 0x09, 0xA7, 0xFF, 0xE8, 0x4C, 0xA7, 0xFF, 
	0xE4, 0x00, 0x01, 0x00, 0x12, 0x00, 0x18, 0x00, 0x1B, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x13, 0x00, 
	0x00, 0x00, 0x1A, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x08, 0x00, 0x06, 0x00, 0x12, 0x00, 
	0x08, 0x00, 0x18, 0x00, 0x0A, 0x00, 0x1B, 0x00, 0x09, 0x00, 0x1C, 0x00, 0x0A, 0x00, 0x15, 0x00, 
	0x00, 0x00, 0x07, 0x00, 0x02, 0x00, 0x5A, 0x07, 0x00, 0x16, 0x00, 0x09, 0x00, 0x17, 0x00, 0x18, 
	0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x21, 
	0xBB, 0x00, 0x0B, 0x59, 0xB7, 0x00, 0x0C, 0x4C, 0xBB, 0x00, 0x0B, 0x59, 0xB7, 0x00, 0x0C, 0x4D, 
	0x2B, 0xB6, 0x00, 0x0D, 0x2C, 0xB6, 0x00, 0x0D, 0xB2, 0x00, 0x02, 0x12, 0x0E, 0xB6, 0x00, 0x06, 
	0xB1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x13, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x06, 0x00, 0x00, 0x00, 
	0x11, 0x00, 0x08, 0x00, 0x12, 0x00, 0x10, 0x00, 0x14, 0x00, 0x14, 0x00, 0x15, 0x00, 0x18, 0x00, 
	0x17, 0x00, 0x20, 0x00, 0x18, 0x00, 0x01, 0x00, 0x19, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1A, 0x00, 
	0x00, 0x00

コンパイルして実行すると、UARTでマルチスレッドっぽくなっているところが確認できると思います。
f:id:Lynx-EyED:20130809213540p:plain


◆参考
Chapter 4. The class File Format
Javaのhello worldの読み方 | mwSoft




*1:LPC81xは省メモリで安価なデバイスですので機能ごとに複数のデバイスに分けて平行動作させたほうがデバッグ、機能安全の観点からもメリットがありますし、省電力デバイスなのでパワーマネージメントの設計に注力した方が良いと思われます

画像認識におけるフーリエ変換とフーリエ記述子(その1)

はじめに

一連のFPGA関連ネタとして、画像認識を扱おうと思います。
ここでは周波数スペクトルを推定する離散フーリエ変換からはじまり、フーリエ記述子、ウェーブレット変換、ニューラルネットワークに至るまで取り扱うつもりです。

画像の特徴を抽出する

 入力画像から特定の形状を抽出して、基準画像と比較することは画像認証ではよく行う方法です。そのためには、入力画像データから図形を抽出し、数式化する必要があります。
 例えば一辺の長さが2の正方形を画像の中から抽出し数式化しなければならないとします。もし図1の様に、画像の縦横に対して図形が平行なら、数式化は難しくありません。
f:id:Lynx-EyED:20130612191453p:plain
図1:一辺が2の正方形

この正方形は以下の式で表せます。
f:id:Lynx-EyED:20130612235544p:plain

しかし、図2の様に正方形に傾きがある場合、数式化は難しいかもしれません。不可能ではないですが、その数式から正方形であるかどうか判別するのに時間がかかるはずです。

f:id:Lynx-EyED:20130612191505p:plain
図2:傾きのある正方形

このように傾きがある場合でも、画像中に正方形が存在する事を数式として表現できるでしょうか。
一つの答えは、図3のように正方形上を移動する点Pを用意する事です。
f:id:Lynx-EyED:20130612191514p:plain
図3:正方形上を移動する点P

言葉で表現すると以下の様になります。

  1. 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に90度回転
  2. 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に180度回転
  3. 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に270度回転
  4. 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に360度回転


このように表現すると正方形の傾きは関係なくなります。
グラフ化してみましょう。点Pが移動する距離をs、開始時のベクトルと現時点でのベクトルのなす角度をθとします。先ほどdegreeで角度を表現しましたが、便宜上radian表示に直しました。
f:id:Lynx-EyED:20130612191530p:plain
図4:点Pの軌跡をグラフ化

少し違和感のあるグラフになりました。点Pはそもそも正方形上を移動しているので2x4=8だけ進むと周期的に元の場所に戻って来ているはずです。

そこで、点Pの軌跡を「正規化」します。*1
先ほどの様に言葉で表現するとこうなります。

  1. 距離2だけ進み、正の方向(反時計回り)に90度回転
  2. (1)を4回繰り返す


グラフで表現しましょう。「正規化」しているので少し複雑になります。

θの正規化偏角関数をθN(s)、
正方形の全長をL、
開始時のベクトル方向をθ(0)、
現在の角度とθ(0)とのなす角をθ(s)

とすると、
f:id:Lynx-EyED:20130612192029p:plain
となり、グラフ化すると図5の様になります。
f:id:Lynx-EyED:20130612191540p:plain
図5:正方形に対する正規化偏角関数

図5から、正方形を傾きに依存せず周期的な関数で表せる事がわかります。*2

このように、図形の持つ角度(または周波数成分)から、どのような形状をしているかを知る(推定する)ことが可能であり、情報圧縮や特徴抽出に利用されます。

 取得したデータにどの周波数成分がどの程度含まれているかを知る上でフーリエ変換は有効と言えます。

フーリエ変換

周期T[s]の周期関数の基本角周波数ω[rad/s]は
f:id:Lynx-EyED:20130612234001p:plain…(1)

となります。
フーリエ級数における基本的な概念は、
「全ての周期信号はバイアス成分とω[rad/s]およびその整数倍の角周波数をもつ三角関数の和として表現できる」
というものです。
ある時間変化する周期信号 s(t)に対して、
f:id:Lynx-EyED:20130612234014p:plain…(2)

が成り立ちます(実フーリエ級数展開)。
ただし三角関数の直交性から
f:id:Lynx-EyED:20130612234023p:plain…(3)

となります。
式(2)を複素指数関数で表現すると、以下の様になります。
f:id:Lynx-EyED:20130612234031p:plain…(4)


式(4)は、分解して考えると
f:id:Lynx-EyED:20130612234040p:plain…(5)


式(5)と式(2)の係数をオイラーの公式を考慮しつつ対応させて考えると、以下の様になります。
f:id:Lynx-EyED:20130612234048p:plain…(6)


式(3)でも示しましたが、フーリエ級数展開は周期関数に対して有効な手がかりです。しかし非周期信号や孤立波を対象にする場合はこの方法は有効ではありません。どうすれば良いでしょうか?

非周期信号を周期無限大の周期関数とみなせばよいのです。*3

具体的には積分を±無限大の範囲で行う事になります。
しかし数学的にはその手法をとる事は可能ですが、コンピュータでは離散化された信号が対象であり、有限回で計算を終える必要があります。そのために

時間Tのあいだにサンプリングした全データNが周期的に繰り返されるものとみなす

という工夫をして、この問題を解決*しようと*しています。(なぜ「解決しています」では無いのかは後述) これが離散フーリエ変換の考え方になります。

離散フーリエ変換(DFT)

前述のフーリエ級数展開では、信号s(t)は連続時間tの間に変化する信号であったので時間軸に対して積分を行っていました。ここではN[個]のサンプリングデータに対して演算を行います。従って複素フーリエ係数を求める際には時間tではなく、i=0番目から(N-1)番目のサンプリングデータに対して積分(正確に言うと総和)を求めます。

アナログ入力信号: s(t)
サンプリング周期: τ[s/回]
総サンプリングデータ: N[回]またはN[個]
サンプリング時間: T[s]

とする。s(t)を標本化したデータのうちi番目のものs[i]は、

f:id:Lynx-EyED:20130614223033p:plain …(7)

時間t[s]は標本化されたiτ[s]に置き換えられます。
また、
f:id:Lynx-EyED:20130614223108p:plain …(8)

f:id:Lynx-EyED:20130614223119p:plain …(9)


前述した通りN[個]のサンプリングデータに対して演算を行うため、式(5)で求めた複素フーリエ係数は離散化したデータにおいて、
f:id:Lynx-EyED:20130614223132p:plain …(10)

式(10)が離散フーリエ変換と呼ばれるものになります。また、式(4)を直接離散化すると、 逆離散フーリエ変換(IDFT)が得られ、

f:id:Lynx-EyED:20130614223144p:plain …(11)


となります。しかしT=Nτはサンプリング時間であり、信号の周期と必ずしも一致しません。例えば図6のようにサンプリング周期Tと実際の波形の周期T’が異なっている場合を考えましょう。
f:id:Lynx-EyED:20130614223205p:plain
図6:サンプリング周期と実際の周期が異なる場合

この波形に対してDFTを行い、その後IDFTを行うと、図7の様に不連続な波形となってしまい、スペクトルには多くの高次成分が現れてしまいます。
f:id:Lynx-EyED:20130614223231p:plain
図7:不連続な波形として再現されてしまい、多くの高次成分が現れる

この様な現象を抑えるためハミングや、ハニング関数が用いられます。

ちょっと長いので位相回転因子やFFTの話題、ハニング窓関数のお話はまた次回に。


◆参考文献
酒井幸市 (2003);ディジタル画像処理入門 OpenDesignBooks, pp.151-156. CQ出版社
酒井幸市 (2007);改訂版 ディジタル画像処理の基礎と応用 ディジタル信号処理シリーズ, pp.119-125. CQ出版社
後藤尚久 (2001);なっとくする電気数学, pp.99-119. 講談社
佐野理 (1992);キーポイント 微分方程式, pp.108-138. 岩波書店
昌達慶仁 (2010);圧縮処理プログラミング, pp.318-319. ソフトバンククリエイティブ

*1:詳細は後述しますが、不連続点が多いと収束が遅くなるのでなるべく単純な周期関数になるようにします

*2:詳細は後述する、有限個のフーリエ係数で図形の特徴を記述するフーリエ記述子の項で扱います

*3:厳密に言うと周期無限大=非周期関数

Avalon-MM Master Bridgeを探す

USB付きで簡単に使えるBridge

Qsysを使ってAvalonバスにモジュールをぶら下げて使えるシステムを開発中です。
Alteraのデバイスの以下のシリーズで検討しています。

  • Cyclone IV E / GX
  • Cyclone V E
  • MAX V CPLD

Nios IIは規模が500LE未満と軽量なソフトマクロなのでこれをAvalon-MM マスタとしてインプリメントするのが正解かもしれませんが、以下の理由からこれを見送りました。

・CPUとロジックの各部を物理的に分けておきたい(問題発生時の切り分け、USBによる外部との通信)
CPLDの場合はNios IIは通常インプリメントできない
・システム動作中に適宜、リコンフィグレーションする用途を考えているので、CPUは外部に欲しい

そこで外部にUSBデバイスorOTG対応マイコンの採用を考えました。Avalonバスを選択してもバスマスタが別個に選択できるのは便利な点です。マスタ候補は以下の通りです。

  • Vinculum II
  • LPC11U37
  • PIC32MX250F128/220F032

ここから、安価で、部品点数が減らせて、開発が容易でそこそこ演算能力がある(配列を持たせ、その積和を求める事があるので)デバイスを探しました。

  • Vinculum IIはおなじみFTDIのチップでUSBホストにもなるフレキシブルなデバイスですが、開発が謎のRTOSベースでちょっとよくわかんないし思考回路が爆発四散したのでやめ(ぉぃ
  • LPC11U37はNXPUSBLibなど豊富なライブラリに支えられており、とても使いやすい環境を提供されているのですが、BOMが増大する傾向にあったので今回は採用を取りやめました。残念。
  • PIC32MX250Fは(ESDプロテクションを考慮しないなら)そのままUSBバスに直結が可能な32bit MIPS M4KコアPICです。ライブラリが豊富でUSB OTGにも対応しているので今回採用しました。高速ADコンバータも魅力です。

PIC32MXでUSB

PIC32MXでLチカ - Lynx-EyEDの電音鍵盤 新館で触れていたので、MPLABXで開発しました。USB-CDCで動作確認をします
ハードは以前作ったものに、USB mini-Bを追加工したもの。
おもて↓
f:id:Lynx-EyED:20130527232550j:plain

うら↓
f:id:Lynx-EyED:20130527232530j:plain


必要なソフト

基本的にこのライブラリにはいっている"Device - CDC - Serial Emulator"で良いのですが、PIC32MX220F/250Fベースのものがないのと、パイロットランプ用LEDやプッシュスイッチなどは不要、またifdefで宣言されているPIC16/18/24のコードは不要ですので、ばっさり削除しました。

githubにそのときのコードを上げてあります
lynxeyed-atsu/USB_CDC_on_PIC32MX220 · GitHub

ファイルツリー構成は以下の図の様にしてください。
f:id:Lynx-EyED:20130527231144p:plain

メイン動作はUSB_CDC.cのProcessIO()関数が行っています。
この関数の後半部を

if(USBUSARTIsTxTrfReady()) {
		numBytesRead = getsUSBUSART(USB_Out_Buffer,64);
                putUSBUSART("unko",4);
	}
	CDCTxService();

とかきかえて、PIC32MX220F/250Fに書き込み。
Macでは

$  screen /dev/tty.usbmodem621

のようにオープンすると、怒濤のunkoが出迎えてくれる*1
f:id:Lynx-EyED:20130527232241p:plain
うん。もういい。

ちゃんと認識されてる事も確認。
f:id:Lynx-EyED:20130527234646p:plain

というわけで、サクッと動くことがわかったので、コレから作るデバイスのブロック図を作ってみました。以下の図はCyclone Vのものですが、他のデバイスもおおよそ同じ構成となります。
f:id:Lynx-EyED:20130527233200j:plain

設計途中の基板の3Dビュー(下図はMAX V(5M2210)+PIC32MX)
PIC32MXはボード裏側に配置しました。
f:id:Lynx-EyED:20130527233249p:plain
f:id:Lynx-EyED:20130527233258p:plain



◆参考
no crystal usb
PIC32MX220F032B クリスタル無し、内蔵FRCでUSB|marumonのページ

*1:githubに上げてあるコードはちゃんとエコーバックのコードなのであしからず

裏付けの無い開発手法とFPGA石器時代

それは10年前と変わっていないのか

以前の記事( 魔法のようなSoCはない - Lynx-EyEDの電音鍵盤 新館)に付け加えたい事があったのでちょっと書いてみました。

最近FPGAの図書がまた増えつつあるCQ関連ムックですが、
例えばコレとか FPGAスタータ・キットで初体験!オリジナル・マイコン作り
Qsysをつかってモジュールをマウスでバスにぶら下げる…はずが、なぜかHDLを1から書いて車輪の再発明していたりとか、ゼロから作る○○の様な記事が多い。

正直これらの記事を見てて、
FPGAってこんなに開発難しいんですか。該当するインターフェース乗ったマイコンの方が遥かに良いですね☆
という感想持たれてもしかたないのでは。


現在では各FPGAベンダで各モジュールをブロックの様にくみ上げて、最後にトップモジュールにインスタンシエートする機構を用意しています。ここにソフトマクロCPUがほぼ必然的に入ってしまうベンダもありますがw(コレについては結論参照)

使用可能ロジックギリギリで設計してしまったなら*1いざ知らず、なぜこのような機構を利用しないのでしょうか。


この根底にあるのは、紛れも無く
HDL書かない手法とか負けだと思うんだ
という圧力でしょう。


どこからの圧力?自分自身の内面からです。
残念ながら今まで培って来た(それも10数年以上)努力を否定されている様に思えるのでしょう。


この意見に対する反論は大まかに言って以下の通りです

  1. 1から組めば理解できるんですっ!!!
  2. ベンダ提供のお気楽ツール使うと一つのFPGAベンダに依存してしまうことになり、他ベンダFPGAへの移植が困難
  3. どうせその手のツールはつかったところで動かない。だっていままでそうだったし

それに対する答えは要約すると以下の通りです

  1. 理解する前にハードルが高すぎて死ぬ。それに検証どうするんですか?
  2. え、その前にLUTオンリーなドノーマルFPGAってあるんですか?
  3. 今あなたは管理職なんですね。お疲れさまです


(1) 1から組めば理解できる

この台詞、免罪符の如く良く聞きます。
たしかに個人の勉強にはなるでしょう。
例えばHDLで記述した条件分岐がどのような順序でセレクタを推論するのか分かっていた方が良いに決まっています。すこし事情が異なりますがC言語で開発するとしても、そのMCUのアセンブラを多少なりとも理解してた方が対処しやすいのと同じです。
もし、ベンダーが作成した機能モジュールのHDLが理解できないから自作する、という理由であるとすればもう少しHDLの勉強に時間を割くべきです。


加えて、作ったあとどうするのでしょうか?


FPGAで単一モジュールだけを動作させる事はまず無いでしょう。
例えばDRAMアクセスコントローラを入れただけでは、何の役にも立ちません。

  • このモジュールからデータを読み込む機能ブロック
  • 書き込む機能ブロック
  • この両者がぶつからない様に監視するインスタンス

が必要です。

つまるところ、ある程度汎用性のあるバスの規格も考えて自作しなくてはなりません。
さらに帯域を増やすためにこれらを並列化するにはどうしたら良いでしょうか?

自作モジュールの検証は?
自作バスの検証は?


この反論(1)に類似した内容で、「QsysやXPSと言ったツールだって中身を作る人がいるんだ」というものがあります。
→じゃ、そのモジュールの検証結果と妥当性示してくださいね。使えませんから。加えて、もしオリジナルバスで作った場合、他の開発者にそれを学習させて使わせる訳ですが、仕様書って書きました?コードカバレッジどうなってます?



いずれにせよ、莫大なリソースをつぎ込める大企業ならともかく、これら全ての要件を満たすまえに力尽きるでしょう。



(2) ベンダ提供のお気楽ツール使うと一つのFPGAベンダに依存してしまうことになり、他ベンダFPGAへの移植が困難

なるほど、言いたい事は分かります。時代はグローバル()ですよね。
でもそれって、「C言語で記述すればどんなアーキテクチャのCPUにも簡単に移植できますよね」理論と一緒かも。

ベンダ個性の無いFPGAって無いと思われます…。
例えば、積和演算器はたいていFPGA内部にありますけど、実装がベンダーごとに違いますよね(ex.前置加算付き積和演算器)

大抵、ハードマクロなどベンダごとの差別化要素があり、依存しない訳が無いです。
いま、Cortex-AクラスのハードCPUがオンチップでFPGA搭載される方向に進んでいます。この流れでベンダごとにチップはおろか開発手法から差別化が加速するものと思われます。
すなわち、よほど、単純なロジックでない限りベンダに依存しない開発は不可能です

(3) どうせその手のツールはつかったところで動かない。だっていままでそうだったし

10年前の話をステレオタイプに触れ込むのは、どうなんでしょうね。

いずれにせよFPGAは順序や論理回路を放り込む箱に過ぎず、箱の組み立て方に囚われるのは賢明とは言えません。

Xの功罪

では、HDL推奨派に十字架を背負わせて魔女裁判でもすれば良いのでしょうか。

決してそうでは無いでしょう。

例えばザイリンクス。
開発者は検証されたバスに準拠したモジュールを使う方が容易で合理的である事に気が付いてはいるものの、そのバスを使うとなると必然的にソフトマクロCPUのおまけ付きになってしまうのです。バスを選択するとCPUとそのアーキテクチャも必然的に選択してしまっている訳です。
C/C++開発環境と、HDL開発環境を行き来しなくてはなりません。


もちろんCPUを切り離して、オリジナルバスマスタを用意できなくはないですが、新たに有償ツールを使うはめになったり、車輪の再発明が容易ならぬ事態になる事も多くここで敗戦処理と軍事裁判がですね(ry

また、機能モジュールをブロックの様に組み合わせても、そのモジュールのHDLソースが隠蔽されているケースも多く、開発者に不安を与える一因となっています。

こうなると、HDLのみの開発に頼るのも理解できなくはないです。

AlteraいいよAltera

AlteraのQsysでは抽象化を一部犠牲にし、バスとCPUとCPUアーキテクチャの関係を完全に分離しています。

NiosIIがないとしてもUSB Blaster経由でJTAGから、あるいはSPIを使って外部からバス配下のモジュールにアクセスできます。
つまりソフトマクロCPUを載せる前からデバッグ出来るので、問題の切り分けが容易です。このモジュール自身も複数のアダプタで構成されており、自分の好むインターフェースに挿げ替えも可能です。

Qsysを使った開発手法では、マウスで欲しい機能を選択し組み立てて行くだけですが、必要な場合はHDLコードを見たり自分でAvalonバスインターフェースを持ったモジュールを作成することもできます。
もちろん検証モジュールも用意されています。

ソフトマクロCPUで抽象化を図ったベンダとは異なり、モジュール間の接続を完全に意識させ、デバッグ環境を充実させています。JTAGからバスマスタにブリッジ出来るのはとても便利です。


最近CQから出版されたDE0本は何故か、Alteraツールで開発していながら、Qsysをあまり活用できていない様に思います。コレについてはしかるべき方法で取り上げたいと思います。

*1:そのような設計をした時点でアウトだけど

LPC810でArduino API互換環境eXodusinoを使うまとめ

8ピンDIP LPC810でArduinoライクな開発をする

Cortex-M0/M0+向けArduino API互換環境eXodusinoはLPC81xシリーズに対応開始しました。
f:id:Lynx-EyED:20130403134145p:plain
ここでは、8ピン DIPバッケージLPC810で使う方法を取り上げます。

  1. LPCXpresso IDEのダウンロード
  2. スイッチマトリクスツールのダウンロード
  3. eXodusinoのダウンロード
  4. スイッチマトリクスツールでペリフェラルが使うIOピンを指定する
  5. eXodusinoプロジェクトにコピー
  6. マイコンへの書き込み
  7. eXodusinoのLPC810M021F用プログラムのビルド方法


基本的な使い方は以前に書いたまとめとほぼ一緒です。
Arduino API互換環境eXodusinoをLPC1114 DIPで使うまとめ - Lynx-EyEDの電音鍵盤 新館

今回はスイッチマトリクスツール関連が新たに加わっています。

■必要なもの

  • PC
  • LPCXpresso IDE ver5.0.12以上
  • LPC810M021F(DIPのLPC810マイコン)
  • LPC-Link か USB-UART(プログラム書き込みに使う)


(1) LPCXpresso IDEの用意
インストール方法はCQのページから
LPCXpresso IDEのインストール

LPCXpresso IDEダウンロードは(要ログイン)
Home Page | LPCXpresso, powered by Code Red Technologies

ここではLPCXpressoの詳細設定を取り扱うことは致しません。ダウンロード後、無事起動できる事を確認してください。



(2) スイッチマトリクスツールのダウンロード
LPC81xはSPIやUARTやI2CなどのIOピンを電源以外のほぼ全てのピンから自由に割り当てる事が出来ます。
たとえばSPI0は以下のように指定します

	/* SPI0_MOSI */  // PIO0_14 -> MOSI (LSB)
	/* SPI0_MISO */  // PIO0_6  -> MISO
	/* SPI0_SSEL */  // not in use 
	LPC_SWM->PINASSIGN4 = 0xffff060eUL;

でも、このように手作業で書くのは面倒ですし、GUIツールに任せた方がよいので以下のツールの力を借ります。(ダウンロードにログインは不要)


NXP Switch Matrix Tool for LPC800 | www.LPCware.com




(3) eXodusinoのダウンロード
eXodusino[エクソダシノ]とは簡単に言うとArduinoの記述をCortex-M0/M0+に理解させるための手段です。
現状でLPC81xで動作しているのは以下の機能です

  • Serial
  • SPI (一部未実装)
  • 割り込みを除くGPIOの機能
  • delay
  • millis


ここからダウンロードできます。
lynxeyed-atsu/eXodusino · GitHub
f:id:Lynx-EyED:20120901141814p:plain
(図:この部分の「ZIP」とかいてあるボタンをクリック)
ダウンロード後、解凍すると「lynxeyed-atsu-eXodusino-(数字)」という謎フォルダ名になってしまうので、「exodusino」(鍵かっこ含まず)という名前に変更する。
これをLPCXpresso IDEの作業用ディレクトリに移動など、作業しやすい場所に移動してください。

LPCXpresso IDEを起動します
f:id:Lynx-EyED:20120901142635p:plain
QuickStartウィンドウの[Import Existing Projects]を選択し、先ほど移動したexodusinoフォルダを指定してください。
インポートが完了するとProject Explorerウィンドウに下図のような構成でeXodusinoプロジェクトが展開されます。
f:id:Lynx-EyED:20130403093504p:plain
ここでLPCXpresso IDEの必要最小限の設定はおしまいです。



(4) スイッチマトリクスツールでIOピンを指定する
ダウンロードして来たスイッチマトリクスツールのjarファイルを起動します。Mac OSXの場合はダブルクリックで起動するはずですが、起動しない場合は、ターミナル.appで

java -jar Switch\ Matrix\ Tool\ -\ LPC8xx\ ver1\ 20121210.jar 

のように起動します。
今回は8pinのLPC810M021FN8を選択します。
f:id:Lynx-EyED:20130403121554p:plain

ペリフェラルが使うIOを指定します。今回はUARTとLED点滅用のGPIOピンを使おうと思います。
UARTはTXとRXの2ピンが必要です。どのピンにも指定可能ですが、今回は

PIO0_4 -> UART0_TXD
PIO0_0 -> UART0_RXD

に指定します。USART0をクリックします
f:id:Lynx-EyED:20130403123902p:plain


次にPIO0_4をクリックし、ポップアップから、U0_TXDを選択します。
f:id:Lynx-EyED:20130403124257p:plain



同様にPIO0_0をクリックし、ポップアップから、U0_RXDを選択します。
f:id:Lynx-EyED:20130403124521p:plain

これで設定は完了です。ウィンドウ下部のswm.cをクリックし、SwitchMatrix_Init()関数の中身を貰ってきます。右クリックではコピーメニューが出ませんので、ショートカットキー[Ctrl + C]でコピーできます。
f:id:Lynx-EyED:20130403130416p:plain



(5) eXodusinoプロジェクトにコピー
/eXodusino/src/core/device_dependent/lpc800/swm.cpp にSwitchMatrix_Init()関数の中身をペーストします。
f:id:Lynx-EyED:20130403130741p:plain



(6)マイコンへの書き込み
ここではリンクを紹介するにとどめます。NXPのfacebookからですが、

  • LPCXpressoからの書き込み方法
  • flash magicを使う、シリアルからの書き込み方法

が紹介されています。

「LPC810 (= DIP8_ARMマイコン) + LPCXpresso」で,Lチカ (LED点滅)プログラムを動かしてみる | Facebook
LPC810 (= DIP8_ARMマイコン) のフラッシュへの書き込みをシリアルインターフェースで行なう | Facebook



(7)eXodusinoのLPC810M021F用プログラムのビルド方法
今回のビルド条件は以下のようなものです
デバイス:LPC810F021F
LPCXpressoから直接書き込み
マイコンの内蔵クロックで動作

LPCXpresso IDEのProject Explorer → eXodusinoプロジェクトトップフォルダ右クリック → Propertiesを選択
f:id:Lynx-EyED:20120901152810p:plain
[C/C++ Build]の項目を選択 → Manage Configurations... → [LPC810M_F021F_DIP8]を選択 → [Set Active]
[OK] → [Apply] → [OK]
f:id:Lynx-EyED:20130403132220p:plain

これで完了です。*1

Arduinoのスケッチに相当するファイルが[eXodusinoプロジェクトトップ] → [src] → [user_application.cpp]です。

USB-UARTのRX,TXをLPC810のPIO0_4,PIO0_0にそれぞれ接続するとPCからシリアルコンソールで確認する事が出来ます。また、PIO0_1にLEDを接続すると、1秒周期で点滅します。

#include <libmary.h>

void setup(void)
{
	pinMode(P0_1, OUTPUT);
	Serial.begin(9600);
	Serial.println("Start..");
}

void loop(void)
{
	digitalWrite(P0_1,HIGH);
	delay(500);
	digitalWrite(P0_1,LOW);
	delay(500);
	Serial.println(millis() / 1000, DEC);

}

一旦、メニューのProject → Cleanを選択し、その後、Project → Build Allします。
エラーが出現しなければマイコンへ書き込みをします。

[eXodusinoプロジェクトフォルダ]/LPC810M_021F_DIP8/eXodusino.axf
が書き込むバイナリです。


■配線図
書き込み後、以下の回路でテストしました。
f:id:Lynx-EyED:20130403150358p:plain
3.3V電源で駆動してください。

シリアルから9600baudで
1秒ごとに経過時間が表示され、LEDが1秒間隔で点滅します。
f:id:Lynx-EyED:20130330131831p:plain

実験風景
f:id:Lynx-EyED:20130330124030j:plain


番外編:LPC812も使ってみる

いつものフラクタルを描いてみました。今回はLPCXpresso LPC812とMAPLEボードを使っています。一部、OLEDの配線の関係でジャンパーを飛ばしました。
f:id:Lynx-EyED:20130318232303j:plain
詳細はまた、別記事にて。

*1:DIP8パッケージを選択した場合、eXodusinoはInternal RCで動作するようコンフィグレーションします

eXodusinoのフレームワーク

対応デバイスの増加と機能追加に対応する

現在、eXodusinoのgithubで公開しているプロジェクトでは、

  • LPC1114L系列*1
  • LPC1115/301

に対応しています。

[ lynxeyed-atsu/eXodusino · GitHub]

現在、国内とUSAのコミッタを合わせた5人で、複数のNXP Cortex-Mxに対応するような努力が払われており、最終の調整を進めています。

NXPのマイコンはローエンドからミッドレンジまでペリフェラルIPが統一されているので、少しの変更でかなりのデバイスをカバーできるはずですが、例えば、SPIのコアはLPC8xxとLPC11xxで異なります。また、GPIOやSystick以外のタイマーはそれぞれのデバイスに応じて実装されているので、これらの差分もeXodusinoは吸収しなければならない事になります。つまりAPIの抽象度を上げて、それよりも抽象度の低いフレームワークをコールし、このフレームワークがデバイス依存の低レベルのコードを読み出します。

もっとも、某R社のように同じIPと見せかけてデバイス毎に全く違うドライバを書かなきゃいけない、ミスを誘発するような仕組みにはなっておらず、差分もわずかなので、少しの調整で済むと思われます。*2

いままで、Arduino APIデバイスドライバをべた書きしてしまっているコードも多く、汎用性に乏しかった事もあり、この機会に書き直す事にしました。

幸い、NXPがそこそこ汎用性のあるデバイスドライバを用意してくれているので、コレの上にeXodusinoを構築する事にしました。↓こんな感じ。いままで、APIにべた書きしていた部分を完全に分離するのが目標です。

f:id:Lynx-EyED:20130311233348p:plain

*1:XL系列の動作は確認していない

*2:GPIOを除く:GPIOはローエンド機種ほど省電力動作を視野に入れているためかデバイスごとに全く違う機能を持っている事が多く、これはデバイスごとにローレベルまで作り込まなければならない上、ピン名も異なるので、あまり抽象化する必要は無いと判断しています