本記事は読者からの投稿です。
著者: 陈显达
原著タイトル: 【マイコン入門】(四) アプリケーション層ソフトウェア開発者のためのマイコン学習ロード――ESP32 開発ボード PWM モーター制御と割り込みの使い方
原文リンク: https://www.cnblogs.com/1996-Chinese-Chen/p/16846218.html
はじめに
皆さん、こんばんは。前回のブログでは、UART シリアル通信とは何か、そして USB-TTL 変換器を使ってマイコンと C# 上位機とのシリアル通信を実現する方法について説明しました。今回は、PWM の概念・原理と実際のケース、PWM を使ったモーター速度制御について解説します。このコースの最終目標は赤外線リモコンで操作するスマートカーを作ることなので、モーター 4 個、ドライバ 4 個、タイヤ 4 個が必要です。そのため PWM は最終成果物にとって極めて重要であり、実際の開発でも PWM はよく使われる速度制御方式です。
概念
PWM は Pulse Width Modulation(パルス幅変調)の略で、その基本原理はインバータ回路のスイッチング素子のオン・オフを制御し、出力端に振幅は等しいが幅の異なる一連のパルスを生成し、これらのパルスで正弦波や必要な波形を代替する方式です。つまり、出力波形の半周期内に複数のパルスを生成し、各パルスの等価電圧が正弦波形になるようにすることで、得られる出力は滑らかで低次高調波が少なくなります。一定の規則に従って各パルスの幅を変調することで、インバータ回路の出力電圧の大きさを変えたり、出力周波数を変えたりできます。
上記の原理説明は公式的すぎて理解しにくいかもしれません。平たく言えば、電子部品の回路に高低レベルを制御し、一定時間内に高低レベルが出力に波形の揺れを生じさせ、これを PWM 波形と呼びます。コードで PWM の出力波形を制御し、その波形内でハイレベルが通電している時間(すなわちハイレベルの時間)が全体の時間に占める割合、またその PWM 波形内でハイレベルとローレベルが切り替わる頻度を設定することで波形が形成されます。ここで、デューティ比 (Duty Ratio) と周波数という 2 つの概念が導入されます。デューティ比は、ハイレベルの通電時間の総時間に対する比率(この波形におけるハイレベルとローレベルの合計時間)を表し、周波数はこの波形内でハイレベルとローレベルが切り替わる頻度を表します。
下図のように、Arduino シリアルプロッタでは鋸歯状波形が表示されています。下の GIF を見ると、対応するモーターの動きが速い状態から遅い状態へと変化しているのがわかります。

コード解説
void setup() {
Serial.begin(9600);
ledcSetup(0, 5000, 8);
ledcAttachPin(12, 0);
}
void loop() {
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
ledcWrite(0, dutyCycle);
delay(7);
Serial.println(dutyCycle);
}
}
Arduino では LEDC を使って PWM を制御できます。純粋な C 言語の ESP32 開発ボードでは MCPWM を使えますが、Arduino では MCPWM が使えないため、代替として LEDC が用意されています。ESP32 は 16 チャンネルの LED PWM コントローラを搭載しており、これは Espressif の LED PWM 制御を利用しています。ESP32 LED PWM は 8 つの高速チャンネルと 8 つの低速チャンネルに分かれており、異なる周波数とデューティ比を使用してモーターの回転速度制御を実現します。
上記のコードでは、まず ledc のチャンネルを 0、周波数を 5000、8 番目の低速 LED コントローラに設定しています。コードは ledcSetup(0, 5000, 8); です。次に、チャンネルとピンを関連付けるために ledcAttachPin(12, 0); を使い、ピン 12 をチャンネル 0 に接続します。loop 内では、書き込む最大デューティ比が 255 で、0~255 の合計は 256 であることがわかります。これはデューティ比がチャンネルに関係しているためです。前述のとおり、LED PWM コントローラは全部で 16 個あり、ここでは 8 を使用しており、256 は 2 の 8 乗の値なので、デューティ比の最大値は 256 になります。もし値を 10 に設定すれば、デューティ比の最大値は 1024-1 になります。ledcWrite(0, dutyCycle); でデューティ比を対応するチャンネルに書き込み、PWM によるモーター速度制御の設定が完了します。

Arduino が ESP32 (Espressif) の PWM をラップしたものとして、現在知られているのは LEDC で、インストール不要でデフォルトで使用できます。他にも PWM をラップしたライブラリはありますが、個人的に試した限りではこれほど使いやすいものはありませんでした。今後、皆さんも他の便利な PWM ライブラリを探してみてください。
割り込み
PWM の説明が終わったので、次に割り込みとその実際の使用例について説明します。割り込みとは、その名の通り、プログラム実行中に何らかのイベントが発生した場合、現在の作業を中断してそのイベントを先に処理することを指します。この動作が「割り込み」です。コード内でバックグラウンドタスク(純粋な C 言語では)を登録して無限ループで待機させることも可能ですが、それではマイコンの性能を十分に活かせません。そこで、このようなシナリオでは割り込みを使用して、ボタンで LED の点灯を制御するなどの機能を実現します。
Arduino では attachInterrupt 関数を使ってピンに割り込みを追加し、detachInterrupt で割り込みを解除できます。
attachInterrupt 関数には 3 つのパラメータが必要です。1 つ目は割り込みに使用するピン番号、2 つ目は割り込み時に実行する関数、3 つ目は割り込みのタイプです。ESP32 の割り込みでは、Arduino では関数名の先頭に IRAM_ATTR を付けて割り込み関数であることを明示する必要があります。1 つ目のパラメータの digitalPinToInterrupt(27) はピン 27 を割り込みにバインドするための関数です。他にも方法はありますが、公式では推奨されていません。
以下のコードでは、ESP32 の 27 番ピンの割り込みを処理する change 関数を定義し、27 番ピンのレベルで LED の 2 番ピンのレベルを制御して LED の点灯/消灯を切り替えています。まずピン 2 を出力モード、ピン 27 をプルアップ入力モード(プルアップ抵抗が必要な場合に一般的に使われるモード)に設定します。次にピン 27 を割り込みに関連付け、割り込み関数を change、モードを CHANGE に設定します。そして loop 関数内で、ピン 2 に state の値を書き込みます。change 割り込み関数に入ると state が反転され、その後 loop で値が書き込まれます。これにより LED の点灯と消灯を制御できます。ここで注意点があります。マイコンでは割り込みやタイマーは非ブロッキングモードですが、Serial.println 関数はブロッキングでバッファに書き込むため、割り込み関数内でこれを使用すると以下のエラーが連続して発生します。
エラー: Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).
これは println 関数がブロッキング動作を行うため、タイマーが継続実行できなくなることが原因です。どうしてもこの関数を使いたい場合は、中間変数を用意し、loop 関数内で値が変更されたかどうかを判定してからシリアルに出力するようにしてください。
下の GIF では、ボタンを使って LED の点灯/消灯を制御している様子が確認できます。
volatile byte state = LOW;
void IRAM_ATTR change()
{
state = !state;
}
void setup() {
Serial.begin(9600);
pinMode(2, OUTPUT);
pinMode(27, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(27), change, CHANGE);
}
void loop() {
digitalWrite(2, state);
}
2 つ目の方法として、割り込み番号を直接渡す方法もありますが、ESP32 の割り込み番号は公式ドキュメントに記載されていないため、最初の方法(ピン経由)を使う必要があります。最後の方法も使える可能性はありますが、ここでは試していないので、興味があれば試してみてください。
mode パラメータには Arduino では 5 つのモードがサポートされています。
1 つ目は LOW – 低レベルのときに割り込み関数がトリガーされます。
2 つ目は CHANGE – レベルが high から low へ、または low から high へ変化するたびにトリガーされます。
3 つ目は RISING – ピンが low から high に変化するとき(high になってからではありません)にトリガーされます。
4 つ目は FALLING – ピンが high から low に変化するときにトリガーされます。
5 つ目は HIGH – ピンが high レベルのときにトリガーされます。


おわりに
今回は PWM と割り込みの使い方について説明しました。一度に多くの内容をお伝えしたので、理解が難しいかもしれません。わからないことがあればいつでも質問してください。また、今後は更新のペースを少し落として、説明が速すぎて理解できないことがないようにします。次回以降は I2C や SPI のケーススタディも行います。これらの基礎が終わったら、最終目標であるスマートカー製作に取り掛かります。そのために必要な部品をこの 2 日間でまとめてグループに投稿し、購入リンクも共有します。興味のある方は QQ グループに参加し、一緒に学び、議論しましょう。私自身もマイコンを始めたばかりの初心者で、今後は STM32 シリーズのマイコンも研究する予定です。どなたでも気軽に参加して議論・学習してください。
