DRV8830キットとI2C接続 |
ESPr Developerから、模型等に使われるモーターを動かしてみようと思います。今回は秋月電子通商で販売しているI2Cインターフェース内蔵の“DRV8830使用DCモータードライブキット”を使います。DRV8830キットは、I2C接続でCONTROLレジスタに回転方向・停止などの制御内容と速度(電圧)パラメータを書き込んでコントロール出来ます。
接続方法は、秋月電子通商のHPページに説明書や接続例へのリンクがあります。今回は、リンクの接続例と同じ接続です。
先ずはどの様にプログラミングするのかを単純な接続で確認します。過去に投稿したインターネットサイコロや温度データ測定で使用した基板セット(写真右側後方)にDRV8830キットをI2C接続しています。DRV8830キットは接続例通り、電源側にコンデンサーを接続しています。
次の写真は配置を変え、配線を少し判りやすく撮影したものです。ESPR基板セット側ピンソケットコネクタの空き端子に5V電源、GND、I2C端子(SCL・SDA)を外部に取り出せる様にジャンパー配線しています。これらの端子から、DRV8830キット側に電源とI2C信号を接続します。ESPR基板セット側のジャンパー接続前の配線は、下記にありますので参考にしてみて下さい。
DRV8830キット プログラム |
DRV8830キットのI2Cデバイスアドレスは、過去にも使った “ i2c_scanner.ino”なるI2Cバス上に接続されているデバイスをチェックするプログラムを使って調べました。DRV8830のI2Cアドレス設定端子をオープンの状態で“0x64”でした。実際にモータ駆動も特に問題無い様でしたのでこのまま使用しています。
次の動画は確認用プログラムの動作状況です。次の動作フローを繰り返しています。“正転(低速)→正転(高速)→正転(低速)→停止→反転(低速)→反転(高速)→反転(低速)→停止”
低速(0x06 (0.48V))、高速(0x25 (2.97V))の2段階の速度設定しかしていませんが、説明書によると、0x06 (0.48V) ~ 0x3F (5.06V) まで、約0.08V単位で設定できる様です。
プログラムについて簡単に説明します。他のI2Cデバイスと同じようにデバイスアドレス,コントロールレジスタアドレスを指定後、設定値を書き込みます。取扱説明書に記載がある通り、コントロールレジスタの中身は、上位6ビットが速度、下位2ビットで動作内容を指定します。
行番4~6では、デバイスアドレス,レジスタアドレスを設定しています。行番9~12で動作内容、行番15で今回使用する速度を変数に格納します。行番18~24はレジスタアドレス,速度,動作内容を引数として受け取り、I2C通信によってデバイスアドレスの中身を書き変える関数です。行番19で速度データを2ビットシフトし上位6ビットに移動したものと動作内容(下位2ビット)のビットオア(論理和)処理し、書き込みデータとしています。
4つの動作モードがありますが、実際にプログラム中でこの関数を呼び出していますので確認してみて下さい。それぞれプログラム内にコメントがありますので、詳細説明は省略します。
#include <Wire.h> // I2Cアドレス #define ADR_DVC 0x64 // I2Cデバイスアドレス(DRV8830) #define CTR_ADR 0x00 // CONTROL_レジスタ #define FLT_ADR 0x01 // FAULT___レジスタ // 駆動制御 #define D_RDY 0x00 // スタンバイ #define D_REV 0x01 // 逆転 #define D_STD 0x02 // 正転 #define D_BRK 0x03 // ブレーキ // 電圧設定 byte SET_V[]={ 0x00 , 0x06 , 0x25}; // 制御コマンド送信 void w_rg_v(byte reg , byte v_set , byte cont_d){ int reg_data = v_set << 2 | cont_d ; Wire.beginTransmission(ADR_DVC) ; Wire.write(reg) ; Wire.write(reg_data); Wire.endTransmission(true); } void setup() { Wire.begin(12,14); // スタンバイ w_rg_v(CTR_ADR , SET_V[0] , D_RDY); delay(1000); } void loop() { // 順方向 始動(低速) w_rg_v(CTR_ADR , SET_V[1] , D_STD); delay(3000); // 順方向 加速(高速) w_rg_v(CTR_ADR , SET_V[2] , D_STD); delay(3000); // 順方向 減速(低速) w_rg_v(CTR_ADR , SET_V[1] , D_STD); delay(3000); // ブレーキ(停止) w_rg_v(CTR_ADR , SET_V[0] , D_BRK); delay(3000); // 逆方向 始動(低速) w_rg_v(CTR_ADR , SET_V[1] , D_REV); delay(3000); // 逆方向 加速(高速) w_rg_v(CTR_ADR , SET_V[2] , D_REV); delay(3000); // 逆方向 減速(低速) w_rg_v(CTR_ADR , SET_V[1] , D_REV); delay(3000); // ブレーキ(停止) w_rg_v(CTR_ADR , SET_V[0] , D_BRK); delay(3000); }
外部操作用接続 |
次に実際に外部スィッチ,LEDをつけて、人の操作機能を追加してみます。下の写真は、以前 Arduino UNO で評価したI/Oエキスパンダー(MCP23017-E/SP)でデジタル入出力を拡張したものです。MCP23017-E/SPのデバイスアドレスを設定する15~17pinを全てGNDに落としていますので、デバイスアドレスは“0x20”です。他の電源、LED、スィッチ等の接続は宜しければ過去投稿を参考にしてみて下さい。
【参考】I2C接続のI/Oエキスパンダーを使ってみる
尚、計画性なく継ぎ足していますので判りにくく申し訳ありません。ただ、この様に追加しやすいのが、I2C接続の利点であると思います。
I/Oエキスパンダー(MCP23017-E/SP)には、写真の通り左からLED1~7、トグルスィッチ(SW1,SW2)、プッシュスィッチ(SW3)を接続しています。トグルスィッチは左・中間・右の3ヶ所で停まり、中間ではSW1,2ともオフ状態になります。
下の表はI/Oエキスパンダー(MCP23017-E/SP)と各SW,LEDの接続状態です。今回のプログラムで使用するのは、トグルスィッチ(SW1,SW2)と LED1~3です。
外部操作用プログラム |
(1)スィッチ入力時のプロペラ駆動プログラム①
外部スィッチ,LEDを使って、人の操作を考慮したプログラムを検討します。動作としては、トグルスィッチ(SW1)入力時にプロペラは正転、LED1点灯、トグルスィッチ(SW2)入力時にプロペラは逆転、LED2点灯、トグルスィッチ未入力時プロペラは停止、LEDは全消灯になる様にします。
先ず、今回I/Oエキスパンダー(MCP23017-E/SP)を追加していますので、デバイスアドレス定義(行番3~5)、I2C接続による書込関数(行番30~36)/読出関数(行番38~47)を追加しています。
尚、DRV8830への書き込み関数(行番24~28)は、速度データと動作内容を書き込みデータに変換する処理のみを行い、書き込みは書込関数(行番30~36)に集約しています。I/Oエキスパンダーのポート入出力設定は、行番58~62で行っています。
次にメインループ内の処理について説明します。
行番69でI/OエキスパンダーのAポートから、SWの入力状態を読み取った後、行番70でビット反転処理を行っています。(入力がある場合にビットが”1”になる様にしています。)
行番72で1ビット目(SW1)の入力判定を行い、入力がある場合、行番73でLED1を点灯し、行番74で変数 r_MODE に動作内容(正転)を設定します。
同様に行番75で、SW2の入力判定を行い、行番76でLED2を点灯、行番77で変数 r_MODE に動作内容(逆転)を設定します。
行番78はトグルスィッチが中間位置の場合です。(1、2ビット入力がない状態)この場合はLED3を点灯し、動作内容(ブレーキ)を設定します。
動作内容が “ブレーキ” 以外の場合、行番84~94が実行され、トグルスィッチの入力に対応する方向に、低速→高速→低速 の順に回転します。その後、動作内容に関係なく、行番97~99によって3秒間停止し、再びポートA読み取りから処理が再開されます。
#include <Wire.h> // ★ IO-EXPANDER(MCP23017)定数・変数 #define DVC_MCP23017 0x20 // IO-EXPANDER_00 アドレス(外部端子設定) uint8_t U_PA = 0 ; // I/O 読込変数 // 共通 uint8_t r_MODE ; // ◆ DRV8830 I2Cアドレス(モータ制御基板) #define ADR_DVC 0x64 // I2Cデバイスアドレス(DRV8830) #define CTR_ADR 0x00 // CONTROL_レジスタ #define FLT_ADR 0x01 // FAULT___レジスタ // ◆ 駆動制御 #define D_RDY 0x00 // スタンバイ #define D_REV 0x01 // 逆転 #define D_STD 0x02 // 正転 #define D_BRK 0x03 // ブレーキ // ◆ 電圧設定 byte SET_V[]={ 0x00 , 0x06 , 0x25 }; // ◆ 制御コマンド送信 void w_rg_v(byte reg , byte v_set , byte cont_d){ int reg_data = v_set << 2 | cont_d ; wrt_reg( ADR_DVC , reg , reg_data); } // ★ 指定レジスタにデータ書き込み void wrt_reg(byte dvc_adrs , uint8_t reg , uint8_t value) { Wire.beginTransmission(dvc_adrs); // デバイス指定、通信開始 Wire.write((uint8_t)reg); // レジスタ指定 Wire.write((uint8_t)(value)); // データ書込 Wire.endTransmission(); // 送信完了 } // ★ 指定レジスタからデータ読み出し uint8_t read_reg(byte dvc_adrs , uint8_t reg) { Wire.beginTransmission(dvc_adrs); // 送信処理開始 Wire.write(reg); // レジスタ指定 Wire.endTransmission(false); // 送信完了(コネクション維持) Wire.requestFrom(dvc_adrs , 1); // 1byteデータに要求 if (! Wire.available()) return -1; // データ有無判定 return (Wire.read()); // 1byteデータ } // 起動後、初回処理 void setup() { Wire.begin(12,14); // スタンバイ w_rg_v(CTR_ADR , SET_V[0] , D_RDY); Serial.begin(115200); Serial.println("STAND BY"); // DVC_MCP23017(DVCアドレス:0x20) wrt_reg( DVC_MCP23017 , 0x00 , 0xFF ); // I/O-PortA 入力設定 wrt_reg( DVC_MCP23017 , 0x0C , 0xFF ); // I/O-PortA 入力プルアップ設定 wrt_reg( DVC_MCP23017 , 0x01 , 0x00 ); // I/O-PortB 出力設定 wrt_reg( DVC_MCP23017 , 0x13 , 0x00 ); // I/O-PortB 全OFF delay(1000); } // 繰り返し処理 void loop() { U_PA = read_reg( DVC_MCP23017 , 0x12 ); // デバイス・レジスタ指定しデータ読取 uint8_t P_PA = 255 - U_PA ; // 補数(High/Low反転処理) if ((P_PA & 1 ) == 1 ){ wrt_reg( DVC_MCP23017 , 0x13 , 0x01 ); // DVC00 I/O-PortB 出力設定 r_MODE = D_STD ; }else if ((P_PA & 2 ) == 2 ){ wrt_reg( DVC_MCP23017 , 0x13 , 0x02 ); // DVC00 I/O-PortB 出力設定 r_MODE = D_REV ; } else { wrt_reg( DVC_MCP23017 , 0x13 , 0x04 ); // DVC00 I/O-PortB 出力設定 r_MODE = D_BRK ; } if (r_MODE != D_BRK){ // 順方向 始動(低速) w_rg_v(CTR_ADR , SET_V[1] , r_MODE); delay(3000); // 順方向 加速(高速) w_rg_v(CTR_ADR , SET_V[2] , r_MODE); delay(3000); // 順方向 減速(低速) w_rg_v(CTR_ADR , SET_V[1] , r_MODE); delay(3000); } // ブレーキ w_rg_v(CTR_ADR , SET_V[ 0 ] , D_BRK); delay(3000); }
一応、動きましたがこのプログラムには気になることがあります。行番83~99の “delay(3000);” を含むプロペラ運転している間はスィッチ入力を検出することが出来ず、スィッチを変更しても長いときには12秒程度(=3秒X4回)反応しないのです。プログラム②では、応答時間の改善を試みます。
(2)スィッチ入力時のプロペラ駆動プログラム②
トグルスィッチ切り換え時にすぐに反応するプログラムに変更してみました。先ずは動画を確認します。
プログラムの概要を説明します。ループ内の処理を止めてしまう “delay();” は基本的に除去しました。文字変数“DRV_STS”に各処理完了時に異なる文字を設定、次処理に移行します。例えば行番96で“DRV_STS”を”STS01″に設定していますが、この次に実行できる可能性がある処理は行番101~105です。但し、前の処理が完了した time_bef から3秒以上経過時に条件を満たし、その後処理を実行します。時間の計測にはプログラム開始後の経過時間を得ることが出来る millis() という関数を使っています。
この様にすることで、ループの中の処理が繰り返し実行される様になり、スィッチ等の外部入力をリアルタイムで検出できる様になります。
#include <Wire.h> // ★ IO-EXPANDER(MCP23017)定数・変数 #define DVC_MCP23017 0x20 // IO-EXPANDER_00 アドレス(外部端子設定) uint8_t U_PA = 0 ; // I/O 読込変数 // 共通 uint8_t r_MODE = 0 ; uint8_t r_MODE_bef = 0 ; String DRV_STS = "" ; unsigned long time_bef ; // ◆ DRV8830 I2Cアドレス(モータ制御基板) #define ADR_DVC 0x64 // I2Cデバイスアドレス(DRV8830) #define CTR_ADR 0x00 // CONTROL_レジスタ #define FLT_ADR 0x01 // FAULT___レジスタ // ◆ 駆動制御 #define D_RDY 0x00 // スタンバイ #define D_REV 0x01 // 逆転 #define D_STD 0x02 // 正転 #define D_BRK 0x03 // ブレーキ // ◆ 電圧設定 byte SET_V[]={ 0x00 , 0x06 , 0x25 }; // ◆ 制御コマンド送信 void w_rg_v(byte reg , byte v_set , byte cont_d){ int reg_data = v_set << 2 | cont_d ; wrt_reg( ADR_DVC , reg , reg_data); } // ★ 指定レジスタにデータ書き込み void wrt_reg(byte dvc_adrs , uint8_t reg , uint8_t value) { Wire.beginTransmission(dvc_adrs); // デバイス指定、通信開始 Wire.write((uint8_t)reg); // レジスタ指定 Wire.write((uint8_t)(value)); // データ書込 Wire.endTransmission(); // 送信完了 } // ★ 指定レジスタからデータ読み出し uint8_t read_reg(byte dvc_adrs , uint8_t reg) { Wire.beginTransmission(dvc_adrs); // 送信処理開始 Wire.write(reg); // レジスタ指定 Wire.endTransmission(false); // 送信完了(コネクション維持) Wire.requestFrom(dvc_adrs , 1); // 1byteデータに要求 if (! Wire.available()) return -1; // データ有無判定 return (Wire.read()); // 1byteデータ } // 起動後、初回処理 void setup() { Serial.begin(115200); Wire.begin(12,14); // スタンバイ w_rg_v(CTR_ADR , SET_V[0] , D_RDY); // DVC_MCP23017(DVCアドレス:0x20) wrt_reg( DVC_MCP23017 , 0x00 , 0xFF ); // I/O-PortA 入力設定 wrt_reg( DVC_MCP23017 , 0x0C , 0xFF ); // I/O-PortA 入力プルアップ設定 wrt_reg( DVC_MCP23017 , 0x01 , 0x00 ); // I/O-PortB 出力設定 wrt_reg( DVC_MCP23017 , 0x13 , 0x00 ); // I/O-PortB 全OFF delay(1000); } // 繰り返し処理 void loop() { U_PA = read_reg( DVC_MCP23017 , 0x12 ); // デバイス・レジスタ指定しデータ読取 uint8_t P_PA = 255 - U_PA ; // 補数(High/Low反転処理) if ((P_PA & 1 ) == 1 ){ wrt_reg( DVC_MCP23017 , 0x13 , 0x01 ); // DVC00 I/O-PortB 出力設定 r_MODE = D_STD ; }else if ((P_PA & 2 ) == 2 ){ wrt_reg( DVC_MCP23017 , 0x13 , 0x02 ); // DVC00 I/O-PortB 出力設定 r_MODE = D_REV ; } else { wrt_reg( DVC_MCP23017 , 0x13 , 0x04 ); // DVC00 I/O-PortB 出力設定 r_MODE = D_BRK ; } Serial.println(r_MODE_bef) ; if (r_MODE != D_BRK && (r_MODE_bef == 0 || r_MODE_bef == r_MODE)){ r_MODE_bef = r_MODE ; // 順方向 始動(低速) if(DRV_STS==""){ w_rg_v(CTR_ADR , SET_V[1] , r_MODE); DRV_STS = "STS01" ; time_bef = millis() ; } // 順方向 加速(高速) if (DRV_STS == "STS01" && millis() - time_bef > 3000){ w_rg_v(CTR_ADR , SET_V[2] , r_MODE); DRV_STS = "STS02" ; time_bef = millis() ; } // 順方向 減速(低速) if (DRV_STS == "STS02" && millis() - time_bef > 3000){ w_rg_v(CTR_ADR , SET_V[1] , r_MODE); DRV_STS = "STS03" ; time_bef = millis() ; } // ブレーキ if (DRV_STS == "STS03" && millis() - time_bef > 3000){ w_rg_v(CTR_ADR , SET_V[ 0 ] , D_BRK); DRV_STS = "STS04" ; time_bef = millis() ; } // 遅延処理 if (DRV_STS == "STS04" && millis() - time_bef > 3000){ r_MODE_bef = 0 ; DRV_STS = "" ; } }else{ // ブレーキ w_rg_v(CTR_ADR , SET_V[ 0 ] , D_BRK); r_MODE_bef = 0 ; DRV_STS = "" ; delay(500) ; } }
まとめ |
最初モーターのドライバーとして手持ちの東芝“TA7291P”という部品を使うことを考えていましたが、ESPr DEVELOPER が3.3Vなので、レベル変換をしなくてはいけないのか(?)と面倒に思っていたところ、DRV8830キットを見つけ使ってみました。しかしながら、I/Oエキスパンダー(MCP23017-E/SP)使っていますので、I/Oエキスパンダー経由で東芝“TA7291P”を制御すれば良いということに気づきました。
まとめとしましては、計画的に進めようということです。
折角ですので、次回はモーターを使ってちょっとしたおもちゃを作ってみようと思っています。