2 * @file doxygen_firmware.c
3 * @author Shinichiro Nakamura
4 * @brief Doxygen用のファイル。ファームウェアに関するトピックを記述してある。
7 * @page firmware ファームウェアに関するトピック
9 * ファームウェアに関する既知の問題点については @ref problems_firmware をご覧下さい。
11 * @section task_structure_design タスク設計
13 * @subsection task_userinput ユーザインプットタスク(task_userinput)
15 * ユーザインプットタスクは4系統のユーザ入力の変化を観察する
17 * 変化があればデータキューを介してシステムコントロールタスクに
20 * @subsection task_menu メニュータスク(task_menu)
22 * メニュータスクはユーザにシステムのサービスを提示し、
23 * ユーザからの要求をシステムに伝達する役目を果たします。
25 * ユーザは各タスクから次のマクロのみで指示することができます。
28 * USERMSG(device, value);
31 * - deviceは以下から選択できます。
32 * - スイッチ: SW0, SW1, SW2, SW3
33 * - ボリューム: VOL0, VOL1, VOL2, VOL3
38 * - 長押し状態フラグ:SW_LONG_PUSH
42 * このタスクへの指示はデータキューを介して行われます。
43 * データキュー内部データ構造は以下のようになっています。
46 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
47 * |15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| Description |
48 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
49 * | |<- [9:0] ->| Value |
50 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
52 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
53 * |<-[15:12]->| | Device |
54 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
57 * @subsection task_led LEDタスク(task_led)
59 * LEDタスクはLEDの点灯を制御するタスクです。
61 * 点灯は各タスクから次のマクロのみで指示することができます。
64 * LEDMSG(target, control);
67 * - targetは以下から選択できます。
68 * - デバッグ用LED: DBLED0, DBLED1, DBLED2, DBLED3
69 * - スイッチ用LED: SWLED0, SWLED1, SWLED2, SWLED3
73 * - controlは以下から選択できます。
79 * このタスクへの指示はデータキューを介して行われます。
80 * データキュー内部データ構造は以下のようになっています。
83 * +---+---+---+---+---+---+---+---+-------------+
84 * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Description |
85 * +---+---+---+---+---+---+---+---+-------------+
86 * | |<- [3:0] ->| Target |
87 * +---+---+---+---+---+---+---+---+-------------+
88 * | | x x x | | Reserved |
89 * +---+---+---+---+---+---+---+---+-------------+
91 * +---+---+---+---+---+---+---+---+-------------+
94 * @subsection task_display ディスプレイタスク(task_display)
95 * ディスプレイタスクは有機ELディスプレイを制御するタスクです。
97 * ディスプレイの制御は以下のマクロを使って行うことができます。
100 * DISP_CLEAR(R, G, B);
101 * DISP_LINE(X1, Y1, X2, Y2, R, G, B);
102 * DISP_BOX(X1, Y1, X2, Y2, R, G, B);
103 * DISP_FILLBOX(X1, Y1, X2, Y2, R1, G1, B1, R2, G2, B2);
104 * DISP_TEXT(X, Y, R, G, B, "TEXT");
105 * DISP_BMPFILE("0:FILENAME");
106 * DISP_AUDIO_LEVELMETER(L, R);
109 * タスク間はメールボックスでやりとりされます。
110 * マクロはこれを隠蔽した実装としました。
111 * メールボックスで陥りがちな同期問題を未然に防ぐ対策として
112 * メモリプール管理機能を使ってデータ領域に対するアクセスを
115 * 今回の実装では受信側が処理を完了するまで送信側が次の処理に
117 * 今回のタスク設計ではメニュータスクがブロックの対象となります。
119 * @subsection task_init 初期化タスク(task_init)
122 * @subsection task_ntshell ナチュラルタイニーシェルタスク(task_ntshell)
123 * システムをコンソールから制御することのできるインターフェース
126 * @subsection task_audio オーディオタスク(task_audio)
127 * オーディオを処理するためのタスクです。
129 * このタスクにはパラメータを指定するためのマクロがあります。
132 * AUDIO_PARAM(TARGET,VALUE);
135 * - TARGETとVALUEは以下から選択します。
136 * - TARGET: VAR0, VAR1, VAR2, VAR3
137 * - VALUE : The value of the target.
142 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
143 * |15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| Description |
144 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
145 * | |<- [11:0] ->| Value |
146 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
147 * |<-[15:12]->| | Target |
148 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
151 * VAR0, VAR1, VAR2, VAR3の各値をどのように使うのかは実装される
153 * 詳しくはaudio_effect.cを御参照下さい。
155 * @section application_examples アプリケーション例
162 * @section task_priority プライオリティ
164 * タスクプライオリティに関しては以下のように定めた。
165 * 一般にRTOSのプライオリティはサーバ>クライアントとなる。
166 * これに層別の視点を加えてプライオリティを決定した。
175 * <td>リアルタイム・タスク</td>
176 * <td>@ref task_audio</td>
180 * <td rowspan="2">サーバ・タスク</td>
181 * <td>@ref task_display</td>
185 * <td>@ref task_led</td>
189 * <td rowspan="2">クライアント・タスク</td>
190 * <td>@ref task_menu</td>
194 * <td>@ref task_userinput</td>
199 * <td>@ref task_init</td>
204 * <td>@ref task_ntshell</td>
209 * @section sec_audio_effect_refactoring オーディオ処理の効率改善。
211 * ここではオーディオ処理の効率改善について記す。
213 * @subsection sec_audio_effect_refactoring1 オリジナルの設計
215 * オリジナルの実装では以下のような処理となっていた。
217 * - DMA転送されたオーディオバッファサイズ分のデータがrxbufに入っている。
218 * - 並び替えながらrxbufからaudio_data.inputBufferにコピーする。
220 * - ここでは処理に応じてaudio_data.inputBufferからaudio_data.outputBufferへのコピーが発生する。
222 * - 並び替えながらaudio_data.outputBufferからtxbufにコピーする。
225 * 図を用いて整理すると以下のようになる。
228 * +-----+ +-----------+ +------------+ +-----+
229 * |rxbuf| -> |inputBuffer| -> |outputBuffer| -> |txbuf|
230 * +-----+ +-----------+ +------------+ +-----+
233 * 要するに主記憶上におけるメモリコピーが少なくとも3回発生している。
235 * また、このメモリコピーはforループで実装されており、3回のforループによる性能への影響も気になる。
237 * 通常、性能という観点で見た場合、メモリコピーや重複したforループは性能劣化の主要な要因のひとつとなる。
239 * そこで、今回は上記処理の効率改善を行う。
241 * まず初めに、最小限の処理について考えてみる。
249 * 入力をそのまま出力に伝達する場合、単なるメモリコピーで良い。
251 * 入力に何らかの処理を加え、出力に伝える場合でもこの入出力間のメモリコピーの間に何らかの処理を追加するだけで済むので、本質的に上記と変わらない。
253 * オリジナルの実装ではコーデックのデータ形式を鑑みて工夫がしてある。
257 * まず初めに入力されたデータを内部で扱いやすい形式にメモリコピーする。
259 * これはrxbufからinputBufferへのコピーである。
262 * +--------------------------+
263 * |+-----+ +-----------+| +------------+ +-----+
264 * ||rxbuf| -> |inputBuffer|| -> |outputBuffer| -> |txbuf|
265 * |+-----+ +-----------+| +------------+ +-----+
266 * +--------------------------+
271 * これはinputBufferからoutputBufferへのコピーである。
273 * オーディオの処理を実装する過程で、ここに様々な演算が入ることになる。
276 * +---------------------------------+
277 * +-----+ |+-----------+ +------------+| +-----+
278 * |rxbuf| -> ||inputBuffer| -> |outputBuffer|| -> |txbuf|
279 * +-----+ |+-----------+ +------------+| +-----+
280 * +---------------------------------+
285 * outputBufferからtxbufへのコピーである。
287 * これはオーディオバッファの内容を、都合の良い形式に前段で並び替えた結果発生する作業である。
290 * +---------------------------+
291 * +-----+ +-----------+ |+------------+ +-----+|
292 * |rxbuf| -> |inputBuffer| -> ||outputBuffer| -> |txbuf||
293 * +-----+ +-----------+ |+------------+ +-----+|
294 * +---------------------------+
298 * - コーデックから得られたデータ形式は扱いにくいので並び替える。
299 * - 並び替えは主記憶上でMCUが実行する。
300 * - 並び替えたデータは、コーデックがそのまま扱えないので再変換する。
303 * コードブロックは以下のようになっていた。(一部はオリジナルと少し異なる。)
307 * for (sample = 0; sample < AUDIOBUFSIZE / 2; sample++) {
308 * for (ch = 0; ch < 2; ch++) {
309 * audio_data.inputBuffer[ch][sample] = rxbuf[index++];
312 * audio_effect_through(
314 * audio_data.inputBuffer,
315 * audio_data.outputBuffer,
318 * for (sample = 0; sample < AUDIOBUFSIZE / 2; sample++) {
319 * for (ch = 0; ch < 2; ch++) {
320 * txbuf[index++] = audio_data.outputBuffer[ch][sample];
325 * オーディオエフェクト処理の前後でデータ形式変換を行なっていることがわかる。
327 * 前段と後段で各((AUDIOBUFSIZE / 2) x 2)回分のメモリコピーを行なっている。
329 * 実際にオーディオ処理関数内部の実装も見る。(一部はオリジナルと少し異なる。)
332 * void audio_effect_through(
333 * effect_param_t *param,
334 * AUDIOSAMPLE input[2][AUDIOBUFSIZE / 2],
335 * AUDIOSAMPLE output[2][AUDIOBUFSIZE / 2],
340 * const int var0 = param->var0;
341 * const int var1 = param->var1;
342 * for (i = 0; i < count; i++)
344 * output[LCH][i] = (input[LCH][i] >> 10) * var0;
345 * output[RCH][i] = (input[RCH][i] >> 10) * var1;
350 * ここで上位から渡されるcountは(AUDIOBUFSIZE / 2)である。
352 * よって、ここでも((AUDIOBUFSIZE / 2) x 2)回分のメモリコピーを行なっていることになる。
354 * @subsection sec_audio_effect_refactoring2 改善の提案
356 * ここまではオリジナルの設計について述べた。
358 * それでは実際にオーディオ処理の効率改善について述べる。
361 * - 主記憶上におけるメモリコピーは性能に対して著しい劣化を伴う。
362 * - より多くの処理を実現するためにはメモリコピーを排除すれば良い。
363 * - メモリコピーを行なっている主な理由はデータ形式変換である。
364 * - データ形式変換が不要となるような枠組みを用意すれば、データ形式変換が不要となるはずである。
365 * - データ形式変換が不要となれば、必要となるバッファも削減することができ、RAM容量という観点から見ても有利である。
368 * 要するに「データ形式変換」を実現しながらも、「メモリコピー」を発生させないという矛盾を解決すれば良い事になる。
369 * この中でforループについても削減可能と判断した。
371 * オリジナルの実装ではオーディオ処理関数に渡るデータ形式が重要であった。
373 * この点は改善案でも特に変わるものではない。
375 * オリジナルと異なるのはその実現手法である。
380 * for (index = 0; index < AUDIOBUFSIZE; index+=2) {
381 * audio_effect_through(
383 * rxbuf + (index + 0), rxbuf + (index + 1),
384 * txbuf + (index + 0), txbuf + (index + 1));
388 * オーディオ処理関数へL-Rのステレオデータを揃えて渡す部分はコールバック関数とし、オーディオ処理関数内部で直接出力データを格納させる形式とした。
390 * これによりオリジナルに存在した前段と後段でのメモリコピーを排除できる。
392 * オリジナルの実装では、前段と後段で(AUDIOBUFSIZE / 2) x 2回分のメモリコピーを必要としていたが、これを置きかえる形となる。
394 * これによりinputBufferとoutputBufferも不要となった。
396 * audio_effect_throught関数はオーディオデータをスルーコピーする関数である。
399 * void audio_effect_through(
400 * const effect_param_t *param,
401 * const AUDIOSAMPLE *in_left,
402 * const AUDIOSAMPLE *in_right,
403 * AUDIOSAMPLE *out_left,
404 * AUDIOSAMPLE *out_right)
406 * const int var0 = param->var0;
407 * const int var1 = param->var1;
408 * *out_left = ((*in_left) >> 10) * var0;
409 * *out_right = ((*in_right) >> 10) * var1;
413 * オーディオエフェクト関数には1サンプル毎に処理を依頼する。
415 * 処理結果は関数に渡されたバッファへのポインタを用いて直接格納する。
417 * 上記により中間バッファを排除しながらも、渡されるデータ形式はL-Rのステレオで揃っているという状況を作ることができる。
419 * また、バッファサイズ分のforループも1回で済むようになり、効率改善が期待できる。
421 * @subsection sec_audio_effect_refactoring3 改善の効果
423 * ここで実際の処理時間に与える影響を調査した。
425 * 処理時間はオーディオ処理ブロックに差し掛かったところでGPIOをハイレベルにし、オシロスコープにより観測した。
427 * ここでの処理対象は1オーディオサンプルブロックでAUDIOBUFSIZE分のデータである。
429 * 初めにオリジナル実装でオーディオスルーにかかっている処理時間を示す。
433 * @image html task_audio_before.bmp
435 * 次に示すのは改良した実装でオーディオスルーを行なった場合の処理時間である。
437 * 約25[us]の時間で処理できている。
439 * @image html task_audio_after.bmp
441 * 上記のようにオリジナルの実装に対して35.7%の時間で同等の処理が実現できる事が確認できた。
443 * 削減できた約45[us]は別の演算に割り当てることができる。
447 * @subsection sec_audio_effect_refactoring4 改善のまとめ
449 * 本改善提案によれば、処理を非常に簡単に記述することが出来る上、主記憶上のメモリコピーが必要ない。
451 * このため大幅な処理時間の短縮が可能となった。
453 * これによりMCUの演算能力を効率的にオーディオ処理に割り当てる事が可能となる。
455 * オーディオエフェクト関数を同じパラメータで作成すれば、関数ポインタの切り替えのみでオーディオエフェクト処理を切り替えることが可能となる。
457 * 通常、オーディオ処理では特定サンプルに対して、時間軸方向前後のデータも用いてフィルタリングを行なう。
459 * 提案手法ではオーディオ処理関数に渡ってくるデータは1サンプル分のみであるが、オーディオエフェクト関数内部で静的メモリを保持し、バッファリングしながら処理をすれば、時間軸の前後方向のデータも用いて処理することが可能である。
461 * なお、実験結果はコンパイラの最適化オプションを外した状態で行なった。最適化を施すことで、場合によってはより多くのパフォーマンス改善が得られる可能性もある。