メニューとか作っています

stm32f4xx基板とPOVの実験装置には、表示装置と操作用のスイッチが付いており、単体でいろいろ操作できるようになっている。そのためにメニューを表示したり、選択したりするプログラムが必要になる。そういうものは過去に何度か作ったことがあるので同じようなものを作って使っていたのだが、メニューを追加する必要が出てきた時に、非常に面倒に感じ、考えこんでしまった。

というのは、メニューを選択するとサブメニューが表示され、その下にさらにサブメニューがありというようなTree構造を持つメニューなので、以下の構造体を定義し

typedef const struct _menu_mode_s {
  char *title;
  const struct _menu_mode_s *parent;
  const struct _menu_mode_s *next;
  const struct _menu_mode_s *prev;
  const struct _menu_mode_s *child;
  const struct _menu_mode_s  *(*callback)(const struct _menu_mode_s *mode, 
    menu_event_t ev);
} menu_mode_t;

メニュー自体は、変数の初期値で以下のように書き、メニュー間の相互リンクも手作業で張っていたのだが、これを、もっと分かりやすく簡単に書く方法は無いかと悩んでしまったわけである。

const menu_mode_t me_top = {
  "Top Menu",
  NULL, NULL, NULL, &me_setting, me_top_callback,
};

const menu_mode_t me_setting = {
  "Setting",
  &me_top, &me_status, NULL, NULL, me_setting_callback,
};

const menu_mode_t me_status = {
  "Status",
  &me_top, &me_about, &me_setting, &me_pulse_monitor, 
  me_status_callback,
};

const menu_mode_t me_about = {
  "About",
  &me_top, NULL, &me_status, NULL, me_about_callback,
};

const menu_mode_t me_pulse_monitor = {
  "Pulse",
  &me_status, NULL , NULL, NULL, me_pulse_monitor_callback,
};

特に nextとprevのリンクを手で書くのが嫌なので、menu_mode_tの配列にすれば分かりやすいかとも思って試してみたが、まだ気持ち悪い。結局、別の言語で分かりやすく書いて、それからCのソースを自動生成させてみようと思い付き、perlで試してみたら、割りとスムーズにできた。

まず、書いたのは、ソースに当たる部分

use Menu;
use strict;
my $menu = &menu("me_top", "Top Menu",
		 &menu("me_seting","Setting"),
		 &menu("me_status","Status",
		       &menu("me_pulse","Pulse", 
                             "me_pulse_monitor_callback")),
		 &menu("me_about", "About", "me_about_callback"));
print Menu::header;
print "\n";
print "#include \"main.h\"\n";
print "\n";
print $menu->code;

メニューのTree構造も素直に表現できてわかりやすい。あとは Menu.pmを書けばいい。ということで書いたのが以下のコード。

# -*-perl-*-

sub menu{
    my ($name,$title, @args) = @_;
    my $menu = {name=>$name, title=>$title};
    my @submenu;

    foreach (@args) {
	if(ref($_) eq "Menu") {
	    push @submenu, $_;
	} else {
	    $menu->{'handler'} = $_;
	}
    }

    if(@submenu > 0){
	$menu->{'submenu'} = \@submenu;
    }
    bless $menu,Menu;
}

sub Menu::header {
    join("\n",
	 ( "/*",
	   " * this code was generated by Menu.pm",
	   " * don't chage manually",
	   " */"))."\n\n";
}

sub Menu::code {
    my $this = shift;
    $this->make_link;
    return $this->declaration . "\n" . $this->entity;
}

sub Menu::link_name {
    my $this = shift;
    '&' . $this->{'name'};
}

sub Menu::make_link {
    my ($this, $parent) = @_;
    $this->{'parent'} = $parent;
    my @sm = @{$this->{'submenu'}};
    if(@sm > 0){ $this->{'children'} = $sm[0]->link_name;}
    for(my $i=0; $i < @sm; $i++){
	if($i > 0)    { $sm[$i]->{'prev'} = $sm[$i-1]->link_name;}
	if($i < @sm-1){ $sm[$i]->{'next'} = $sm[$i+1]->link_name;}
	$sm[$i]->make_link($this->link_name);
    }
}

sub Menu::declaration {
    my $this = shift;
    my $str = "";
    
    foreach (@{$this->{'submenu'}}){
	$str .= $_->declaration;
    }
    $str .= sprintf("extern menu_mode_t %s;\n",$this->{'name'});
    return $str;
}

sub Menu::get_link {
    my ($this, $name) = @_;
    my $link = $this->{$name} || "NULL";
    return $link;
}

sub Menu::get_links {
    my $this = shift;
    ($this->get_link('parent'),
     $this->get_link('next'),
     $this->get_link('prev'),
     $this->get_link('children'),
     $this->get_link('handler'));
}

sub Menu::entity {
    my $this = shift;
    my $str = "";

    foreach (@{$this->{'submenu'}}){
	$str .= $_->entity;
    }
    $str .= sprintf("menu_mode_t %s = {\n",$this->{'name'});
    $str .= sprintf("  \"%s\",\n", $this->{'title'});
    $str .= sprintf("  %s, %s, %s, %s, %s\n", $this->get_links);
    $str .= sprintf("};\n");
    return $str;
}

1;

実行後出力されるコードは、こんな感じ。

/*
 * this code was generated by Menu.pm
 * don't chage manually
 */


#include "main.h"

extern menu_mode_t me_seting;
extern menu_mode_t me_pulse;
extern menu_mode_t me_status;
extern menu_mode_t me_about;
extern menu_mode_t me_top;

menu_mode_t me_seting = {
  "Setting",
  &me_top, NULL, &me_status, NULL, NULL
};
menu_mode_t me_pulse = {
  "Pulse",
  &me_status, NULL, NULL, NULL, me_pulse_monitor_callback
};
menu_mode_t me_status = {
  "Status",
  &me_top, &me_seting, &me_about, &me_pulse, NULL
};
menu_mode_t me_about = {
  "About",
  &me_top, &me_status, NULL, NULL, me_about_callback
};
menu_mode_t me_top = {
  "Top Menu",
  NULL, NULL, NULL, &me_seting, NULL
};

というわけで、右の写真のようにPOVボードでメニューが動くようになりました…というのは嘘で、これは、まだ配列で実装したメニューで動いている。これも上記の自動生成したメニューに移行する予定。

C言語の変数の初期値の機能は強力で、大概のデータ構造はかけてしまうし、初期値として書きこめば、プログラムへのデータ組込が完了しているわけなので大変便利だ。今後も、こんなプログラムを、いろいろ書きそうな気がする。

ところで、DE0用ビデオデコーダボードの進捗状態だが、QuartusIIの環境構築に手間取りやっと1年前のソースをコンパイルして実行できるようになったところ。そのそも1年前DE0でVGA出力ができるようになったあと、OSをwindows XPから7に入れ替え、Quarus2すらインストールしていなかったので、今回は、そこから始める必要があった。そうするとQuartus2 web-edition最新版11.1sp2ではusb-blasterが認識されないので11.1sp1をインストールし直す必要があったり、Nios2のeclipseで何故か1年前のプロジェクトが認識されなかったり、いろんなことを忘れていたりで結構大変だった。まぁ、期限が有るわけではないので、少しづつやっていこうと思う。

Leave a Reply

メールアドレスが公開されることはありません。