〇PWMによる疑似アナログ出力

DAコンバーターの無いマイコンではONとOFFしか出力できずアナログ信号を出力できません。
しかし、PWMを利用すると一定周期で発生するパルスのONの時間を広げたり縮めたりして平均電圧を制御することにより疑似的にアナログ信号を作り出す事が出来ます。

ATmega328を搭載しているArduinoボードでは、デジタルピン3、5、6、9、10、11が利用できます。
PWM の周波数はおよそ 490Hz ですが、ATmega328 では 5 番と 6 番ピンは 980Hzです。
5番と6番が周波数が高い理由は、AVRマイコンの3つのタイマーのどれで動作しているのかによる違いです。

Timer0 : システムの動作や時間の管理などで使われている、5と6番のPWM出力で利用される。
Timer1 : Servoライブラリで使われており、デジタルピン9・10番のPWMで利用される。
Timer2 : tone()とデジタルピン3・11番のPWMで利用される。

タイマーを利用する場合にはちょっと注意が必要です。


▼PWMの呼び出し

PWMを利用するには、analogWrite(ピン番号,デューティー比) を事前設定なしでいきなりコールすれば動作します。
一度呼べば再度デューティー比をセットするまで出力し続けます。
デューティー比は0から255(0xFF)までの8ビットの値を取ります。

次のプログラムではGNDとデジタルピン3番の間に疑似アナログ出力をします。
テスターやLEDを取り付けると明るさが変化するのが確認できます。

void setup() {
}

unsigned char i;
void loop() {
  analogWrite(3,i++);
  delay(100);
}

unsigned char型は8ビット変数のため加算の結果、オーバーフローして0に戻り、それを繰り返します。
ATmega328は8ビットマイコンですのでunsigned int型でも変わりありません。

LEDの明るさを調整したり、モーターの回転数を調整したりする分には人間の感覚的にパルスを捉える事ができないため問題を感じませんが、
一見アナログの制御の様に見えても短い時間でON・OFFを繰り返しているパルスであることに変わりがなく、
アナログに近づけるにはLow Pass Filterを通して高い周波数の成分を逓減させる必要があります。

コンデンサと抵抗器による単純なローパスフィルタ


▼マイナスのアナログ出力をする

GNDとデジタルピンの間でプラスのアナログ出力をしましたが、マイナス側のアナログ出力をしたいと思います。
マイコンの構成しているCMOSはONの時にはプラスの出力をして、OFFの時にはマイナスの出力をします。
マイコンのデジタル出力はONの時にはGNDとの間にプラスの出力をしますが、OFFの時にはVccとの間にマイナスの出力をするということです。

つまり、ボード上の5V もしくは Vcc とデジタルピンとの間はマイナス側のPWM出力が出来るという事です。
試しに上のPWMの出力プログラムを走らせた状態で5Vとデジタルピンとの間にテスターやLEDを取り付けると確認できます。




■PWMによる赤外線障害物回避センサーの自動調整

ボリュームを回す事により感度などの閾値を調整するセンサーは多々ありますが、そのボリュームを回す行為をPWMで代用したいと思います。


今回の犠牲となる赤外線障害物回避センサー

このセンサーの回路図はGoogleで検索すると出てきますが、ボリューム部分を簡易的に書くとこのようになります。



可変抵抗を取り外して、このように改造しました。



可変抵抗が入っていた部分はArduinoの3番ピンに接続、OUTは5番ピンに接続しました。
なぜ、コンデンサと抵抗が必要なのかというと、この抵抗とコンデンサでローパスフィルタを構成しており、 高い周波数の成分を逓減させアナログに近づけているからです。


ちなみに、付いていた10KΩの可変抵抗はこのような端子になっていました。

▼プログラムの作成

Arduinoにはこのようなプログラムを書き込みました。

unsigned char i=0xFF;
void setup() {
  Serial.begin(9600);
  pinMode(5, INPUT);
  
  analogWrite(3,i);
  delay(1500);

  while(!digitalRead(5) && i){
    analogWrite(3,i--);
    Serial.println(i,DEC);
    delay(100);
  }
}

void loop() {
}

途中の変数をシリアル通信で出力していますので、シリアルモニタを開くと設定値が徐々に小さくなっていくのがわかります。

起動時にPWM出力を最大にして1.5秒間待ち、ローパスフィルタの電圧を安定させます。
(手元にあった大きな抵抗100KΩと大きなコンデンサ10μFを使ったため、電圧が安定するのに時間がかかります)
その後、センサーの出力がOFFになるまでPWM出力を減らしてゆきます、センサーがOFFになったら設定完了です。
待機時間が100ミリ秒であり短すぎて調整の値が目標値を通り過ぎますが、センサーのONとOFFの揺らいている所を通り抜けるためとりあえず感度の調整が出来ます。


センサーの裏面の様子、100KΩの抵抗が見えます。


実際に作成して感度が自動で調整されるのを確認できました。
しかし、遅くて調整完了までに時間がかかります。

▼もっと早く自動調整されるようにする

あまりにも調整時間がかかって遅いのでもっと早く調整できるようにします。
まず、回路の抵抗値の変更


100KΩを1KΩの抵抗値に変更します。
さすがに抵抗を無しにするとセンサーのONとOFFの揺らいている所が広くなり挙動がおかしくなりますが、 1KΩまで抵抗値を落としても問題なく動作しました。
100KΩだとセンサーが安定するのに1.5秒ぐらいかかりましたが、1KΩだと100ミリ秒以下になりました。


▼プログラムの変更

バイナリサーチをイメージしてセンサーの最適な値を検出する事にします。
PWM出力の値の範囲は0x00から0xFFと決まっていますので、まず値を半分に分けて真ん中の値をセンサーに送りON・OFFを判定します。
次に、ONだったら前半部分、OFFだったら後半部分の真ん中の値をセンサーに送りON・OFFを判定します。
これを繰り返す事により、ONとOFFの境界線が検出できるはずです。
その後、ONとOFFの境界線から、OFFの値が2秒ほど安定するまで感度を落とせば設定完了です。

以上の事柄をイメージしてプログラムを作成しました。

void setup() {
  Serial.begin(9600);
  pinMode(5, INPUT);

  int first=0,middle,last=0xFF;
  check(last);//PWMの電圧を安定させるため空で呼び出す

  //バイナリサーチをイメージしてセンサーの最適な値を検出
  while(!((last-first)<=1)){
    middle=first+(last-first)/2;
    if(check(middle)){
          Serial.print("on ");
          Serial.println(middle,DEC);
          last=middle;
    }else{
          Serial.print("off ");
          Serial.println(middle,DEC);
          first=middle;
    }
  }
  
  //OFFの値が2秒ほど安定するまで感度を落とす処理
  for(unsigned long t=millis()+2000;t > millis();){
    if(!digitalRead(5)){
        check(--middle);
        Serial.println(middle,DEC);
        t=millis()+2000;
    }
  }

  Serial.print("completion ");
  Serial.println(middle,DEC);
}

int check(int i){
  analogWrite(3,i);
  delay(100);
  return !digitalRead(5);
}

void loop() {
}

このプログラムをArduinoで走らせると4〜5秒の間にセンサーの自動調整が出来るようになりました。
人間が可変抵抗を操作するよりシビアに素早く調整出来ました、ちょっと感動です。
問題になるとしたらちょっとシビア過ぎる所でしょうか。


▲トップページ > マイコンなど