DCモーター制御の極意:PID制御とPWM出力による速度・方向制御のエキスパートガイド
必要な部品と用品
| Arduino Due × 1(他のArduinoボードでも可) |
| Makeblock Me TFT LCD × 1(オプション。他のディスプレイや無しでも可) |
| 抵抗 4.75kΩ × 4(実装により値は変わる) |
| ロータリーポテンショメータ (汎用) × 4(3つはPID係数調整用オプション) |
| 汎用PNPトランジスタ × 4(実装により選定) |
| DCモーター (汎用) × 1 |
| スライドスイッチ × 1(方向選択用) |
| フォトエレクトリック速度センサー HC-020K × 1 |
| ブレッドボード (汎用) × 1 |
| ジャンパーワイヤー (汎用) × 1 |
アプリとオンラインサービス
- Arduino IDE
このプロジェクトについて
PIDコントローラーとPWM出力によるDCモーターの速度・方向制御
はじめに
ほとんどのプロジェクトではモーターの速度と方向を同時に制御したいと考えますが、多くはモーター制御回路を介して直接PWMをモーターに送る方法を取ります。しかし、この方法では「摩擦」と「慣性」という双子の存在により、目標速度と正確に一致させることはできません。
(この双子を責めないでください。何らかのアクションを起こそうとすると、常に現れて全てを制御下に置こうとします。慣性はアクション前に「考える」時間を与え、摩擦は加速と速度を制限します。「パワー」も完全な制御下にないなら「無」に等しいのです。)
直接PWMで速度制御を試みると、実際の速度は目標値(セットポイント)に決して達せず、大きな誤差が生じます。そこで必要になるのが「PID制御」です。
PIDコントローラーとは
車の運転を想像してください:完全停止から発進するには、巡航時よりアクセルを強く踏みます。ほぼ一定速度で走行中はわずかな調整で済み、加速が過剰ならアクセルを戻します。これが「効率的な運転」であり、PIDコントローラーも全く同じことをします。
コントローラーはセットポイントと実際の出力の差である「誤差信号(e)」を読み取り、3つの成分で制御します:
- 比例 (P): 実際の誤差信号に対するコントローラー出力の傾斜(加速度)を決定
- 積分 (I): 誤差信号を時間積分し、定常偏差を最小化
- 微分 (D): 誤差信号の加速度を監視し、「調整」を加える
詳細な理論はインターネットで検索してください。ここでは実装に焦点を当てます。
ArduinoでのPID実装
float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd) {
float P, I, D;
/* 基本式: U = _Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */
P = _Kp * _E; // 比例成分
I = _Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; // 積分成分
D = _Kp * _Kd * (_E-_Eprev) / _dT; // 微分成分
return (P+I+D);
}
最終出力値は、現在の出力値にPIDコントローラーの出力を加算して決定:
/* 誤差信号、PIDコントローラー出力、モーターへの最終出力(PWM) */
E = RPMset - RPM;
float cPID = controllerPID(E, Eprev, dT, Kp, Ki, Kd);
if ( RPMset == 0 ) OutputRPM = 0;
else OutputRPM = OutputRPM + cPID;
if ( OutputRPM < _minRPM ) OutputRPM = _minRPM;
DCモーター駆動回路
Arduino出力から直接DCモーターを駆動することは推奨しません。モーターには制御ボード出力では供給できない大電流が必要だからです。リレーを使う方法もありますが、機械的接点は中長期で故障します。そこでトランジスタを使用します。
実はDCモーターは電圧ではなく電流で駆動されます。この原理を利用し、4個のBC307A PNPバイポーラトランジスタをブリッジ構成にして、モーターコイルへの電流方向を制御しました(NPNのBC337ならより大きなコレクタ電流に耐えられ好ましいですが、手元にありませんでした)。
モーター電流はトランジスタのエミッタ-コレクタ間を流れるため、ほぼ同一の直流電流増幅率(hfe)を持つトランジスタを選ぶ必要があります。以下の回路で確認し、アンペアメーターの読みがほぼ同じものを選別します。
トランジスタ選定の考慮点
- ベース-エミッタオン電圧 (VBEon): トランジスタをオンにするためにベースに印加する最小電圧
- 典型的な直流電流増幅率 (hfe): モーター電流に近いコレクタ電流でのIC/IB比
- 最大連続コレクタ電流 (ICmax): モーターの直流電流がこの絶対値を超えないこと
ベース接続抵抗値の決定手順:
- コントローラーボード出力の制限を考慮し、ベース電流を最小に(hfeは最大が望ましい)
- コントローラー出力電圧を「トリガー電圧 (VT)」とする
- 必要ベース電流 IBreq = IM / hfe(IM: モーター電流)
- 抵抗両端電圧 VR = VT - VBEon
- 抵抗値 R = VR / IBreq = (VT - VBEon) * hfe / IM
実例:
- モーター電流 IM = 70 mA
- BC307A: ICmax = 100 mA, hfe ≈ 140 (実測), VBEon = 0.62 V
- トリガー電圧 VT = 3.3 V (Arduino Due PWM出力)
- R = 5360 Ω → 2.2kΩ + 2.7kΩ = 4.9kΩを使用(フルRPM範囲カバー、PWM出力から約0.6mA消費)
方向転換と重要な注意点
DCモーターの回転方向を変えるには電流の向きを反転させます。4トランジスタのブリッジ回路で実現:PWM出力#2でT1A/T1B、PWM出力#3でT2A/T2Bを駆動し、モーターへの電流方向を切り替えます。
重要: モーター起動時には定格電流の約130%(小型モーターの場合)もの突入電流が流れます。動作中に極性を反転させると、モーターが完全停止していないため極端な大電流が流れ、電源破損やコイル焼損の原因になります。トランジスタ駆動なら電流制限が働くためこの問題は回避できますが、安全のためプログラム側でも対策を講じています:
if ( Direction != prevDirection ) {
/* モーターへの両PWM出力をゼロに */
analogWrite(_chMotorCmdCCW,0);
analogWrite(_chMotorCmdCW,0);
/* モーターが完全停止するまで待機 */
do {
RPM = 60*(float)readFrequency(_chSpeedRead,4)/_DiscSlots;
} while ( RPM > _minRPM );
}
速度測定
安価なHC-020K速度センサーを使用。供給電圧5V仕様だが、Arduino Dueは3.3V系なので、Dueの3.3V出力から直接給電しても動作しました。以下の関数で周波数を読み取ります:
int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed) {
pinMode(_DI_FrequencyCounter_Pin,INPUT);
byte _DigitalRead, _DigitalRead_Previous = 0;
unsigned long _Time = 0, _Time_Init;
float _Frequency = 0;
if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1);
else {
_Time_Init = micros();
do {
_DigitalRead = digitalRead(_DI_FrequencyCounter_Pin);
if ( (_DigitalRead_Previous==1) && (_DigitalRead==0) ) _Frequency++;
_DigitalRead_Previous = _DigitalRead;
_Time = micros();
} while ( _Time < (_Time_Init + (1000000/_ReadingSpeed)) );
}
return (_ReadingSpeed * _Frequency);
}
HC-020Kのホイールは20スロットなので、読み取った周波数を20で割って1秒あたりの回転数とし、60を掛けてRPMを得ます:
RPM = 60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots;
グラフィカル表示
Makeblock Me TFT LCDで入力と結果を表示。CommandToTFT()関数でシリアル接続先を一箇所で変更可能に。Cartesian_Setup()、Cartesian_ClearPlotAreas()、Cartesian_Line()でグラフ描画領域の準備、クリア、プロットを行います。詳細はMakeblockマニュアルを参照してください。
まとめ
グラフィカル機能あり/なしの2バージョンのプログラムを用意しました。コード内に各関数の詳細な説明があります。
通常、無負荷でもDCモーターの速度を定格の10-20%以下に下げるのは困難ですが、PID制御を用いれば起動後、ほぼ5%まで低速化可能です。
追記 (2018/2/25): NPNトランジスタを使用する場合、電流の向きがPNPと逆になることに注意。PNPはエミッタ→コレクタ、NPNはコレクタ→エミッタ方向に電流が流れるため、極性を逆にする必要があります(PNP: E(+) C(-)、NPN: C(+) E(-))。
コード
グラフィカル機能付き版
完全なコードは長大なため、主要関数のみ掲載。全文は添付ファイルまたはGitHubを参照してください。
グラフィカル機能なし版(速度・方向制御のみ)
シンプルな実装で、PID制御の核心部分のみを含みます。主要関数(readFrequency、controllerPID、setup、loop)は上記と共通です。
回路図
PIDコントローラーによるDCモーター速度制御と、方向転換時の考慮事項を説明するプロトタイプ回路図です。
製造プロセス
- Satellite CCRMAキットでセンサー駆動オーディオエフェクトを構築
- Arduino Nanoとジョイスティックで2軸ステッピングモーターを制御:Visuinoで簡単プログラミング
- Arduino Unoで8×8 LEDマトリックスを制御:ステップバイステップガイド
- 学校向けスマート体温監視システム: 正確、リアルタイム、安全
- Arduino用8ビットI/Oポートライブラリ:デジタル制御を拡張
- Arduino 64キー プロトタイピング キーボードマトリックス: シフトレジスタで自作キーボード
- Arduino Nano用 高性能2.8インチTFTシールド - 320×240 SPIディスプレイ
- Arduino用絶縁アナログ入力回路の設計:LM331 V-F変換+フォトカプラでノイズ耐性を確保
- Arduino Unoで作る低コスト落雷検知器
- Arduino向けスマート屋内ナビゲーションロボットキット
- タングステン・銅合金でDCモーター性能を向上