はじめに
ESP32でLチカができるようになったので、続いてはLEDのフェード機能をつけようと思います。
じわじわ明るくなったり、暗くなったりするものですね。
Arduinoを開いて、ファイルからスケッチ例 → ESP32 → AnalogOut → LEDCFade。
このプログラムを開くと、フェードの機能をつけることができます。
ledc〇〇がたくさんあって、便利な関数が実装されているので、すぐ導入することができます。
公式ガイドはこちら
LEDCとは
はじめに、ledcとは何かを見てみましょう。
ざっと概要ですが、LED PWM、今後ledcと言いますけど、これはLEDのフェード機能に使うものです。
実際に何をやっているかというと、デューティー比を調整しながら、
カウンターオーバーフローで割り込みが発生してフェードが終わるという形ですね。
では、実際に私たちは何をやるのか。カウンターと周波数、あとフェードの時間をそれぞれ設定してやると、
設定に応じてフェードする形になっています。
出力波形を見る
実際の波形を見てみましょう。こんな感じで、一周期の中でオン時間が変わっていますよね。
このオンとオフの比をデューティ比と言います。
今、5kHzで設定しているので、200マイクロ秒単位でデューティが変わっているのがわかると思います。
LEDCの中身
ledc自体は2つのグループを持っていまして、1つのグループには4つのタイマーと8つのチャンネルがあります。
これがそれぞれのグループにあって、1個は高速で、もう1個は低速です。
これらは内部のハードの構成が違うらしいです。
サンプルプログラムだと、初期設定では高速の方に割り当てられます。
ソースコードを見る
プログラム中でやることとしては、まず最初にタイマーの設定をしてやって、その後チャンネル設定になります。
チャンネル設定の時に、どのタイマーをどのピンに割り当てるか設定します。これを踏まえた上で、ソースコードに戻ります。
定数部
まず、上からざっと見ていくと、定数を定義していて、12ビット分解能で動かします。タイマーの周波数は5kHzです。
4番のピンを指定して、START_DUTY、TARGET_DUTYと書いてあるんですけど、おそらくタイマーカウンターのことだと思われます。
START_DUTYが初期値、TARGET_DUTYが目標値ですね。FADE_TIMEが実際にフェードさせる時間。
そして、フラグがfade_ended。これは「フェードが終わりました」というフラグで、
fade_onがフェードの方向。明から暗ならfalse、逆ならtrueです。
タイマーがTARGET_DUTYの定数のところまで達したら、割り込みが入ってきて、ここでfade_endedの変数がtrueに変換されます。
ledcattach関数、ledcAttachChannel関数
まずsetup関数で、ledcattachという関数が定義されていて、引数にピン番号、周波数、タイマ分解能を入れます。
中身を見ると、チャンネルの発番をしています。
もともと入っているused_channels変数の中身をビット反転して1足したものの論理積をとって、
設定されれば1になるはずです。もし0なら、チャンネル不足でfalseを返します。
ledcAttachChannel関数にさらに飛ばして、設定ミスがあればfalseでreturnします。ここら辺の処理は割愛します。
ledcAttach関数内で自動発番したチャンネルを基に計算して、1/8=0。groupは0が入って、
timerは(1/2)%4なので0。timer(=タイマー番号)はゼロになるはずです。
タイマー設定
最初にグループとタイマーを設定した上で、ledc_timer_config_t構造体の中、
スピードモード(=グループ)、タイマー、分解能、周波数、クロックソースを入れて、
構造体変数をledc_timer_config関数に突っ込んで、問題なく設定できればESP_OKが返ります。
チャンネル設定
続いてチャンネルの設定です。
今度はledc_channel_config_t構造体に、
スピードモード(=グループ)、チャンネル、タイマー、LEDC_INTR_DISABLEは割り込み禁止設定、GPIOピン設定、現在のデューティカウンタを設定して、
構造体の変数をledc_channel_configに入れてチャンネルの設定がされます。
動的メモリで設定を参照できるようにする
この後、プログラムで扱いやすいように、ポインタでmallocで動的にメモリを取って、ピンとチャンネルの割り当てを格納します。
handleのポインタ経由でいろいろ参照するようです。
ledcAttach関数の動作説明でした。まとめると、タイマー設定、チャンネル設定が行われます。
どのタイマーを使って、どのピンに割り当てるかですね。
timer0、5kHz、12bit分解能のタイマーを、チャンネル1にタイマー0、GPIO4を割り当てています。
ledcFadeWithInterrupt関数
この後、ledcFadeWithInterrupt関数で動作開始です。
どのピンを、カウンターはどこからどこまで、フェード時間は初期設定だと3秒。
カウンターが目標値まで来たらLED_FADE_ISR。
ISRはInterrupt Service Routineといって、割り込みハンドラを設定しています。
fade_endedをtrueにします。
ledcFadeWithInterrupt関数でフェードが開始され、カウンターが目標値まで到達したらfade_endedがtrueになります。
この後、loopでfade_endedがtrueならfade_endedをクリアして、fade_onをもとに方向指定して動かすプログラムになっています。
設定時間と実動作時間が合わない問題
動作に問題があって、明から暗、暗から明、それぞれフェード時間として3秒持っているので、
3秒+3秒=6秒になると予想できますが、実際の動きを見てみましょう。
見ていただいたように、フェードが6秒と予測されたのに、実際は5秒です。1秒分ずれています。
今回3秒にしたんですけど、他にもフェード時間を変えて計測しました。
理論的には、フェード時間の往復なので2倍に単純計算で求められます。
フェード時間が1秒なら合計2秒にならず、1.6秒です。フェード時間1.5秒にしても1.6秒で変わらず。
フェード時間2秒だと実測3.2秒で、実測値が理論値に対して0.8秒分ずれています。
最初に言った通り、フェード時間はカウンターと周波数の比率に対して、自動計算されるステップ間隔の計算式で、実際のフェード時間が決まります。
例えばFADE_TIME = 1000の時は、片方のカウント4096なので、往復で2を乗算。
4096*2に対して、周波数5kHzで、ステップ間隔が自動で1となっているので、
この1.6に対して自動計算されたステップ間隔の1が掛け算されて、結果1.6秒になっている動作のようです。
ステップ間隔が自動計算されるのでずれる
問題なのは、ステップ間隔が整数倍にしかならず、しかもESP32側で自動で近似値が割り当てられるので、
フェード時間は自分たちが想定した時間より結構ずれることがあります。
もし精度が欲しいのであれば、FADE_TIMEや、関数の中身をいじって微調整していくしかないです。
ということで、ESP32に入っているLEDCについて雑多になりますけども、まとめてみました
以上で終わります、ありがとうございました!
コメント