「PWM制御」とはマイコンボード等でLEDランプの明るさを調整したり、モーターの速度を変えたりする時に使用される制御方法です。
「PWM制御」の基本動作から「Arduino(ESP32)コマンド」を使ったプログラミング方法、サンプルプログラムを使用して、音(ブザー)と光(LED)で動作確認しながら使い方を詳しく紹介します。
PWM制御でRCサーボモータを使用したラジコンの作り方も、以下のリンクで詳しく紹介しています。
1.PWM制御とは
2.Duty比について
3.分解能と周波数について
4.Arduinoコマンドを使用したPWM制御方法
・PWM制御コマンド(Ledc 〜)一覧
・初期設定
・PWM出力実行①(初期設定の内容でDuty比を指定して実行)
・PWM出力実行②(周波数を指定して実行)
・PWM出力実行③(音階、オクターブを指定して実行)
・その他のコマンド
5.PWM制御を音と光で動作確認(M5StickC Plus使用)
・PWMでLED調光、ブザー音制御(動作確認)
・サンプルプログラム(コピペ)
・プログラムの解説
6 .まとめ
1.PWM制御とは
「PWM制御」とは「パルス幅変調(Pulse Width Modulation)制御」とも呼ばれ、マイコンボード等の出力端子に接続した「負荷の電力を調整」する制御方法です。
「負荷の電力を調整」できるということはLEDランプの明るさを調整したり、モータの速度を可変できるという事になります。
「ON/OFF制御」が「ON」か「OFF」かの2つの状態しか持たないのに対して「PWM制御」では「ON」と「OFF」をパルス状に繰り返し「ON/OFF」のパルス幅を制御することで「ON」から「OFF」までの中間の電圧レベルを作り出すことができ、これにより「負荷の電力を調整」することができます。
「ON/OFF制御」と「PWM制御」をLEDランプの点灯を例に電圧波形で確認してみましょう。
・ON/OFF制御
「ON」で電圧がHighの状態になるとLEDに電流が流れ、LEDランプが点灯します。
「OFF」で電圧がLow(0V)になるとLEDには電流が流れなくなり、LEDランプは消灯します。
・PWM制御
波形の中央部が「PWM制御」部です。ここでは「ON」と「OFF」が同じ時間間隔で交互に繰り返しています。この時LEDには電流が流れたり、流れなくなったりを繰り返します。
「ON」と「OFF」の繰り返しがゆっくりの場合、LEDランプは点滅して見えますが、超高速で点滅する場合「ON」と「OFF」の比率で「ON」の時間比率分の明るさで点灯しているように見えます。
今回の場合は「ON」と「OFF」の間隔が 1:1で同じため、LEDは50%の明るさで点灯しているように見えます。
電圧は常に一定ですが、連続して「ON」している時よりも「ON」の時間が50%になるため、同じ時間で流れる電流の量が50%になり、負荷の電力も50%になります。
このように電圧の「ON/OFF」を繰り返し、「ON」の時間比率(Duty比)を調整して、流れる電流の大きさを変化させることで、負荷の電力を制御することを「PWM制御」といいます。
2.Duty比について
「Duty比」とは「PWM制御」の電圧波形の「High/Low」時間の組み合わせを1周期とした「PWM周期」の「High」時間の比率を「%」で表したものです。
「Duty比」ごとに電圧波形を確認すると以下のようになります。
電圧Highで点灯するLEDランプで例えると「Duty比 20%」より「Duty比 80%」の方が明るく点灯します。
3.分解能と周波数について
「PWM制御」における「分解能」と「周波数」について、先に紹介した「Duty比」と合わせて、下図のPWM出力波形で詳しく紹介します。
「PWM制御」における「分解能」とは「PWM周期を分解できる能力」になります。
PWM周期を何分割できるかを決める値になり、「分解能」が高ければ高いほど、その中で細かく「Duty比」を設定することができます。(最小単位:1/分解能)
「分解能」は「bit数」で決まり、以下の式で求められます。
「8bit」の場合の分解能は、2の8乗で「256」、「12bit」では2の12乗で「4096」となります。
「bit数、分解能」ごとの設定可能な最小Duty比 [%] と「Duty比」ごとの設定値は以下表のようになり、「分解能」が高いほど「Duty比」が細かく設定できるのがわかると思います。
bit数 | 分解能 | 設定可能な 最小Duty比 [%] | Duty比約20% の時の設定値 | Duty比約50% の時の設定値 | Duty比約80% の時の設定値 | Duty比100% の時の設定値 |
---|---|---|---|---|---|---|
4 | 16 | 6.2 | 3 | 8 | 13 | 16 |
8 | 256 | 0.3906 | 51 | 128 | 205 | 256 |
12 | 4096 | 0.0244 | 819 | 2048 | 3277 | 4096 |
16 | 65536 | 0.0015 | 13107 | 32768 | 52429 | 65536 |
「PWM制御」における「周波数(Hz)」とは「1秒間のPWM周期の数」です。
1秒間に「PWM周期」の数が50個の場合は「50Hz」1000個の場合は「1000Hz = 1kHz」となります。
LEDランプの場合「周波数」が低いと点滅しているような「チラツキ」が目立つため、周波数を高くすることで連続点灯しているように制御することができます。
ブザー等スピーカーを接続して「音階」の調整を行う場合は「Duty比」ではなく「周波数」で調整します。
4.Arduinoコマンドを使用したPWM制御方法
M5Stack等に使用されている「ESP32」を搭載したマイコンボードで使用できる「PWM制御」用コマンドの使い方について詳しく紹介します。
・PWM制御コマンド(Ledc 〜)一覧
「PWM制御」を行う「Arduinoコマンド」には「analogWite()」というものもありますが、こちらは周波数が500〜1000Hzで固定で「Duty比」のみの制御となります。
実用的な「PWM制御」を行うためには、周波数の設定も可能な「Ledc 〜」コマンドが便利です。
以下が「Ledc 〜」コマンドの一覧です。
ledcAttachPin( 端子番号 , チャンネル ) ; //チャンネルに対する出力端子を設定
ledcWrite( チャンネル , Duty比 ) ; // 指定したDuty比でPWM出力
ledcWriteTone( チャンネル , 周波数 ) ; // 指定した周波数でPWM出力
ledcWriteNote( チャンネル , NOTE_音階 , オクターブ ) ; // 指定した音階でPWM出力
ledcDetachPin( 端子番号 ) ; // PWM出力端子とチャンネルの結びつきを解除
ledcRead( チャンネル ) ; // チャンネルに指定したデューティ比を取得
ledcReadFreq( チャンネル ) ; // チャンネルに指定した周波数を取得
これらの使い方について、次から詳しく紹介していきます。
・初期設定
「PWM制御」を行うためには以下のように初期設定を行います。
ledcSetup ( チャンネル , 周波数 , bit数(分解能) ) ; //PWM出力波形の初期設定
ledcAttachPin( 端子番号 , チャンネル ) ; //チャンネルに対する出力端子を設定
・チャンネル:PWM出力タイミングを生成するチャンネルで 0〜15の中から指定
16個のチャンネルは 0/1 、2/3 〜 12/13、14/15 でペアになっており、
各ペアで周波数とbit数(分解能)は同じに設定する必要があります。
・周波数:PWM出力周波数を設定します。
・bit数(分解能):PWM周期の分解能をbit数(1〜16)で指定
・ledcAttachPin:チャンネルに対する出力端子を設定
・端子番号:PWM出力を行うマイコンボードの出力端子番号を設定
「周波数」の最大値は使用するマイコンボードのPWM出力波形を生成するための「クロック周波数」から「bit数、分解能」によって以下の式で決まります。
「M5Stack」に使用されている「ESP32」のクロック周波数の80MHzから「bit数、分解能」ごとに設定できる「周波数」の最大値を求めると以下表のようになります。
bit数 | 分解能 | 周波数最大値 |
---|---|---|
1 | 2 | 40,000,000 |
2 | 4 | 20,000,000 |
3 | 8 | 10,000,000 |
4 | 16 | 5,000,000 |
5 | 32 | 2,500,000 |
6 | 64 | 1,250,000 |
7 | 128 | 625,000 |
8 | 256 | 312,500 |
bit数 | 分解能 | 周波数最大値 |
---|---|---|
9 | 512 | 156,250 |
10 | 1024 | 78,125 |
11 | 2048 | 39,063 |
12 | 4096 | 19,531 |
13 | 8192 | 9,766 |
14 | 16384 | 4,883 |
15 | 32768 | 2,441 |
16 | 65536 | 1,221 |
・PWM出力実行①(初期設定の内容でDuty比を指定して実行)
チャンネルに指定した周波数、分解能でPWM出力を実行するためには以下のコマンドを実行します。
一度実行すると指定したPWM波形を出力し続けます。
・チャンネル:初期設定で設定した、使用するチャンネル番号を指定
・Duty比 :PWM周期(分解能)の「Duty比(Highレベルの割合)」を指定
0〜分解能の最大値(0% 〜 100%)
・PWM出力実行②(周波数を指定して実行)
周波数を指定して、以下のようにPWM出力を行うこともできます。
この場合、先に行った初期設定の ledcSetup() の設定は無効になります。
・チャンネル:PWM出力タイミングを生成するチャンネルで、0〜15の中から指定
16個のチャンネルは 0/1 、2/3 〜 12/13、14/15 でペアになっており、
・周波数:出力したい「周波数(Hz)」を数値で指定します。
・PWM出力実行③(音階、オクターブを指定して実行)
音階(ドレミ〜)、オクターブを指定してPWM出力を行うこともできるため、出力端子にスピーカーを接続すれば、指定した音階で音を鳴らすことができます。
この場合、先に行った初期設定の ledcSetup() の設定は無効になります。
・チャンネル:PWM出力タイミングを生成するチャンネルで、0〜15の中から指定
16個のチャンネルは 0/1 、2/3 〜 12/13、14/15 でペアになっており、
・音階:NOTE_音階 「C(ド)〜B(シ)」で指定。
・オクターブ:オクターブ(同一音階で倍の周波数)を 0〜8段階で設定
・その他のコマンド
その他にも以下のようなコマンドがあります。
ledcRead( チャンネル ) ; // チャンネルに指定したデューティ比を取得
ledcReadFreq( チャンネル ) ; // チャンネルに指定した周波数を取得
5.PWM制御を音と光で動作確認(M5StickC Plus使用)
実際にマイコンボード「M5StckC Plus」に内蔵されている「LED」と「ブザー」を使用して動作確認してみましょう。
サンプルプログラム(コピペ)も準備しましたので、書き込んで動作確認してみるとより理解が深まると思います。
「M5StickC Plus」については、以下のリンクで詳しく紹介しています。
開発環境の「ArduinoIDE」の使い方については、以下のリンクで詳しく紹介しています。
・PWMでLED調光、ブザー音制御(動作確認)
「M5StackC Plus」の外部出力端子には「抵抗内蔵LED」の「+(アノード)」を「G0」に、「ー(カソード)」を「GND」に接続します。
「GROVEコネクタ」には「ボリューム(ANGLE UNIT)」を下写真のように接続しています。
「抵抗内蔵LED」と「ボリューム(可変抵抗器)」については、以下のリンクで詳しく紹介しています。
ボリュームを回すことで「Duty比(または周波数、音階の場合はオクターブ)」を調整することができます。
「LED」について、下図のように、本体の「LED(赤)」はPWM出力がLow(0V)の時に点灯します。
対して、外部出力端子に接続した「抵抗内蔵LED(青)」はPWM出力がHigh(3.3V)の時に点灯します。
本体LED赤、出力「Low」で点灯
外部LED青、出力「High」で点灯
このため「Duty比」を変化させて「LED(赤)」が明るくなると「LED(青)」は暗く、LED(青)」が明るくなると「LED(赤)」は暗くなるという動作になります。
このように、接続する負荷の接続方法によっても変化のしかたは異なるので、これについても動作確認してみましょう。
ブザーについて、本体正面のボタンAを押すたびにブザーの「ON/OFF」を切り替えられます。
LEDとブザーには同じPWM波形が出力されます。
音と光でどのような違いになるかを確認してみましょう。
実際に動作確認を行ったものが下写真になります。
まず、本体の液晶画面の表示内容について紹介します。
液晶画面には「ledcSetup()」で設定した「チャンネル、周波数、ビット数(分解能)が表示されます。
「ADC:」にはボリュームからのアナログ入力値(分解能 0〜4095/電圧 0〜3.3V)が表示されます。
「Duty:」には実際に出力されている波形の「Duty比」の設定値を取得して表示し「%」にも換算して表示しています。「Duty比」はボリュームを調整することで変動します。
実際にボリュームで「Duty比」を可変させLEDの動作確認を行うと下写真のようになります。
Duty比 0%
「Duty比」が 0%の時、本体LED(赤)の明るさは100%、外部の抵抗内蔵LED(青)は消灯になります。
Duty比 100%
「Duty比」が 100%の時、本体LED(赤)は消灯、外部の抵抗内蔵LED(青)は明るさが100%になります。
「Duty比」を変化させると、それぞれのLEDが交互に明るくなったり、暗くなったりすることが確認できます。
Duty比 10%
Duty比 50%
Duty比 90%
本体ボタンAを押すごとに、本体ブザーのON/OFFが切り替えられます。
「Duty比」の変化でブザーの音がどのように変化するかが確認できます。
プログラムを書き換えることで、PWM出力を、周波数や音階(オクターブ)で指定して動作確認を行うことができます。
ledcWriteTone()で「周波数(Hz)」を指定してPWM出力を実行した場合は上画像のようになります。
ボリュームのアナログ入力値がそのまま周波数として出力されます。
本体ボタンAを押すとブザーが鳴り、音の変化が確認できます。
ledcWriteNote()で「音階(オクターブ)」を指定してPWM出力を実行した場合は上画像のようになります。
ボリュームのアナログ入力値によって設定した音階(上写真ではラのA)のオクターブが調整できます。
周波数(Hz)の表示は設定した音の周波数がオクターブ倍で段階的に表示されます。
本体ボタンAを押すとブザーが鳴り、音の変化が確認できます。
・サンプルプログラム(コピペ)
サンプルプログラムは以下になります。「コピペ」して書き込んでください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。
/* PWM出力動作確認 */
#include <M5StickCPlus.h> // ヘッダーファイル
// 端子割り付け
#define ADC_PIN 33 // アナログ入力端子
// PWM出力端子設定
#define PWM_OUT_PIN 0 // PWM出力端子番号(外部出力)
#define PWM_LED_PIN 10 // PWM出力端子番号(本体LED赤)
#define PWM_BUZZER_PIN 2 // PWM出力端子番号(本体ブザー)
// PWM出力設定(周波数と分解能はチャンネルのペアでは同じに設定する)
#define CH 1 // PWM出力チャンネル(0,1/ 2,3/ 4,5/ 6,7/ 8,9/ 10,11 /12,13 /14,15でペア)
#define FREQ 1000 // PWM出力周波数(最大周波数 : 80MHz / 2の「bit数」乗)
#define BIT_NUM 10 // bit数(1bit〜16bit)
// 変数設定
float ad_val; // アナログ入力値格納用
float duty; // Duty比
float resolution; // 分解能
float battery; // バッテリー残量表示用
bool state = false; // ボタン操作状態保持用
// 初期設定 -----------------------------------------
void setup() {
M5.begin(); // (LCD有効, POWER有効, Serial有効)
Serial.begin(9600); // シリアル出力初期化(今回は未使用)
// アナログ入力設定
pinMode(ADC_PIN, ANALOG); // アナログ入力
// PWM出力に使用する端子を出力設定
pinMode(PWM_OUT_PIN, OUTPUT); // 外部出力端子
pinMode(PWM_LED_PIN, OUTPUT); // 本体LED
pinMode(PWM_BUZZER_PIN, OUTPUT); // 本体ブザー
// PWM初期設定
ledcSetup(CH, FREQ, BIT_NUM); // PWM設定(チャンネル, 周波数, bit数)
ledcAttachPin(PWM_OUT_PIN, CH); // PWMチャンネル割り当て(端子番号, チャンネル)(外部出力)
ledcAttachPin(PWM_LED_PIN, CH); // PWMチャンネル割り当て(端子番号, チャンネル)(本体LED)
// 液晶表示初期設定
M5.Lcd.fillScreen(BLACK); // 背景色
M5.Lcd.setRotation(1); // 画面向き設定(USB位置基準 0:下/ 1:右/ 2:上/ 3:左)
M5.Lcd.setTextSize(1); // 文字サイズ(整数倍率)
M5.Lcd.setTextFont(4); // フォント
}
// メイン -------------------------------------------
void loop() {
M5.update(); //本体ボタン状態更新
// アナログ入力処理
ad_val = analogRead(ADC_PIN); // アナログ入力値(0〜4095)を取得
// アナログ入力値(0〜4095)を Duty比に換算
resolution = pow(2, BIT_NUM); // 分解能(2のnビット乗)
duty = resolution * (ad_val / 4095); // 換算
// PWM出力実行(以下の3ついずれか1つを有効にして動作確認)
ledcWrite(CH, duty); // チャンネルに指定した周波数、分解能で実行する場合(チャンネル、Duty比)
// ledcWriteTone(CH, ad_val); // 周波数を指定して実行する場合(チャンネル、周波数)
// ledcWriteNote(CH, NOTE_A, (int)(ad_val / 500.0)); // 音階を指定して実行する場合(チャンネル、音階、オクターブ0〜8)
// ド、ド#、レ、ミ♭、ミ、ファ、ファ#、ソ、ソ#、ラ、シ♭、シ
// NOTE_*(*部に音階指定):*= C、 Cs、D、 Eb、 E、 F、 Fs、 G、 Gs、A、 Bb、B
//ボタンAで本体ブザーON/OFF(PWMチャンネル割り当て、解除)
if (M5.BtnA.wasPressed() == 1 && state == false) { // stateがfalseで本体ボタンが押されていたら
ledcAttachPin(PWM_BUZZER_PIN, CH); // PWMチャンネル割り当て(ピン番号, チャンネル)(本体ブザー)
}
if (M5.BtnA.wasPressed() == 1 && state == true) { // stateがtrueで本体ボタンが押されていたら
ledcDetachPin(PWM_BUZZER_PIN); // PWMチャンネル割り当て解除(ピン番号)(本体ブザー)
}
if (M5.BtnA.wasReleased()) { // 本体ボタンが離されていたら
state = !state; // ボタン操作状態反転
}
// バッテリー残量(MAX約4.2V、限界電圧3V)パーセント換算表示
battery = (M5.Axp.GetBatVoltage() - 3) * 90; // バッテリー残量取得、換算
if (battery > 100) { battery = 100; } // 換算値が100以上なら100にする
// LCD表示処理
M5.Lcd.setTextFont(2); // フォント
M5.Lcd.setCursor(200, 3); M5.Lcd.setTextColor(DARKGREY, BLACK);
M5.Lcd.printf("%3.0f%% ", battery); // バッテリー残量表示
M5.Lcd.setTextFont(4); // フォント
M5.Lcd.setCursor(5, 5); M5.Lcd.setTextColor(WHITE, BLACK); // 座標設定(x, y)、(文字色, 背景)
M5.Lcd.print("PWM TEST"); // タイトル
M5.Lcd.setCursor(5, 30); M5.Lcd.setTextColor(ORANGE, BLACK);
M5.Lcd.printf("ch : %d Hz : %4.1f ", CH, ledcReadFreq(CH)); // チャンネル、実際の出力周波数
M5.Lcd.setCursor(5, 55); M5.Lcd.setTextColor(CYAN, BLACK);
M5.Lcd.printf("bit : %d ( %d ) ", BIT_NUM, (int)resolution); // ビット数、分解能(int型にキャスト)
M5.Lcd.setCursor(5, 80); M5.Lcd.setTextColor(GREEN, BLACK);
M5.Lcd.printf("ADC : %4.0f ( %1.1fV ) ", ad_val, ad_val * (3.3 / 4095.0)); // アナログ入力値表示(電圧換算)
M5.Lcd.setCursor(5, 105); M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.printf("Duty : %d( %4.1f%% ) ", ledcRead(CH), ledcRead(CH) * 100.0 / resolution); // Duty比(High状態比率)
delay(100); // 遅延時間(ms)
}
・プログラムの解説
サンプルプログラムの10〜13行目で「チャンネル」「周波数」「bit数」を設定しています。
ここの値を色々なパターンで書き換えて動作確認してみましょう。
29〜32行目で「本体LED(赤)G10」「本体ブザー G2」「外部抵抗内蔵LED(青)用端子G0」を出力端子として設定しています。
35行目でPWM出力波形の初期設定を行なっています。
36, 37行目で「本体LED(赤)G10」と「外部抵抗内蔵LED(青)用端子G0」に使用するチャンネルを設定しています。
「本体ブザー G2」は「本体ボタンA」を押すごとにON/OFFを切り替えるため63行目で設定、66行目で解除しています。
55行目でPWM出力を実行しています。55〜57行目の3つのパターンでPWM出力を実行することができます。どれか1つを有効にして、他の2つはコメントアウトして無効にしましょう。
6 .まとめ
「PWM制御」について紹介しました。
「PWM制御」とは、電圧の「ON/OFF」を繰り返し「Duty比」を調整することで流れる電流の大きさを調整して、負荷の電力を制御する制御方法です。
「Duty比」とは出力電圧波形の「High/Low」時間の組み合わせを1周期とした「PWM周期」の「High」時間の比率を「%」で表したものです。
設定できる「Duty比」の精度は「bit数」から「分解能」で決まり、設定できる最小値は「1/分解能」、最大値は「2のbit数乗」となります。
「周波数(Hz)」は「1秒間のPWM周期の数」で、1秒間に「PWM周期」の数が50個の場合は「50Hz」1000個の場合は「1kHz」となります。
LEDランプの場合「周波数」が低いと点滅しているような「チラツキ」が目立つため、周波数を高くします。ブザー等で「音階」の調整を行う場合は「Duty比」ではなく「周波数」で調整します。
「PWM制御」のプログラムは「Arduino(ESP32)のコマンド(Ledc 〜)」が便利で「M5StickC Plus」等で簡単に動作確認することができます。
「PWM制御」は「LED」の明るさ調整や、モータの回転制御、ブザー音の制御等、用途は幅広く欠かせない技術です。プログラム言語が変わっても基本原理は同じため基本を理解して、使いこなせるようになりましょう。
コメント