Pico三昧(11) Pico C/C++SDKでinterp#5、補間ついでに配列舐める

Joseph Halfmoon

今回は前々回のBlendモード再びです。その上、Datasheetのサンプルプログラムほぼほぼそのままです。何といっても中の人の書いたサンプルプログラム流石すぎます。Blendモードで補間をするついでにデータ配列を舐めるためのポインタアドレスまで作ってました。私のような凡人には考えつかないテクであります。

凡人がプチ感涙にむせんでおりますのは、以下のデータシートの 2.3.1.6.4. Sample Use Case: Linear Interplolationという奥深くに隠されている?サンプルプログラムであります。

RP2040 Datasheet

前々回Blendモードを使ってみたときには、当然のごとくに線形補間対象のデータ2個は、interpolator とは無関係にとってきてinterpolatorにセットする前提でした。そして補間する点も、何%点を頂戴という感じで外から指定して補間結果を計算してもらってました。凡人には分かり易く、そのままでも十分便利ではあるのですが、これはinterpolatorの実力の一部しか使っていなかったことに今回ようやく気が付きました。

サンプルプログラムの動作の解読

サンプルプログラムはごく短いものですが、その動作を理解するのには凡人はちょいと難渋しました。まずは、Blendモードの基本動作のおさらいから。

  1. CPU毎に2個あるinterpolatorの0番のみで使える。
  2. 2個の値の中間値を線形補間してくれる。
  3. 2個の値はBASE0とBASE1に格納する。
  4. 求める中間値を示す割合はレーン1のアキュムレータに設定する
  5. 設定後レーン1をPEEKすると補間された値が読める

こうしてみると上記の方法ではinterpolator0番の3つあるレーンのうち1しか使っておりませんな。しかしサンプルプログラムではレーン0とレーン2も活躍するのです。私の勝手な理解ではこんな感じ。

  1. レーン0のアキュムレータには、補間対象データを納めた配列への「一種のポインタ」を格納する。
  2. 一種のポインタというのは、整数部分が配列の添え字、小数点以下部分が補間する位置、という感じのもの。例えば添え字1と添え字2のデータの中央を補間計算するときには、レーン0のアキュムレータは1.5 を指している
  3. レーン0アキュムレータをシフタとマスクを通して処理するとデータ配列へのアクセスに使えるオフセット(バイトアドレス)が得られる
  4. BASE2レジスタにデータ配列の先頭番地をセットしておくことで、レーン2をPEEKするとレーン0で生成される配列の先頭番地+アドレス・オフセットのポインタが読み出せる。
  5. 上記のポインタをつかってデータ配列の隣り合う2個のデータを読み出せる

というわけでデータ配列のアクセスにも活躍。さらに、

  1. レーン1(ブレンドモード)では、「クロス読み出し」機能を使ってレーン0のアキュムレータの小数点以下部分(補間位置)を読み出す。
  2. これにより、ブレンドモードの基本機能通り、base[0]とbase[1]の間を補間したものがレーン1から読み出せる。

ということのようでございます。

動作確認

冒頭のアイキャッチ画像にデバッガ使ってサンプルプログラムの動作を観察しているところを掲げました。デバッガと標準出力へのprintfで上記動作はほぼほぼ見える感じであったのですが、適当な変数に代入しないとデバッガのブレークポイントで止めてウオッチできないところがあったので、恐れ多いですがちょいと無駄なコードを追加して動かしたものが以下に。

int16_t samples[] = {0, 10, -20, -1000, 500};
uint step = (1 << uv_fractional_bits) / 4;

interp0->accum[0] = 0;
interp0->base[2] = (uintptr_t) samples;
for (int i = 0; i < 16; i++) {
    int16_t *sample_pair = (int16_t *) interp0->peek[2];
    interp0->base[0] = sample_pair[0];
    interp0->base[1] = sample_pair[1];
    // dummy variables for monitor
    uint32_t peek0 = interp0->peek[0];
    printf("peek0: %x\n",peek0);
    // end of dummy
    printf("%d\t(%d%% between %d and %d)\n", (int)interp0->peek[1],
        100 * (interp0->add_raw[1] & 0xff) / 0xff,
        sample_pair[0], sample_pair[1]);
    interp0->add_raw[0] = step;
}

それを動かして標準出力を見たところが以下に。

terminal2

とても、自力ではこのようなテクを思いつくことはないデス。ラズパイの中の人はやっぱり頭が良いな。まあ、一度教えてもらえれば凡人でも真似することはできるか?ホントか?

Pico三昧(10) Pico C/C++SDKでinterpその4、クランプモード へ戻る

Pico三昧(12) Pico C/C++SDKで4.096MHz、クロック生成その1 PWM へ進む