2012年10月31日水曜日

ビデオやオーディオのDMAバッファの管理手法 (動かして遊べるソースコード付き!)

DMAバッファを管理する

ビデオやオーディオのように絶え間なくやってくるデータの入出力を処理する場合、一般にDMAを使います。
システムの中において、バッファの状態は主に「DMA中」、「DMA完了」、「未使用」の3種類に分類する事ができます。

DMAは、リクエストを出してからバッファにデータが完全にやってくるまでに時間がかかります。
DMAのリクエストを出してから即座にそのバッファに対して処理を開始する事は通常しません。

ビデオやオーディオのDMAバッファの管理は、DMAを出してからDMA完了イベントを待ち、到着したデータに対して処理を始める事になります。

配列による手法

DMAバッファを管理する機構を実現する際によく見かけるのは以下のような配列を用いた手法です。


バッファや管理構造体を配列として保持し、何番目が「DMA中」、何番目が「DMA完了」といった具合にインデックスで管理する方法です。非常にシンプルですし、何の問題もないのですが、実装してみると意外とごちゃごちゃした感じになる傾向があります。これは、「システムの全体挙動を見る」という視点を持って見た場合、「配列にアクセスする」という実装詳細が目に入ってきてしまうためなのかもしれません。

モデルベースによる手法

少し考え直して、以下のような簡単なモデルを作ってみました。
このモデルには登場人物が4人います。


queueは、任意の長さを構成可能なFIFOで、「未使用」状態のバッファを格納しておく場所です。
delayは、任意の遅延数を構成可能なFIFOで、設定した遅延数でデータが出てくるディレイ・ラインです。

viprocは、ビデオやオーディオを入力するモジュールで、データを入力すべきタイミングでのみ実行されるものです。viprocは、自身が呼ばれたらqueueからバッファを取りだし、バッファにデータを入力するためにDMAをリクエストします。加えて、バッファをdelayに押し込んで終了します。

voprocは、ビデオやオーディオを出力するモジュールで、データを出力すべきタイミングでのみ実行されるものです。voprocは、自身が呼ばれたらdelayからバッファを取りだし、バッファからデータを出力するためにDMAをリクエストします。加えてqueueにバッファを押しこんで終了します。

上記設計の場合、queueの深さは出力DMAが完了するために必要な深さが必要です。
その点さえ注意すれば、上記のモデルは以下のようなメリットがあります。
  • 実装の抽象度が高く、メインテナンスが容易。
  • データ入力が確定するまでの遅延数をdelay(ディレイ・ライン)の長さで任意に調整可能。
  • 入出力が完全同期系でなくても、delayとqueueの範囲で吸収可能。
  • 使用可能なバッファ数はqueueの長さで決められ、先読みなどの処理も容易。
  • その他。
先の配列による手法では、バッファ管理のためのインデックス変数などが上位に出てきて、ごちゃごちゃした感が否めませんでした。
モデルベースによる手法を用いる事で実装の抽象度が上がってシンプルな実装を実現できます。

上記に示したメリットは配列による手法でも当然のように実現可能です。
が、実際に実現した実装を比較してみるとモデルベース設計の効果がわかると思います。

動かして遊べるソースコード

実際に上記のモデルを動かして遊べるソースコードを用意しました。
viomodel.tar.gzからダウンロードして下さい。

2013/03/31追記

「DMAバッファ管理手法の続編」に続編を書きました。

2012年10月8日月曜日

TOPPERS/ASP for LPCにおける複数UART対応コード(の誤ち)

ほぼ名前だけのメンテナー(私)による改悪

TOPPERS/ASP for LPCとは、酔漢さんがホストされているTOPPERS/ASPのNXPセミコンダクターズ社向けプロセッサへのポートです。

私はほぼ名前だけのメンテナーですが、過去にコミットした私のコードでちょっと良くない点があったので御紹介します。普通はこういう事は書かないのですが、何を考えてそうして、何が良くなかったのかを示してみたい衝動にかられました。

さて、問題のコードはTOPPERSの中でも悪名高きUARTに関連する箇所です。
オリジナルのtarget_config.cは以下のようになっていました。


SIO_PORTIDを参照して必要なコードを生成させるという記述です。
ちなみにPINSELは以下のようなマクロです。


シリアルポートは「ひとまずシステム唯一」という仮定を用い、ここでよろしくやってくれるわけです。
加えて、「使いたいポートと違ったらアプリケーション・プログラマがよろしくやってくれ!」と主張しています。

私はここでちょっと「え?」と思いました。
「イマドキの人」にそういうのって通じるの?という具合です。

ここから私の余計なお世話が始まりました。
まずはSIOポートの初期化です。

デフォルトでパワーオンされていないペリフェラルに対してパワーオンが必要です。
また、同じTXDやRXDであっても、割り当て可能なピンが幾つか存在します。

「一行記述を見れば何が割り当てられているのかわかるのが親切というものではないか!」という主張を基に、以下のような実装に改悪しました。


で、それらのマクロは以下のようにしました。


これでアプリケーションプログラマが「おや?私の使いたいピンと違うぞ!」と直ぐに気付く事ができるし、加えて予めマクロが全てのパターンで用意されているので差し替えるだけで準備オッケイ!と相成ります。

こりゃ便利だなんて思うわけです。

さて、何が改悪なのでしょうか?
一見便利になったように見えますが、実は壮大な問題が含まれています。

改悪による壮大な問題

さて、先ほどのコード、パッと見は良いのですが、オリジナルにない修正を加えています。
  1. SIO_PORTIDを参照して初期化するのではなく、SIO_BAUD_RATE_PORTNを見て初期化。
  2. target_uart_initの呼び出し。
さて、SIO_PORTIDを参照して初期化する場合、該当するある一つのシリアルポートのみが初期化されます。実はとあるプロジェクトで「複数のUARTポートを同等に扱いたいなぁ」と考えていました。
これが最初の改悪の動機になりました。

「SIO_BAUD_RATE_PORTNが定義されていたらそのシリアルポートを使う!」で良いんじゃない?
それって凄く便利かも~!です。

一見良さそうですが、他のポートは依然「SIO_PORTIDを参照して初期化」を期待しています。
先の改悪のコードは一切「UARTを初期化しない」というおかしな挙動を生む原因になります。

加えてtarget_uart_initの呼び出しです。
この関数はpdic/uart/uart.cの関数を呼び出しています。

「あれ?pdicって抽象化されたデバイスドライバだよね?」とはたと気づくわけです。
要するにここで行なっているのは、下層から上層への呼び出しという、主従関係が逆転する極めて重大な問題というわけです。

というわけで・・・

というわけで私の浅はかな判断により改悪したコードによって、メインコミッタである酔漢さんにご迷惑をおかけした次第です。
LPC1830を試す(3)でサラッと触れてありますが・・・。恐ろしくて真相は聞けません。

M25P16をフルコントロール!

M25P16は、SPIバス互換シリアルインターフェースを持つ、16Mビットのフラッシュロムです。
8ピンの小さなパッケージから選択可能で、気軽にフラッシュロムを搭載する事ができます。


内部ブロックは以下のようになっています。
ページサイズ(Xアドレス)は256バイトで、256ページで1セクタです。
M25P16で消去可能な最小単位はセクタですので、256ページをまとめて消去(実際にはビットを全て1に)する事になります。


ちなみに、M25P16の場合、32セクタあって、総計8192ページ存在します。
なかなか広大な敷地が広がっています。嬉しい。

コマンド群は以下のようになっています。
データライト系のコマンドは「PAGE PROGRAM」、「SECTOR ERASE」、「BULK ERASE」です。


何か別の開発を中心に考えている場合、「一応ちゃんと実装したいなぁ」とか考えて上記の対応を始めると意外に時間がかかります。

今日は意外に時間がかかった(わけでもない気がするけど・・・)M25P16の実装を公開します。

ダウンロードはこちらから。

ソースコードとヘッダから構成されています。
ソースコードのSPIマクロに実装詳細を提供すれば、すぐに使えます。
SPIのキャラクタについてはデータシートを参照して下さい。


以下の検証を実施済みです。
  1. セクタ消去を実行する。
  2. M系列信号発生器から系列信号を取り出して「PAGE WRITE」操作用データを生成する。
  3. ページライトを実行する。
  4. M系列信号発生器を再度同じシード(種)で初期化する。
  5. 「READ DATA BYTES」操作でデータを読み込んで系列信号と比較。
  6. 上記を全セクタに対して実行する。

M25P16の全バイトをくまなくI/Oできる事を確認して満足満足・・・。
ですが、「本当にやりたい事」はこの先にあります。

2012年10月1日月曜日

Androidタブレット端末「IMPRESSION KT-i7A4.0」を準備する

やぁ、Android!

某所でAndroidタブレット端末「IMPRESSION KT-i7A4.0」を入手しました。
タブレットを、将来的に組み込みシステムのフロントエンドとして使おうと考えています。
ひとまずAndroidの開発に慣れておこうという算段です。


この機種には残念な事にBluetoothが搭載されていないのですが、気にしません。
Nexus7なんて別に欲しくないしっ!(←まったくもって嘘!)

http://developer.android.com/index.htmlからSDKをダウンロードしてインストールします。

次にドライバ関連の設定。

PCに最初に接続した時にはドライバはインストールされないと思います。
不明なデバイスとして出ているアイコンからプロパティを辿って下さい。
以下は認識後の画面ですが、プロパティに対する値は同じモノが出てきます。


ハードウェアIDをandroid-sdk\extras\google\usb_driver\android_winusb.infに追加します。

;KT-i7A
%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_0003&MI_01
%CompositeAdbInterface% = USB\VID_18D1&PID_0003&REV_0230&MI_01

Eclipse Plug-inの設定を実施。
HelpメニューからInstall New Softwareを選択。

https://dl-ssl.google.com/android/eclipse/

これで開発環境のセットアップが完了です。

フォントをきちんと日本語化

デフォルトのフォントがあんまりな感じなので、フォントを変えて気分一新します。
android-sdk\platforms\android-16\data\fontsから以下のフォントを使います。

DroidSansJapanese.ttf
MTLc3m.ttf
MTLmr3m.ttf

本体にこれらのフォントを転送します。

まずはReadOnlyでマウントされている/systemを書き込める状態でマウントしなおします。
adbを使ってシェルインターフェースを出します。

adb shell

次にマウント状態を確認します。

root@android:/# mount

表示されたマウントデバイスから/systemを含むブロックを探し出します。

/dev/block/nandd /system ext4 ro,relatime,user_xattr,barrier=0,data=ordered 0 0

これから/systemの下を書き換えるのでマウントし直します。

mount -o rw,remount /dev/block/nandd /system
exit

これで/systemの下を書き換える事ができます。
さて、フォントファイルの転送です。

adb push フォントファイル名 /system/fonts

転送した時の様子を以下に示します。

C:\Program Files\Android\android-sdk\platform-tools>adb push DroidSansJapanese.ttf /system/fonts
3460 KB/s (1173140 bytes in 0.331s)

C:\Program Files\Android\android-sdk\platform-tools>adb push MTLmr3m.ttf /system/fonts
2708 KB/s (2871020 bytes in 1.035s)

C:\Program Files\Android\android-sdk\platform-tools>adb push MTLc3m.ttf /system/fonts
3143 KB/s (1924864 bytes in 0.598s)

ちゃんと転送できていますね。

次にフォールバック設定を更新します。

adb pull /system/etc/fallback_fonts.xml 書き込み可能なローカルディレクトリ名

フォールバック設定に上記の3つのフォントを書き加え、本体に書き戻します。


adb push fallback_fonts.xml /system/etc/

これでフォントはばっちり日本語化されます。

2012年9月30日日曜日

「茶室で楽しむKOZOS拡張基板」企画の開催報告 ~少し優しい気持ちになって、いつもと違うく組み込み開発を!~

「茶室で楽しむKOZOS拡張基板」企画の開催報告です。

2012/09/30追記:
@kanpapaさんが別途レポートをまとめてくれました。
雰囲気がよく出ているのでそちらもお楽しみ下さい。
ウェブサイトきょうのかんぱぱの「茶室で楽しむKOZOS拡張基板に参加してきました」のページです。

まずは、なぜ会場に都筑民家園を選んだのか?について。
キーワードは「少し優しい気持ちになれる」です。


駅周辺は商業施設でごちゃごちゃしています。
しかし、少し離れるだけで田舎の雰囲気を味わえるという場所。




弥生時代の集落がそのまま復元されています。



都筑民家園は、約200年前の家を移築したもの。
私のお気に入りは屋根の構造です。





で、ここをぶらりと訪れた時に繋がったのがKOZOS拡張基板だったわけです。


皆様にいつもと違う環境で組み込み開発して頂ければという企画でした。


茶室を除けばそこは組み込み開発室。(プライバシーに配慮してぼかしてあります。)


茶室の横を流れる水の音を楽しみながらの開発は、今までに味わったことのない楽しさを提供してくれました。


こんな感じで、茶室と組み込み開発の不思議なコラボレーションとなりました。


ちなみに、KOZOS拡張基板は当然ながら全て無事に動作しました。
まぁこれが当たり前なのですが、主催者としては一安心というところ。
(KOZOSのブログ:オープンソースカンファレンスでの展示の様子も是非!)


参加された皆様には、後からでも楽しめるようにちょっとした資料も一緒にお渡ししました。


組み込み開発は、どうしても世知辛い事になりがちです。
開発の性質上仕方のない事ですが、常に違う視点も取り入れる工夫を続けたいですね。

お忙しい中御参加頂いた皆様、ありがとうございました。
はるばる遠方(大阪府!福島県!)からお越しいただいた方、ありがとうございました。

2012年9月23日日曜日

小規模組み込みシステムで使えるBASICインタプリタをKOZOSと同時に楽しむ! (NT-Basic on KOZOS)

先の記事「NT-Basic on KOZOSのために・・・ (NT-Basicの改良)」では、小規模組み込みシステムで使用可能なBASICインタプリタであるNT-Basicの改良を行ないました。

やっとこさ使えそうな物になってきたので、NT-Shellと組み合わせてKOZOS上で動作させてみました。



デモの中で流れている音楽は、KOZOS EXPBRD上に搭載されたマイクロSDカードから再生しています。
と同時に、シリアルコンソールからBASICインタプリタを楽しむ事ができるというのが今回のデモです。

何かオペレーティングシステムを使っているからこその動作が欲しかったので作ってみました。

端末の処理はNatural Tiny Shell (NT-Shell)の処理系を使用しています。
自然な入力が可能で、入力処理が破綻せずスムーズにプログラムの編集ができます。

KOZOS EXPBRDには、グラフィックLCDや入力スイッチが搭載されています。
NT-Basicに命令を追加してグラフィック制御などもできるようにするとゲームも作れそう。

NT-Basicの拡張でKOZOSも使うからKZ-Basicなんていうのも洒落ています。
KZ-Basic (拡張NT-Basic)という名称で拡張していく事を考えています。

今回のソースコードは「茶室で楽しむKOZOS拡張基板」でもお渡しする予定です。

2012年9月22日土曜日

NT-Basic on KOZOSのために・・・ (NT-Basicの改良)

NT-Basic on KOZOS(NT-Basicの改良)

SWEST14での刺激を受けた結果、インタプリタの実装に興味が出てきてNT-Basicなるものを出力しました。

それではという事でKOZOSと組み合わせて使おうと思ったのですが、実際にあまり使い勝手が良くなかったのでNT-Basicの改良から行うことにしました。

エディタが必要だ

KOZOS上で実際に動作させる場合、単にファイルシステムからコードを読み込んで動作させるだけでは面白くありません。ターミナルエミュレータから実際にコードを入力して実行するようなインタラクティブな動作を実現したいのです。

この時に必要になるのが実用的なエディタです。入力を間違えても再編集できて、昔ながらのBasicシステムに似たような動作を提供できるのが理想です。viのように本格的なエディタを設計する気分にはなれなかったので、以下のような戦略をとることにしました。
  1. 行編集機能はNT-Shellを用いて機能提供する。
  2. プログラムは新たに作るtexteditモジュールが保持する。
  3. ターミナルにrunコマンドを与えた時点でイタンプリタを動作させる。
こうする事でtexteditモジュールのみを設計実装すれば済みます。
texteditモジュールのインターフェースは以下のようにしました。

int textedit_init(textedit_t *p);
int textedit_clear(textedit_t *p);
int textedit_insert(textedit_t *p, const int line_number, const char *text);
int textedit_delete(textedit_t *p, const int line_number);
int textedit_fetch(textedit_t *p, const int line_number, char *text, const int maxsize);

NT-Basicはプログラムを文字列として受け取ります。
エディタを設計するならば、内部データは双方向リンクリストで構成する行データの集合として表現したいのですが、NT-Basic側の都合に合わせて単純な一つの文字列で複数の行編集が可能なtexteditモジュールとして設計しました。

これでプログラムの編集は実現できます。

プログラムエラー発生時の挙動修正

初版のNT-Basicでは、プログラムエラーが発生した時に最終的にハードウェア抽象化層のhal_halt関数を呼び出していました。この関数は無限に処理を返さない関数として実装する事を想定しており、「プログラムエラー=処理を永遠に返さない」というあんまりな挙動を提供する結果を生んでいました。

「それはないだろう」という事で、エラーを検出した時点で、そこから最終的にntbasic_executeまでエラーを伝搬させるようにしました。HAL層にhaltがあると意味がわからないので、hal_haltは削除しました。ちなみに、c89で上位にエラーを伝搬させるのは丁寧に実装するしかない、みたいな感じの実装になっています。

上位でエラー発生時の行番号の取得も可能です。

NT-Basicの実行方法

初版の実装でもう一つ致命的に使いにくいのが、実行方法でした。ntbasic_executeは、実行したらプログラム終了まで処理を返さないという仕様だったのです。が、これではベアメタルシステムで使いにくくて仕方ありません。NT-Basicでチョコチョコ処理をしながら、別の作業をさせたい事もあるわけです。

実際に使ってみると、先ほどのエラー時の挙動と合わせて組み込みシステムで実質使えないような挙動になっていました。

そこで、改良したNT-Basicでは、ntbasic_executeを呼ぶ度に1ステートメントのみ処理するように仕様を変更しました。こうする事でベアメタルシステムでもNT-Basicを導入しやすくなります。

  ntbasic_t ntbasic;
  ntbasic_error_t error;

  /*
   * Setup and execute.
   */
  error = ntbasic_setup(&ntbasic, program);
  while (error == NoError) {
    error = ntbasic_execute(&ntbasic);
  }

  /*
   * Check the termination code.
   */
  if (error != EndOfTheProgram) {
    core_error_message(&ntbasic, error);
  }

上記のような簡単なコードを上位に加えれば従来のような使い方もできます。

辛かった変数名の制限

最初の実装では変数名はAからZまでの26文字という小さなBasicではありがちの制限を持っていました。

実際にプログラムを組んでみるとわかるのですが、これがかなり辛い制限です。
「あれ?この変数は何を入れてるんだっけ?」みたいにすぐに迷子になれます。
「えーと、Rはアレの略にしたから・・・、で、Aは・・・何だっけ?」みたいに脳内記号表を使う羽目になるわけです。本来、これは機械にやらせるべき作業内容です。

そこで、自由に変数名を付けられるような仕様変更を行いました。


内部設計と実装は至ってシンプルです。
小さな記号表モジュールを作って対応しました。

記号表の考え方は非常に面白いものです。
ここでハッシュ法を使った動作を簡単なモデルで紹介しましょう。

ハッシュ法を用いた記号表の実現

記号表は、変数名や型、長さやその他属性を管理するテーブルです。
NT-Basicの場合、変数は内部的にintしか持たないので型や長さは存在しません。
記号表における一番シンプルな例という事になります。

で、与えられた変数名から実際の値を取りだす場合、テーブルを探索する事になります。
線形探索した場合、最悪の場合テーブルのサイズ分の文字列比較作業が必要になります。


上記の図でGHIという変数を探索した場合、先頭から3番目で探索完了になりますが、実際にテーブルの最後に存在した場合、テーブルサイズ分の探索作業を行なう事になります。

これでは変数にアクセスする度に膨大な時間が必要になるので効率的とは言えません。
そこで考えられたのがハッシュ法です。

基本的な考えは非常にシンプルです。

まず、与えられた文字列から導出可能なハッシュ値を得ます。
このハッシュ値は文字列から得られる値で、同じ文字列からは毎回同じ値が得られます。


次に、この得られたハッシュ値を元にテーブルの参照先インデックスを決定します。
例えば、参照先インデックスとして28という数値が得られたら、記号表のインデックス28を参照するという仕組みです。

有限長のテーブルの場合、「参照先インデックス % テーブルサイズ」などとして参照先インデックスを決定します。


整理すると「文字列からハッシュ値」、「ハッシュ値からテーブル参照先インデックス」という過程で、変数名を与えるだけで一意に決まる(ように見える)インデックス番号が求まりました。

さて、ここで疑問が沸いてきます。
仮にテーブルのサイズが16だったとして、ハッシュ値0と16の結果はどうなるでしょうか?

先ほどの例では「参照先インデックス % テーブルサイズ」なのでテーブルの参照先がどちらも0になってしまいます。

変数名として与えられた文字列が異なるものなので、理想的には得られたハッシュ値は異なる結果となります。それにも関わらずテーブルの参照先が同じになってしまいました。

安直な実装の場合、参照先インデックスに格納されている変数名と照合して、合致すれば期待する変数が格納されているものとして処理します。

そして、期待する変数名でなければ近い位置のテーブルを探します。
これにより、線形探索よりも遥に少ない処理で期待する変数値を得ることができます。

実際には、ハッシュ値自身の衝突も考える必要があります。
ハッシュ値自身の衝突の場合も、上記のように期待する変数でなければ近い位置のテーブルを探索するという方法を取ります。

ちなみに、流石に上限なしで対応させるとメモリが幾らあっても足りないので、デフォルトの定義では変数名8文字、変数32個までにしてあります。これはヘッダファイルを書き換えるだけで大きくする事ができます。組み込むシステムに合わせて定義を修正すれば良いでしょう。

この改良によって、以下のようなプログラムが見違えるように判りやすく書けるようになります。
以下はアリスとボブとキャロルの年齢を総計として出力する例です。(ちょっと無理矢理な例)

改良前
INPUT "A=",A
INPUT "B=",B
INPUT "C=",C
T = A + B + C
PRINT "T=",T

改良後
INPUT "Alice=",Alice
INPUT "Bob=",Bob
INPUT "Carol=",Carol
Total = Alice + Bob + Carol
PRINT "Total=",Total

まとめ

今回は小規模組み込みシステムで楽しむためのNT-Basicの改良として、実行方法、エラー時の挙動、変数名の制限について改良した内容について示しました。

ちなみに、NT-Basicは総行数が約2000行とコンパクト。
1人で簡単に読み切れるサイズです。

--------------------
Lines File
--------------------
438 core.c
281 expression.c
109 hal.c
106 main.c
157 ntbasic.c
269 ntlibc.c
443 statement.c
111 tinyrand.c
135 variable.c
--------------------
2049 Total

最新のリリースは以下からダウンロードできます。
http://shinta.main.jp/firmware/ntbasic/ntbasic_ja.html