ESP32においてLEDC(LED PWM Controller)に設定する分解能をExcelシートで検討する

- 2023/4/27 修正  
　PWM周波数60KHzにおいてresolution_bits=2を10に修正しました。PWM周波数の精度を上げるためには分解能を大きくすることが有効です。

# はじめに
　ESP32のPWM機能であるLEDCの設定において、resolution_bitsの検討にExcelシートを役立てました。Excelファイルは以下のリンクからダウンロートできます。

https://github.com/botanicfields/BF-005/blob/master/ledc_param.xlsx

# 1. LEDC
　LEDCは、ESP32[^1][^2]が内蔵しているPWM機能です。GPIOからPWM信号を出力できます。LEDの明るさやサーボモーターの角度をコントロールしたり、任意の周波数を生成するなど広く応用できます。

LEDC: LED PWM Controller
ESP32: Espressif Systems社が提供しているSoC
SoC: System on a Chip
PWM: Pulse Width Modulation
GPIO: General-Purpose Input/OutPut
LED: Light-Emitting Diode

[^1]: [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
[^2]: [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/)

# 2. LEDCの設定とPWM出力
　ESP32をArduino-IDE[^3]で使用する場合、標準機能でLEDCを使用できます。主な設定項目は以下です。

| 項目 | 内容 | 意味 |
|:-:|:-:|:-|
|freq|PWM周波数[Hz]|PWM 1サイクルの周期[sec]の逆数|
|resolution_bits|分解能 [bits]|PWM 1サイクルの分割数 $=2^{resolution\\_bits}$|
|duty|デューティ|PWM 1サイクルの分割数の中のオン時間の数|
|pin|出力ピン番号|GPIO番号を指定|
|channel|チャネル番号|ESP32内に複数あるPWMハードウェア資源から選択|

　これらを指定する関数の宣言を以下に抜粋します。

```esp32-hal-ledc.h
uint32_t    ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits);
void        ledcAttachPin(uint8_t pin, uint8_t channel);
void        ledcWrite(uint8_t channel, uint32_t duty);
```

　LEDCは、APB_CLK(80MHz)かREF_Tick(1MHz)のいずれかを元に動作します。指定したfreqやresolution_bitsの値によっては、LEDCが出力するPWM周波数が必ずしもfreqの値ぴったりにはなりません。実際に得られるPWM周波数がledcsetup()の戻り値で示されます。

IDE: Integrated Development Environment

[^3]: [Arduino core for the ESP32](https://github.com/espressif/arduino-esp32)

# 3. LEDC設定値のExcelシートでの検討
　PWMを実現するためには、freqを $2^{resolution\\_bits}$ 倍した基準信号(Reference Pulse)が必要です[^4]。Reference Pulseは、Div_Numレジスタを経由して生成します。Reference Pulseを誤差なく生成できれば、意図したfreqを出力できます。

$$Reference Pulse = freq\times2^{resolution\\_bits} \fallingdotseq \frac{APB\\_CLK \times 256}{Div\\_Num}$$

※ APB_CLKにおいて $Div\\_Num > 0x3FFFF$ となる場合、REF-Tickが選択される

　LEDCの分解能に関する条件は、以下の4項目です。

1. resolution_bitsの範囲 1 .. 31bit
2. Reference PulseがAPB_CLK/REF-Tick以下
3. Div_Numの設定可能範囲 1..0x3FFFF
4. Div_Numが整数に収まればfreqの誤差が少ない

適切な分解能を決めるためにexcelシートを利用しました。

[^4]: [ESP32 の LED_PWM において元クロックを生成する仕組み](https://qiita.com/BotanicFields/items/5f90888e95fe5090c1d1)

## freq=50Hz
　TowerPro社のマイクロサーボSG-90[^5]は、周期20msのPWM信号で制御します。PWM周波数を50Hzぴったりで出力できるのはresolution_bitsが5～17の範囲です。5～10ではREF_Tickが選択され、11～17ではAPB_CLKが使用されます。SG-90の分解能(Dead Band Width)は1～10μsの範囲で所説ありますが、制御するPWM信号としてはDead Band Widthよりも十分高い分解能を設定すべきと思います。仮にDead Band Width=1μsとすると$20ms/1μs = 20,000レベル$となり、resolution_bits=15(32,768レベル)以上が必要です。Dead Band Widthが10μsとすると2,000レベル以上となり、12(4,096レベル)以上でよいことになります。resolution_bitsを設定可能な最大の分解能に設定すれば楽ですが、実機で影響を確認しながら調整する必要があるかもしれません。

![ledc50.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/24ac5113-2581-89bf-8190-df41b2cfaf89.jpeg)

[^5]: [マイクロサーボ９ｇ　ＳＧ－９０](https://akizukidenshi.com/download/ds/towerpro/SG90_a.pdf)

## freq=40kHz
　電波時計のJJY信号を疑似的に生成する場合に使用できます[^6]。最大分解能としては、resolution_bits=10とすることで40kHzぴったりの周波数で1,024レベルのPWM信号を取り出せます。もっともデューティは50%と0しか使いませんので、resolution_bitsとしては2～10が使えます。

![ledc40k.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/ac8a2e8f-b186-498e-94f7-33c0173d5bd9.jpeg)

[^6]: [標準電波 JJY もどきを M5StickC / M5Atom の Ticker で生成する](https://qiita.com/BotanicFields/items/a78c80f947388caf0d36)

## freq=60kHz
　電波時計のためのJJY信号は、福岡からは60kHzで送信されています。LEDCでは60kHzぴったりを生成できないことがわかります。resolution_bits=10とすることで、60kHzに近い周波数で1024レベルのPWM信号を取り出せます。
　ぴったりの周波数を生成できない場合、resolution_bitsはできるだけ大きな値を選択すべきです。resolution_bitsの値が小さいと基準信号の周波数が低くなり、PWM周波数の組成が粗くなるため、PWMのジッタが増加します。

![ledc60k.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/5acd3612-66e8-7224-74df-43c2b187e04a.jpeg)

## freq=2MHz
　テキサスインスツルメント社のLEDコントローラLSIであるTLC5940[^7]は、それ自身がLEDを直接接続可能なPWMポートを16チャネル備えています。このPWMの基準クロック信号として2MHzをLEDCで生成しました[^8][^9]。resolution_bits=5とすることで2MHzぴったりの周波数で最大分解能32レベルの信号を生成することができました。

![ledc2M.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/93151988-ee0c-2470-ff76-e5fc80d77691.jpeg)

[^7]: [TLC5940 EEPROM ドット補正機能とグレイスケール PWM 制御機能搭載、16 チャネル LED ドライバ](https://www.ti.com/product/ja-jp/TLC5940)
[^8]: [How to make GPIO Signals to Control Hardware](https://www.slideshare.net/YoshiyukiUehara/how-to-make-gpio-signals-to-control-hardware-227027550)
[^9]: [BF-005 16 Segment LED x 16 driven by TLC5940 and M5Stack/ESP32](https://github.com/botanicfields/BF-005)

# 4.LEDCのduty最大値
　ledcWrite()において、設定された分解能における最大のdutyを指定すると、PWM出力が強制的にHigh固定となります。例えばresolution_bits=3(8レベル)の場合、duty=0のPWM出力はLow固定、duty=1～6でパルス幅が1/8～6/8に順調に広がります。しかしduty=7ではパルス幅が7/8ではなく強制的にHigh固定となります。したがってresolution_bits=1は意味がありません。指定してもduty 50%は実現できません。

# 5.その他
　生成したいPWM周波数の誤差と分解能についてのみ考察してきました。ESP32は、Reference Pulseの生成のため1段分周した信号と256レベルで混合しています。ミクロにみるとジッタが発生しているかもしれません。ジッタが問題となる応用は少ないとは思います。

# 参考資料
　参考資料については、文末の脚注を参照ください。