2015年11月25日 | 公開。 |
前のページでは、LED制御基板でLEDの点滅制御を行いましたが、次は、LEDの明るさがなめらかに変化する調光を行います。理屈は後回しにして、LEDの明るさが変化する様子を、動画で確認してください。
LEDの明るさを調整するといっても、LEDはマイコンのデジタル出力ピンに接続されているので、ある一瞬に注目すれば、LEDは光っているか、消えているかのどちらかで、それらの中間の状態は存在しません。しかし、人の目の残像を利用して(動画を撮影する場合は、ビデオカメラの撮像素子の残像を利用して)、LEDの見かけ上の明るさを調整しています。
具体的にはどうするかというと、LEDの制御信号(D0~D3)を図8の様に変化させ、LEDを周期的かつ高速に点滅させるのです。
1秒当たりの点滅回数が少ない(例えば2回)だと、LEDはチカチカ点滅して見えますが、点滅が速くなり、1秒当たり30~50回(30~50Hz)程度になると、LEDがずっと点灯している様に見えるようになります。この時、全体の時間の中で、LEDの点灯している時間の割合が多いほど、LEDが明るく光っているように感じます。
参考:人の目が、光の点滅を感じなくなる境界の点滅周波数の事を、フリッカー値と呼びます。フリッカー値は、疲労が蓄積するほど低下する事が知られており、人の疲労の度合いを定量的に測定するのに用いられています。(参考サイト:フリッカーヘルスマネジメント株式会社のサイト) この様に、光のちらつき(フリッカー)の感じ方は個人差や、その人の状態によって差がありますが、十分に速く(例えば100Hzで)LEDを点滅させると、どの人の目にも、LEDがちらつかず、一定の明るさで見える様になります。
一定周期でHとLが切り替わるデジタル信号において、周期T[s]の中で、信号がHになっている時間がTH[s]だったとします。この時D=THTの事を、デューティ比といいます。定義より、デューティ比は0~1の範囲の数になりますが、パーセント表記して、0~100%の範囲で表示される事もしばしばあります。
Hの時の電圧をVH[V]とし、Lの時の電圧が0[V]だとすると、信号の平均電圧Vm[V]はVm=DVHで得られます。つまり、デューティ比を変化させることで、平均電圧を変化させる事ができるのです。この様に、デューティ比により平均電圧を変化させる方法を、PWM変調(パルス幅変調)といいます。また、PWM変調によりLEDの明るさやモーターの回転数などを制御する方式を、PWM制御といいます。
LEDをPWM制御すると、デューティ比が大きいほどLEDが明るく見えるわけですが、今回は、D0~D3の制御信号でコントロールされるLEDを、それぞれ独立に9段階のデューティ比(0%, 12.5%, 25%, 37.5%, 50%, 62.5%, 75%, 87.5%, 100%)で変化させます。(先ほどの動画の中では、8段階の明るさの制御をしていると説明していますが、9段階の誤りでした)
ATtiny85(を含むほとんどのマイコン)には、タイマと呼ばれる周辺回路が内蔵されています。タイマは、時間を計ったり、設定した時間が経過したら合図を送ったりという事に使われる回路ですが、ATtiny85のタイマには、PWM変調した方形波(以降、PWM信号と呼ぶ)をI/Oピンに出力する機能があります。
Arduino環境では、analogWrite関数でPWM信号を出力する事ができます。書式は次の通りです。
第1引数のpinが、PWM信号を出力するピンを表わし、第2引数のvalueがデューティ比を表わします。valueは0(常にL)~255(常にH)の間の整数で指定します。
例えば、analogWrite(0,31);
とすれば、D0ピンにデューティ比31/255のPWM信号を出力します。
また、analogWrite(0,201);
とすれば、D0ピンにデューティ比を201/255のPWM信号を出力します。
この様に、Arduino環境からは、analogWrite関数を使う事で非常に手軽にPWM信号を扱えるようになっています。ただし、ハードウェア上の制約から、analogWrite関数でPWM信号を出力できるピンは限定されています。次の表に、ATtiny85およびATmega328P(Arduino Uno)で、analogWrite関数からPWM信号を出力できるピンの一覧を示します。
マイコンの種類 | analogWrite関数で制御できるピン |
---|---|
ATtiny85 | D0、D1 |
ATmega328P | D3、D5、D6、D9、D10、D11 |
今回はD0~D3の4本の信号線をPWM変調したいので、analogWrite関数ではD2とD3のピンが制御できないという問題があります。また、analogWrite関数を使ったのでは、PWM信号の発生方法が隠蔽されており、具体的にどのようにすればPWM変調が行えるかといった、原理的な理解ができません。そこで、今回は、ソフトウェアによりPWM信号を発生する事にします。
ソフトウェアによりPWM信号を発生するには、digitalWrite関数を使い、信号を適切なタイミングでL→H、H→Lに切り替えて、図8の様な波形を作る必要があります。
PWM調光版のスケッチを次に示します。点滅版のスケッチと同様の方法でATtiny85に書き込んでください。
const byte TickNum=8;
const byte interval=50;
unsigned long StartTime;
void setup()
{
pinMode(0,OUTPUT);
pinMode(1,OUTPUT);
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
StartTime=millis();
} // setup
void loop()
{
static byte tick=0;
static byte cnt=0;
byte duty;
digitalWrite(4,cnt==0 ? HIGH : LOW);
if(cnt>TickNum) {
duty=TickNum*2-cnt;
} else {
duty=cnt;
} // if
digitalWrite(0,tick>=duty ? HIGH : LOW);
digitalWrite(2,tick< duty ? HIGH : LOW);
duty=(cnt+TickNum/2)%(TickNum*2);
if(duty>TickNum) duty=TickNum*2-duty;
digitalWrite(1,tick>=duty ? HIGH : LOW);
digitalWrite(3,tick< duty ? HIGH : LOW);
tick=(tick+1)%TickNum;
if(millis()-StartTime>interval) {
StartTime+=interval;
cnt=(cnt+1)%(TickNum*2);
} // if
} // loop
それほど長くないスケッチなのですが、動作はやや複雑です。スケッチを機能単位に分解して、動作を考えて見ましょう。
setup関数では、D0~D4の信号を、pinMode関数により全て出力に設定した後、millis関数でStartTime変数を初期化しています。
millis関数は、スケッチが動作し始めてからの時間を、ミリ秒(1/1000秒)単位で返す関数です。StartTime変数の働きについては、後ほど説明します。
なお、ミリ秒という単位はmsという記号で表示します。以後、ミリ秒の単位の表示にはmsを使います。
loop関数の中で、tickというbyte型の変数が宣言されています。この変数は、static宣言されていますので、loop関数を抜けた後も値を保持する事に注意が必要です。また、0で初期化されていますので、スケッチが実行される時点で0に初期化されます。
loop関数の中で、tick変数の値に変化を与える行は、次の1行のみです。
tick=(tick+1)%TickNum;
TickNumは、スケッチの最初の行で8と定義されている定数です。このTickNumは、明るさの調整の段階数から1を引いた値となります。今TickNum=8と宣言されていますので、9段階で明るさを調整する事になります。
tick変数は、loop関数を実行するたびに、tick変数の値に1を足し、それをTickNum(つまり8)で割って、その余りの値に更新されます。よってtick変数は、0→1→2→3→4→5→6→7→0→1→2…と周期8で変化(loop関数が8回呼ばれると元の値に戻る)していきます。
loop関数の中で、cntというbyte型の変数が宣言されています。この変数はtick変数と同様、static宣言されていますので、loop関数を抜けた後も値を保持します。また、0で初期化されていますので、スケッチが実行される時点で0に初期化されます。
loop関数の中で、cnt変数に変化を与える部分のみを抜き出すと、次の様になります。
if(millis()-StartTime>interval) {
StartTime+=interval;
cnt=(cnt+1)%(TickNum*2);
} // if
最初の2行の
if(millis()-StartTime>interval) {
StartTime+=interval;
により、その次の行が定数intervalで表わされた時間(単位はms)ごとに実行されるようになります。intervalはスケッチの最初の部分で50と定義されていますので、50[ms]ごとに(言い換えれば一秒間に20回)、cnt=(cnt+1)%(TickNum*2);
が実行され、cnt変数が更新されます。
グローバル変数StartTimeは、最後にcnt関数が更新された時刻を表わしています。
最初の2行は、
if(millis()-StartTime>=interval) {
StartTime=millis();
としても、おおむね正しく動作するのですが、if分の条件判定をしてからStartTimeが更新されるまでに時間がかかることや、millis関数の分解能が必ずしも1msではない事を考えると、cnt関数が更新される間隔の誤差が毎回蓄積され、正しい頻度(周波数)でcntが更新されません。
TickNumが8である事を考慮すると、cnt=(cnt+1)%(TickNum*2);
が実行されるたび(すなわち50[ms]ごと)に、cnt変数が0→1→2→3→(中略)→13→14→15→0→1→2→3…と、周期16で変化する事が分かります。cnt変数が50[ms]ごとに更新されて、周期が16で変化するので、周期を時間に換算すると、50[ms]×16=800[ms]=0.8[s]となり、cnt変数は0.8秒周期で変化する事になります。
次のページでは、PWM調光版のスケッチの説明の残りの部分を書き、電池寿命の計算も行います。
商品名 | Arduino用ブートローダ/スケッチライタキット | |
税抜き小売価格 | 3000円 | |
販売店 | スイッチサイエンス | |
サポートページ | Arduino用ブートローダ/スケッチライタキットサポートページ |