2019/03/31

Microblaze に豊四季タイニーBASIC を移植する

前回、Arty に Microblaze+カスタムペリフェラルを組み込んだ回路を作り上げました。
Microblaze に豊四季タイニーBASICを移植して、カスタムペリフェラルを動かしてみます。

BASIC のようなインタープリタ言語が入っているとカスタムペリフェラルの検証もお手軽ですね。

XSDK を直接立ち上げてプログラムを書くこともできるようですが、Vivado で前回作ったプロジェクト Arty_GSMB プロジェクトが立ち上がっているところから操作します。XSDK がインストールされていなかったら、しておきましょう。

Vivado のメニューの File から Launch SDK をクリックし、XSDK を立ち上げます。

場所はこのままで OK します。


XSDK の Project Explorer にはチュートリアルでエクスポートしたデータがあれば system_wrapper_hw_platform_0 と、今回新しく作った回路のデータ system_wrapper_hw_platform_1 が存在しています。なければ Vivado からエクスポートして、XSDK を立ち上げなおします。
system.hdf というファイルを開くと alu_double_0 がインスタンスされていることがわかります。無ければ‘Bitstream を更新していないとか、何か手順が抜けているはずです。見直してください。


ここまで出来ていればあとは移植するだけ。まずは Application Project を開いて、コードのひな型を作ります。豊四季タイニーBASIC を移植するにあたって UART は必須なので Hello World のテンプレートが役に立ちます。Hardware Platform を間違えないように。system_wrapper_hw_platform_1 ですよ。





このテンプレートのコードのトップは hello.c なので、main.c に名前変えました。なんとなく。変えなくてもいいですけど気持ち悪いので。

さ、下準備は整いました。あとは本当に移植するだけ。豊四季タイニーBASIC のソースコードを引っ張ってきます。いくつか種類があるのですが、移植が簡単だった Arduino 版がおすすめ。ところで上記のブログのコメント欄を見ないと気付かないのですが、確定版のソースは残念ながらバグがあります。これはパッチを当てて欲しかったです。詳細はコメントを見てください。

ソースは github の ココから落としてきました。展開すると basic.cpp と ttbasic.ino の二つのファイルがあり、普通の C での main.c に相当する ttbasic.ino ファイルから必要部分を main.c に移植し、basic.cpp をインポートして必要部分を追加修正します。

ちなみにこの basic.cpp、コメントが日本語なところが原因で、XSDK にインポートすると文字が化け化け。コンパイルすら通らなくなります。basic.c というファイル(中身は C なので cpp である必要はなしです) の空ファイルを作って、XSDK のエディタでこのファイルを開いているところに、別なエディタで開いた basic.cpp の中身をコピーしてペーストすると文字化けなどの問題がなくなります。

ttbasic.ino でやっていることは、乱数の初期化と basic() 関数の呼び出しです。basic() 関数は中で whle (1) の無限ループを回しているので呼び出すだけでいいです。従ってそのあとのコードは意味ないですが残しています。乱数の初期化はsrand() を呼び出しているのですが、ランダムなシードのソースが無いので(ADC の入力を使ってもいいとは思いますが)、仮に固定値 10 を与えています。main.c (hello.c) は以下のように書き換えます。

#include <stdio.h>
#include <stdlib.h>
#include "platform.h"

void basic(void);

int main()
{
    init_platform();

    srand(10);
    basic();

    cleanup_platform();
    return 0;
}
次に、basic.c を修正して豊四季タイニーBASIC が動作するようにしてみます。
ポイントはシリアル部分の移植で、これができればほぼ移植はできたも同然です。

まずはインクルードを追加修正します。

#include <stdio.h>
#include <stdlib.h>
#include <xuartlite_i.h>

ついでにエディションも変えておきます。起動時にメッセージで表示されます。

// TO-DO Rewrite these functions to fit your machine
#define STR_EDITION "MICROBLAZE"

ではシリアル周りを Microblaze に合わせます。移植は比較的簡単で、以下の三つの関数を移植するだけです。標準入出力は UART にアサインされているので、putchar(), getchar() に置き換えるだけです。c_kbhit() は UART の受信バッファにデータがあるかどうかを調べたい関数なので、UART のレジスタを直接調べます。実装している IP UART lite の仕様書を見ると STAT_REG の bit0 を見ればデータがあるかどうかわかります。下記のコードのようにすればバッファに有効なデータがあるかどうか確認することができます。UART0 マクロはもともと定義されていないので自分で定義していますが、これに関しては後で説明します。

void c_putch(char c){
    putchar(c);
}

char c_getch(){
    return getchar();
}

char c_kbhit(){
    return UART0->STAT_REG & XUL_SR_RX_FIFO_VALID_DATA;
}


XSDK で用意されたマクロや関数があるのですが好きなスタイルではないので、改めてマクロを定義します。ついでにカスタムペリフェラル alu_double のマクロの定義も追加します。

Project Explorer で作ったプロジェクト名 + _bsp 以下の microblaze_0/include/xparameters.h を探します。ここには alu_double だけでなくペリフェラルを含むいろいろなマクロ定義が書かれています。お目当ては UART と alu_double のベースアドレスです。ベースアドレスの定義を見つけたら定義を構造体をこのベースアドレスに割り当てます。

以下がその構造体とマクロの割り当て。

typedef struct {
    u32 RX_FIFO;
    u32 TX_FIFO;
    u32 STAT_REG;
    u32 CTRL_REG;
} UART_Type;

typedef struct {
 u32 DIN;
 u32 dummy1;
 u32 dummy2;
 u32 DOUT;
} ALU_DOUBLE_Type;

#define UART0  ((UART_Type*) XPAR_UARTLITE_0_BASEADDR)
#define ALU_DOUBLE ((ALU_DOUBLE_Type*) XPAR_ALU_DOUBLE_0_S00_AXI_BASEADDR)

構造体の変数は上記のような書き方をすると上から下に向かって変数がメモリに割り当てられます。構造体のポインタ定義をそれぞれのペリフェラルのベースアドレスに割り当てると構造体の変数への読み書きがそのまま、ペリフェラルのインターフェースへの読み書きになります。

あと忘れてはいけないのが乱数生成関数 getrnd() の移植。rand() 関数を使って対応しました。Linux 版がこの実装ですね。

// Return random number
short getrnd(short value) {
    return(rand() % value) + 1;
}

あともうひと手間でとりあえずの移植は完了です。
basic.c の中に putnum() という関数が定義されているのですが、これがすでに存在する関数なので、別な名前にします。tiny_putnum() という名前で置換しておきます。

これで BASIC が走るようになっているはずです。Arty を USB で接続して、Bitstream ファイルを書き込んで、Microblaze のコードを転送しましょう。Program FPGA アイコンをクリックして、Program ボタンをクリックします。Hardware Platform に system_wrapper_hw_platform_1 が選択されていることを確認のこと。



これで Arty に回路が書き込まれました。
最後に BASIC のコードを転送する前に、ターミナルソフトを起動して、シリアル接続をしておきます。9600bps で 8bit パリティ無です。ターミナルソフトの準備が終わったら、Run System Debugger (緑の丸に白三角)から、Run As/Launch on Hardware を選択しましょう。


プログラムが転送されて、Microblaze edition の豊四季タイニーBASIC が立ち上がりましたか?


成功すれば上記のような画面が出るはずです。やったー!

ではいよいよカスタムペリフェラルを BASIC から呼び出せるようにします。DOUBLE() という関数を作成してみましょう。引数に数字が与えられるとその二倍の値を返すようにします。実装には同様に引数を与えて結果が返る RND() 関数を参考にします。

まずは DOUBLE() 関数の本体から。alu_double にアクセスして演算結果を返す関数を作ります。getX2() という32bitの値を受け取って結果を返す関数です。
slv_reg0 の書き込みアドレスには DIN という 32bit の変数をアサインしたので、ALU_DOUBLE->DIN に値を代入することで、slv_reg0 へのデータ書き込みが実行されます。また、slv_reg3 のアドレスには DOUT という変数をアサインしたので、ALU_DOUBLE->DOUT を読み出せば演算結果を得られます。簡単ですねー。

short getX2(short value) {
    ALU_DOUBLE->DIN = value;
    return ALU_DOUBLE->DOUT;
}

あとはこれを呼び出す DOUBLE() 関数の実装です。まずは定義。コマンド名を kwtbl に追加して、i-code を列挙型に追加、後ろに空白を入れない中間コード i_nsa[] にそれぞれ追加します。


// Keyword table
const char *kwtbl[] = {
    "GOTO", "GOSUB", "RETURN",
    "FOR", "TO", "STEP", "NEXT",
    "IF", "REM", "STOP",
    "INPUT", "PRINT", "LET",
    ",", ";",
    "-", "+", "*", "/", "(", ")",
    ">=", "#", ">", "=", "<=", "<",
    "@", "RND", "DOUBLE", "ABS", "SIZE",
    "LIST", "RUN", "NEW"
};

// Keyword count
#define SIZE_KWTBL (sizeof(kwtbl) / sizeof(const char*))

// i-code(Intermediate code) assignment
enum {
    I_GOTO, I_GOSUB, I_RETURN,
    I_FOR, I_TO, I_STEP, I_NEXT,
    I_IF, I_REM, I_STOP,
    I_INPUT, I_PRINT, I_LET,
    I_COMMA, I_SEMI,
    I_MINUS, I_PLUS, I_MUL, I_DIV, I_OPEN, I_CLOSE,
    I_GTE, I_SHARP, I_GT, I_EQ, I_LTE, I_LT,
    I_ARRAY, I_RND, I_DOUBLE, I_ABS, I_SIZE,
    I_LIST, I_RUN, I_NEW,
    I_NUM, I_VAR, I_STR,
    I_EOL
};

// List formatting condition
// 後ろに空白を入れない中間コード
const unsigned char i_nsa[] = {
    I_RETURN, I_STOP, I_COMMA,
    I_MINUS, I_PLUS, I_MUL, I_DIV, I_OPEN, I_CLOSE,
    I_GTE, I_SHARP, I_GT, I_EQ, I_LTE, I_LT,
    I_ARRAY, I_RND, I_DOUBLE, I_ABS, I_SIZE
};

最後に中間コードを実行する関数 ivalue() に DOUBLE() を実装すれば出来上がりです。I_RND の隣にでも加えておきましょうか。

case I_DOUBLE:                 //関数DOUBLEの場合
    cip++;                             //中間コードポインタを次へ進める
    value = getparam();     //括弧の値を取得
    if (err)                            //もしエラーが生じたら
        break;                        //ここで打ち切る
    value = getX2(value); // x2 の計算結果を取得
    break;                            //ここで打ち切る

さぁ、もう一度プログラムを転送しなおして実行してみましょう。

うまくいったようです!ちゃんと演算結果を返してくれていますね。

ここまで出来ると、いろいろ夢は膨らみますね。とりあえず次は割り込み付きのペリフェラルを実装してみたいところです。ディスプレイを駆動する回路やら、キーボード入力を受け付ける回路を書いてみるとそれもまたいいですよねー。他には SDK プロジェクトを SPI flash に書き込んで毎回電源を入れるだけでブート出来るようにもしてみたいです。




Arty に Microblaze を導入して、カスタムペリフェラルを組み込んでみる

FPGA などで RTL で作ったハードワイヤードの回路というのは全くもって融通が利かないし、かといってシビアなタイミングや本当の並列動作はプロセッサと組み込みのソフトでは厳しいですよね?

「だったら両方入ってればいいじゃん!」

はい、どっちも入れましょう。プロセッサ+カスタムペリフェラルという形で。仕事じゃないので好き放題できます(笑)

手元には二年半ほど前に買って、埃をかぶっている Arty が転がっているのでこれを使います。今回はプロセッサには流行りの RISC-V は今回は敬遠して、Arty に簡単に導入できる Microblaze を採用してみます。ちなみに Microblaze も RISC プロセッサです。RISC プロセッサに関してはこちらの有名な本をどうぞ。

Arty

ところで Arty は Digilent の FPGA 評価ボード。いくつか種類があるんですが、入手性はどうなんだろう?特にこの安い無印 Arty。これは確か DigiKey で購入したような。。秋月でも取り扱いがあります(2019.3)。他には Avnet から直接購入することも出来るようです。

Microblaze は Arty 以外でも導入することが出来るので、Arty にこだわる必要はありません。ただ、Arty 安いし、小型なので Cafe でデバッグ出来ますよ(笑)

さて前置きはこれぐらいにして、Arty に Microblaze を導入して、AXI バスに適合するペリフェラルを作って、Microblaze にくっつけましょう。最後に Microblaze に豊四季タイニーBASICを移植して、カスタムペリフェラルを BASIC から動かせるようにしてみます。
それでは、

  • Microblaze を組み込む
  • カスタムペリフェラルを作って Microblaze にくっつける
  • 豊四季タイニーBASICを移植する

を順に追っていきたいと思います。

  1. Microblaze を組み込む
    Arty に関しては沢山のドキュメントが揃っています。これを参考にします。豊四季タイニーBASICを動かすことを目標にしているので、Getting Started with Microblaze がぴったりです。Microblaze と UART (シリアル) が入っているだけですが、豊四季タイニーBASIC はシリアル端末で動作させる仕様なので十分です。とりあえずこのチュートリアルに従って Microblaze を入れてみましょう。
    ここで注意点が1つ。Xilinx の FPGA のツール Vivado の最新版 2018.3 (2019.3 時点)がこのチュートリアルに沿って Microblaze を導入しようとすると途中でコケます。あまり深く追求したく無いので、とりあえず 2016.4 を導入してみたらこのチュートリアル通りにはできましたのでこのバージョンで試した結果を書いておきます。そのうち新しいバージョンでも再度挑戦してみようとは思いますが、めんどくさいのでいつになるやら。あと高位合成などしないので、フリーのライセンスで OK。あと、Microblaze のプログラムをコンパイルするために Vivado のインストール時に XSDK もインストールすることをお忘れなく。
    とりあえず適当なターミナルソフト(チュートリアルでは Tera Term 使ってる)で Hello World が出ればよしです。ここで作るプロジェクト名はチュートリアルと同じ Arty_GSMB にしておきます。
  2. カスタムペリフェラルと作って Microblaze にくっつける
    AXI バスに適合したインターフェースを持つ回路を用意できればいいのですが、Vivado では AXI スレーブ・マスターのひな型を作ることができて、ライブラリに登録されるので、他のプロジェクトから簡単に導入できるんですね。AXI バスとひな型の作り方はこちらのブログで紹介されているので、こちらを参考にしました。

    とりあえず、電子工作をまったく行わずに独自のペリフェラルを作ってみます。
    題して
    「2倍演算機!」
    です。

    32bit のデータを与えるとそのデータを二倍にして返すというとてもシンプルな回路です。ペリフェラルにデータを与えて結果を受け取るという基本中の基本ができればあとは応用ですから。

    Microblaze において AXI バスのペリフェラルはメモリマップドI/Oと言って、そのインターフェースはメモリー上に位置しています。プログラムから見れば特定のアドレスにデータを読み書きすることでペリフェラルにアクセスすることができます。

    こんな感じ。

    ではさっそくカスタムペリフェラルを作ってみます。ここからは先ほどのブログと被るところが多々ありますが、一応書いておきます。
    最初に上記のチュートリアルで作成したプロジェクト Arty_GSMB を開いているならば、Vivado のメニューの Tools から、Create and Package New IP をクリックします。



    Create a new AIX4 peripheral を選択して Next。



    IP の Name を適当につけてあげます。alu_double にしました。



    AXI バスのインターフェースを決めます。割り込みの信号もここで用意できるみたいですが、今回はいらないので有効にしません(今度試してみよう)。Lite, Slave でバス幅を 32、レジスターの数も最小の 4 にしておきます。



    ひな型ができたらすぐ編集するので、Edit IP を選択して Finish。



    続いて Project Manager の Hierarchy の Design Sources の下にある alu_double_v1_0_S00_AXI をダブルクリックするとひな型のコードが右側に現れます。



    このコードには以下の4つのレジスタ slv_reg0,1,2,3 を AXI バスから読み書きするという簡単なコードが書かれています。ここに値を二倍する回路を組んでいきます。



    二倍演算といっても、C などのプログラミング言語で左に1ビットシフトすればよいだけですよね。RTL でも一緒です。そこで、slv_reg0 に書かれた値を二倍して、本来 slv_reg3 を読み出す代わりに、二倍された値を読み出せるようにします。ちなみに slv_reg0 を読み出す代わりに演算した結果でもいいんですけど、ここではあえてそうしてみました。以下の always 文は、指定されたアドレスの値を読み出し用のデータバスに出力するマルチプレクサのコードです。マークしたところが書き換えた部分。簡単ですね。実際合成される回路ではビットシフトは LSB には GND が接続され、他は単なる配線の付け替えという論理回路の消費ゼロなお手軽な演算回路が出来上がります。これでも立派な演算回路ですよね(笑)



    ではここで、最初に作ったプロジェクト Arty_GSMB に戻ります。Project Manager の Design Sources から system_wrapper の下の system_i をダブルクリックするとブロックダイアグラムが開きます。





    ダイアグラムエディタの左にある Add IP アイコンをクリックして、Search で alu とタイプしてみると、先ほど作成した alu_double がみつかります。ENTER して追加すると、ブロックエディタにこの IP がインスタンスされます。



    Run Connection Automation をクリックすると



    以下のようなウィンドウが現れるので、そのまま OK すると AXI バスに自作 IP を自動的に接続してくれます。アドレスのアサインも適宜おこなってくれています。回路作成はこれでおしまい。簡単ですね。素晴らしい!



    新しく作成した回路の Bitstream を生成して、XSDK にエクスポートしておきます。これは最初に Microblaze を組み込んだ時と一緒です。エクスポートする際には Include bitstream にチェックすることを忘れないように。




    以上で HW の作成はおしまいです。豊四季タイニーBASICの移植は分けてアップします。

    それにしても、AXI スレーブのひな形がかんたんに作れて、接続できてしまうのはいいなぁ。仕事ではこういったのは全て手で書き起こしていたのでとても便利に感じます。












2019/03/26

MacBook Pro Mid 2015 の外付け SSD に Windows10 をインストール出来たー

macOS に対応していないツール (Xilinx Vivado) を動かすために 2016/11 ごろに外付け SSD に VirtualBox の仮想マシンを入れてそこで Windows やら Ubuntu をインストールし、ツールを動作させようとしばらく頑張っていましたが挫折していました。

VirtualBox 上でもツールはどうにかインストール出来て Bitstream を吐き出すところまではうまく行くものの、XSDK というツールから USB 経由で FPGA ボード (Arty) に Bitstream を書き込んで MicroBlaze (Xilinx のソフトマクロのプロセッサです)のプログラムを書き込むというところが安定して動作せずに諦めていました。しかもツールの動作遅いし。

MacBook Pro 本体の SSD に Bootcamp で Windows をインストールすればいいんだろうとは思っていましたが、256GB しかない容量に macOS と Windows の共存は無理で、どうしても外付けの SSD か HDD にインストールしたかったのです。しかし数年前は外付けの SSD に Bootcamp で Windows が入れられるとは思っていなかったので(そもそも正式対応しているインストール方法では無いので)、しばらくブランクがあったわけです。



しかし、以下の URL の情報を頼りに MacBook Pro に Windows10 をインストールすることに成功しました (2019/3)。ありがとうございます!

https://ameblo.jp/dai-vocalist/entry-12423490407.html

私の試した環境は以下。

MacBook Pro Mid 2015 (Core i7, 2.2GHz, Memory 16GB)
SSD: Elecom ESD-EC0240GRD
USB memory: Toshiba 16GB TOUSB16GWH USB2.0
Windows10: 1803 Japanese x64 ISO (ググって探しました)
Rufus: v2.18
WindowsSupport: macOS Mojaive 10.14.3 付属の Boot Campアシスタント 6.1.0 からダウンロード

まず上記 Windows と Rufus のバージョンを 2019/3 時点で最新のものを用意し、
https://blue1st.hateblo.jp/entry/2018/02/18/061620
を参考に DiskPart で Windows をインストールすることを試したのですが、最初はマウスやキーボードも認識するし、どうにか起動したものの、WindowsSupport を適用後から起動が怪しくなりました。先走って購入した Windows のライセンスが要らなくなるかもしれないとブルーになりました。。

そこでものは試しと
https://ameblo.jp/dai-vocalist/entry-12423490407.html
を参考に再度挑戦したところ、インストールは成功し、安定して動作するようになりました!Windows Update もかかるのですが、その後も特に問題なさそうです。
バージョンの問題だったのか、インストールの仕方だったのか。
とにかくまともに動いてるからよしと。

一点だけ再確認はちゃんとできていないのですが、Windows を触らずにほっておくとしばらくすると OS がスリープしてしまうのですが、そのタイミングで再起動がかかってしまったらしい事象に2度ほど会ったような気がします。気がつくと macOS が起動してしまっていたので。これを避けるために電源を繋いでいるときはスリープに入ることを禁止しました。とりあえず今の所問題なさそうです。


2018/11/03

オンセミの LV8548MCSLDGEVK でエナメル線で作ったモーターを回してみた!

オン・セミコンダクター社のモータドライバモジュールソリューションキットを使った、間違いなく世界初のアホな試みである。

エナメル線自作モーターは単三電池に巻きつけて形を整えたよくあるタイプです。磁石の置き方が変わってるかも。クリップにいい感じで貼り付けられます。磁力が足りないかも知れないと感じて、2つの磁石の同じ局を対向させています。原理的にはこの配置もアリかな?

これでも立派な DC ブラシモーターなので、LV8548MC で回すことができる。最大 1A 流せるドライバ IC なので、快調に回ってくれる。もちろん電源は 5V 4A 流せる秋月の電源から供給しているので十分電流を流せる。無駄にw

エナメル線自作DCブラシモーター


よく回ってます♪

アップした動画では、PWM duty 100% で回し始めて、だんだん duty を下げて、また 100% まで duty を上げて最後に駆動を止める、という一連のシーケンス。。をやってるのだけど、なんとなく分かります?

エナメル線でモーターなんて小学生か中学生以来。
繊細なので綺麗に回せるものを作るのは案外難しかったです。



2018/09/20

会社近所の風景

絵に描いたようなオフィスビル周辺

Tシャツとジーンズでプラプラしてる自分はちょいと似合わない場所です。

単なるblogger更新テスト。

2018/08/23

メダカ自動餌やり器

夏休みに家族旅行に出ることになりました。

旅行中でも餌あげられるように調べてみると、魚自動給餌器って2000円も出せば手に入るんですね?

でも今年は手元にいいオモチャがあるんで、それで魚自動給餌器作ってみました。去年はメダカを実家に預けたのだけど、今年はうちで面倒見ちゃいます!

その前に、どう作るかですよね。。

いくつか考えて見たけど、メカ的機構に凝ると大変なので(というかそういう作り込み苦手なので)簡単な物で済ませました。

原理は簡単♪
香辛料の容器みたいに穴の空いた容器を穴を下にすれば、餌がこぼれ出てくるようなモンです。容器を下にする時間をコントロールすれば、餌の量も調整可能です。


穴を上にすれば止まるし。
そうそう、この筒を回転させれば、それが出来るんですよね。回転速度と回数を変えれば餌の量もコントロールできそうです。一回転とかキッチリ回すにはステッパモータが丁度いいですよね〜。

さて、100均で買ってきた容器に穴を開けて、餌を入れてみて穴から餌が出てくることを確認してみます。3φ程度でいい感じです。

そこまで確認したら早速工作してみます。

材料はこんな感じ

モーターは後述する Solution Kit に付属のステッパモータを使います。この軸が6φ何ですけど、どう接続するか悩んだ挙句、秋葉原のヴィストンロボットセンターに飛び込んで探してみました。ロボット関連のお店なら何かあるだろうと目論んで。そして見つけたのが写真左上に写っているタミヤの AO-8038 ギヤードモーターハブシャフト。これがぴったりハマりました。

イモネジでモータに固定します

付属するネジはナイロン入りで締めるのが大変なので、別途4φのナットを購入です。そして途中まで組み立てたのですが、、水槽固定用に買ってみた部品が入らない!甘かった。これは別途解決しました。

お、おぅ。入らない。。

さて、この機構を早速動かしてみます。

そこで活躍するのが、オン・セミコンダクターモータ・ドライバ・ソリューション・キット。現在、DCブラシモータまたはステッパを回せる LV8548 のキットが MouserDigiKey で入手可能です。

電源が別途必要なんですが(秋月で売ってる電源でOK。LV8548 は 4〜12V が適当)、他はモータ含めてキットに入ってるので、モータと PC をくっつけて、付属の Arduino Micro に Web から落としてきた GUI 用のファームを Arduino IDE を使って書き込んであげて、Windows の GUI を立ち上げると GUI からモータを回せちゃいます。マイナスドライバーも附属してるので、半田付けすらいらないです。

※さりげなく macOS High Sierra 上の VirtualBox から Windows10 走らせて、そこで GUI を立ち上げています。保障外でしょうが、一応動いています。ただし、VirtualBox 上の Windows10 から Arduino IDE を使ってプログラムの書き込みは出来ないので、Mac 上で動かしてる Arduino IDE からファームを書いています

こんな画面でポチポチと

使い方は何となく分かりますね。注意するところはステッパなので、ステップ角(Step Angle) をセットしないと有効にならない設定があるところぐらいですか。ステップ角はモータの仕様を見て、適宜設定してください。

あとは GUI でポチポチとパラメータを変えながら最適な餌の量を落とせる設定を探してみます。回転周波数と餌の量のグラフでもと思ってわざわざディジタルの計りまで買ったのだけど、餌の重さが小さすぎて面倒なのでやめました。さて、いい感じに餌が落ちるようになったら、Generate Program というボタンがあるので、押してみます。

生成されたスケッチ

今までの操作が、API を使った Arduino のスケッチの形で出力されます。

必要なのは初期設定とモータを回す、止めるといった部分のコードだけなので、余計な部分ぽいコードはガシガシ削除。あとは定時に餌をあげられるように時計などのコードを実装したら完成です!

簡単、簡単〜。



こんな風に回ります



実際動かしてみると、ステッパーモータのガタガタした動きが、実はいい感じに瓶をシェイクしてくれます。餌の固まりを防げるし、餌の出が良くなると思います。むしろマイクロステップなんぞいらないですよ。このアプリケーションに関しては。

Full Step と Half Step の違いも試してみましたが、どっちもどっちな感じです。LV8548 は単純な構成なので、Half Step だとステップが細かくなって滑らかになるはずが、トルクが一定でないので味のある動きをします。LV8702 だと Half Step でもトルクが一定になるよう電流のコントロールをするモードがあるのですが(詳しくはデータシート参照)、滑らかになって餌やり器には多分都合が悪いです。

ちなみに LV8548 を搭載した場合はベースボードにある CN8 の A2-A5 端子が全部使えます(Arduino Micro の A2-A5 に直結されてる)。温度センサーをつけて、水温次第で餌をあげる回数や量をコントロールするのも楽しそう!

あと、プチ改造ですが、


ベースボードの VCC と VIN をショート


ベースボードの VCC と Arduino の VIN を直結してあげると、USB から Arduino に給電する必要がなくなるので、モータ用の電源だけでボードを動作させることができます。
Arduino の VIN は 7V〜12V なので、モータに給電する電源次第では Arduino がうまく動かなくなったり、壊したりするので要注意です。

さ、これで安心して旅行に出かけられます!じゃ!

ソースコードはこちら↓
#include <LV8548_STEP_Lib.h>
#include <TimerOne.h>
#include <TimerThree.h>

// LV8548 Stepper library のインスタンス化
Lib_LV8548Step Lib;

/**
 * @brief モーターの制御を表す値
 *
 * hms_def 時間構造体の変数 r に格納される
 */
enum {MT_FEED = 1, MT_FREE = 0};

/**
 * @brief 時間格納構造体
 *
 * モーター制御用の変数を含む。RTC またはシーケンサーに使われる。
 */
typedef struct {
  uint8_t h; ///< 時
  uint8_t m; ///< 分
  uint8_t s; ///< 秒
  uint8_t r; ///< モーター制御
} hms_def;

// RTC 用、時間変数
hms_def rtc_time = {0, 0, 0, MT_FREE};

// シーケンサー用時間変数の配列
// 好きなように設定してして下さい
// ここでは毎回励磁を停止していますが、止めなくても大丈夫です。
// むしろ励磁しておくと止まっている位置から瓶が回転しにくくなるので、
// 不用意に穴の位置が変わってしまうことを低減できます。モーターは熱くなりますが。
hms_def timer_list[] = {
  { 7, 30,  0, MT_FEED}, ///< 朝の餌やり開始
  { 7, 30, 15, MT_FREE}, ///< 餌やり終了、360度回転後少ししてからモーターの励磁を停止
  {12,  0,  0, MT_FEED}, ///< 昼の餌やり開始
  {12,  0, 15, MT_FREE}, ///< 餌やり終了、360度回転後少ししてからモーターの励磁を停止
  {16, 30,  0, MT_FEED}, ///< 夜の餌やり開始
  {16, 30, 15, MT_FREE}, ///< 餌やり終了、360度回転後少ししてからモーターの励磁を停止
};

// シリアル通信用の文字列バッファと Index 用変数
char read_buff[5]; // FMT: x:dd + \0
int buff_ptr = 0;

/**
 * @brief setup()
 */
void setup()
{
  // コマンド入出力用
 Serial.begin(19200);
  
  // LV8548 のための初期化
 Lib.initLib();
 Timer1.initialize(65);
 Timer1.attachInterrupt(interrupt);
  
  // RTC 用の 1秒割り込みを Timer3 で作る
  Timer3.initialize(1000000); // 1sec
  Timer3.attachInterrupt(interrupt3);
 delay(100);
 
  // ステッパーの1ステップの角度設定
 Lib.setStepAngle(7.5);  
}

/**
 * @brief Timer1 割り込みハンドラ
 *
 * LV8548 Stepper library が使用している。
 */
void interrupt()
{
 Lib.timerFire(100);
}

/**
 * @brief 24時間時計
 *
 * リアルタイム更新される時計で、時、分、秒の値を保存する。一秒に一度呼び出されることを期待しています。
 */
void updateRtc()
{
  // Inc 秒
  rtc_time.s += 1;
  if (rtc_time.s == 60) {
    rtc_time.m += 1;
    rtc_time.s = 0;
  }
  // Inc 分
  if (rtc_time.m == 60) {
    rtc_time.h += 1;
    rtc_time.m = 0;
  }
  // Inc 時
  if (rtc_time.h == 24) {
    rtc_time.h = 0;
  }
}

/**
 * @brief hms_def 時間構造体の時間要素の比較
 *
 * 時、分、秒の値を比較する
 *
 * @param  st      ソース時間
 * @param  dt      ディスティネーション時間
 * @retval true    時間一致。
 * @retval false   時間不一致。
 */
 bool timeIsEqualTo(hms_def *st, hms_def *dt)
{
  return ((st->h == dt->h) &&
          (st->m == dt->m) &&
          (st->s == dt->s));
}

/**
 * @brief hms_def 時間を表示する
 *
 * %H:%M:%D のフォーマットでシリアルに出力。
 *
 * @param  t 表示したい時間構造体
 */
void printTime(hms_def *t)
{
  Serial.print(t->h);
  Serial.print(":");
  Serial.print(t->m);
  Serial.print(":");
  Serial.print(t->s);
}

/**
 * @brief hms_def 時間を表示する
 *
 * Timer3 の割り込みハンドラ
 */
void interrupt3()
{
  // RTC の時間を更新する
  updateRtc();

  // RTC の時間をシリアルに出力
  printTime(&rtc_time);

  // シーケンサーの時間を1つずつ確認する
  for (int i = 0; i < sizeof(timer_list) / sizeof(hms_def); i++) {
    // 現在時刻と一致するかどうか
    if (timeIsEqualTo(&rtc_time, (timer_list + i))) {
      if (timer_list[i].r == MT_FEED) {
        // 餌を与える時間
        // モーターの回転速度次第で餌の量が変わりますので
        // 適宜第一引数を調整して下さい。
        // Half step はステップごとにトルクの変動があります。
        // Full step との振動の違いも比較すると面白いです。
        Serial.print(" <- FEED");
        Lib.motorRotationDeg(12, 360.0, 0, 1);
        
      } else if (timer_list[i].r == MT_FREE) {
        // 餌を与えた後、モーターが回り切ってから低消費電力のため励磁を停止する。
        Serial.print(" <- free");
        Lib.motorRotationFree();
        
      }
    }
  }
  Serial.println("");
  
}

/**
 * @brief シリアルで受信した文字列をパースする
 *
 * RTC の時、分、秒を更新することができる。
 * 設定範囲外の場合は無視される。
 *
 * @param  t 受信文字列
 */
void parseBuff(char* s)
{
  int para = atoi(s + 2);
  switch (s[0]) {
    case 'h':
      if (para >= 0 && para < 24) {
        rtc_time.h = para;
        Serial.print("Set h = ");
        Serial.println(para);
      }
      break;
    case 'm':
      if (para >= 0 && para < 60) {
        rtc_time.m = para;
        Serial.print("Set m = ");
        Serial.println(para);
      }
      break;
    case 's':
      if (para >= 0 && para < 60) {
        rtc_time.s = para;
        Serial.print("Set s = ");
        Serial.println(para);
      }
      break;
  }
}

/**
 * @brief シリアルで文字列を受信する
 *
 * 文字列バッファー read_buff のサイズに達した場合、もしくは '\n' (LF) を
 * 受信した場合は文字列の最後に '\0' (NULL) を埋め込み、parseBuff() をコールする。
 *
 * @param  t 受信文字列
 */
void serialRead()
{
  if (Serial.available() > 0) { // 受信したデータが存在する
    char c = Serial.read(); // 受信データを読み込む
    read_buff[buff_ptr++] = c;
    if (buff_ptr == (sizeof(read_buff) - 1) || c == '\n') {
      read_buff[buff_ptr] = '\0';
      buff_ptr = 0;
      parseBuff(read_buff);
    }
  }
}

/**
 * @brief loop()
 */
void loop()
{
  serialRead();
  delay(100);
}

2018/02/13

micro:bit で低温調理

このネタは自分の facebook のタイムラインにも投下してるんですが、もう少し説明をクドクドと付け加えてブログに書き起こし直しました。

システム全景

低温調理でローストビーフを調理するのに、Anova とか出来合いをマシンを買うのはつまらないし、安くないから電子工作しよう!

という話です。

ことの始まりは、ローストビーフを炊飯器で作るレシピを試してみたら温度コントロールが思うようにならないので、電気ポット使ってどうにかならないかと思い、ググったら既にやってる人 Raspberry Piでつくる! 柔らかローストビーフ♪を見つけました。
ラズパイは持ってるんですが、温度調整ごときにラズパイ使うのも勿体無いので、別環境に移植しました。SSR も 3.3V 駆動したかったのと、コストを抑えるため、別なものを使っています。

最初に言います。PI 制御部分のコードは上記ブログの niku.py version 2.0 から移植させていただきました。ありがとうございます。パラメータもチューニングしようかと思ったんですが、同じ値を使ってみたら悪くないので、そのまんま。。重ね重ねありがとうございます。

おかげさまで我が家ではこのマシンはローストビーフのシーズンに活躍しております。ローストビーフだけでなく、低温調理チャーシューもお気に入りです。

まず最初に一昨年ぐらいに Nucleo の mbed に実装したのですが、去年末には micro:bit に移植しました。ここでは micro:bit 版について書いておきます。

回路図
手書きですみません

実体配線図1

実体配線図2

LM60 はスーパーX で固めて防水加工
GND 端子がチップに直結しているので
温度が伝わりやすいよう GND は
気持ちスーパーXを薄めにしてます

回路は簡単かと思います。

回路概要ですが、温度センサー LM60BIZ の出力 Vo を ADC 入力 AIN0 に入れ、ポットの電源のコントロール出力を P8 に出しています。100V の電源を ON/OFF している SSR は 100V AC のケーブルの片側を切って挟んでいるだけ。micro:bit とは取り外しの手間とか考えてステレオミニジャックで繋いでますが、直接接続しても問題ありません。使用した 秋月の SSR キット は 3.3V でも ON/OFF 出来るので、micro:bit の出力で直接コントロール出来ます。また SSR には秋月で大きめな?ヒートシンクつけています。なお AIN1 にはリファレンス電圧をシャントレギュレータ NJM431L から供給しています。

基本的には LM60 (秋月へのデータシートへのリンク)は温度に比例した電圧を出力するので、その電圧を micro:bit の ADC で測ればいいのです。ADC の結果はリファレンス電圧 3.3V の電圧を 1024 分割した値として得られます。

ちなみに micro:bit の回路図は https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf のようです。

micro:bit の ADC のリファレンス電圧 3.3V は上記回路図を見るとわかるように、多分 nRF51288 の AVDD の電位のことを指していて、+V_TGT より供給されています。

+V_TGT は3通りの電圧の供給方法があって、 NXP のマイコン MKL26Z128VFM4 に入力された USB からの 5.0V 電源によって作られた 3.3V VREG33OUT か、CON1 コネクタに繋がれたバッテリー VBAT もしくは micro:bit のエッジコネクタから +V_TGT に直接供給される電源ということになります。

MKL26Z128VFM4 は USB I/F と 3.3V レギュレータを兼ねているようなのですが(レギュレータ出力以外は nRF51822 の SWD に繋がる線がある)、そのレギュレータ出力 VREG33OUT は MIN 3V, TYP 3.3V, MAX 3.6V とリファレンスには程遠く、しかもその出力は BAT60A というショットキーを挟んで +V_TGT に接続されます。いくらそのショットキーが低ドロップ電圧と言っても多少なりとも下がるわけで(MIN 0.1V の模様)。。全く信用なりません。他の経路からは電池からの供給だったりするので(アルカリ電池だったら 1.5V x 2 = 3.0V)、3.3V ですらないわけです。

という訳なので別途リファレンス電圧を用意して、その ADC の結果と、LM60 の ADC の結果から LM60 の出力電圧を計算にて求めて、少しは精度をあげてみようという試みを行なってみました。

リファレンス電圧には NJM431L シャントレギュレータを使いました。秋月で入手できるし、使い方も簡単。少しはマシなはずです。

NJM431 は、抵抗を通して VKA に VREF〜36V の電圧を与えると VKA には VREF の電圧が出力される素子です。回路構成は NJM431 のデータシートの TEST CIRCUITS の 1. で、その INPUT に micro:bit の VDD (3.3V ぐらい) を接続し、抵抗には 200Ωを挟んでみました。ELECTRICAL CHARASTERISTICS にあるように、VKA には TYP = 2495mV 出てるはずです。念のために VKA をテスターで電圧を測ってみたら 2475mV でしたので、コードには AIN1 に 2475mV 入ってくると仮定して、AIN0 に入ってくる LM60 の出力 Vo を計算で求めてます。AIN0/AIN1=Vo/2475 の単なる比例計算から求まります。どれ程精度が上がったのかはちゃんと確認してませんが、電源が変わってもリファレンスは一定のはずです。さー、どこまで効果があるのやらw

あと、LM60 の Vo と GND 間には 0.1uF のコンデンサを入れるといいです。出力の安定感がかなり向上します。VS+ GND 間はそれほどでもなかったので省略してます。お好みでどうぞ。また ADC の結果は複数回取って平均した方がいいです。そうしないと結果がバラつきます。

ちなみに回路図には書いてありませんが、micro:bit 用電源のために、USB 電源プラグ用のコンセントが AC 電源プラグに並列に接続してあります。無くてもいいですが、一つにまとまってると使いやすいのでそうしてます。

部品表
ちょっとうろ覚えなところもあり(2018/2現在)。
ざっくりこんなもんだと思って下さい。
このリスト+電気ポットです。

部品価格購入先
micro:bit2160円秋月電子
micro:bit Breakout (+ headers)842円マルツ
ソリッド・ステート・リレーキット25A(20A)250円秋月電子
ヒートシンク54x50x15mm110円秋月電子
LM60BIZ 温度センサー100円秋月電子
NJM431L シャントレギュレータ30円秋月電子
200Ω カーボン抵抗1/4W6円千石電商
0.1uF 積層セラミックコンデンサ15円秋月電子
ブレッドボード270円秋月電子
ブレッドボード・ジャンパーワイヤ10cmセット180円秋月電子
3.5mmステレオミニジャックDIP化キット150円秋月電子
3.5mmステレオジャックMJ-073H60円秋月電子
ステレオミニジャックケーブル108円百均
micro USB ケーブル108円百均
AC USB 電源プラグ108円百均
2P ACソケット105円千石電商
ケース108円百均
ケーブル類数百円〜秋月電子・千石電商
ゴムブッシュ秋月電子
3φネジ少々秋月電子
電気ポットを除くとざっくり 5000円ぐらいでしょうか。
安い。。とは言えないか。
でも micro:bit は使い回し出来るのでそれは差っ引いてもいいですかね?

コード
さて micro:bit でハマった点は、JavaScript ブロックエディタだと、整数演算しか出来ないところ。最初気づかずに結果が合わずに悩みました。mbed 環境にしてしまえばなんでもござれなんでしょうが、ここは敢えて JavaScript ブロックエディタで挑みます。従って、少数は整数に桁上げをしたり、演算の順序を気にして精度落とさないようにしたりして最後に割って桁戻しています。32bit の整数での演算だろうから、演算範囲から多分オーバーフローはしないなという仮定でコードを簡単に書いています。


ちなみに PI 制御部分のコードは冒頭に書いた通りです。上記ブログに PI 制御の概要が書かれているので興味ある方は読んで見るといいですよ。

プログラムの動作について:

  • デフォルトで表示されている値はPWMの値。0〜A です。スクロールしないよう一桁に抑えたかったので16進数です。
  • A+B キーでモードを切り替えられます。
  • ノーマルモード(デフォルト)では A ボタンで今の温度を表示、B ボタンでターゲット温度を表示。
  • ターゲット変更モードでは A でマイナス、B でプラス。温度表示は、温度 x 10℃です。32.5 ℃だったら、325 と表示されます。
  • USB の方のシリアルに温度情報とかをテキストで出してますので必要に応じてモニタしてもらえればと。Kp, Ki パラメータをチューニングしたい時にも役に立つと思います。

コードの中で特筆すべきテクニックを駆使しているようなところは全然ありませんので参考までに。デフォルトでPWMの値を表示させているのは半分デバッグの意味合いもあります。温度表示でもいいんですが、PWMループをある程度正確に保つためにリクエストがあった時のみ温度表示させています。温度は複数桁(3桁)を表示させるのでスクロールになってしまい、時間がかかり、PWMループが狂ってしまうからです。

キャリブレーション
実はコードそのままでは若干温度がズレてしまいました。変数 toffset で調整することが出来るようになっています。うちの環境では +2℃ほどズレているようなので、-20を入れて調整しました。温度計がない場合は沸騰したお湯とか氷水で温度のチェックをして、この値で調整してみてください。

そのうちキャリブレーションにも対応させて、値を外付けの EEPROM に保存しようかと思って、EEPROM をブレッドボード上に載せてます。micro:bit のフラッシュも不揮発性メモリとして使えるようですが、プログラムを書き換えると飛んでしまうようなので、外付けの EEPROM を使おうと思っています。氷水と沸騰したお湯の温度を測って toffset を計算させて、EEPROM に格納しようかと。micro:bit 起動時に EEPROM にマジックナンバーが書いてあれば、toffset の値をロードさせるというようなコードにするつもりです。

改良予定
まずはキャリブレーションと EEPROM 書き込みを追加したいです。
あとは、タイマーとブザーかな。Nucleo (mbed)版はタイマーとブザーは付けたんですが micro:bit 版は見ての通りまだです。それ程困らないし。ま、いっか。

レシピ
あ、肝心のローストビーフのレシピです。
このマシンのお陰て安定して作れるようになりました。

材料:
牛もも肉500g程度
塩胡椒またはシーズニングを適当に(肉の重さの1%程度がいいみたいです)

手順:

  1. 肉は常温に戻しておく(冷蔵庫から出して30分程度放置)
  2. 塩胡椒またはシーズニングをまぶす(3. の直前に)
  3. フライパンで全てにお好みの時間焼き目をつけます。
    そんなに簡単に奥まで火は通りません。
    火の通っていない断面で確認しながら調整して下さい。
    ちゃんと焼き目を入れるのは表面の殺菌にもなるので必ず焼きましょう。
  4. ジップロックに肉を入れる。口は完全に閉じずに端にストローを挿す。
  5. ジップロックを水を張った電気ポットに沈め、
    ストローから空気を吸って空気を抜いたらジップロックを閉める。
    水に沈めると空気を抜きやすいです。
    空気の層が残っていると温度が伝わりにくいです。ちゃんと抜くこと。
  6. micro:bit で温度調整開始!
    ミディアムレアの 57〜58℃だと 500g なら 60分もあれば出来上がります。
    それより長時間放っておいても火が入りすぎることは無いです。
    時間が長い方が柔らかくなる。。ような気がする。色々お試しください。
  7. 出来たらジップロックごと氷水に入れて急速冷凍。
    細菌の繁殖する温度をスキップさせることと、肉を落ち着かせるのが目的。
  8. 30分程度は放っておいてからカットして食べましょう!
    お好みで再度焼き目を入れてからでもいいですよ。
    お好みのソースやら、ホースラディッシュなどの薬味を添えて。

くれぐれも食中毒には気をつけて下さい!

余談
2011年にアメリカに出張した時に、プライムリブっていうのを食べた衝撃がすごくて、自分のうちでもいい感じに火を通したローストビーフが食べたい!と思ったのがそもそものきっかけ。あ、うちでもプライムリブやってみたいんだけど、なかなかハードル高いので(肉の値段やら入手やらサイズなど)、そこらへんで入手可能なモモ肉や肩ロース肉で我慢してます。それにしてもアメリカでは指定した焼き加減でキッチリ焼いてくれたりと、流石は肉食文化、スゲーと感心したもんです。

プライムリブ1ポンド(確か$30以下)
カリフォルニア、ミルピタスの Black Angus にて
柔らかくて、とろけるようです。うっとり。

アメリカに行ったらもう一度食べたい。うー。