Blog2Viewer

以前、Movable Typeで運用していたブログの内容を表示させるために作成したページです。
メニューとか作っています
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年前のプロジェクトが認識されなかったり、いろんなことを忘れていたりで結構大変だった。まぁ、期限が有るわけではないので、少しづつやっていこうと思う。
DE0ビデオデコーダボード進捗
謎基板ことDE0用ビデオデコーダボードの進捗だが、あまり進んでいない。まず、読み込んだデータを出力しやすいように24bpp化しようと考えたが、そもそもDE0のVGA出力はRGB各4bitの12bppだった。データの読み出しだけでも24bpp化しようとするがSGDMAがエラーになる。メモリーの構成を調べるとSDRAMのデータ幅は16bitで、50MHzで動作していた。25MHzで32bitのデータを読み出そうとしても間に合うはずがない。SDRAM自体は166MHzまで動作できるようなので、クロックを100MHzにあげてみたが、反応は変わらない。

もうちょっと頑張ればなんとかなりそうだが、この辺で悩み始める。ビデオデコーダボードやDE0が使えるようになって作りたいのは、VGA信号のセレクタみたいなもの。解像度としてはSXGA(1200x1024)程度ぐらいまで扱いたい。そうするとピクセルレートは108MHzで、これを同時に読み書きできるぐらいでないと無理そうだ。

DE0ではメモリーの速度の面から無理ということが良くわかった。安く作りたいのでパッケージをtftpに限定し、digikeyで捜すと200MHzのDDRは買えそうだ。これを32bit幅で接続すれば、なんとか可能かもしれない。

DE0では今後、ビデオデコーダの動作の確認とか、その他FPGAの使い方の基本的な所を練習して行こう。
DE0で12bpp表示
やっとDE0のVGA出力で12bpp表示ができた。FPGA ボードで学ぶ組込みシステム開発入門 ~Altera編~が参考になった。

VGA出力はSOPC builderでsgdma→dc_fifo → pixel_converter → video_sync_generatorと構成し行なっているのだが、pixel_converterが8bppか24bppしか受付けてくれない。メモリー上では32bppで割り当ててpixel_converterとvideo_sync_generatorでは24bppで出力、そこから12bitだけ使うという方法を考えたが、25MHzで32bitを呼び出すことになる。SDRAMは16bit幅、50MHzで接続されているので、間に合うわけがない。そこでSDRAMとCPUを100MHzで動かしてみたが、うまく動いていない... というところまで前回報告した。

SDRAMを100MHzで動かしても、32bpp/25MHzは無理そうなので、メモリー上は16bppにすることを検討する。pixel_converterのverilogファイルをみると、何をしているわけでもない。入力をそのまま出力するだけだ。sopc_builder上では、8 [bit/symbol] x 4 [symbol/beat] を 24bit [bit/symbol] x 1 [symbol/beat]に変換してくれたりしていたりする。pixel_converter無しでは、symbolのサイズが違うとかエラーが出て sgdmaの出力streamをvideo_sync_generatorに接続することができない。

pixel_converter関連のファイルは pixel_converter.vとpixel_converter_hw.tclだけのようで、どちらも簡単なファイルなので、これを改造して16bpp版のpixel_converter16を作ることにした。ファイルは簡単にできた。しかし、SOPC Builderを再起動しても認識されない。pathの指定ぽいファイルをイジってみるが変わらない。諦めてsopc_builderの New... ボタンを押すと、verilogファイルを指定すると、pixel_converter16_hw.tclが作成され、sopc_builderで指定できるようになった。sopc_builderで配置し、接続しようとすると何か信号がおかしい。元の *_hw.tclファイルを参考に修正し、接続できるようになった。

これで回路はできて、DE0にダウンロードできるのだが、ソフトをダウンロードしようとするとエラーになり失敗する。ひつこく何度も試すとダウンロードできることもあるのだが、動作が不安定。ここで、もう少し地味に勉強してみようかと考え、FPGA ボードで学ぶ組込みシステム開発入門 ~Altera編~の評判が良さそうなので買ってみようと考え、実際に買ったのが昨日。

で、ざーと目を通す。コラム(p.262 コラムH)に原因不明のエラーで進めなくなったら、プロジェクトを新規に作り直し、入力したファイルをコピーし、やり直すのが結局速いとか書いてある。そうなのかと、安心したり不安になったりする。

で、電源について書いてあるところがあった。(p.65 「USBのバスパワーでも動作する!?」)。小規模な回路ならバスパワーでも動作するのだが、大規模になると危ないのでACアダプタは忘れずに接続しておきましょう、とある。そう、私はACアダプタを接続していなかった。クロックを100MHzにあげたら動作が不安定になるというのも合点がいく。

で、ACアダプタを接続しためすと、書き込みエラーが完全になくなったわけではないが、かなり減少し、ソフトウェアのデバッグを行うことができて、無事12bppで動かすことができた。 これで、次に進める。

ところで、DE0いじりでQuartusIIとかSOPC Builderとかいじっていると、コンパイルなどに結構時間がかかるので、家でTVを見ながら作業しやすい。