Arduino – ESP32 の PWM ( LEDC )で 40MHzまでの安定した高周波パルスを思い通りに出せたぞ

esp32_ledc00 ESP32 ( ESP-WROOM-32 )

今回の実験で使った物

ESP32 関連モジュール

ESP32 や ESP32-WROOM-32  搭載のものならば以下のどれでも検証できます。

M5Stack

(追記)
M5Stack Basicは、この記事を書いた当時より格段にバージョンアップしております。
以下のスイッチサイエンスさんの公式サイトをご参照ください。
https://www.switch-science.com/collections/%E5%85%A8%E5%95%86%E5%93%81/products/9010

ESPr Developer 32

ESPr Developer 32
スイッチサイエンス(Switch Science)

ESP32-DevKitC

waves ESP32 DevKitC V4 ESP-WROOM-32 ESP-32 WiFi BLE
waves
¥1,170(2024/04/24 13:05時点)

M5Camera

これについては、以下の記事を参照してください。
M5Camera をレビューしてみた。分解したり、Arduino IDE でスマホに映したりする実験

https://www.switch-science.com/catalog/5207/

オシロスコープ(理想サンプリング周波数 200MHz 以上)

今回、最大 40MHz の波形を測定するので、200MHz 以上のオシロスコープが理想ですが、かなり高価です。
今回は手持ちで 100MHz のものしか持っていませんのでそれを使っています。

その他、パソコン、USBケーブル等

私は Windows 10 パソコンで実験しました。

Arduino – ESP32 のインストールを予め済ませておく

Arduino IDE は ver 1.8.9 で実験しました。
Arduino core for the ESP32 ( 以下 Arduino – ESP32 ) は stable 1.0.1 で実験しました。

Arduio – ESP32 のインストール方法は以下の記事を参照してください。

Arduino core for the ESP32 のインストール方法

LEDC ライブラリ関数の分解能 16bit で最大 1.22kHz までPWM 出力するスケッチ

では、Arduino – ESP32 の LEDC サンプルスケッチを改変して、オシロスコープで波形を見てみたいと思います。
まず、分解能を最大の 16bit とした場合、GPIO から PWM 信号を出力してみます。
先ほども述べたように、16bit の場合、最大周波数は約 1.22kHz です。

サンプルスケッチの LEDCSoftwareFade を以下のように書き換えてみます。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#define LEDC_CHANNEL_0 0 //channel max 15
#define LEDC_TIMER_BIT 16 //max 16bit
//16bitの場合、最大周波数1220Hz
#define LEDC_BASE_FREQ 1220.0 //double 1220.0Hz以下
#define GPIO_PIN 5 //GPIO #36~#39 は設定不可

void setup() {
  ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_BIT);
  ledcAttachPin(GPIO_PIN, LEDC_CHANNEL_0);
  //16bit の場合 duty 0x0000~0xFFFF (max:65535)
  //duty 50%: 0x8000 (=32768)
  ledcWrite(LEDC_CHANNEL_0, 0x8000);
}

void loop() {
}

【解説】

LEDC ライブラリソースコードは、Arduino core for the ESP32 の esp32-hal-ledc.c にあります。

●1行目:
最初の方で述べたように、ESP32 の PWM 出力は最大 16 チャンネルまでなので、0~15 までの値で定義します。
注意したいのは、M5Stack 等のディスプレイバックライトに LEDC を使っていると、チャンネル番号が競合する場合があり、正しく表示できなくて焦ります。
その場合は別のチャンネルにしてみて下さい。

●2行目:
分解能 16bit で定義します。
その場合、最初の方で述べたように、最大周波数は 1220.70 Hz になります。
もっと周波数を大きくしたい場合は分解能を下げなければなりません。

●4行目:
先ほど述べたように、16bitの場合は最大周波数が 1220.70 Hz です。
1220.70Hz 以下の場合は LEDC_BASE_FREQ の値を変更して周波数を変えられます。
ただし、それ以上の周波数、例えば 40MHz で定義しても、周波数は変わらないので要注意です。
周波数を上げたい場合は分解能を下げる必要があります

因みに周波数は倍精度浮動小数点の double 型にします。

●5行目:
LEDC で使う analogWrite 系の関数は、入力用端子の GPIO #34~#39 は設定できないので要注意

●8行目:
LEDCライブラリのセットアップです。

●9行目:
ここで、LEDC PWM の設定を ESP32 の GPIO ピンに割り当てます。

●12行目:
PWM のデューティ比を 50% とする場合、16bit の最大値 65536 の 2分の1の値、32768 を ledcWrite関数に代入します。
32768 は 16進数で 0x8000 です。

では、スケッチをコンパイルして実行させてみてください。
そして、オシロスコープで GPIO #5 の電圧波形を測定してみると、下図のようになります。

バッチリ正確なドッシリと安定した波形が出ました。
よく見ると、パルス幅がキッチリ50%になっていないように見えますが、波形の立下りから立ち上がりの切り替わり地点で時間を測ると、キッチリ50%になっていてカウンタがちゃんと機能していることが判明しました。

ここで、先ほどのサンプルスケッチの LEDC_BASE_FREQ を 2kHz や 40MHz、80MHz にしてみてください。
オシロの波形周波数は変化しないと思います。

では、今度は逆に、1220.70 Hz 以下にしてみて下さい。
周波数が変化すると思います。
ただし、先ほど述べたように、
1周期は 12.5ns の倍数 25ns単位でしか変化できないことに要注意
です。
819200ns + 25ns = 819225ns ( 1220.66Hz )
となり、その間は変化しません。
つまり、周波数が変化する値は以下のようになります。

1220.70Hz
1220.66Hz
1220.62Hz
1220.59Hz



etc

実際は、1215 Hz 以下にすれば、オシロスコープで目視で明確に周波数の変化が確認できました。
以上から、
16bit の場合、1220Hz 以下ならば周波数やデューティ比を比較的自由に設定できるが、1220.70Hz以上は不可
ということが分かると思います。

LEDC ライブラリ関数の分解能 8bit で最大 312.5kHz までPWM 出力するスケッチ

では、今度は分解能を 8bit とした場合の LEDC ライブラリで GPIO から PWM 信号を出力してみます。
スケッチは以下です。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#define LEDC_CHANNEL_0 0 //channel max 15
#define LEDC_TIMER_BIT 8 //max 16bit
//8bitの場合、最大周波数 312500Hz
#define LEDC_BASE_FREQ 312500.0
#define GPIO_PIN 5 //GPIO #36~#39 は設定不可

void setup() {
  ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_BIT);
  ledcAttachPin(GPIO_PIN, LEDC_CHANNEL_0);
  ledcWrite(LEDC_CHANNEL_0, 0x80); //0x80 = 128
}

void loop() {
}

2行目を 8bit に換えています。
最初の方で述べたように、8bitの場合、最大周波数が 312.5kHz になるので、その値で定義しています。

では、これをコンパイル書き込み実行して、オシロスコープで GPIO #5 の電圧波形を測ってみると、以下のようになりました。

高周波になって来ると、さすがに波形の立ち上がりや立下り時にスイッチングノイズが目立ってきますね。
そして、キッチリ 312.5kHz 出力できています。

先ほどと同様に
LEDC_BASE_FREQ の値を312500Hz 以上にしても周波数は変化しません

逆に、312500Hz 以下にする場合は、最初の方で述べたように、
1周期(1波長分の経過時間)は 25ns 単位でしか変えられません

すると、
3200ns + 25ns = 3225ns ( 約 310077 Hz )
になるので、312500 Hz の1つ下の周波数は 310077 Hz ということになります。
よって、8bit で LEDC_BASE_FREQ の値を変える場合、以下のようなステップになります。

312500 Hz
310077 Hz
307692 Hz
305343 Hz



etc

よって、
高周波になると、1Hz単位で細かく周波数を変えるということができません。
要注意!

LEDC ライブラリ関数の分解能 1bit で最大 40MHz までPWM 出力するスケッチ

では、今度は分解能を最小の 1bit とした場合の LEDC ライブラリで GPIO から PWM 信号を出力してみます。
1bit ということは、0と1の2種類しか値が無いので、必然とデューティ比が50%になります。

スケッチは以下です。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#define LEDC_CHANNEL_0 0 //channel max 15
#define LEDC_TIMER_BIT 1 //max 16bit
//1bitの場合、最大周波数 40MHz
#define LEDC_BASE_FREQ 40000000.0
#define GPIO_PIN 5 //GPIO #36~#39 は設定不可

void setup() {
  ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_BIT);
  ledcAttachPin(GPIO_PIN, LEDC_CHANNEL_0);
  ledcWrite(LEDC_CHANNEL_0, 1); //しきい値 1 duty: 50%
}

void loop() {
}

最初の方で述べたように、2行目を 1bit とすると、必然的に最大周波数は 40MHz となり、デューティ比は 50% になります。
10行目のように「しきい値」は 0 か 1 の2種類しかないので、1にします。

これでコンパイル書き込み実行させて、オシロスコープで GPIO #5 の電圧波形を見ると、以下のようになりました。

これはもはや私の手持ちオシロスコープの分解能を超えているので、波形は信用できません。
ですが、たぶん HIGH レベルと LOW レベルは切り替わっていると思われます。
このレベルの波形を正確に測定したければギガヘルツ級のオシロが欲しいですね。

このように、
1bit の分解能の場合、最大 40MHz までのクロック波形を GPIO から出力することができるようです
前にも述べたように、LEDC_BASE_FREQ を 80MHz に替えても周波数に変化はありません。

逆に、それ以下の周波数、
40MHz 以下にしたい場合は、1周期が 25ns 単位でしか変えられません
結果、以下のステップで周波数を変更することになります。

40MHz
20MHz
13.33333…MHz
10MHz
8MHz



etc

以上から、
LEDC ライブラリで PWM の周波数を上げたければ、PWMの分解能ビット値を下げれば良いわけです。
16bit、15bit、14bit、・・・、3bit、2bit、1bit というように、1bit単位で分解能を変更できます。

どうでしょうか?
この私の解釈で間違えていないですよね?
ちょっと小難しいと思いますが、一度理解すると
「なーんだ! そういうことか!」
と、芋づる式に分かって来ると思います。

このように高周波まで安定したクロックを GPIO から出力できるようになれば、何でもできそうな気がしてきますね。
これは地味だけど、なかなかスゴイことだと思っています。

まとめ

今まで LEDC ライブラリ関数が良く分からなかった方々、これで理解できましたでしょうか?
まとめると以下のようになります。

●16bit の場合、最大周波数 1.22kHz まで可能。それ以下の周波数は比較的自由に設定可能
●8bit の場合、最大周波数 312.5kHz まで可能。周波数やデューティ比の設定に制限あり。
●1bit の場合、最大周波数 40MHz まで可能。デューティ比は 50% のみ。周波数設定の自由度は低い。
●クロック1周期は 25ns 単位でしか変えられない為、それ相当の周波数単位でしか変更できない。

要は、高周波にしたい場合は分解能ビット数を下げればOK.
デューティ比や周波数を細かく設定したい場合は分解能ビット数を上げれば良いが、低周波に限られるということです。

以上を理解すれば、LEDC ライブラリで安定した高周波クロックパルスを自分の意図した通りに  GPIO から出力できるようになると思います。
これはいろいろな用途に活躍できそうです。

先ほど述べたように、以前のこちらの記事のプログラムを LEDC の 40kHz のクロックに変更したら、バッチリ全ての電波時計が最短時間で合うようになりました。
デジタル音楽の世界でも、クロックのジッタが音質に影響があるようなことが言われていますが、ジッタが極力少なく、安定した揺らぎの無いクロックというものがとても大事なんだなぁとつくづく実感しました。

ということで、今回はここまでです。

ではまた・・・。

コメント

  1. 匿名 より:

    波長って表現は間違ってませんか?

    • mgo-tec mgo-tec より:

      記事をご覧いただき、そして、コメント投稿していただき、ありがとうございます。

      早速、記事を修正しました。

      確かに波長という表現は誤りでした。
      「1周期」に修正しました。
      この当時は自分の頭の中では、1波長分の時間経過というのはあって、周期という言葉が浮かばず、そのまま波長と書いていました。
      何しろ独学なもので、電波時計合わせ回路作成の過程で、自分が分かり易いように記述していました。
      よくよく読み返してみると、明らかに誤りでした。
      大変失礼しました。
      ご指摘ありがとうございました。

      まだ誤りがありましたらコメントお願いいたします。
      m(_ _)m

  2. nyanta より:

    「ESP32のPWM出力何本あるのかな?」という検索をして辿り着きました。
    そして周波数の設定のことまで詳しく書かれていて、
    現時点で知りたい事が分かりやすく書かれていて有難いです。
    インバータの実験ができそうです。
    ありがとうございます。

  3. mick24 より:

    簡便な素人工作で電波時計を合わせたいと思ってたどり着きました。
    いざ真似をさせていただこうと思ってESP32のDACのドキュメントを見ていたら
    Cosine Wave Outputというものに気づきました。
    https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/dac.html
    https://github.com/krzychb/dac-cosine
    40KHzの生成に使えそうに思いました。
    (やってみてから書けよ、という話ですが。。。)

    • mgo-tec mgo-tec より:

      mick24さん

      記事をご覧いただき、ありがとうございます。

      こんなAPIできたんですね!!!
      最近できたのかな?
      40kHzでキレイなサイン波形が出力できるといいですね。
      自分は多忙で今は全く試せませんが…。
      有益な情報、ありがとうございま~す!!!
      m(_ _)m

タイトルとURLをコピーしました