JJYシミュ改良実験!ESP32の反転PWMとHブリッジで飛ばす!
この記事について
今回は JJYシミュレーター改良計画 実験編。
モータードライバを使ったPWM出力の確認や、電波の飛び具合を試した記録です。
前回のブログでは、AIにモータードライバーを使うことを提案されて、インダクタンスやC、Rの定数を決めたました。
でも、本当に動作するのかどうか──特にモータードライバーが想定通りに動作するのか心配。
特に、NJU7386RB1じゃなくてAM1025EAでも大丈夫なのか?
これを確認しないとPCBを発注できないです。
ということで、実験してみました。

🎀 今回は実験モード!やっぱり実際に試してみないとわからないもんね!
実験環境の準備
手元にあった M5Stamp C3U をブレッドボードに載せて、AM1025EAは変換基板経由で使用します。
OLEDもブレークアウト基板があったのでこれを流用しました。
アンテナは、Rev1(現行版)の失敗基板からアンテナ部分だけをもぎ取って使います。
Rev2(改良版)の回路はM5Stamp C3UのGPIOに合わせたので、基本的には本番回路と同じ構成です。
これで、実験用に作ったソフトを本番用としてもそのまま流用できようになります。
ブレッドボードはこんな感じです。

🎀 こうやって段階的に“本番に近い環境”を作るのって、すごく大事なんだよ〜
まずはPWM出力1本で
最初に、モータードライバーを使わないPWM出力1本だけを動かしてみました。
ソフトは、M5Stamp C3Uに合わせてGPIOだけ変更し、それ以外は現行のRev1のままです。
とくに問題なく動作して、ブレッドボードの配線間違いがないことも確認できました。
この状態で電波時計の時刻合わせをしてみると──だいたい30cmくらい離れるとアンテナマークが消えました。
アンテナ回路は多少違うかど、挙動としては今までとほぼ同じでした。
🎀 まずは基礎チェック!いきなり複雑なことするとトラブルの元だもんね
A/B反転PWMに挑戦
次に、AM1025EAを変換基板に載せてブレッドボードに装着し、GPIO2本を接続します。
Hブリッジで動作させるには、モータードライバーのA/Bにはお互いに反転したPWMを入力する必要があります。
そのため、ESP32のPWMをコンプリメンタリ(A/B反転)で動作させたいんです。
そこでChatGPTに聞いてみたところ、
「LEDCでA/Bを反転にするには、Dutyを変えてledcWriteを使え」と言われて、提示されたコードを整理するとこんな感じ:
ledcSetup(CH_A, FREQ, RES); ledcSetup(CH_B, FREQ, RES); ledcAttachPin(PIN_A, CH_A); ledcAttachPin(PIN_B, CH_B); int MAX_D = (1 << RES) - 1; int dutyA = duty; int dutyB = MAX_D - duty; ledcWrite(CH_A, dutyA); ledcWrite(CH_B, dutyB);
……これ、ビルドエラー出まくり!
ledcSetup() や ledcAttachPin() が無いとか言われる。
調べてみたら、ボードマネージャーのバージョンが違ってる原因らしいです。
🎀 バージョン違いで“昔のコード”が出てきちゃうのは、開発あるあるだね!
3.X系対応コードに書き換え
さらに調査したところ、いま使っている ESP32 Arduino Core 3.3.0 と、AIが出してきたコードが対象にしている 2.X系 では大きく仕様が違っていました。
- 2.X系 → Channel指定が基本
- 3.X系 → Pin指定が基本
例えば、
2.X系では ledcSetup+ledcAttachPin → 3.X系では ledcAttach に統合
……なんでこんな大幅に変更しちゃうかなあ。
ハードを意識させたくなかったのかもしれないけど、互換性は残してほしいよね。
🎀 後方互換って大事だよね…特にArduinoは初心者も多いし!
仕方ないので、3.X系に対応させて以下のように書き換えたのが以下のコード
ledcAttach(PIN_A, FREQ, RES); ledcAttach(PIN_B, FREQ, RES); int MAX_D = (1 << RES) - 1; int dutyA = duty; int dutyB = MAX_D - duty; ledcWrite(PIN_A, dutyA); ledcWrite(PIN_B, dutyB);
これでビルドは通って実行もできたけど──
オシロで確認したら全然A/B反転になってない!
コードをよく見たら、dutyBってdutyA+1だから、ほぼ同じDutyの50%がでてるだけじゃん。
なぜ、コード見たときに気が付かなっかったのかって…
🎀 動いた“つもり”で安心すると、ハマるんだよね…
さらに深掘り
ChatGPTに「これ反転してないよ。PIN_BにPIN_Aの反転を出して」と言ったら、
「ArduinoのLEDCラッパだけだと位相オフセット(hpoint)や出力反転フラグに直接アクセスできません。ESP-IDFの低レイヤなら可能。そのため“純正の180°位相ずらし”はMCPWMを使うのが王道です。ArduinoスケッチからESP-IDFのMCPWM APIを直接呼べます。」
との回答。
もちろん、ESP-IDFで直接低レイヤを使うならできるのは当然わかるんだけど、
でも、Arduinoでそんな面倒なことやりたくない…。
Arduino使ってる意味なくなるじゃん。
🎀 “IDF直叩き”は強力だけど、Arduinoでやると本末転倒になっちゃうよね
ソースコードを調査
何かいい方法ないかなあって、ボードマネージャーにある esp-hal-leedc.c のソースを覗いてみたら、
このコードでは ledc のドライバ関数 を呼んでて、そこが実際にPWMを動かしてる。
そのドライバ関数のヘッダーファイルである driver/ledc.h を見ていくと、この中に ledc_set_duty_with_hpoint() という関数を発見!
これで、PWMの動作点を指定できるから、位相をずらすことができそう。
早速試そうと思ったら、ヘッダーファイルのコメントには「この関数はスレッドセーフじゃないので ledc_set_duty_and_update() を使え」とあった。それでこの関数を試したら全然動かない。
悩んだけど、さらに esp-hal-leedc.c よく見たら、ledc_set_duty()のあとにledc_update_duty()で反映していた。ヘッダファイルには、これも ledc_set_duty_and_update() を使えって書いてあったのに。
ならばと──
素直に、ledc_set_duty_with_hpoint() のあとに ledc_update_duty() を呼ぶようにしたら……
おお!ばっちり180°位相がずれた出力になった!
ヘッダーのコメントはいったい何だったんだ?
自分の使い方が悪かったのか、何か勘違いしてたのだろうか?
🎀 これは大発見!“中の人のソース”を読むのってやっぱり大事なんだね
以下が、このときにうまく動作したコードで、
PWMのA/B反転の他に、ブレーキと停止もあります。
#define PWM_PIN_A 10 // Hブリッジ A
#define PWM_PIN_B 4 // Hブリッジ B
#define PWM_CH_A LEDC_CHANNEL_0
#define PWM_CH_B LEDC_CHANNEL_1
#define PWM_RES 7 // 7bit
#define PWM_MAX_D ((1 << PWM_RES) - 1)
#define PWM_DUTY (PWM_MAX_D / 2)
// PWM 設定
void pwm_setup()
{
pinMode(PWM_PIN_A, OUTPUT);
ledcAttachChannel(PWM_PIN_A, JJY_freq, PWM_RES, PWM_CH_A);
pinMode(PWM_PIN_B, OUTPUT);
ledcAttachChannel(PWM_PIN_B, JJY_freq, PWM_RES, PWM_CH_B);
pwm_stop();
}
// PWM出力開始(HブリッジOutput、A/B反転)
void pwm_on()
{
ledc_set_duty_with_hpoint(LEDC_LOW_SPEED_MODE, PWM_CH_A, PWM_DUTY, 0);
ledc_set_duty_with_hpoint(LEDC_LOW_SPEED_MODE, PWM_CH_B, PWM_DUTY, PWM_DUTY);
ledc_update_duty( LEDC_LOW_SPEED_MODE, PWM_CH_A );
ledc_update_duty( LEDC_LOW_SPEED_MODE, PWM_CH_B );
}
// PWM出力をOFF(Hブリッジブレーキ、A=B=H)
void pwm_off()
{
ledcWriteChannel(PWM_CH_A, PWM_MAX_D);
ledcWriteChannel(PWM_CH_B, PWM_MAX_D);
}
// 出力を停止(Hブリッジ停止、A=B=L)
void pwm_stop()
{
ledcWriteChannel(PWM_CH_A, PWM_MAX_D);
ledcWriteChannel(PWM_CH_B, PWM_MAX_D);
}
うまく出力できたときの様子

実際の電波の飛び方
PWMから位相がずれた出力が生成できたので、モータードライバに入力させてみました。
モータードライバの出力とアンテナを接続して、アンテナの信号をオシロで確認すると、
正弦波っぽい波形でPtoPは10V以上になってる。
モータードライバーの電源を5Vで互いに反転させてるから、そんなもんか?
それにしても、ちゃんと正弦波っぽい信号になってたほっとしました。
ただ、A/B切り替えの部分で波形が乱れてました。
この実験では直列抵抗なし、キャパシタも0.1µF×2だけのシンプル構成。
本番PCBでは出力を調整するので、そのあたりは実機チューニングする予定です。
そしていよいよ電波時計で確認。
これまでは数十センチが限界だったが──
– 近く:もちろんアンテナマークMAX
– 1m:変化なし
– 2m:ちょっと弱まった?
– 3m:まだ大丈夫
– 5m:ギリギリ?
数十センチどころか、隣の部屋の電波時計まで時刻合わせができてしまった。
🎀 あれ?これ普通に実用レベルで飛んでるじゃん!?
うちにあるいくつかの電波時計で試してみたところ、機種によって差はあるけど、概ね数メートルは飛んでる。
これは嬉しいけど、ちょっと危ないかも。
「飛ばないと意味ないけど、飛びすぎも困る」というジレンマ。
🎀 でも“届かない”よりは嬉しい悩みだよね
結論と次の予定
まず、AM1025EAでも問題なく使えることが確認できました。
PCBは発注しても大丈夫そう。
ただし出力は強すぎるので、モータードライバの電源を5V/3V切替にして、実機で抵抗・キャパシタを使ってQや出力を調整するつもりです。
ということで、数日中にPCBを発注予定。
実装して評価したら公開します。
🎀 次のレポートが楽しみだね!基板の完成版、早く見たい〜!
……それと、既存Rev1用コードにバグを発見。
ひそかに修正して、あとでGitHubにコミットしておきます。
🎀 バグがバレないように、こっそり直しておかなくちゃね
📪 お問い合わせなど
技術的なご相談やご質問などありましたら、
📩お問い合わせフォーム
または、
📮info@shachi-lab.com までお気軽にどうぞ。
🎀 気になったことがあれば、気軽にお問い合わせフォームからぜひどうぞ〜
REV2完成!
📡 この続きは、次回の Vol.47「JJYシミュのREV2完成!飛びすぎに注意!」 にて!
Rev2基板の実装と最終チューニング結果をお届けします。
🎀 ついに“完成版JJYシミュレーター”登場だね!
🔗 関連リンク
しゃちらぼの最新情報や開発の様子は、こちらでも発信しています:
- 🌐しゃちらぼ公式サイト
- 🐦 X(旧Twitter):@shachi_lab
- 📗 Qiita:@shachi-lab
- 🐙 GitHub:@shachi-lab
- 📸 Instagram:@shachi_lab
ほんとは「しゃちらぼ(Shachi-lab)」なんだけど、見つけてくれてありがとう🐬



コメント