OSDN Git Service

First commitment for the BlackTank LPC1769.
[blacktank/blacktank.git] / doxygen_firmware.c
1 /**
2  * @file doxygen_firmware.c
3  * @author Shinichiro Nakamura
4  * @brief Doxygen用のファイル。ファームウェアに関するトピックを記述してある。
5  * @details
6  *
7  * @page firmware ファームウェアに関するトピック
8  *
9  * ファームウェアに関する既知の問題点については @ref problems_firmware をご覧下さい。
10  *
11  * @section task_structure_design タスク設計
12  *
13  * @subsection task_userinput ユーザインプットタスク(task_userinput)
14  *
15  *  ユーザインプットタスクは4系統のユーザ入力の変化を観察する
16  *  タスクです。
17  *  変化があればデータキューを介してシステムコントロールタスクに
18  *  通知されます。
19  *
20  * @subsection task_menu メニュータスク(task_menu)
21  *
22  *  メニュータスクはユーザにシステムのサービスを提示し、
23  *  ユーザからの要求をシステムに伝達する役目を果たします。
24  *
25  *  ユーザは各タスクから次のマクロのみで指示することができます。
26  *
27  *  @code
28  *  USERMSG(device, value);
29  *  @endcode
30  *
31  *  - deviceは以下から選択できます。
32  *    - スイッチ: SW0, SW1, SW2, SW3
33  *    - ボリューム: VOL0, VOL1, VOL2, VOL3
34  *    .
35  *  .
36  *
37  *  - Typeはビットマッピングです。
38  *    - 長押し状態フラグ:SW_LONG_PUSH
39  *    .
40  *  .
41  *
42  *  このタスクへの指示はデータキューを介して行われます。
43  *  データキュー内部データ構造は以下のようになっています。
44  *
45  *  <pre>
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  *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
51  *  |           |11:10|                             | Type             |
52  *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
53  *  |<-[15:12]->|                                   | Device           |
54  *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
55  *  </pre>
56  *
57  * @subsection task_led LEDタスク(task_led)
58  *
59  *  LEDタスクはLEDの点灯を制御するタスクです。
60  *
61  *  点灯は各タスクから次のマクロのみで指示することができます。
62  *
63  *  @code
64  *  LEDMSG(target, control);
65  *  @endcode
66  *
67  *  - targetは以下から選択できます。
68  *    - デバッグ用LED: DBLED0, DBLED1, DBLED2, DBLED3
69  *    - スイッチ用LED: SWLED0, SWLED1, SWLED2, SWLED3
70  *    .
71  *  .
72  *
73  *  - controlは以下から選択できます。
74  *    - LED点灯:LEDON
75  *    - LED消灯:LEDOFF
76  *    .
77  *  .
78  *
79  *  このタスクへの指示はデータキューを介して行われます。
80  *  データキュー内部データ構造は以下のようになっています。
81  *
82  *  <pre>
83  *  +---+---+---+---+---+---+---+---+-------------+
84  *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Description |
85  *  +---+---+---+---+---+---+---+---+-------------+
86  *  |               |<-   [3:0]   ->| Target      |
87  *  +---+---+---+---+---+---+---+---+-------------+
88  *  |   | x   x   x |               | Reserved    |
89  *  +---+---+---+---+---+---+---+---+-------------+
90  *  |[7]|                           | Control     |
91  *  +---+---+---+---+---+---+---+---+-------------+
92  *  </pre>
93  *
94  * @subsection task_display ディスプレイタスク(task_display)
95  *  ディスプレイタスクは有機ELディスプレイを制御するタスクです。
96  *
97  *  ディスプレイの制御は以下のマクロを使って行うことができます。
98  *
99  *  @code
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);
107  *  @endcode
108  *
109  *  タスク間はメールボックスでやりとりされます。
110  *  マクロはこれを隠蔽した実装としました。
111  *  メールボックスで陥りがちな同期問題を未然に防ぐ対策として
112  *  メモリプール管理機能を使ってデータ領域に対するアクセスを
113  *  管理しています。
114  *
115  *  今回の実装では受信側が処理を完了するまで送信側が次の処理に
116  *  遷移しない実装にしてあります。
117  *  今回のタスク設計ではメニュータスクがブロックの対象となります。
118  *
119  * @subsection task_init 初期化タスク(task_init)
120  * 初期化するタスクです。
121  *
122  * @subsection task_ntshell ナチュラルタイニーシェルタスク(task_ntshell)
123  *  システムをコンソールから制御することのできるインターフェース
124  *  タスクです。
125  *
126  * @subsection task_audio オーディオタスク(task_audio)
127  *  オーディオを処理するためのタスクです。
128  *
129  *  このタスクにはパラメータを指定するためのマクロがあります。
130  *
131  *  @code
132  *  AUDIO_PARAM(TARGET,VALUE);
133  *  @endcode
134  *
135  *  - TARGETとVALUEは以下から選択します。
136  *    - TARGET: VAR0, VAR1, VAR2, VAR3
137  *    - VALUE : The value of the target.
138  *    .
139  *  .
140  *
141  *  <pre>
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  *  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+------------------+
149  *  </pre>
150  *
151  *  VAR0, VAR1, VAR2, VAR3の各値をどのように使うのかは実装される
152  *  オーディオエフェクトに依存します。
153  *  詳しくはaudio_effect.cを御参照下さい。
154  *
155  * @section application_examples アプリケーション例
156  *
157  * - オーディオエフェクタ
158  * - メトロノーム
159  * - 多機能時計
160  * - ピンボールゲーム
161  *
162  * @section task_priority プライオリティ
163  *
164  * タスクプライオリティに関しては以下のように定めた。
165  * 一般にRTOSのプライオリティはサーバ>クライアントとなる。
166  * これに層別の視点を加えてプライオリティを決定した。
167  *
168  * <table>
169  *   <tr>
170  *     <th>カテゴリ</th>
171  *     <th>タスク</th>
172  *     <th>プライオリティ</th>
173  *   </tr>
174  *   <tr>
175  *     <td>リアルタイム・タスク</td>
176  *     <td>@ref task_audio</td>
177  *     <td>10</td>
178  *   </tr>
179  *   <tr>
180  *     <td rowspan="2">サーバ・タスク</td>
181  *     <td>@ref task_display</td>
182  *     <td>11</td>
183  *   </tr>
184  *   <tr>
185  *     <td>@ref task_led</td>
186  *     <td>11</td>
187  *   </tr>
188  *   <tr>
189  *     <td rowspan="2">クライアント・タスク</td>
190  *     <td>@ref task_menu</td>
191  *     <td>12</td>
192  *   </tr>
193  *   <tr>
194  *     <td>@ref task_userinput</td>
195  *     <td>12</td>
196  *   </tr>
197  *   <tr>
198  *     <td>イニシャライザ</td>
199  *     <td>@ref task_init</td>
200  *     <td>13</td>
201  *   </tr>
202  *   <tr>
203  *     <td>デバッガ</td>
204  *     <td>@ref task_ntshell</td>
205  *     <td>14</td>
206  *   </tr>
207  * </table>
208  *
209  * @section sec_audio_effect_refactoring オーディオ処理の効率改善。
210  *
211  * ここではオーディオ処理の効率改善について記す。
212  *
213  * @subsection sec_audio_effect_refactoring1 オリジナルの設計
214  *
215  * オリジナルの実装では以下のような処理となっていた。
216  *
217  * - DMA転送されたオーディオバッファサイズ分のデータがrxbufに入っている。
218  * - 並び替えながらrxbufからaudio_data.inputBufferにコピーする。
219  * - オーディオ処理を実行する。
220  *   - ここでは処理に応じてaudio_data.inputBufferからaudio_data.outputBufferへのコピーが発生する。
221  *   .
222  * - 並び替えながらaudio_data.outputBufferからtxbufにコピーする。
223  * .
224  *
225  * 図を用いて整理すると以下のようになる。
226  *
227  * <pre>
228  * +-----+      +-----------+      +------------+      +-----+
229  * |rxbuf|  ->  |inputBuffer|  ->  |outputBuffer|  ->  |txbuf|
230  * +-----+      +-----------+      +------------+      +-----+
231  * </pre>
232  *
233  * 要するに主記憶上におけるメモリコピーが少なくとも3回発生している。
234  *
235  * また、このメモリコピーはforループで実装されており、3回のforループによる性能への影響も気になる。
236  *
237  * 通常、性能という観点で見た場合、メモリコピーや重複したforループは性能劣化の主要な要因のひとつとなる。
238  *
239  * そこで、今回は上記処理の効率改善を行う。
240  *
241  * まず初めに、最小限の処理について考えてみる。
242  *
243  * <pre>
244  * +-----+      +-----+
245  * |rxbuf|  ->  |txbuf|
246  * +-----+      +-----+
247  * </pre>
248  *
249  * 入力をそのまま出力に伝達する場合、単なるメモリコピーで良い。
250  *
251  * 入力に何らかの処理を加え、出力に伝える場合でもこの入出力間のメモリコピーの間に何らかの処理を追加するだけで済むので、本質的に上記と変わらない。
252  *
253  * オリジナルの実装ではコーデックのデータ形式を鑑みて工夫がしてある。
254  *
255  * この処理を順次見ていくことにする。
256  *
257  * まず初めに入力されたデータを内部で扱いやすい形式にメモリコピーする。
258  *
259  * これはrxbufからinputBufferへのコピーである。
260  *
261  * <pre>
262  * +--------------------------+
263  * |+-----+      +-----------+|      +------------+      +-----+
264  * ||rxbuf|  ->  |inputBuffer||  ->  |outputBuffer|  ->  |txbuf|
265  * |+-----+      +-----------+|      +------------+      +-----+
266  * +--------------------------+
267  * </pre>
268  *
269  * 次にオーディオの処理を実行する。
270  *
271  * これはinputBufferからoutputBufferへのコピーである。
272  *
273  * オーディオの処理を実装する過程で、ここに様々な演算が入ることになる。
274  *
275  * <pre>
276  *              +---------------------------------+
277  * +-----+      |+-----------+      +------------+|      +-----+
278  * |rxbuf|  ->  ||inputBuffer|  ->  |outputBuffer||  ->  |txbuf|
279  * +-----+      |+-----------+      +------------+|      +-----+
280  *              +---------------------------------+
281  * </pre>
282  *
283  * 最後に結果を出力バッファに書き込む。
284  *
285  * outputBufferからtxbufへのコピーである。
286  *
287  * これはオーディオバッファの内容を、都合の良い形式に前段で並び替えた結果発生する作業である。
288  *
289  * <pre>
290  *                                 +---------------------------+
291  * +-----+      +-----------+      |+------------+      +-----+|
292  * |rxbuf|  ->  |inputBuffer|  ->  ||outputBuffer|  ->  |txbuf||
293  * +-----+      +-----------+      |+------------+      +-----+|
294  *                                 +---------------------------+
295  * </pre>
296  *
297  * まとめると以下のようになる。
298  * - コーデックから得られたデータ形式は扱いにくいので並び替える。
299  * - 並び替えは主記憶上でMCUが実行する。
300  * - 並び替えたデータは、コーデックがそのまま扱えないので再変換する。
301  * .
302  *
303  * コードブロックは以下のようになっていた。(一部はオリジナルと少し異なる。)
304  *
305  * @code
306  *  index = 0;
307  *  for (sample = 0; sample < AUDIOBUFSIZE / 2; sample++) {
308  *      for (ch = 0; ch < 2; ch++) {
309  *          audio_data.inputBuffer[ch][sample] = rxbuf[index++];
310  *      }
311  *  }
312  *  audio_effect_through(
313  *          &effect_param,
314  *          audio_data.inputBuffer,
315  *          audio_data.outputBuffer,
316  *          AUDIOBUFSIZE / 2);
317  *  index = 0;
318  *  for (sample = 0; sample < AUDIOBUFSIZE / 2; sample++) {
319  *      for (ch = 0; ch < 2; ch++) {
320  *          txbuf[index++] = audio_data.outputBuffer[ch][sample];
321  *      }
322  *  }
323  * @endcode
324  *
325  * オーディオエフェクト処理の前後でデータ形式変換を行なっていることがわかる。
326  *
327  * 前段と後段で各((AUDIOBUFSIZE / 2) x 2)回分のメモリコピーを行なっている。
328  *
329  * 実際にオーディオ処理関数内部の実装も見る。(一部はオリジナルと少し異なる。)
330  *
331  * @code
332  *  void audio_effect_through(
333  *          effect_param_t *param,
334  *          AUDIOSAMPLE input[2][AUDIOBUFSIZE / 2],
335  *          AUDIOSAMPLE output[2][AUDIOBUFSIZE / 2],
336  *          int count)
337  *  {
338  *      int i;
339  *
340  *      const int var0 = param->var0;
341  *      const int var1 = param->var1;
342  *      for (i = 0; i < count; i++)
343  *      {
344  *          output[LCH][i] = (input[LCH][i] >> 10) * var0;
345  *          output[RCH][i] = (input[RCH][i] >> 10) * var1;
346  *      }
347  *  }
348  * @endcode
349  *
350  * ここで上位から渡されるcountは(AUDIOBUFSIZE / 2)である。
351  *
352  * よって、ここでも((AUDIOBUFSIZE / 2) x 2)回分のメモリコピーを行なっていることになる。
353  *
354  * @subsection sec_audio_effect_refactoring2 改善の提案
355  *
356  * ここまではオリジナルの設計について述べた。
357  *
358  * それでは実際にオーディオ処理の効率改善について述べる。
359  *
360  * 基本的な思想は以下の通りである。
361  * - 主記憶上におけるメモリコピーは性能に対して著しい劣化を伴う。
362  * - より多くの処理を実現するためにはメモリコピーを排除すれば良い。
363  * - メモリコピーを行なっている主な理由はデータ形式変換である。
364  * - データ形式変換が不要となるような枠組みを用意すれば、データ形式変換が不要となるはずである。
365  * - データ形式変換が不要となれば、必要となるバッファも削減することができ、RAM容量という観点から見ても有利である。
366  * .
367  *
368  * 要するに「データ形式変換」を実現しながらも、「メモリコピー」を発生させないという矛盾を解決すれば良い事になる。
369  * この中でforループについても削減可能と判断した。
370  *
371  * オリジナルの実装ではオーディオ処理関数に渡るデータ形式が重要であった。
372  *
373  * この点は改善案でも特に変わるものではない。
374  *
375  * オリジナルと異なるのはその実現手法である。
376  *
377  * ここで実際に改善したコードを示す。
378  *
379  * @code
380  *  for (index = 0; index < AUDIOBUFSIZE; index+=2) {
381  *      audio_effect_through(
382  *              &effect_param,
383  *              rxbuf + (index + 0), rxbuf + (index + 1),
384  *              txbuf + (index + 0), txbuf + (index + 1));
385  *  }
386  * @endcode
387  *
388  * オーディオ処理関数へL-Rのステレオデータを揃えて渡す部分はコールバック関数とし、オーディオ処理関数内部で直接出力データを格納させる形式とした。
389  *
390  * これによりオリジナルに存在した前段と後段でのメモリコピーを排除できる。
391  *
392  * オリジナルの実装では、前段と後段で(AUDIOBUFSIZE / 2) x 2回分のメモリコピーを必要としていたが、これを置きかえる形となる。
393  *
394  * これによりinputBufferとoutputBufferも不要となった。
395  *
396  * audio_effect_throught関数はオーディオデータをスルーコピーする関数である。
397  *
398  * @code
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)
405  *  {
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;
410  *  }
411  * @endcode
412  *
413  * オーディオエフェクト関数には1サンプル毎に処理を依頼する。
414  *
415  * 処理結果は関数に渡されたバッファへのポインタを用いて直接格納する。
416  *
417  * 上記により中間バッファを排除しながらも、渡されるデータ形式はL-Rのステレオで揃っているという状況を作ることができる。
418  *
419  * また、バッファサイズ分のforループも1回で済むようになり、効率改善が期待できる。
420  *
421  * @subsection sec_audio_effect_refactoring3 改善の効果
422  *
423  * ここで実際の処理時間に与える影響を調査した。
424  *
425  * 処理時間はオーディオ処理ブロックに差し掛かったところでGPIOをハイレベルにし、オシロスコープにより観測した。
426  *
427  * ここでの処理対象は1オーディオサンプルブロックでAUDIOBUFSIZE分のデータである。
428  *
429  * 初めにオリジナル実装でオーディオスルーにかかっている処理時間を示す。
430  *
431  * 約70[us]の時間を要している。
432  *
433  * @image html task_audio_before.bmp
434  *
435  * 次に示すのは改良した実装でオーディオスルーを行なった場合の処理時間である。
436  *
437  * 約25[us]の時間で処理できている。
438  *
439  * @image html task_audio_after.bmp
440  *
441  * 上記のようにオリジナルの実装に対して35.7%の時間で同等の処理が実現できる事が確認できた。
442  *
443  * 削減できた約45[us]は別の演算に割り当てることができる。
444  *
445  * 従来より高度な演算も可能となる。
446  *
447  * @subsection sec_audio_effect_refactoring4 改善のまとめ
448  *
449  * 本改善提案によれば、処理を非常に簡単に記述することが出来る上、主記憶上のメモリコピーが必要ない。
450  *
451  * このため大幅な処理時間の短縮が可能となった。
452  *
453  * これによりMCUの演算能力を効率的にオーディオ処理に割り当てる事が可能となる。
454  *
455  * オーディオエフェクト関数を同じパラメータで作成すれば、関数ポインタの切り替えのみでオーディオエフェクト処理を切り替えることが可能となる。
456  *
457  * 通常、オーディオ処理では特定サンプルに対して、時間軸方向前後のデータも用いてフィルタリングを行なう。
458  *
459  * 提案手法ではオーディオ処理関数に渡ってくるデータは1サンプル分のみであるが、オーディオエフェクト関数内部で静的メモリを保持し、バッファリングしながら処理をすれば、時間軸の前後方向のデータも用いて処理することが可能である。
460  *
461  * なお、実験結果はコンパイラの最適化オプションを外した状態で行なった。最適化を施すことで、場合によってはより多くのパフォーマンス改善が得られる可能性もある。
462  */
463