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/

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