SSブログ

PWM+ローパスで擬似D/A - その4 [Pi Pico]

先日コメントをもらって大恥をかいたんだけど、恥はかき慣れている。本家Raspbery PiのハードウェアPWMには Mark-SpaceモードとBalancedモードのふたつあるということを改めて知った(実は忘れてただけだけど)。Mark-Spaceモードは普通のサーボモータなんかに使う1周期に1回だけ反転するモードで、Balancedモードでは、擬似D/A変換に使いやすいようにエネルギーがなるべく高い周波数成分に寄るように反転の回数を増やすということになっている。

もうちょっと詳しく見てみようと思った...

Raspberry PiのPWMはコメントにもらったようにBCM2835-ARM-Peripherals.pdfのp138に書いてある。ここにはBalancedモードについて
The desired sequence should have 1’s and 0’s spread out as even as possible so that during any arbitrary period of time duty cycle achieves closest approximation of the value. 
望ましいシーケンスは、任意の期間にデューティサイクルが値に最も近い近似値を達成するように、1と0ができるだけ均等に分散している必要がある
とあって$N=4$、$M=8$の場合の望ましいシーケンスが書いてある。ここで$M$はPi Picoのマニュアルにあるwrap valueのことで、$N$はその中の「1」の個数である。$N=4$、$M=8$の場合は$M/N$が割り切れるので簡単である。

そして割り切れない場合も含めてこれを実現するアルゴリズムとして
    Set context = 0
    context = context + N
    if (context >= M)
        context = context – M
        send 1
    else
と書いてある。どうも途中で途切れている(ループになっていないし、ifも閉じてない。このあたりPi Picoのドキュメントに比べると適当さが現れている)ようで、これだけではアルゴリズムはよくわからない。僕はこれを見て${\rm LCM}(N,M)$、つまり最小公倍数の長さ分だけ伸ばすということだと思ってしまったんだけど、それは間違いということがあとでわかった。

ちゃんと考えて書いてみれば
    context = 0;
    for(int i = 0 ; i < M ; i++) {
        context += N;
        if (context >= M) {
            context -= M;
            send 1;
        } else
            send 0;
    }
となる(はずである。まんまCの字面になったけど)。

これだと0が先行するので、こないだの書き方では簡単に表せない。でも式に書かなくてもわかりやすい超シンプルなアルゴリズムである。つまり$N-1$個の0とひとつの1を$N\times M$の長さにしてそれを長さ$M$の中に折り返す、というものである。すごく簡単で結構良さそうなアルゴリズムに見える。

実際にそうなってるか実機で試してみた。僕がいつも使っているpigpioライブラリはBalancedモードをサポートしていないので、その前に使っていたbcm2835ライブラリを使うことにする。とりあえずの実験なのでCでベタ書きにして
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <bcm2835.h>

int main(int argc, char *argv[])
{
    int    err;
    if (argc < 2) {
        fprintf(stderr, "specify range and data as arguments\n");
        return 0;
    }
    
    if ((err = bcm2835_init()) == 0) {
        fprintf(stderr, "error in bcm2835_init().");
        return -1;
    }
    int range = atoi(argv[1]);
    int data = atoi(argv[2]);
    fprintf(stderr, "set range = %d\tdata = %d\n", range, data);
    bcm2835_pwm_set_clock(64);
    
    bcm2835_gpio_fsel(18, BCM2835_GPIO_FSEL_ALT5);
    bcm2835_pwm_set_range(0, range);
    bcm2835_pwm_set_mode(0, 0, true);
    bcm2835_pwm_set_data(0, data);
    
    bcm2835_gpio_fsel(19, BCM2835_GPIO_FSEL_ALT5);
    bcm2835_pwm_set_range(1, range);
    bcm2835_pwm_set_mode(1, 0, true);
    bcm2835_pwm_set_data(1, 1);
    
    sleep(3);
    
    bcm2835_pwm_set_data(0, 0);
    bcm2835_pwm_set_mode(0, 0, false);
    
    bcm2835_pwm_set_data(1, 0);
    bcm2835_pwm_set_mode(1, 0, false);
    bcm2835_close();
    return 0;
}
みたいなのにした。2チャンネルのPWMを両方とも同じ$M$にして、チャンネル0を与えられた$N$で、チャンネル1にはつねに$N=1$にして、チャンネル1をトリガにする。チャンネル0の出力が可能なピンのうちGPIO18に割り当てて、チャンネル1がGPIO19に出るようにしている。sleepしている間信号を出し続けて、そのあとは単に終了するだけ。Raspberry Pi4を使ったのでベースクロックは54MHzになるようである。クロックデバイダ(64)は値によってなぜか信号が出たり出なかったりしたので、出る値を選んだだけで値そのものに意味はない。

これをソフトウェアオシロでみたのが
1114softwarescope.png
$M=64$にして絵の一番上がトリガに使ったチャンネルのアナログで、その下の小さいのが3.3Vロジックとしてみた$N=17$の場合の出力である。$M$より長い周期はないのはあきらか(トリガごとに同じものが繰り返されている)で、最初に僕が考えてたのは勘違いだったということがわかる。

このソフトウェアオシロのウィンドウではジャギーなビットマップになってしまって見にくくて比べることができないので、いったん信号の立ち上がり立ち下がり位置をファイルに落として、それをMathematicaで読んで描き直してみた。$M=64$の場合に$N=1$〜32までそのデータをとって
1114pioriginalpwm.png
となった。前回の$W_0[w:n]$や$W_1[w:n]$に比べるとずっとバランスが良いように見える。とくに$N$が少ない場合は$W_1[w:n]$によく似てるけど多くなってくるとPiのPWMのほうがずっときれいにバラけているように見える。これで十分じゃないか。

でも残念ながらPi PicoにはBalancedモードはない。今後追加されるとしても回路が増えてしまうので全体の設計をやり直さないかぎり変更されることはない。大した規模じゃないと思うんだけど、なんでやめちゃったのかなあ。本家Piに比べればベースクロックが高い、ということかなあ。でもPi4ですでに54MHzなので、たかだか倍ちょっとでしかないんだけどなあ。
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。