Home > 組み込みソフトウェア Archive

組み込みソフトウェア Archive

GIOChannelの使い方

こんにちは、稲垣@CEREVOです。今回はGTK+に関する (正確にはGLibに関することなのですが……) 話題で、GIOChannelの使い方を見てみたいと思います。

なぜGIOChannelを使うのか

GTK+でGUIアプリケーションを書くと、プログラムは基本的にイベントドリブンになります。つまり、メインループの中でイベントが起きるのを黙って待って、何か起きたらコールバックの中で処理します。こうしたフレームワークではファイルやソケットの読み書きでブロックされる(待ちが発生する) のは嬉しくありません。ブロックされている間は基本的に他の処理ができず、たとえば処理中のアニメーションが止まったりします。GIOchannelを使えば、ブロックされない状態になってから処理を開始することができます。

なお、今回はあまり関係ありませんが、テキストのエンコーディングを適当にUTF-8に変換してくれる機能もあります。

GIOChannelの使い方

  • g_io_channel_unix_newで生成 (unix系のシステムを想定しています……)
  • NONBLOCKに設定する (設定しないとG_IO_STATUS_AGAINが返るかわりにブロックされます)
  • g_io_add_watchでイベントソースをデフォルトメインループに追加
    (頻繁に掛け外しをする場合は、g_io_create_watchで作ったイベントソースを自分で扱った方がいいかも知れません)
  • コールバック
    • どんなイベントが起きたのか、GIOConditionを見て判断する
    • GIOChannelを読み書きしてG_IO_STATUS_NORMALやG_IO_STATUS_AGAINが返ってきたらTRUEを返す
      G_IO_STATUS_EOFやG_IO_STATUS_ERRORが返ってきたらFALSEを返してイベントソースを外す

読み込みサンプル

標準入力から読み込んでg_messageでメッセージを表示するサンプルです:


#include <glib.h>
static gboolean read_callback(GIOChannel *io, GIOCondition cond, gpointer user_data) {
  GMainLoop *loop = user_data;
  gboolean continue_to_watch = FALSE;
  if (cond & G_IO_IN) {
    GError *e = NULL;
    char *text;
    switch (g_io_channel_read_line(io, &text, NULL, NULL, &e)) {
    case G_IO_STATUS_NORMAL:
      g_message("%s: read line: %s", __func__, text);
      g_free(text);
      continue_to_watch = TRUE;
      break;
    case G_IO_STATUS_AGAIN:
      g_message("%s: AGAIN", __func__);
      continue_to_watch = TRUE;
      break;
    case G_IO_STATUS_ERROR:
      g_message("%s: error: %s", __func__, e->message);
      g_error_free(e);
      break;
    case G_IO_STATUS_EOF:
      g_message("%s: EOF", __func__);
      break;
    default:
      break;
    }
  }
  if (! continue_to_watch) {
    g_main_loop_quit(loop);
    g_main_loop_unref(loop);
  }
  return continue_to_watch;
}

static void sample_loop(int fd, GIOCondition cond, GIOFunc callback) {
  GMainLoop *loop = g_main_loop_new(NULL, FALSE);
  GIOChannel *io = g_io_channel_unix_new(fd);
  guint tag = g_io_add_watch(io, cond, callback, g_main_loop_ref(loop));
  g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL);
  g_io_channel_set_close_on_unref(io, TRUE);
  g_io_channel_set_encoding(io, NULL, NULL);
  g_main_loop_run(loop);
  g_io_channel_unref(io);
  g_main_loop_unref(loop);
}

int main(int argc, char *argv[]) {
  sample_loop(0, G_IO_IN, read_callback);
  return 0;
}

G_IO_INは読み込み可能を示すフラグです (読み込み専用のfdについてはこのフラグしか立たないようです――man poll参照)。ただし、読み込み可能と言っても、読んでみたらすぐにG_IO_STATUS_EOFが返ってくることもあります。EOFが返ってきたらもうチャンネルに用はないのでFALSEを返してイベントソースを外します。

書き込みサンプル

標準出力 (のバッファ) が書き込み可能になるのを待って書き込みまくるサンプルです。なおsample_loop関数は前節の関数をそのままコピーして使ってください。実行すると大量のメッセージが出力されますから、

./sample | read i

などとして、パイプが適当に閉じられるようにして実行してください:


#include <glib.h>
static gboolean write_callback(GIOChannel *io, GIOCondition cond, gpointer user_data) {
  GMainLoop *loop = user_data;
  gboolean continue_to_watch = FALSE;
  if (cond & (G_IO_ERR | G_IO_HUP))
    g_message("%s: channel is closed", __func__);
  else if (cond & G_IO_OUT) {
    GError *e = NULL;
    char *text = "BABEL\n";
    int len;
    switch (g_io_channel_write_chars(io, text, -1, &len, &e)) {
    case G_IO_STATUS_NORMAL:
      g_message("%s: wrote %d chars", __func__, len);
      continue_to_watch = TRUE;
      break;
    case G_IO_STATUS_AGAIN:
      g_message("%s: wrote %d chars, AGAIN", __func__, len);
      continue_to_watch = TRUE;
      break;
    case G_IO_STATUS_ERROR:
      g_message("%s: error: %s", __func__, e->message);
      g_error_free(e);
      break;
    case G_IO_STATUS_EOF:
      g_message("%s: EOF", __func__);
      break;
    default:
      break;
    }
  }
  if (! continue_to_watch) {
    g_main_loop_quit(loop);
    g_main_loop_unref(loop);
  }
  return continue_to_watch;
}

int main(int argc, char *argv[]) {
  sample_loop(1, G_IO_OUT | G_IO_ERR | G_IO_HUP, write_callback);
  return 0;
}

チャンネルが閉じられるとコンディションにG_IO_ERRのビットが立ちます。同時にG_IO_OUTビットが立つこともありますが、それは単にバッファに空きがあるというだけで、書き込んでも誰も見てくれないので無視するようにしました。

またg_io_channel_write_charsでは、本来は書き込まれたバイト数もチェックしなければならないのですが、長いデータを書き込まなければ途中で切れることはないようなので、今回はチェックしていません。

読み書き用の場合

ソケットは読み書き両用なので、必要なら読み書き両用のコールバックを書くことができます。そのときは上記のread_callback関数とwrite_callback関数を適当に結合させればいいでしょう。なお、ソケットの相手側が閉じられていても (コンディションにG_IO_ERRビットが立っていても)、バッファに読み込み可能なデータが残っていることがあります。プロトコルによりますが、必要ならG_IO_INビットをチェックしてG_IO_STATUS_EOFが返ってくるまで読み出してやることもできます。

おわり

GIOChannelを使えば、GLibのメインイベントループの中で、ブロックされることなく入出力を処理することができます。イベントドリブンなアプリケーションを書くために是非とも使い方を把握しておきたいものです。

Happy hacking!

MSP430のPWM出力を増やす方法

こんにちは、稲垣@Cerevoです。今回はまたMSP430の話題です。

※一応、使っているのはMSP430F247であると断わっておきます。

MSP430には二つのタイマが入っていて、PWMを自動で (CPUが割り込みの中で操作しなくても) やってくれます。今回は普通にタイマでPWMをする方法を紹介し、さらに頑張って普通はPWMに使えないポートでも半自動でPWMをやってみたいと思います。

タイマの概要 (コンペアモード)

タイマにはタイマレジスタとキャプチャ・コンペアブロック0から2 (もしくは0から7) があり、大体以下のように動作します:

  • タイマレジスタ
    • 値が0になるときに割り込みを発生させる (オーバーフロー割り込み)
    • タイマAでは16ビット幅で固定、タイマBでは16/12/10/8ビット幅で可変
    • 三つのタイマモード
      • ビット幅全てを使ってカウントアップ (連続モード)
      • コンペアレジスタ0の値までカウントアップ (アップモード)
      • コンペアレジスタ0の値までカウントアップ、さらに0までカウントダウン (アップダウンモード)
  • キャプチャ・コンペアブロック
    • キャプチャ・コンペアレジスタ (コンペアレジスタと略します) と出力ビットがある
    • コンペアレジスタの値がタイマレジスタの値と等しくなるとき割り込み (コンペア割り込み)
    • 同時に出力ビットをセット・リセットする
  • キャプチャ・コンペアブロック (0以外)
    • タイマレジスタの値がコンペアレジスタ0の値と等しくなるときに (すなわちコンペア割り込み0のタイミングで)、出力ビットをセット・リセット・トグルする (出力モードによる)

キャプチャ・コンペアブロック (0以外) には、出力ビットの変化するタイミングが

  • 自分のコンペア割り込み
  • ブロック0のコンペア割り込み

の二つあることに気づかれましたか。変化のタイミングが二つあるので、各ブロックごとに異なったデューティー比のPWM出力ができるわけです。つまり、普通のPWMとしての使い方は次のようです:

  • アップモード
  • コンペアレジスタ0: 変調周波数を決める
  • コンペアレジスタ0以外: デューティー比を決め、適当な出力モードでPWM出力

キャプチャ・コンペアブロック0でもPWM

上記のような特性から、ブロック0ではPWM出力はできません (パルス幅が0固定のPWMと言えなくもないのですが)。かといって、連続モードに設定して、割り込みが発生するたびにコンペアレジスタを設定しながらGPIOを叩くのもやや無駄な話です。そういうのは各ブロックで別々の割り込み周期を使いたい場合にすることです。もうちょっとCPUがサボりながらPWM出力をする方法があります。ハードウェアに足りない機能だけ、ソフトウェアで実現するべきです。

PWMのキモはこの特徴です:

コンペア割り込みのタイミングで出力ビットをセット・リセット・反転

これでデューティー比が決まるわけです。この特徴は全てのキャプチャ・コンペアブロックに存在します。活用しましょう。ただし、キャプチャ・コンペアブロック0で活用するには、連続モードにしなければいけません。代償として変調周波数は自由に設定できなくなります。

一方、キャプチャ・コンペアブロック0には、この機能がありません:

ブロック0のコンペア割り込みタイミングで出力ビットをセット・リセットする

当然ですね。この部分をオーバーフロー割り込みのタイミングでソフトウェアで処理すれば、キャプチャ・コンペアブロック0でもPWMができます。つまりオーバーフロー割り込みで、出力ビットを0または1に設定すればいいのです。

なお、出力ビットは、通常の出力モードでは値を設定できません。手動設定のモードに切り替えてから設定し、また通常のモードに戻すことになります。なかなか普通じゃない感があります。

そして自由な変調周波数

先に、「代償として変調周波数は自由に設定できなくなります」と書きましたが、自由にする方法が無いわけではありません。というのは、タイマAのタイマレジスタはビット幅が16ビット固定で、連続モードに設定すると周期がかなり長くなってしまうのです。通常の1MHzのクロックだとおよそ16Hz、8MHzのクロックを使ってもおよそ128Hzです。省電力な低速クロックではさらに遅くなりますし、そもそも16HzなんてPWMでLEDを光らせるには遅すぎます。

解決方法は簡単で、周期を短くしたければ、オーバーフロー割り込みの中でタイマレジスタの値を適当に大きくすればいいのです。例えば、0xff00を代入すれば、カウンタは8ビット幅になったも同然。こうして自由な変調周波数を手に入れることができます。コンペアレジスタの値も対応する値にしておきましょう。

まとめ

まとめると次のようにタイマを設定することになります:

  • 初期設定
    • 連続モード
    • コンペアレジスタ0にデューティー比を設定 (必要なら、(0×10000 – 周期 + デューティー比) の値にする)
    • 出力モードはトグル
  • オーバーフロー割り込み
    • 出力モードを手動に設定し、出力ビットを0 (ないし1) に初期化
    • 出力モードをトグルに設定し直す
    • 必要ならタイマレジスタの値を (0×10000 – 周期) の値に設定し直す

ちなみに、タイマのクロックがCPUのクロックと同期していない場合についてデータシートには色々と注意書きがありますが、今回のような使い方では特に気にするべきことはないようです。

おしまい

実は事の発端はタイマBの出力0にLEDをつなげてしまったことだったりします。プリント基板を作る前にチップの仕様をよく確認しないと、ソフトにしわ寄せがくるようですね (寄せることができるともいう)。
ともあれ、Happy hacking!

すごくシンプルなハミング距離計算

ハミング距離とはなんぞや……という話はWikipediaでも見ていだたくとして、要するに「ビット列を比較して値の異なる位置を数えたい」ということです。例えば01010011と01010111のハミング距離は1です。

異なるビット

二つのビット列のうち、ビットの異なる位置を抽出することは簡単です。基本です:

d = a ^ b;

ビットを数える

あとはここから立っているビットを数えるわけですが、普通に考えると

  • ビット列の幅の分だけループさせる
  • プロセッサのビットサーチ命令を使う

という方法を使うと思います。前者は当然遅いので嫌ですね。後者はインラインアセンブラを使ってプロセッサ依存にしないといけませんし、ビットマスクを作ってビットを消しながら数えなくてはならず、いかにも面倒です (←根拠もなくシフト命令は遅いと思っているヤツ)。

そこで私が使う方法は次のようなものです:

d &= d - 1; // 立っている最下位ビットを消す

全ビットが消えるまでループすればハミング距離が分かります:

d = a ^ b;
i = 0
while (d) {
  d &= d - 1;
  i++;
}

おしまい

このコードを何に使うのかというと、BCH(15,5)符号などをパターンマッチングで誤り訂正しようということだったりします。15ビットもあるのに内容は5ビット(32パターン)しかないので、ハミング距離が一番小さい符号語が訂正結果ということにした方が簡単なのではないかというわけです。別にガロア体がよく分からんとかそういう理由ではないんですよ。
Happy hacking!

GTK+自作ウィジェットの描画処理を軽くする

グラフィックアクセラレータのない組み込み環境でGTK+を使う場合、描画処理はけっこう負荷の高い処理です。例えば画像を大量に描画すると、その負荷が高くてバックグラウンドで別の処理を進めることができないということも起こりえます。
そこでGtkDrawingやGtkLayoutに自作の描画ハンドラ (exposeイベントのシグナルハンドラ――以下、exposeハンドラ) を作るときに無駄な描画処理をしないためのテクニックを紹介したいと思います。

背景色を正しく設定する

ウィジェットのある部分についてexposeイベント (ひらたく言えば描画イベント) が発生すると、当該部分が背景色で塗り潰されてからexposeハンドラが呼ばれます。ここを別な色で塗り直すのは当然無駄なわけで、ウィジェットの生涯を通じて最も描画面積の大きい色を背景色として設定しておけば、描画処理は軽くなるでしょう。

クリッピングする

ウィジェットの重なりが変更されたり、GtkLabelなどの自分のGdkWindowを持たないウィジェットの内容が変更されたりすると、
下になっているウィジェットの重なっている部分が再描画されます。
exposeハンドラでは、GdkGCを設定して、ウィジェットの必要な部分だけを書き換えるようにしましょう。

GdkRegionを使う方法

この方法が推奨されているようです:

static gboolean
my_widget_expose_event_handler (GtkWidget *widget, GdkEventExpose *event) {
  GdkGC *gc = widget->style->fg_gc[GTK_WIDGET_STATE(widget)];
  gdk_gc_set_clip_region(gc, event->region);
  /* 描画処理 */
  gdk_gc_set_clip_region(gc, NULL);
  /* 他の処理 */
}

GdkRectangleを使う方法

static gboolean
my_widget_expose_event_handler (GtkWidget *widget, GdkEventExpose *event) {
  GdkGC *gc = widget->style->fg_gc[GTK_WIDGET_STATE(widget)];
   gdk_gc_set_clip_rectangle(gc, &event->area);
  /* 描画処理 */
  gdk_gc_set_clip_rectangle(gc, NULL);
  /* 他の処理 */
}

ウィジェット全体を再描画するのはやめよう

クリッピングとも関係のある話ですが、手抜きをしてgtk_widget_queue_redraw_areaを使うのは避けるべきです。gtk_widget_queue_redraw_area を多用している場合は設計を見直した方がいいかも知れません。
特に画像を重ねて描画している場合、効率よく再描画するために重なりを管理する処理を書くことになりがちです。それはGTK+が既に持っている機能なので自分で作るべきではありません。

バグのあるウィジェットは使わない

バグのあるウィジェットとはGtkFixedのことですが、子ウィジェットを動かすとGtkFixed全体が再描画されるというバグがあります。

具体的なことはgtk_fixed_moveとgtk_layout_moveの実装を比較すると分かりますし、直すことも難しくありません。しかしGtkFixedは設計も古く (よく言えば単純で)、あんまりメンテナンスされていないようなので使わない方がいいでしょう。

おしまい

ぶっちゃけGTK+は独学なのであまり偉そうなことは言えません。むしろ教えてください。
Happy hacking!

Introduction of UBIFS

はじめまして、Cerevoの中河です。
ソフトウェア担当で主にLinuxカーネル/ドライバ回りを担当しています。
今回はUBIFSとういうLinuxで使用できるファイルシステムについて書きたいと思います。

UBIFSって?

UBIFSとはNANDフラッシュメモリ向けに開発されたファイルシステムです。
フラッシュメモリと言うと、一般的にはUSBメモリやSSDが連想されるかもしれませんが、それらのデバイスはハードディスクと同じような扱いが出きるようハードウェア的な仕組みが入っている為、ここでは該当しません。
UBIFSが対象としているのは、あくまでもCPUのNANDフラッシュメモリ・コントローラに直接接続されたNANDフラッシュメモリです。

何故UBIFS?

フラッシュメモリ上では、JFFS2というファイルシステムが広く使用されてきました。
しかし、JFFS2にはフラッシュメモリの容量に比例して、マウント時間やメモリ使用量が大きくなってしまうという設計上の問題があり、昨今の大容量NANDフラッシュメモリには対応できなくなってきました。
そういった問題を解決するために設計・開発されたのがUBIFSです。

JFFS2と比較すると、UBIFSには以下のような特徴があります。
- 高速マウント
JFFS2の様にマウント時にパーティション全体をスキャンする必要が無く、高速にマウントできる。
- 省メモリ
JFFS2はファイルシステムのインデックスをメモリ上に置いていたため、容量に比例して大量のメモリを消費していましたが、UBIFSではインデックスをフラッシュメモリ上に置いているため、メモリ消費量が少ない。
- Write-Backサポート
Write-Backをサポートしているため高速に書き込み処理が出来る。
- UBI
JFFS2はMTDと呼ばれるフラッシュメモリ・デバイスドライバを抽象化したサブシステム上で動作しますが、UBIFSはMTD上にさらにUBIという、ウェアレベリング※1と論理ボリュームを実現するレイヤをのせ、その上で動作します。

UBIFSを使用してみる

- まずホスト環境で、UBIツールをクロスビルドします。
$ git clone git://git.infradead.org/mtd-utils.git
$ cd mtd-utils/
$ CROSS=arm-uclinuxeabi- make

- ビルドしたflash_eraseallとubimkvolコマンドをターゲットにコピーしてください。
$ cp arm-uclinuxeabi/flash_eraseall [ターゲットのroot]/usr/bin
$ cp arm-uclinuxeabi/ubi-utils/ubimkvol [ターゲットのroot]/usr/bin

- 続いてターゲット環境上でUBIFSで使用するmtdパーティションを初期化します。
$ flash_eraseall /dev/mtd1

- 以下のブートパラメータを追加します。
ubi.mtd=1

- 最後にUBI論理ボリュームを作成 & マウントします。
$ ubimkvol /dev/ubi0 -n 0 -N ubifs -m
$ mount -t ubifs ubi0_0 /mnt

まとめ

駆け足でしたが、UBIFSを紹介してみました。
UBIFSは製品レベルで十分使用できるものと考えていますので、大容量NANDフラッシュメモリを使用する場合は、是非UBIFSの採用を検討してみてください。
またそのうち、UBIFSやUBIの内部構造もご紹介できればと思います。

参考
UBIFS – http://www.linux-mtd.infradead.org/doc/ubifs.html
UBI – http://www.linux-mtd.infradead.org/doc/ubi.html

※1 http://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%AC%E3%83%99%E3%83%AA%E3%83%B3%E3%82%B0

なんとなく分かるgtkrcの書き方

こんにちは、稲垣@Cerevoです。
今回はアセンブリ言語から離れてGTK+に関することを書いてみたいと思います。

gtrkrcの書き方

gtkrcをちょっと書いてみようとして、ググったけどまともな解説がなくて挫折したという方、
けっこういるのではないでしょうか?
実は、書くときに必要な知識はGTK+のドキュメントに書いてあります。
http://library.gnome.org/devel/gtk/stable/gtk-Resource-Files.html

しかし他のドキュメントを参照しないと分からない部分もありますし、
そういう部分に限ってドキュメントのポインタが示されていなかったりするのも事実です。

そこで、gtkrcを書こうとしたけどよく分からなかったという人や
自分のプログラムでどう利用したらいいか分からないという人向けに、
gtkrcの書き方・使い方をまとめてみたいと思います。

スタイル

GTK+では、色やフォントの設定はスタイル (GtkStyle) としてまとめられています。
gtkrcでは次のような記述でスタイルを定義します。

style "hoge" {
   fg  [NORMAL] = "#fff"
   font_name = "M+1P+IPAG 12"
   xthickness = 2
   GtkWidget::focus-line-width = 3
}

fg bg text base bg_aaの部分の5種類の部分について、
NORMAL ACTIVE PRELIGHT SELECTED INSENSITIVEの各状態の色を指定します:

fg[NORMAL] = "#fff"

普段はNORMALの色、カーソルが当たったときはPRELIGHTの色などと使い分けることが想定されているようですが、
実際のところ、これらの色を実際にどこに配置するかはウィジェット次第です。
例えばGtkNotebookはちょっとそれ違わない?って感じの配色をしますね。
極端な話、GtkDrawingやGtkLayoutを使う場合は、5×5=25色のパレットだと思ってしまってもいいかもしれません。
お行儀よくはありませんが。

フォント

font_name = "M+1P+IPAG 12"

などと指定します。fontconfigが面倒を見てくれます。

厚み

xthicknessとythicknessはボタンの厚みやボックスのマージンなんかに使われる、
と思います。ほとんどいじったことはありません。

各クラス固有のスタイルプロパティ

普通のプロパティとは別にスタイルプロパティというものが各クラスに定義されており、
gtkrcで設定することができます。

たとえばGtkWidgetにはfocus-line-widthというスタイルプロパティがあり、

GtkWidget::focus-line-width = 3 

などと指定すると、
フォーカスの当たっているウィジェットに破線で描かれる枠が太くなります。

プログラムからの利用

ウィジェットはスタイルを持っており、公開されていますから、普通にポインタでアクセスできます。
例えばNORMAL状態の前景色のGdkGCは

widget->style->fg_gc[GTK_STATE_NORMAL]

としてアクセスできます。
プログラムの中でGdkGCやPangoを操作するのは面倒ですし 、
デザイン変更のたびにリコンパイルするのも大変ですから、
積極的に利用したいものです。

バインディング

バインディングはキーにアクションシグナルを結びつけます:

binding "fuga" {
  bind "Return" { "move-current" (next) }
  bind "Left" { "cancel" () }
}

解説を探してもEmacsキーバインディングにする方法しか書いてないことがほとんどで、
たとえば「どんな名前のキーがあるのか」ということはよく分からないという
ちょっと厄介な部分です。

バインディングも、スタイルと同様、一式を定義してからウィジエットに結びつけるという形をとります。

キーの名前

使うキーの名前は gdk/gdkkeysyms.h に書かれています。
たとえばGDK_Rightと書かれていたら、gtkrcには”Right”と書きます。
どのキーなのかは、おおむね見れば分かります。それで深く考えたことはありませんが、
どうしても気になるときは、GdkEventKeyをダンプするプログラムでも書いたら分かるんじゃないんでしょうか。

アクションシグナル

アクションシグナルの定義は、スタイルプロパティ同様、各クラス (と親クラス) のドキュメントに書かれています。
引数は、基本的に文字列か数か (上記の”move-current”に対するnextのような) シンボルです。
シンボルに関しては、アクションシグナルの定義をみると型が宣言されているので、それを見ればなんとなく分かります。

たとえばGtkMenuShellの”move-current”シグナルは引数にGtkMenuDirectionTypeを取るのですが、
その型は次のように宣言されています:

typedef enum
{
  GTK_MENU_DIR_PARENT,
  GTK_MENU_DIR_CHILD,
  GTK_MENU_DIR_NEXT,
  GTK_MENU_DIR_PREV
} GtkMenuDirectionType;

この場合、型がGtkMenuDirectionTypeなので、シンボルのGTK_MENU_DIRの部分は型名だと思って切り捨てて、
gtkrcには next と書きます。

例えば「キー”Right”にアクションシグナル”activate”をバインド、引数なし」という設定は
次のように書けます:
bind "Right" { "activate" () }
連続して複数のアクションシグナルを送ることもできますから (;で区切る必要はありません)、
ある程度複雑な動作をキーに割り当てることができると思います。

パスによる指定

定義したスタイルはウィジェットに”装備”させないと意味がありません。
ウィジェットは、ディレクトリと同様に階層構造を成しているので、
パス (path) とよく似たウィジェットパスによって特定することができます
(ウィジェットにはクラスがあるのでCSSのセレクタの方が近い)。
パスの区切り文字は “.” です。
いわゆるshell glob構文が使えると書いてあるのですが、[abc]みたいのは使えないようなので、
ワイルドカードに使えるのは?と*のようです。

GTK+2.10以降では、クラスを指定する部分に<someclass>のように書くと派生クラスにもマッチします。
例えば “<GtkBox>” は GtkHBoxにもGtkVBoxにもマッチします。

widget

例えば

widget "mainwindow.GtkHBox.okbutton" style "hoge"

のように、ウィジェットの名前でパスを指定します。
ウィジェットの名前はgtk_widget_set_nameで設定され、デフォルトではクラス名です。

widget_class

例えば

widget "GtkWindow.GtkHBox.GtkButton" style "hoge"

のように、パスをウィジェットのクラス名によって指定します。

class

例えば

class "GtkMenuShell" binding "fuga"

のように、階層構造を無視してウィジェット単体のクラス名によって指定します。

実際のところ、

widget_class "*GtkMenuShell" binding "fuga"

と書くのとほとんど変わらないと思うのですが……

まとめ

gtkrcを書くときに必要な知識を、分かりにくそうなことを中心になんとなくまとめてみました。
gtkrcを活用すればデザインをプログラムから分離することができ、開発が効率的になるでしょう。
Happy hacking!

DM355のインストールディスクを作る 後編

こんにちは、Cerevoの稲垣です。

前回は、DM355のブート処理を概観し、SDカード用のブートローダ (SD-UBL) を試してエラーを起こすところまで扱いました。今回はSD-UBLを分析・修正して、実際にインストールディスクを作ってみたいと思います。

メッセージを分析

SD-UBLが出すメッセージはけっこう冗長なので保存して比較してみると、ブートモードとインストールモードではカーネル (Linux) とRAMディスクのロード先が入れ替わっていることが分かります。関係するメッセージだけ引用します:

ブート時のメッセージ:
  * Loading kernel
 sdcard_read sdc_src_addr=0x00081000 dst=0x82000000 len=0x00200000
  * Loading ramdisk
 sdcard_read sdc_src_addr=0x00281000 dst=0x80700000 len=0x00400000
インストール時のメッセージ:
  * Flashing kernel
 sdcard_read sdc_src_addr=0x00081000 dst=0x80700000 len=0x00200000
  * Flashing Root FS
 sdcard_read sdc_src_addr=0x00281000 dst=0x82000000 len=0x00400000

SDカードにアクセスできないU-Bootを使っていますから、ロード先が入れ替わっては起動するはずがありません。しかし、ひとまずU-Bootのパラメータをデフォルトから変更すれば起動しそうです。実際にU-Bootの自動起動を中止して起動スクリプトを書き直してやると起動しました。

ただし、このままのメモリ配置では9MiBより大きなRAMディスクをロードできません。やはりSD-UBLの修正が必要です。それでハックの方針は以下のようになります:

  • Linuxとramdiskのロード先アドレスを変更する
  • ロードサイズを変更する

なお、インストールモードのフラッシュ書き換え機能は、ブートメッセージによれば、TIがリリースしているユーティリティをベースにしているらしいので使いません。そのTIのユーティリティはNANDフラッシュの書き込みエラー処理とかしてないっぽいので、信用できないからです。

SD-UBLを解析する

まずはディスクイメージからSD-UBLを探します。RBLの仕様はTIが公開しているDM355のARM subsystemのデータシートに書かれていますから、RBLになったつもりでディスクイメージを見ていきます……すぐに見つかりますね。ダンプしてみると、第1セクタにUBLディスクリプタが書かれています:

 00000200:  00 ed ac a1 00 01 00 00  2e 00 00 00 09 00 00 00

左から順に、マジックナンバー0xa1aced00、エントリポイントが0×100、サイズが0×2eセクタ、先頭は第9セクタ、という意味です。なおUBLはメモリ空間の0×0020にロードされるので解析には注意が必要です。

位置が分かったのでSD-UBLを切り出します:

 dd if=dm355_boot.sdcard of=sd-ubl.bin bs=512 skip=9 count=$((0x2e))

逆アセンブルします:

 arm_v5t_le-objdump -b binary -m arm -D sd-ubl.bin > sd-ubl.s

逆アセンブルしたソースを検索してみると、ロード元のアドレス0×41000とか0×81000を引数にして同じサブルーチンが3回ほど続けて呼ばれていることが分かります。関係する部分を引用します:

    2fd8:	e59f3064 	ldr	r3, [pc, #100]	; 0x3044
    2fdc:	e59f4064 	ldr	r4, [pc, #100]	; 0x3048
    2fe0:	e5932000 	ldr	r2, [r3] 	; 0x15aa4
    2fe4:	e1a01004 	mov	r1, r4
    2fe8:	e1a02482 	mov	r2, r2, lsl #9
    2fec:	e3a00a41 	mov	r0, #266240	; 0x41000
    2ff0:	ebfffe76 	bl	0x29d0

    3044:	00015aa4 	andeq	r5, r1, r4, lsr #21
    3048:	81080000 	tsthi	r8, r0
    304c:	00015594 	muleq	r1, r4, r5

    5a84:	0000012c 	andeq	r0, r0, ip, lsr #2

ARMのgccの関数呼び出し規約ではr0からr3が引数ですので、そっちのレジスタも見てみると、ロード先とサイズも引数として渡されているらしいことが分かります (簡単に書いてますが劇的な場面ですよ)。実にExcellentなコードですね。

0×15aa4という変なアドレスにアクセスしているので解説しておきましょう。TCMは0×00000と0×10000の両方からアクセスできるようになっているので、アドレス0×15aa4は0×5aa4と同じです。さらに、UBLのロードされるアドレスは0×00020ですから、0×15aa4へのアクセスは 0×15aa4 = 0×10000 + 0×20 + 0×5a84 と分解することができ、ソースの5a84:の部分へのアクセスになるわけです。

同様に解析するとU-Boot、Linux、ramdiskのロードがほぼ同じように書かれているらしいことが分かります。ロードする長さだけは変数としてメモリ上に置いてあります (グローバル変数なのでしょう……なぜだろう)。

バイナリパッチ

結局、具体的には以下のようにバイナリエディタでハックします (Emacsでは M-x hexl-find-file):

  • Linuxとramdiskのロード先はハードコードされているので入れ替える:
    • 3000: e3a01482 を e59f1054 に変更
    • 301c: e59f1038 を e3a01482 に変更
  • グローバル(?)変数になっているそれぞれのイメージサイズを変更する:
    • 5a84: U-Bootのセクタ数
    • 5a88 ramdiskのバイト数
    • 5a8c Linuxのバイト数

淡々と結果だけ書いてしまいましたが、ARM命令は32bit固定長なのでバイナリパッチが簡単なのです。今回は値を入れ替えたりそのままメモリ上に変数として置いてある値を書き換えるだけなのでコードも増えませんし、PC相対のディスプレースメントだけちょっと計算すればお終いです。

あとはU-Bootの環境変数を書き換えておいて、SDカードに書き込めばインストールディスクのできあがりです。好きなインストーラが起動するように仕込みましょう:

 dd of=/dev/sdc bs=512 seek=9 if=sd-ubl.bin
 dd of=/dev/sdc bs=512 seek=520 if=uboot.bin
 dd of=/dev/sdc bs=512 seek=1032 if=uImage  #linux
 dd of=/dev/sdc bs=512 seek=5128 if=fs.bin    #ramdisk

おしまい

SD-UBLがバグっているので多少バイナリパッチをしましたが、ARMのバイナリは割とハックしやすいと思います。ブートローダ程度のものなら皆さんもハックしてみてはいかがでしょうか?

DM355のインストールディスクを作る 前編

こんにちは、Cerevoの稲垣です。今回も割と低レイヤーな話です。

今どきのPCは、買ってくるとHDDが内蔵されていてOS (Windowsとか) がインストールされているのが普通です。組み込みの機器も、やはり工場でファームウェアをインストールされて出荷されます。例えばNOR型のフラッシュROMを使う場合、最初からファームウェアの書かれたチップをハンダ付けするそうです。対してNAND型のフラッシュROMだと、生ROMを載せておいて基板が完成してから書き込むことになります。これはNANDフラッシュには不良ブロックがあるので、各自で対策を講じつつ書き込まないといけないからです。ちょうどPCのインストールで使うようなインストールディスクを作って量産工場に渡しておかないといけません。つまり今回はブートローダとかインストーラとかそういう話です。

DM355のブート処理

CPUに電源が入ると、PCの場合はROMに入っているBIOSが最初に走りだすわけですが、私が今相手にしているTI (Texas Instruments) のDM355でもやはりROMに入っているBIOSのようなものが走ります。TIの用語ではこれをRBL (ROM BootLoader) と呼んでいます。なおRBLによってロードされるプログラムのことは、TIの用語でUBL (User BootLoader) と言います。RBLはCPUに直結したスイッチによって四通りの場所からUBLをロードすることができます:

  • NANDフラッシュROM
  • NORフラッシュROM
  • SDメモリカード
  • シリアルポート

したがってインストール“ディスク”はSDカードだったりします。もちろんシリアルポートにPCを繋げてインストールとかいう方法も使えなくはありませんが、遅いしPCが必要だし面倒なのでやらないと思います。

RBLはロード作業以外の、DDRメモリの初期化とかは (恐らくは) してくれません。この辺はややこしいこと満載なのですが、DM355のCPUコアはARMなので、TCM (Tightly Coupled Memory――密結合メモリ) とかAIM (ARM Internal Memory――ミサイルじゃないよ) とか呼ばれるメモリを *CPUに内蔵* することができるのです。キャッシュとは別物です。DM355の場合、TCM領域は64KiBあり、32KiBのRAMと、8KiBのROMと、24KiBの予約領域が配置されています。ともかく、RBLはCPU内のROMに書かれていて、CPU内のRAMにプログラムをロードしてくれるわけです。RBLではCPU内部のメモリしか使わないので、DDRメモリの初期化なんかは段階的にロードされたプログラムがやる必要があるわけですね。その後、ロードされたUBLは、DDRメモリを初期化してNANDフラッシュなりSDカードなりからU-BootなどのLinux用ブートローダを読み込むわけですが、この辺はフツーなので割愛します。

DM355の起動に関する仕様はだいたいこんな感じです。身も蓋もない言い方をすれば、インストールSDカードにはSDカード用のUBLを入れればいいのです。ところが、TIはSDカード用のUBLをリリースしていません。TIの掲示板を見ると……
https://community.ti.com/forums/p/1970/7286.aspx
さすがはTIだ。SDカード対応のU-Bootを先にリリースする予定とは……順序が逆なんじゃないのか。

有志のSDカード用UBLを試す

というわけで、TIよりも先にSDカード対応のUBL (勝手にSD-UBLと呼びます) を開発して、インストールディスクのデモを作った人がいます:
http://community.ti.com/forums/t/2299.aspx
U-BootとLinuxとramdiskイメージをロードして、単に起動したりNANDフラッシュに書き込んだりしてくれるようです (一応、ディスクイメージを見なくてもなんとなく分かるように記事を書いたつもりですが、気になる人はダウンロードして見てください)。これを適当に解析して改造すればインストールディスクが作れそうです。

とりあえずSDカードに書き込んで動かしてみると、シリアルコンソールにバナーとメニューが出ます。ブートとインストール、二つの機能があります:

 SD Card DM35x boot loader
 by Constantine Shulyupin http://www.LinuxDriver.co.il/, sponsored by Applitec
 based on TI DM35x FlashAndBootUtils 1.10  SFT and SpectrumDigital evmdm355 v1
 Compiled on Dec 18 2008 at 13:16:08
 scd_nand_copy
 sd_init
 MMCSD_initCard
 1 - boot; 2 - install; 3 - global flash erase and install

まずは単純にブートさせてみると……正常に起動しません。U-BootがLinuxを起動してすぐリセットがかかります。一方、インストール機能は正常に動作します。まずはデバッグが必要なようです。

つづく

次回の後編では、SD-UBLのバグを探して、バイナリを直接書き直し、実際にインストールディスクを作ります。

Beagle Board用 ツールチェインとAndroidの起動のおまけ

Cerevo まつけんです。

すこし間があいてしまいましたが、Beagle Board用第2弾をお送りしたいと思います。

今回は、BeagleBoard上で実行することが可能なバイナリが作成できるようになるための準備をしたいと思います。

その後、ツールチェインをつくるだけではおもしろくないので、Androidをコンパイルして起動してみましょう。といっても、Androidの場合、ツールチェインが必要になるのは、カーネルコンパイル時だけだったりしますが。

ツールチェインとは

まずは、ツールチェインって何?というところですが、私の理解では、コンパイラ、リンカなどのBeagleBoard上で動くプログラムを作成するためのツール集です。linuxの場合は、大抵、binutils+gcc+glibc or uclibcなどの組み合わせになると思います。(newlibとかもあるのかな)

そして、こういった組込機器向けの場合、これらツールチェインは、ストレージ容量やコンパイル速度の都合上、ビルドはPC上で行うことが多いかと思います。その場合、クロスコンパイラとして、ツールチェインを作成し、PCでビルドして、BeagleBoard上で動作させるという流れになります。

ツールチェイン作成のための環境

まず、今回のビルド作業を2パターンの環境で試しました。どちらでも、作業する内容は同じで問題なく動作しました。

  • KVM(qemu)上のi386なマシン(ホストは、Phenom 9950BE,メモリ8GBで、ゲストには2GB割り当てています。)のGentoo
  • Amazon EC2 c1.xlarge上のGentoo

今回は、EC2を使って作業というのを体験してみたかったので、同時に試してみました。

EC2 c1.xlargeは確かに速いんですが、高いです。今回の作業で20hくらいつかったのですが、結局、カスタムAMIをS3においたり、ビルド作業用ディレクトリをEBSに置いたりすると、30$近くかかりました。興味半分で、c1.xlargeにしたのですが、c1.mediumで十分だと思います。

メリットとしては、自宅などに簡単にlinuxの環境なんか用意できねーよ、みたいな人にはかなりよいかも。ツールチェイン作りって基本的に、CPUパワー命なので、あまり非力なマシンだと時間かかりまくりで悲しくなってしまいますし。

ツールチェイン作成

さて、本題のツールチェイン作成です。作成には、結構いろんなパターンがあります。

一番、漢な手段は全部、手動でコンパイルとかなのでしょうが、当然、そんなのはやりたくありません。逆に、一番、お手軽なパターンは、BeagleBoard向けの場合、もっともメジャーなのは、バイナリな形で配布されているCode Sourcery ARM Sourcery G++ 2007q3.をダウンロードしてきてインストールするというパターンです。

今回は、その中間くらいのパターンとして、ツールを利用して、コンパイルする手段をとりたいと思います。どういう流れでツールチェインが作成されるのかを知りたかったのでこれでいきました。そこで、Gentooには、すばらしいツールがあります。crossdevです。これは、portageで提供されているツールチェイン作成ツールです。

# PORTAGE_OVERLAY=/opt/crossdev crossdev -t arm-gentoo-linux-gnueabi

と実行するだけで、ツールチェインが作成できます。
ただ、”-S”オプションやバージョンを指定しないと、最新バージョンが選択され、結構、Floating Exceptionなどでコンパイルに失敗したりします。その場合は、バージョンをオプションで指定しましょう。
今回は、かなり新しめのでもいけるかなーということで、そのまま実行したら、とりあえず、うまくいったので、それを利用しています。

  • binutils: 2.19
  • gcc: 4.3.2
  • glibc: 2.9

コマンドが完了すると、arm-gentoo-linux-gccなどのコマンドが入ります。

簡単にためすなら、

$ echo 'int main(){return 0;}' > crossdev-test.c
$ arm-gentoo-linux-gnueabi-gcc -Wall crossdev-test.c -o crossdev_test
$ file crossdev_test
crossdev_test: ELF 32-bit LSB executable, ARM, version 1 (SYSV), for GNU/Linux 2.6.16, dynamically linked (uses shared libs), not stripped

と実行して、ARM向けのバイナリであることが確認できます。

Androidのビルド

さて、最後にAndroidのビルドを試してみます。

Android on BeagleBoardに関しては、先人がすでにポーティングしてくれています。それに従いましょう。

Android Porting guide for Beagle Board

最初のカーネルコンパイル時に、

    $make ARCH=arm omap3_beagle_android_defconfig
    $make ARCH=arm CROSS_COMPILE=PATH_TO_CODE_SOURCERY_TOOL_CHAIN uImage

となっていますが、このPATH_TO_CODE_SOURCERY_TOOL_CHAINに先ほど作成したarm-gentoo-linux-gnueabi-を渡します。そうすると、コンパイルされるかと思います。

あとは、ほぼ書かれている手順どおりでいけました。パッチがうまくあたらない部分などは手動であててみました。

実際には、はじめは、linux-omap3のツリーに手動でAndroid用の拡張を自力でパッチを当てていたのですが、そのあとパッチを発見してちょっと悲しい思いをしたりしました。

そして、起動したときのムービーが以下です。

YouTube Preview Image

最後に

というわけで、ツールチェインの作成はおわりました。これで、BeagleBoard上で様々な自作プログラムを動作させられるところまでは来ました。あとは、このツールチェインをつかってrootfsを構築すれば、自分でBeagleBoard上のすべてソフトウェアをコンパイルできるようになります。あとは、お好みの構成を考えて、なんでも好きなものがつくれるようになる。。。はずです。

今回の内容は、Gentoo Embedded Handbookをかなり参考にしています。ユーザランドの構築もこちらには書かれています。是非、参考にしてみてください。

MSP430のコードを小さくするテクニック

Cerevoの稲垣です。

私は組み込みソフトウェア開発の担当で、主にLinuxを扱っていますが、ボードに載っているマイクロコントローラのプログラミングもします。最近はMSP430というTIのマイコンを相手にしているので、MSP430のアセンブリ言語プログラミングについて、x86アセンブリの経験者を対象にして書きたいと思います。

MSP430のレジスタとアドレッシング

MSP430には16本のレジスタがありますが、そのうちのr0はPC (プログラムカウンタ)、r1はSP (スタックポインタ)、r2はSR (ステータスレジスタ) および定数ジェネレータ、r3は定数ジェネレータとなっていて、汎用レジスタとして使えるのはr4からr15の12本のレジスタです。スタックの扱いはx86と同じです (ARMにあるようなリンクレジスタはない)。

MSP430の2オペランド命令はソースとデスティネーションが直交していて、それぞれ以下のモードが使えます:

ソース:

  1. r4          ; レジスタモード
  2. foo(r4) ; インデックスモード
  3. @r4        ; 間接レジスタモード
  4. @r4+      ; 間接自動インクリメント

デスティネーション:

  1. r4       ; レジスタモード
  2. foo(r4) ; インデックスモード

ソース・デスティネーションの両方にメモリオペランドを使うこともできる点がx86とは大きく異なる点と言えます (x86だとそういう命令はpush、pop、movsくらいしかありません)。インデックスモードを使うとディスプレースメントが付くので命令は可変長です。

オペランドのエンコーディングは上記の4種類だけですが、PCおよび定数ジェネレータとの組み合わせによって以下のアドレッシングモード
が実現されています:

  1. foo       ; シンボリックモード (PCを使ったインデックスモード、いわゆるPC相対)
  2. &foo     ; 絶対モード (定数生成レジスタをインデックスにしたインデックスモード)
  3. #foo     ; 即時モード (PC間接自動インクリメントモード……これは面白い実装だと思います)

MSP430命令の注意点

演算命令はx86とほぼ共通なものが揃っていますが細かいところが違います:

  • オペランドはソース, デスティネーション の順に書きます。
  • バイト演算でレジスタをデスティネーションにすると上位バイトがクリアされます。それで、and #0xff, r4 は mov.b r4, r4 で代用すると1ワード節約できます。
  • シフト・ローテートは1ビットづつしかできません。上位バイトと下位バイトを入れ替えるswpb命令が用意されているので組み合わせてシフトすることになります。

フラグ関連は微妙でありながらけっこう重要な違いがあります:

  • bic (ビットクリア)、bis (ビットセット) ではフラグは変化しません。
  • andとxorでは、演算結果が0でなければキャリーフラグがセットされます。
  • subとcmpで生じるキャリーフラグはx86とは逆です。けっこう混乱します。
  • 補助キャリーとパリティはありません。x86でも滅多に使わないフラグですが。

callは、jmpのようなオフセット値によるエンコーディングではなく、通常のソースオペランドで実現されています。したがって、callを使ったコードをROMからRAMにコピーするときはリロケート処理が必要です。あるいはオペランドをレジスタやメモリ(PC相対)にしておく方が楽かも知れません。

MSP430の定数ジェネレータを利用する

定数ジェネレータは -1, 0, 1, 2, 4, 8 を生成することができ、これを使うと即値の分の1ワードを節約することができます。例えば7回ループしたいとき、普通は以下のようにします:

  mov #7, r4    ; カウンタ
foo:
  dec r4        ; 減算
  jnz foo

しかしこれを以下のように置き換えると1ワード節約できます:

  mov #2, r4    ; カウンタ
foo:
  add.b r4, r4  ; シフト
  jnz foo

定数ジェネレータのお蔭で、インクリメントに1以外の値を使ってもコードは短いままです。それで、例えば32回のループは以下のようにすれば短くなります:

  mov #0, r4
foo:
  add.b #8, r4   ; 32回ループ
  jnz foo

他に以下の回数のループは1ワード短く書くことができますので、暇があれば考えてみると面白いかも知れません:
3 5 6 7 9 13 14 15 16 31 63 127 255 8191 16383 32767 65535

MSP430の自動インクリメントも利用する

x86ではinc・decが1バイトでできるため、ループの終了条件はほぼカウンタ一択です。しかし、MSP430ではアドレスの自動インクリメントがあるため、メモリアクセスをループさせる場合は、終了アドレスとポインタを比較した方が短いコードになることがあります。例えば、通常のメモリ間コピーは以下のようにします:

  mov #src, r4
  mov #count, r5
foo:
  mov @r4+, dest-src-2(r4)  ; 自動インクリメント、メモリ間コピー
  dec r5
  jnz foo

しかしcountが定数ジェネレータで生成できない場合、mov #count, r5とdec r5で3ワードも消費してしまいます。これをアドレス比較に書き換えると1ワード節約できます:

  mov #src, r4
foo:
  mov @r4+, dest-src-2(r4)  ; 自動インクリメント、メモリ間コピー
  cmp #src+count*2, r4
  jnz foo

自動インクリメントと定数ジェネレータがあるMSP430ならではの最適化です。

最適化できない場合

x86だと即値とレジスタの間のmovには専用形が用意されていますが、MSP430にはありません。したがって、例えば r4 = r5 ^ 0×1234; を実行する場合、以下のどちらのコードでも結果は全く同じです:

 ; x86ではこっちの方が1バイト短い
 mov #0x1234, r4
 xor r5, r4

 ; x86では1バイト長くなる
 mov r5, r4
 xor #0x1234, r4

MSP430ではデスティネーションのエンコーディングが2種類しかないので、メモリデスティネーションをアクセスするとディスプレースメントが必ずついてしまいます。これを最適化する方法はありません。メモリマップされたペリフェラルを操作することが多いので多分これでいいのですが、なんだかすっきりしない気分になります。

終わりに

MSP430のROMは何KBとある上に、新しいチップは安くてRAMもたくさん載っているので、ここで書いたような1ワードを争う最適化は滅多に必要ありません。でも100クロックかかる処理が90クロックで済むようになったら、消費電力は10%減るわけです。そう考えると、21世紀もまだまだアセンブリ言語の出番はあるのかも知れません。あるといいな……

Home > 組み込みソフトウェア Archive

Search
Feeds
Meta

Return to page top