ちなみに前回のPWMであるが、1ピンだけでよければ、たとえばList 1のように、自分でDigital Outの点滅間隔を細かく調整することで擬似的にエミュレートする、いわば「なんちゃってPWM」も可能である。ここではdisplayLED()という関数を定義し、この中でanalogWrite()に近い動作をさせている。このdisplayLED()では、内部でdelayMicroseconds()(μ秒単位の待ち)を利用することで、0.255ms単位でデューティ比の制御を行っている。ただこのままだとちょっと煩雑なので、これを8回ループさせることで、ほぼ2ms単位という、analogWrite()と同程度の頻度でLEDの輝度調整を行うものだ。こうした使い方をすれば、digitalWrite()を使いながら、あたかもanalogWrite()っぽく動かすこともできるという例である。
List 1:
#define LED_PIN 3
void displayLED( int period )
{
int lpCnt;
for(lpCnt = 0; lpCnt < 8; lpCnt++)
{
digitalWrite(LED_PIN, HIGH);
if (period != 0) delayMicroseconds(period);
digitalWrite(LED_PIN, LOW);
if (period != 255) delayMicroseconds(255-period);
}
}
void setup()
{
pinMode(LED_PIN, OUTPUT);
}
void loop()
{
int period, lpCnt;
for(period = 1; period < 256; period++)
{
displayLED(period);
}
for(period = 255; period > 0; period--)
{
displayLED(period);
}
}
もっともこれ、ある1つのLEDの制御「だけ」をやっている分には差がないのだが、2つ以上のLEDを独立に制御するとか、あるいはLEDの制御以外のこともやらそうとか考えた瞬間に破綻する。というのは、たとえば、
analogWrite(PIN_LED, 127);
を一度実行すれば、その後は何もしなくてもずーっとLEDは定格のほぼ半分の明るさで光り続ける(PWMの制御をMCUがやってくれる)が、List 1の方法を使った場合、2ms毎にdigitalWrite()を呼び出し続けないと、点灯しなくなってしまう。
一般論としてMCUにはタイマー割り込みの機能があり、これを使うことである程度解決できる場合もある。たとえばやはり半分の明るさで点灯させたいと思ったとき、List 1の方法だと図1のような動き方になる。これはタイマー割り込みを使わない場合の典型的な方法で、この結果loop()の処理はdisplayLED()を呼び出す事で埋まってしまい、しかもdisplayLED()は無駄に待機しているという図式だ。
ここで仮にタイマー割り込みが利用できたとすると、プログラムを図2の様な構造に書き換えることができる。これにより、無駄にCPUで待機する必要がないから、間に他の処理をすることも可能で、タイマーが複数利用できるケースならば2つ以上のLEDを「なんちゃってPWM」で制御することも可能だろう。
「ここで仮に」なんて書き出しで始めたからにはお分かりの通り、実際にはArduinoではこの機能は利用できない。Arduino Unoに搭載されているAtmelのATmega328には8bit/16bitの2種類のタイマーが搭載されており、PWM制御に利用されているのだが、PWMに使わなずに内部割込み源として利用することも可能である。ところがArduinoはこのタイマー周りをPWMおよびライブラリ関数であるdelay()/delayMicroseconds()のソースに利用しており、タイマー割り込みをユーザー側で利用することが不可能である。現実問題としてわざわざ「なんちゃってPWM」を使うメリットがあるか? と言われると疑問ではあるのだが、ただこうした形でArduinoには若干の制限が付きまとう事は覚えておいてほしい。
さて、話を進めよう。LEDが1個の場合はまぁ簡単なのだが、もっと数が増えたらどうなるか? とりあえず6個の場合を考えてみる。これを順々に点灯させて行く事をやってみよう。とりあえずPWMの話は措いておき、digitalWrite()を使うタイプの、On/Offだけでよいとする。一番簡単なのは、図3の様な構造だ。要するにピン2~7までにそれぞれLEDを接続するという、あまり頭を使わない構造だ。実態配線図は図4の様な構造になる。
この回路を駆動するSketchとして、とりあえずList 2の様なものを作って見た。これを起動すると、Movie01の様に順に点滅して行くはずだ。
List 2:
void setup()
{
int lpCnt;
for(lpCnt=2; lpCnt<8; lpCnt++) /* Pin 2~8が対象 */
{
pinMode(lpCnt, OUTPUT); /* 当該PinをDigital Outに設定 */
digitalWrite(lpCnt, LOW); /* 初期値は消灯 */
}
}
void loop()
{
int lpCnt;
for(lpCnt=2; lpCnt<8; lpCnt++)
{
digitalWrite(lpCnt, HIGH); /* 点灯 */
delay(500); /* 500ms待機 */
digitalWrite(lpCnt, LOW); /* 点灯 */
}
}
動画 |
---|
Movie01: |
これはこれで簡単でいいのだが、この方式の場合、LEDの数だけpinが必要という、至極当然の限界につきあたる。前にも書いたがArduinoは最大で20pinのDigital Outを持っており、ところがウチ2本はUSBポートとの共用だから、最大でも18個までしかLEDが付けられない。このあたりもう少し考えましょう、という話は次回。
(続く)