ESP32で位相をずらしたPWM信号を生成する

No Image

ロータリーエンコーダのような出力(位相が90度ずれたパルス信号)をマイコンから出力したいなと思って調べているとどうやらESP32にはその機能があるみたいです。
さらには3相相補PWMというモータ制御にも使える機能も実は搭載されているようです。
そこまでしなくても、とりあえずは位相をずらしたPWMを生成できないかと色々試してみました。

これから説明する動作を確認できたのはESP32-C3ESP32-S2ESP32-S3でした。
ESP32(ESP32-WROOM-32等)ではコンパイルが通って書き込めるものの動作はしませんでした。
別の記述方法があるのかもしれません。

スポンサーリンク


普通、PWM信号は立ち上がりが同期しています。
LEDの調光やRCサーボモータのような単体の制御であれば問題になりません。
ただ、モータの制御等で2つのPWM信号を位相をずらして出力したい場合が稀にあります。

公式のリファレンスは上記のリンクです。
これを読むだけでも骨が折れますし、読んだところで実装して動作させるまで理解するには非常に時間がかかります。
まとめてくれている記事やライブラリがあったので、それを使うほうが手っ取り早くていいですよね。

ライブラリをダウンロードしてサンプルをコンパイル、これは楽々...と思ったらコンパイルが通らずエラーを吐きまくりました。
エラー位置がledcAttachPinなどの関数を指しているので、勘の良い私はすぐに気づいてしまいました。
ESP32 Core for Arduino IDE Version 3.0.0 に対応していないという事実を...

2.x系から3.0系(記事作成時はRC3)へのアップデートで破壊的な変更がいつかあったと認識しています。
そのなかでよく使うPWM関係(ledc関数)にも大幅な変更があることは早々に気づいていました。

今までは割り当てるチャンネルを指定したいたのですが、それがなくなりピン番号を指定するだけになりました。

// 変更前(バージョン2.x)-----------------------------------------------------
pinMode(PWM_PIN, OUTPUT);                   // PWM用ピンを出力設定
ledcSetup(CHANNEL, FREQUENCY, RESOLUTION);  // PWM設定(チャンネル, 周波数, 分解能)
ledcAttachPin(PWM_PIN, CHANNEL);            // PWMチャンネル割り当て(ピン番号, チャンネル)

ledcWrite(CHANNEL, DUTY);                   // DUTY比を指定して、チャンネルに対して実行

// 変更後(バージョン3.0)-----------------------------------------------------
pinMode(PWM_PIN, OUTPUT);                   // PWM用ピンを出力設定
ledcAttach(PWM_PIN, FREQUENCY, RESOLUTION); // PWM設定(ピン番号, 周波数, 分解能)

ledcWrite(PWM_PIN, DUTY);                   // DUTY比を指定して、ピン番号に対して実行

スポンサーリンク

遅かれ早かれ3.0.0への対応は必須だと思いますので3.0.0で位相をずらす方法を前述のライブラリ(ESP32-ESP32S2-AnalogWrite)を参考に考えました。
ライブラリの記述で ledcSetup と ledcAttach を置き換えて ledcAttachChannel に置き換えればいいかなと思っていましたが、ライブラリ自体がチャンネル主体で書かれているので、なかなかうまくいかないところもありました。
だったら直書きで...ということで中身から位相をずらすのに必要なところだけを抽出して書くと下記のようになります。

#include <driver/ledc.h>

#define PWM_PIN1 10
#define PWM_PIN2 11

#define FREQUENCY  1000
#define RESOLUTION 10

void setup() {
  pinMode(PWM_PIN1, OUTPUT);                 
  ledcAttachChannel(PWM_PIN1, FREQUENCY, RESOLUTION, LEDC_CHANNEL_0); 
  ledc_set_duty_with_hpoint(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0 511, 0);                  

  pinMode(PWM_PIN2, OUTPUT);                   
  ledcAttachChannel(PWM_PIN2, FREQUENCY, RESOLUTION, LEDC_CHANNEL_1); 
  ledc_set_duty_with_hpoint(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, 511, 256);
}

void loop() {
}

ledc_set_duty_with_hpoint の第4引数(hpoint)で指定した分、出力の位相がずれます。
ESP32 LEDC関数 hpoint
上記の2つのPWMは分解能が10bitですので1024段階、どちらも511と指定しているのでデューティ比は約50%、PWM_PIN2はhpointを256だけずらしているので、256だけ遅れてPWMが立ち上がります。
その結果、PWM_PIN2からの出力はPWM_PIN1に対して位相が約90度ずれることになります。
ちなみに、スレッドセーフな関数として ledc_set_duty_and_update という別の関数が用意されているようですが、うまく動きませんでした。

プログラムを書き込んで波形を見ると、ちゃんと位相がずれています。
位相が90度ずれたPWM
loop関数内などで何度も位相ずれを変更する場合、ledcDetachを実行して一度解除しないと変化しませんでした。

次に上記のライブラリのサンプルにある三相PWMの例を直書きで直してみます。

/* This example demonstrates 3-phase, 10kHz PWM */

#include <driver/ledc.h>

const byte pwmPin[3] = {10, 11, 12};
const uint16_t duty = 341;
const byte deadtime = 21;
const uint16_t phase[3] = {0, 341, 682};
const uint16_t shift[3] = {42, 42, 0};
const uint32_t frequency = 10000;
const byte resolution = 10;

void setup() {
  for (byte ch = 0; ch < 3; ch++) {
    pinMode(pwmPin[ch], OUTPUT);
    ledcAttachChannel(pwmPin[ch], frequency, resolution, ch); 
    ledc_set_duty_with_hpoint(LEDC_LOW_SPEED_MODE, (ledc_channel_t)ch, duty - deadtime, phase[ch] + shift[ch]);
    delay(1);
  }
}

void loop() {
}

delay(1)を入れないと3相目(T相?)の位相がずれません。
スレッドセーフの関数ではないからかもしれません。

さらに、上述ソースコードではうまく動きません...
なぜか3相目の位相がずれすぎます。
120度ずつずれてくれない3相PWM
これについては原因を解明できませんでした、すみません。
詳しい人にバトンタッチです...

無理やり数字をいじくればそりゃ解決しますが、さすがにゴリ押しすぎませんか...?
120度ずつずれた3相PWM
これで解決したとは言えないですね。


とりあえず単純な機能だけですがなんとか使えるようになりました。
ESP32は自分の想像以上に色んな機能がありますね。
Arduino Core では十分に実装されていない機能も実はESP-IDFのドキュメントを見てみると色々あったりして勉強になります。

ESP32で位相をずらしたPWM信号を生成する

スポンサーリンク

Leave a Comment