2011年5月1日日曜日

Saleae LLCのロジックアナライザを購入してみた

検討の経緯

昨年から購入しようかどうか迷っていたロジックアナライザ。

論理的にコードを組み立てて動けば良いけど、動かない時に相当悩むのがI2CやSPIなどのデバッグです。
そんな時、プロトコル解析のできるロジックアナライザが欲しくなります。

国内では秋月電子通商さんストロベリーリナックスさんで扱いのあるZERO PLUS社のロジックアナライザが有名です。

私は昨年からSaleae LLCのLogic USB Logic Analyzerに目を付けていました。

ZERO PLUS社の製品も物の感じとしては悪くないのですが、アプリケーションがWindows専用だったり、Saleae LLCの物に比べて動作が「いかにもWindows風」であまり盛り上がりませんでした。

Saleae LLCのアプリケーションはQtで実装してある模様で、Linux、Windows、Mac OSと対応プラットフォームについては何の不満もありません。
そして、その挙動もマウスでビュンビュン動かせる感じもたまりません。
ハードウェアを購入する前からソフトウェアのみで体験できるので、この時点でかなりニンマリしてしまいました。

国内ではスイッチサイエンスさんで取り扱いがあります。(私は直接購入しました。)
スイッチサイエンスさんのページはこちら

本日荷物が到着しましたので早速開けてみる事にしました。

開けてみる


荷物はこんな感じで到着しました。


Invoice入ってます。


専用ケースに収められ、緩衝材に入っています。


専用ケースはこんな感じの物。
(どうでも良いけどロゴが可愛いです。)


必要な小物と本体の一式がコンパクトに収納されています。


専用ケース、使い方説明、クリップ、本体(小さい!)、ワイヤー、USBケーブルがセットです。
本体はもう少し大きい物かと思っていたので若干拍子抜けですが気にしません。


こんな感じのサイズです。
カードより小さい。


次の記事では実際の使用感などを見ていきたいと思います。

2011年4月12日火曜日

TOPPERSでスタック状況を確認するシステムコールを作る(quick-and-dirty)

概要

TOPPERS/ASPでアプリケーションを実装した後、やっぱり気になるのはスタックの状況です。
商用iTRONのツールの中には当たり前のようにあるスタック状況確認機能ですが、標準システムコールにはありません。

調べてみると非依存部にはスタックの先頭アドレスとサイズしかなく、依存部のタスクコンテキストブロックにスタックポインタがあります。
もしスタック状況を計算するようなシステムコールを標準として作りたいとすると、依存部に対する仕様(スタックポインタはXXXのように用意すること・・・のように。)が必要になるわけです。

スレッド型OSでスタック状況を確認できないのはなんとも気持ちが悪いものです。
そこで、今回はquick-and-dirtyでスタック状況を確認できるメカニズムを作ってみました。

動作

小規模組み込みシステムデバッグ用シェル - Natural Tiny Shell (NT-Shell)と組み合わせた場合の動作は以下のようになります。

>taskinfo
TSKID STACK ADDR (HEAD:TAIL) STACK USED (USED/TOTAL)
=========================================================
 1 0x10000b88:0x10000f87 132/1024
 2 0x10000f88:0x10001787 124/2048
 3 0x10001788:0x10001f87 108/2048
 4 0x10001f88:0x10002787 100/2048
 5 0x10002788:0x10002f87 204/2048
=========================================================

各タスクのスタックアドレスと使用量が一目瞭然で安心です。
上記の出力結果とプロセッサのメモリマップを見て「フムフム」と納得できます。


今回テストで動作させているシステムにはNXPセミコンダクターズのLPC1769が搭載されています。
このプロセッサの内蔵SRAMのアドレスにスタックポインタがあることがわかります。

inf_tskシステムコールを使う

inf_tskシステムコールを使うのは簡単です。
以下のようにタスクIDを指定してシステムコールを呼ぶだけです。

int i;
T_ITSK itsk;
syslog(LOG_NOTICE, "TSKID\tSTACK ADDR (HEAD:TAIL)\tSTACK USED (USED/TOTAL)");
syslog(LOG_NOTICE, "=========================================================");
for (i = 0; i < TNUM_TSKID; i++) {
 const int tskid = 1 + i;
 inf_tsk(tskid, &itsk);
 syslog(LOG_NOTICE, " %2d\t0x%x:0x%x\t%5d/%5d",
 tskid,
 itsk.stk_head, itsk.stk_tail,
 itsk.stk_used, itsk.stk_total);
}
syslog(LOG_NOTICE, "=========================================================");

inf_tskの実装

今回設計したシステムコールはinf_tskという名前です。(あー、そんな名前で作ちゃって!)
ref_tskを参考に実装を追加しました。

まず、include/kernel.hに定義を追加します。

extern ER inf_tsk(ID tskid, T_ITSK *pk_itsk) throw();

次に、システムコールで取得するデータを格納するための構造を用意します。

これもinclude/kernel.hです。

typedef struct t_itsk {
 PRI tsk_pri_curr; /* 現在のプライオリティ */
 PRI tsk_pri_base; /* ベースプライオリティ */
 SIZE stk_used; /* タスクスタック使用量 */
 SIZE stk_total; /* タスクスタック総量 */
 void *stk_head; /* タスクスタック先頭アドレス */
 void *stk_tail; /* タスクスタック最終アドレス */
} T_ITSK;

ここにref_tskが持つタスク状態などを追加すると良いかもしれません。

次にシステムコール本体の実装を加えます。

今回は新たにtask_info.cというシステムコール実装用のソースを用意しました。
kernel/task_info.cがシステムコールの実装です。

/*
 * タスクの情報取得機能
 */
#include "kernel_impl.h"
#include "check.h"
#include "task.h"
#include "wait.h"
#include "semaphore.h"
#include "eventflag.h"
#include "dataqueue.h"
#include "pridataq.h"
#include "mailbox.h"
#include "mempfix.h"
#include "time_event.h"
/*
 * タスクの情報取得機能
 */
#ifdef TOPPERS_inf_tsk
ER
inf_tsk(ID tskid, T_ITSK *pk_itsk)
{
 TCB *p_tcb;
 ER ercd = E_OK;
 uint_t tstat;
 p_tcb = get_tcb(tskid);
 t_lock_cpu();
 tstat = p_tcb->tstat;
 if (TSTAT_DORMANT(tstat)) {
 pk_itsk->stk_used = 0;
 } else {
 pk_itsk->stk_used = p_tcb->p_tinib->stksz
 - (p_tcb->tskctxb.sp - p_tcb->p_tinib->stk);
 }
 pk_itsk->stk_total = p_tcb->p_tinib->stksz;
 pk_itsk->tsk_pri_curr = EXT_TSKPRI(p_tcb->priority);
 pk_itsk->tsk_pri_base = EXT_TSKPRI(p_tcb->priority);
 pk_itsk->stk_head = p_tcb->p_tinib->stk;
 pk_itsk->stk_tail = p_tcb->p_tinib->stk + p_tcb->p_tinib->stksz - 1;
 t_unlock_cpu();
 return ercd;
}
#endif /* TOPPERS_inf_tsk */

後はMakefile.kernelにtask_info.cをコンパイルするような記述を追加してできあがりです。
実装をご覧になってわかる通り、p_tcb->tskctxbは依存部にあるタスクコンテキストブロックで、本来ここで参照してはいけません。

まとめ

今回の実装はTOPPERS/ASPから見ると「最悪」と言えます。
が、システムを実現する立場からすると「安心」が得られます。

スタックオーバーフローによってシステムが意味のわからない挙動になってしまっては、せっかくのRTOSを使った楽しい開発が台無しです。少しの機転でシステムの状態を視覚的に把握することができ、開発を少しでも楽にできれば嬉しいですよね?

謝辞

この実装はマイコン工作実験日記さんのアイデアに触発されています。
素晴らしいアイデアに感謝しております。

2011年4月9日土曜日

子供と一緒にお洒落な家具を作ろう!

概要

今回はちょっとブレイクネタです。

約15年前に作ったMDラックがありまして、引っ越すたびにどうしようかと考えていました。
最終仕上げなしの状態で作ったもので、これを何とかしようというのが今回の記事です。

エンジニアのそこのあなた!
家族を投げ出して開発の事ばかり家で考えていませんか?
それはいけません!(←それは自分だ。)

子供と一緒にお洒落な家具を作りましょう。

用意する物

  • パイン集成材を使った工作物。
  • 水性ウレタンニス。着色剤が入っているもの。
  • つや消し透明アクリルスプレー。
  • 紙ヤスリ。
  • ハケ。(水性ウレタンニスを塗るのに使います。)

手順

家具を作る

できました。(すいません。ここは適当に流します。皆さんで好きな形状にして下さい。)
パイン集成材は柔らかい材料で簡単に加工できます。
また、着色もしやすいのでお勧めの材料です。

水性ウレタンニスを使って木材に色を付ける

さきほど完成した家具はどこにでもありそうな?家具でお洒落かどうかは微妙なところです。
まずはハケを使って水性ウレタンニスを塗りたくります。


ジャンジャン雑に塗って構いません。
塗ると以下のようになります。


ちょっとだけ、アンティークな雰囲気になってきました。
が、ここからの仕上げ作業が肝心です。

紙ヤスリでウレタンニスでついた色を落とす

まずは、紙ヤスリで先ほど塗った色をゴシゴシ落としていきます。
コツは「端を意識的に多めに落とす」です。


端の色を意識的に落とすことで、家具の立体感が出てきます。
使い古された感じがアンティークな家具の雰囲気を醸し出します。

アクリルスプレーでコーティングする

最後にアクリルスプレーでコーティングして出来上がりです。

完成図

このようになりました。


ちょっと古めかしい感じがスタジオジブリか何かの作品で出てきそうな感じで、子供も喜びそうな仕上がりです。
自分たちで作ったワクワク感と相まって大切にしてくれること間違いありません。

2011年4月1日金曜日

ARM Cortex-M3 (NXP LPC1769)を用いたオーディオ処理の効率改善

概要

今回はプロセッサを用いた信号処理における効率改善に関するトピックです。


先日完成したARM Cortex-M3 (NXP LPC1769)を用いたオーディオ基板は @suikan_blackfin さんがお作りになったサンプルアプリケーションのおかげで素早く動作確認することができました。本当にありがとうございます。


氏はリアルタイムOSのポーティングから信号処理まで多岐に渡る分野で活躍されていて、言わば私の心の師匠ですが、恐れ多くもサンプルアプリケーションで気になったオーディオ処理の効率改善について記すことにしました。

巨人の肩に乗りまくりです。

オリジナルの設計

まず初めにオリジナルの設計がどのようなオーディオ処理ステップを踏むのかを整理してみます。
  • DMA転送されたオーディオバッファサイズ分のデータがrxbufに入っている。
  • 並び替えながらrxbufからaudio_data.inputBufferにコピーする。
  • オーディオ処理を実行する。
    • ここでは処理に応じてaudio_data.inputBufferからaudio_data.outputBufferへのコピーが発生する。
  • 並び替えながらaudio_data.outputBufferからtxbufにコピーする。
図を用いて整理すると以下のようになります。

要するに主記憶上におけるメモリコピーが少なくとも3回発生していることになります。
このメモリコピーはforループで実装されており、3回のforループによる性能への影響も気になるところです。

通常、性能という観点で見た場合、主記憶上でのメモリコピーや重複したforループは処理性能低下の主要な要因のひとつとなります。

そこで、今回は上記処理の効率改善を考えてみます。

最小限の処理から考えてみる

まず初めに、最小限の処理について考えます。

入力をそのまま出力に伝達する場合、単なるメモリコピーで済みます。
入力に何らかの処理を加え、出力に伝える場合でもこの入出力間のメモリコピーの間に何らかの処理を追加するだけで済みますので、本質的に上記と変わりません。

オリジナルの実装ではコーデックのデータ形式を踏まえて処理を行なっています。

まず初めに入力されたデータを内部で扱いやすい形式にメモリコピーします。

これはrxbufからinputBufferへのコピーです。


次にオーディオの処理を実行します。
これはinputBufferからoutputBufferへのコピーです。

オーディオの処理を実装する過程で、ここに様々な演算が入ることになります。

最後に結果を出力バッファに書き込みます。
これはoutputBufferからtxbufへのコピーです。

この作業はオーディオバッファの内容を、前段で都合の良い形式に並び替えた結果発生する作業と言えます。


まとめると以下のようになります。
  • コーデックから得られたデータ形式は扱いにくいので並び替える。
  • 並び替えは主記憶上でMCUが実行する。
  • 並び替えたデータは、コーデックがそのまま扱えないので再変換する。
コードブロックは以下のようになっていました。(一部はオリジナルと少し異なります。)

 index = 0;
 for (sample = 0; sample < AUDIOBUFSIZE / 2; sample++) {
 for (ch = 0; ch < 2; ch++) {
 audio_data.inputBuffer[ch][sample] = rxbuf[index++];
 }
 }
 audio_effect_through(
 &effect_param,
 audio_data.inputBuffer,
 audio_data.outputBuffer,
 AUDIOBUFSIZE / 2);
 index = 0;
 for (sample = 0; sample < AUDIOBUFSIZE / 2; sample++) {
 for (ch = 0; ch < 2; ch++) {
 txbuf[index++] = audio_data.outputBuffer[ch][sample];
 }
 }

オーディオエフェクト処理の前後でデータ形式変換を行なっていることがわかります。
前段と後段で各((AUDIOBUFSIZE / 2) x 2)回分のメモリコピーを行なっています。

実際にオーディオ処理関数内部の実装も見てみます。(一部はオリジナルと少し異なります。)

 void audio_effect_through(
 effect_param_t *param,
 AUDIOSAMPLE input[2][AUDIOBUFSIZE / 2],
 AUDIOSAMPLE output[2][AUDIOBUFSIZE / 2],
 int count)
 {
 int i;
 
 const int var0 = param->var0;
 const int var1 = param->var1;
 for (i = 0; i < count; i++)
 {
 output[LCH][i] = (input[LCH][i] >> 10) * var0;
 output[RCH][i] = (input[RCH][i] >> 10) * var1;
 }
 }

ここで上位から渡されるcountは(AUDIOBUFSIZE / 2)です。
よって、ここでも((AUDIOBUFSIZE / 2) x 2)回分のメモリコピーを行なっていることになります。

改善の提案

ここまではオリジナルの設計について整理しました。
それでは実際にオーディオ処理の効率改善をしてみます。

基本的な思想は以下の通りです。
  • 主記憶上におけるメモリコピーは性能に対して著しい劣化を伴う。
  • より多くの処理を実現するためにはメモリコピーを排除すれば良い。
  • メモリコピーを行なっている主な理由はデータ形式変換である。
  • データ形式変換が不要となるような枠組みを用意すれば、データ形式変換が不要となるはずである。
  • データ形式変換が不要となれば、必要となるバッファも削減することができ、RAM容量という観点から見ても有利である。
「データ形式変換」を実現しながらも、「メモリコピー」を発生させないという一見矛盾した課題を解決すれば良い事になります。この中でforループについても削減可能と判断しました。

オリジナルの実装ではオーディオ処理関数に渡るデータ形式が重要でした。
この点は改善案でも特に変わるものではありません。

オリジナルと異なるのはその実現手法です。
ここで実際のコードを示します。

 for (index = 0; index < AUDIOBUFSIZE; index+=2) {
 audio_effect_through(
 &effect_param,
 rxbuf + (index + 0), rxbuf + (index + 1),
 txbuf + (index + 0), txbuf + (index + 1));
 }

オーディオ処理関数へL-Rのステレオデータを揃えて渡す部分はコールバック関数とし、オーディオ処理関数内部で直接出力データを格納させる形式としました。

オリジナルに存在した前段と後段における形式変換用メモリコピーを排除することができます。
これにより内部作業用バッファinputBufferとoutputBufferも不要となりました。

audio_effect_throught関数はオーディオデータをスルーコピーする関数です。

 void audio_effect_through(
 const effect_param_t *param,
 const AUDIOSAMPLE *in_left,
 const AUDIOSAMPLE *in_right,
 AUDIOSAMPLE *out_left,
 AUDIOSAMPLE *out_right)
 {
 const int var0 = param->var0;
 const int var1 = param->var1;
 *out_left = ((*in_left) >> 10) * var0;
 *out_right = ((*in_right) >> 10) * var1;
 }

オーディオエフェクト関数には1サンプル毎に処理を依頼します。
処理結果は関数に渡されたバッファへのポインタを用いて直接格納します。


要するに冒頭にあった最小限の処理に近くなる仕組みです。



上記により中間バッファを排除しながらも、渡されるデータ形式はL-Rのステレオで揃っているという状況を作ることができます。
また、バッファサイズ分のforループも1回で済むようになり、効率改善が期待できます。

改善の効果

ここで実際の処理時間に与える効果を調査しました。
処理時間を測定するために、オーディオ処理ブロックに差し掛かったところでGPIOをハイレベルにし、オシロスコープにより観測します。

ここでの処理対象は1オーディオサンプルブロックでAUDIOBUFSIZEバイト分のデータです。
初めにオリジナル実装でオーディオスルーにかかっている処理時間を示します。
1オーディオサンプルブロックの処理に約70[us]の時間を要しています。


次に改良した実装でオーディオスルーを行なった場合の処理時間を調べます。
同じ1オーディオサンプルブロックの処理を約25[us]の時間で処理していることがわかります。


オリジナルの実装に対して約35%の時間で同等の処理が実現できる事が確認できました。
削減できた約45[us]は別の演算に割り当てることができます。
従来より高度な演算も可能な他、他のタスクにプロセッサを素早く譲ることができるようになります。

改善のまとめ

オーディオ処理関数の呼び出し方を変更し、主記憶上におけるメモリコピーを大幅に削減しました。
結果的に処理時間を削減でき、従来よりもシステムプロセッサを効率的に運用することが可能となりました。

今回の改善でオーディオエフェクト部分の設計者はデータ長を気にすることなく、1サンプルの処理に集中して記述できるようにもなりました。
オーディオエフェクト関数を同じパラメータで作成すれば、関数ポインタの切り替えのみでオーディオエフェクト処理を切り替えることが可能となります。


当然のことですが、オーディオ処理では特定サンプルに対して、時間軸方向前後のデータも用いてフィルタリング処理を行ないます。
提案手法ではオーディオ処理関数に渡ってくるデータは1サンプル分のみですが、オーディオエフェクト関数内部で静的メモリを保持し、バッファリングしながら処理をすることで、時間軸の前後方向のデータも用いて処理することが可能です。

RTOSを用いたシステムを構築する場合、局所的に見た性能改善とシステム全体を見た性能改善の双方の視点が欠かせません。
今回行なった性能改善は局所的なものですが、タスクの性質上高いプライオリティで動作します。
このタスクの処理時間を削減するだけで、他のタスクで行うことの出来るサービスが飛躍的に増えます。
例えば、ディスプレイに表示したい内容を別のタスクに伝達しようとか、そういった付加的なサービスにプロセッサ時間を使う事ができます。こういった要素が「他と違う何か(=付加価値)」に繋がっていきます。

なお、実験結果はコンパイラの最適化オプションを外した状態で行ないました。
最適化を施すことで、場合によってはより大きな改善が得られる可能性もあります。

2011年3月20日日曜日

LPC1769搭載オーディオ基板のハードウェアデバッグが完了

先月からダラダラと進めているARM Cortex-M3(LPC1769)搭載オーディオ基板のハードウェアデバッグが完了しました。
回避不能となるような致命的なバグはなく一安心というところです。


今回のプロジェクトでARM Cortex-M3の感じが大体つかめました。
@suikan_blackfinさんによるオーディオトークスルーもこのとおり動作しています。


会社で周りの人と話していると「マイコン=低機能」という思い込みを持っている人が意外に多いことに気づきます。
確かに業務で少し触れただけの過去の経験から現状を見ると「何が違うの?」ということになるのかもしれません。
これは非常に勿体無い話です。
10年、20年前とは状況が大きく変わっているのだということを実感するには、実際に触ってみるのが一番です。

例えば、今回の基板ではオーディオをコーデック経由でスルーさせながら、バックグラウンドでデバッグ用シェルが走っていたり、マイクロSDカードにアクセスしたり、有機ELディスプレイの表示をしたり、ユーザのスイッチ入力を監視したりしています。これらの仕事をワンチップのマイコンで実現できるわけです。

マイコンのシステムだからと言って、わざわざ周辺環境までチープにする必要はありません。
ストレージ(今回はマイクロSDカード)も欲しいし、デバッグにはシェルが欠かせません。

ARM Cortex-M3はこういった小規模開発の既存概念を変える道具として最適です。
データシート上で見る性能だけでなく、「実際にどこまでできるのか?」を自分で考えてみることで、誰も考えていなかった応用への発展が期待できそうです。

なお、このプロジェクトは引き続きファームウェア開発を行う予定です。

2011年3月16日水曜日

Japan / Earthquake Donation


Dear community peoples.

Sorry about this post.
This is not an electronics contents.

The situation of my country is not good you know.
So I need your help.

But I'm Okey.
It's just for the disaster area.

Please donate for them if you can.
http://www.jrc.or.jp/english/relief/l4/Vcms4_00002070.html

Best regards,
Shin.

2011年3月7日月曜日

LPC1769搭載オーディオ基板のとんでもない実装ミス

昨日のこと、LPC1769搭載オーディオ基板のデバッグを開始して1時間後、とんでもない実装ミスを発見してがっくりしていました。

「なんだかSWDで繋がらないなぁ〜。」なんて思っていたんです。
前日までは「まぁ、デバッガの接続が間違っているんだろう。」なんて思っていました。

さてさて、データシートのパッケージ情報にはきちんと「1ピンはココ!」と当然示されていますね。


で、取り付けた状態の写真が以下です・・・。


ですが、冷静に基板を見て愕然・・・。
「1ピンはそこじゃない!」
なんと、1ピンは左上なのです!


もう全く意味がわかりません。

データシートを何のために見て実装したのでしょうか・・・。
シルクによる心理効果か、はたまた大きい穴に騙されたのでしょうか。

とにかくもう一台作り直しです。
今度はさすがに間違えません。


なんだかんだで2時間のハンダ付けを行ない2台目が完成。
あっさりSWDによる認識も完了しました。


なんと言いますか、一日働いた後で深夜のハンダ付けは避けようかと考えた出来事でした。

P.S. この方のようなユーモアを交えてお伝えするセンスを持ちあわせていないのが残念。

AltStyle によって変換されたページ (->オリジナル) /