こんにちは、ENGかぴです。
ESP32でWebサーバーを実装するとクライアント(スマホなど)からステッピングモーターを操作することができます。WebサーバーとStepperライブラリを使ってスマホに表示したページからステッピングモーターを操作して動作確認しました。
ESP32-WROOM-32E開発ボード(秋月電子)を使用しArduino IDEで開発を行います。Arduinoスターターキットに付属していたステッピングモーター(28BYJ-48-W01)及びドライバー基板を使用しています。
ESP32-WROOM-32Eを使って動作確認したことをまとめています。
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
ステッピングモーターを操作する
ステッピングモーターは回転数を制御するドライバーとセットで使用します。産業用でベルトコンベヤーや水量を調整する弁の開閉に使用されており用途は様々です。
28BYJ-48-W01はユニポーラ方式で巻線に対して一方向の電流を流して起動する電流型です。モーターの巻線の電流により励磁しますが、電流をパターンを切り替えて回転磁界を発生させることで軸(ローター)を回転させる仕組みです。
下記記事ではArduino UNOでステッピングモーターを操作した方法をまとめています。モーターの起動方法やライブラリを使用せずに励磁パターンをつくる方法を説明しています。
本記事ではStepperライブラリを使用してモーターの励磁を行ってステッピングモーターを操作します。モーターの励磁方法は主に以下の通りです。
励磁方式 | 特徴 |
---|---|
1相励磁(Full step) | 1相ずつ順に励磁していくため消費電流は少なくなります。ただし各相に切り替わる際に振動が発生しやすくなります。 |
2相励磁(Full step) | 2相ずつ切り替えるため出力トルクが大きくなりますが消費電流が増えます。1相励磁よりも振動が抑えられるため滑らかな動きとなる。 |
1-2相励磁(Half step) | 1励磁と2相励磁を交互に繰り返す方式。モーターのステップ角の分解能が基本ステップの1/2になりますが、2相励磁よりもさらに滑らかな動きとなる。 |
Stepperライブラリは2相励磁方法でステッピングモーターを回転させる構成です。
Stepperライブラリを使用する
Arduino環境では標準ライブラリでステッピングモーターを操作するStepperライブラリが実装されています。Stepperライブラリを使用するにあたって必要な事項について説明します。押さえておく必要がある項目は以下の3点です。
- 1分間の当たり回転数
- 励磁方式に対する1回転に必要なステップ数
- 回転に必要なステップ数(任意の値)
1.と2.は使用するモーターの仕様に合わせて指定します。28BYJ48-W01は励磁方式として1-2相励磁(Half step)が記載されておりステップ角が5.625/64(Step angle)になっています。これは1-2励磁方式の8ステップ(1セット)を8回(64÷8)繰り返すと5.625度回転します。
無負荷状態では最大で1ms(1000pps)でステップを切り替えることを考慮すると5.625度回転させるまでの経過時間は約64msになります。ギア比の1/64を考慮すると1周に必要な秒数は64ms×64=4096ms(約4秒)となり、1分当たりでは15回転となります。
1回転に必要なステップ数は、5.625度で64ステップ必要であることギア比が1/64であることから64×64=4096ステップになります。
広告
PR:わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
ライブラリの初期化
#include <Stepper.h>
#define PIN_STP_IN1 19
#define PIN_STP_IN2 18
#define PIN_STP_IN3 5
#define PIN_STP_IN4 17
Stepper myStepper(2048, PIN_STP_IN1, PIN_STP_IN3, PIN_STP_IN2, PIN_STP_IN4);
void setup() {
myStepper.setSpeed(15);
}
Stepperライブラリを使用するためにStepper.hをインクルードします。Stepperクラスの変数をmyStepper()でインスタンス化します。引数に使用するステッピングモーターの回転情報や励磁パターンを作るDOピンを指定します。
第1引数にステッピングモーターの1回転当たりのステップ数を指定します。28BYJ48-W01の1回転当たりのステップ数は2048になります。第2引数から第5引数までは励磁に使用するDOピンを指定します。
StepperクラスのsetSpeed()関数で1分当たりの回転速度を指定します。28BYJ48-W01の1分当たりの回転数は無負荷状態で15回なので引数に15を指定しています。回転速度を遅くする場合は15以下の値を指定します。ステッピングモーターのスルー領域を超えるような速度を指定した場合、同期外れ(脱調)のため回転しなくなります。
ステップ数を指定して回転させる
以下の説明ではユーザーがステッピングモーターの軸側を見た時を基準にして回転方向を決めています。
myStepper.step(512); //時計回りの場合
myStepper.step(-512); //反時計回りの場合
Stepperクラスのstep()関数でステッピングモーターを操作するステップ数を指定します。引数にステップ数を指定します。28BYJ48-W01の場合2048を指定すると1回転します。正の値を指定すると時計回りに回転し、負の値を指定すると反時計回りに回転します。
例では90度回転させるため最大値の2048を4で割った512を指定しています。
Webサーバーを使用する
#include <WebServer.h>
WebServer Wserver(80);
void setup() {
Wserver.on("/", HTTP_GET, HtmlSet); //URLを指定して処理する関数を指定
Wserver.on("/", HTTP_POST, HtmlPost); //URLを指定して処理する関数を指定
Wserver.onNotFound(handleNotFound); //URLが存在しない場合の関数を指定
Wserver.begin(); //Webサーバーの開始
}
void loop(){
Wserver.handleClient(); //クライアントからの接続待ち
}
Webサーバーライブラリの使い方は下記記事まとめています。
ESP32-WROOM-32EでWebServerを実装する
WebServerライブラリを使用するためにインクルードします。WebServerの型で変数を宣言しインスタンスを生成します。引数は通信に使用するポート(デフォルトは80)です。初期化関数内でWebServerの設定を行います。
on()関数を使用するとブラウザーで指定したURLで表示するページを分岐させることができます。
on()関数は第1引数にURLアドレスの階層を示す文字、第2引数にHTTPの種類を指定、第3引数に接続要求の処理を行う関数を指定します。
onNotFound()関数はURLが存在しない時に表示する処理を行う関数を指定します。
handleClient()関数でクライアントからのリクエストを常時監視します。クライアントからリクエストがあれば登録したURLに対応する処理が呼び出されます。
クライアントからのリクエストに対してHTMLデータで返信するためHtmlSet()を実装します。
HTMLデータの作成
void HtmlSet(void){
String str = "";
str += "<!DOCTYPE html>";
str += "<html lang = 'ja'>";
str += "<head>";
str += "<meta charset = 'UTF-8'>";
str += "<title>ステッピングモーターの動作確認</title>";
str += "</head>";
str += "<body>";
str += "<h1>ステッピングモーターの動作確認</h1>";
//省略
str += "</body>";
str += "</html>";
Wserver.send(200,"text/html", str);
//HTTPレスポンス200でhtmlデータとして送信
}
HTMLデータの作成方法は様々ですがString型を使って文字列を生成して送信する方法を紹介します。strをString型で宣言しHTMLデータになるように+=でstrに文字列を付け加えていきます。
一文字でも間違うと表示できなくなることがあるためテキストファイルなどでHTMLデータを生成しGoogle Chromeなどのブラウザーで確認することをお勧めします。
回転速度及び回転角度はHTMLデータにはセレクトボックスを使用しています。
<select name='speed' style='font-size:1rem;'>
<option value='0'>15 rpm</option>
<option value='1' selected>12 rpm</option>
<option value='2'>9 rpm</option>
<option value='3'>6 rpm</option>
<option value='4'>3 rpm</option>
<option value='5'>1 rpm</option>
</select>
セレクトボックスの<select>タグでnameをspeedにしています。<option>で選択項目を追加します。<option>タグ内でvalueで選択項目に対する値をセットします。タグ内でselectedを入力するとページを読み込んだ時の初期表示になります。例では12 rpmを表記表示にしています。このソースをページに組み込むとブラウザー状にセレクトボックスが表示されます。
ブラウザー毎に少しフォームの表示が変わりますが、オプションで追加した項目が選択欄に表示されます。
HTMLデータを生成した後はWebServerのsend()関数を使ってクライアントに返信します。
第1引数ではHTTPのレスポンスでとして200(リクエストOK)を指定しています。第2引数では返信するテキスト文字がHTMLタイプであることを通知しています。第3引数では返信する文字列を指定します。
HTMLデータから取得したデータを使用する
void HtmlPost(void){
uint8_t spd_i;
speed= Wserver.arg("speed"); //speedの値を取得
spd_i = speed.toInt(); //整数に変換
switch(spd_i){
case 0:
myStepper.setSpeed(15);
break;
case 1:
myStepper.setSpeed(12);
break;
case 2:
myStepper.setSpeed(9);
break;
}
}
HtmlPost()はHtmlSet()で表示したページからデータ受け取る場合に遷移します。Webサーバーからデータを取得するためにarg()関数を使用します。arg()関数の引数はHTMLデータから取得する要素の名前を指定します。
例ではspeedを取得しています。speedはHtmlSet()のHTMLで設置しているセレクトボックスの任意のグループ名でありブラウザー上で変更するとvalue値が更新されます。arg()関数でvalueの値を取得しています。
arg()関数で取得した値はHTMLデータであるため文字列になります。文字列の数値をtoInt()関数で整数値に変換して回転速度の切り替えや回転角の指定に使用します。
動作確認
ESP32-WROOM-32Eでステッピングモーターを起動する場合、増幅基板を使用してDOの出力電流を増幅してステッピングモーターを励磁します。増幅基板はステッピングモーターとセットになっていることが多く、トランジスタアレイ(ULN2003APGなど)が実装されています。ESP32-WROOM-32EのDOをトランジスタアレイの入力して増幅します。
USB電源のみではステッピングモーター用の消費電流が供給できないため外部からDC5Vを接続しています。ESP32-WROOM-32Eの電源とステッピングモーターのGND(ー)を共通に接続を行います。当記事では電源アダプター(AD-D50P100:秋月電子)を使用しています。
電源をONするとESP32-WROOM-32Eクライアントからの接続要求を待ちます。WiFi通信を使ってブラウザー上でステッピングモーターを操作します。
スマホなどのブラウザーからWiFiのアクセスポイントに接続します。ブラウザーを起動しアクセスポイントのIP「192.168.11.2」を入力します。
速度選択では1分当たりの回転数(rpm)を選択します。15rpm/12rpm/9rpm/6rpm/3rpm/1rpmから選択します。回転角度は90degrees(ステップ数512)/180degrees(ステップ数1024)/270degrees(ステップ数1536)/360degrees(ステップ数2048)から選択します。
速度選択と回転角度を選択した状態で「時計回り」または「反時計回り」のボタンを押すとステッピングモーターが回転します。
速度選択:15rpm、回転角度:360degreesを選択してステッピングモーターが1周するまでの時間を測定すると約4秒となっていることが確認できました。
PR: わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジムPython入門講座の申込
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <Stepper.h>
#include <WebServer.h>
#define PIN_STP_IN1 19
#define PIN_STP_IN2 18
#define PIN_STP_IN3 5
#define PIN_STP_IN4 17
#define PIN_DI 0
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define LED_OFF 100
const int stepsPerRevolution = 2048; //1回転のステップ数
const char *ssid = "EngKapi1"; //SSID
const char *pass = "22223333"; //password
const IPAddress ip(192,168,11,2); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
String speed;
String deg;
String btn;
int16_t deg_v;
uint32_t beforetimCnt = millis();
int16_t timstepoff = TIME_OFF;
bool mstart;
Stepper myStepper(stepsPerRevolution, PIN_STP_IN1, PIN_STP_IN3, PIN_STP_IN2, PIN_STP_IN4);
WebServer Wserver(80);
/* プロトタイプ宣言 */
void mainApp(void);
void mainTimer(void);
void HtmlSet(void);
void HtmlPost(void);
void handleNotFound(void);
void setup() {
Serial.begin(115200);
myStepper.setSpeed(15); //速度の指定
WiFi.softAP(ssid, pass); //WiFiのアクセスポイントの設定
WiFi.softAPConfig(ip, ip, subnet); //アクセスポイントのIP及びサブネットマスク
Wserver.on("/", HTTP_GET, HtmlSet); //URLを指定して処理する関数を指定
Wserver.on("/", HTTP_POST, HtmlPost); //URLを指定して処理する関数を指定
Wserver.onNotFound(handleNotFound); //URLが存在しない場合の処理する関数を指定
Wserver.begin(); //Webサーバーの開始
}
void loop() {
mainTimer();
mainApp();
Wserver.handleClient();
}
/* タイマ管理 */
void mainTimer(void){
if ( millis() - beforetimCnt > BASE_CNT ){
beforetimCnt = millis();
if( timstepoff > TIME_UP ){
--timstepoff;
}
}
}
/* メイン処理 */
void mainApp(void){
if(mstart){
mstart = false;
timstepoff = LED_OFF;
myStepper.step(deg_v);
}
if( timstepoff == TIME_UP ){
timstepoff = TIME_OFF;
//コイルに電流が流れる続けるのを防ぐ
digitalWrite(PIN_STP_IN1, LOW);
digitalWrite(PIN_STP_IN2, LOW);
digitalWrite(PIN_STP_IN3, LOW);
digitalWrite(PIN_STP_IN4, LOW);
}
}
/* クライアントに返信するhtmlデータを生成 */
void HtmlSet(void){
String str = "";
str += "<!DOCTYPE html>";
str += "<html lang = 'ja'>";
str += "<head>";
str += "<meta charset = 'UTF-8'>";
str += "<title>ステッピングモーターの動作確認</title>";
str += "</head>";
str += "<body>";
str += "<h1>ステッピングモーターの動作確認</h1>";
str += "<form method='post'>";
str += "<table border ='1pt' style='background:lightyellow'>";
str += "<tr><th>項目</th><th>回転方向</th></tr>";
str += "<td>";
str += "<label>速度選択:</label>";
str += "<select name='speed' style='font-size:1rem;'>";
str += "<option value='0'";
if( speed =="0"){
str += "selected";}
str +=">15 rpm</option>";
str +="<option value='1'";
if( speed =="1"){
str += "selected";}
str +=">12 rpm</option>";
str +="<option value='2'";
if( speed =="2"){
str += "selected";}
str +=">9 rpm</option>";
str += "<option value='3'";
if( speed =="3"){
str += "selected";}
str +=">6 rpm</option>";
str += "<option value='4'";
if( speed =="4"){
str += "selected";}
str += ">3 rpm</option>";
str += "<option value='5'";
if( speed =="5"){
str +="selected";}
str +=">1 rpm</option>";
str +="</select>";
str +="</td>";
str +="<td>";
str +="<br>";
str += "<input type='submit' value='時計回り' name='btn'";
str += "style='padding: 5px; min-width: 100px;border-radius: 5px;";
str += "font-family: inherit; background: lightgreen; font-size: 1rem'>";
str += "<br><br>";
str +="</td>";
str +="<tr>";
str += "<td>";
str += "<label>回転角度:</label>";
str += "<select name='deg' style='font-size:1rem;'>";
str += "<option value='0'";
if( deg =="0"){
str +="selected";}
str +=">90 degrees</option>";
str +="<option value='1'";
if( deg =="1"){
str += "selected";}
str +=">180 degrees</option>";
str +="<option value='2'";
if( deg =="2"){
str +="selected";}
str +=">270 degrees</option>";
str += "<option value='3'";
if( deg =="3"){
str += "selected";}
str +=">360 degrees</option>";
str +="</select>";
str +="</td>";
str +="<td>";
str +="<br>";
str +="<input type='submit' value='反時計回り' name='btn'";
str +="style='padding: 5px; min-width: 100px;border-radius: 5px;";
str +="font-family: inherit; background: lightgreen; font-size: 1rem'>";
str +="<br><br>";
str +="</td>";
str +="</tr>";
str +="</table>";
str +="</form>";
str +="</body>";
str +="</html>";
Wserver.send(200,"text/html", str);
//HTTPレスポンス200でhtmlデータとして送信
}
/* 送信を押したときに遷移する */
void HtmlPost(void){
String str = "";
uint8_t deg_i;
uint8_t spd_i;
uint8_t spd_v;
deg_v = 0;
speed = Wserver.arg("speed");
deg = Wserver.arg("deg");
btn = Wserver.arg("btn");
Serial.print("btn: ");
Serial.println(btn);
Serial.print("speed: ");
Serial.println(speed);
Serial.print("deg: ");
Serial.println(deg);
spd_i = speed.toInt();
deg_i = deg.toInt();
switch(spd_i){
case 0:
spd_v = 15;
break;
case 1:
spd_v = 12;
break;
case 2:
spd_v = 9;
break;
case 3:
spd_v = 6;
break;
case 4:
spd_v = 3;
break;
case 5:
spd_v = 1;
break;
default:
spd_v = 15;
break;
}
myStepper.setSpeed(spd_v);
switch(deg_i){
case 0:
deg_v = 512;
break;
case 1:
deg_v = 1024;
break;
case 2:
deg_v = 1536;
break;
case 3:
deg_v = 2048;
break;
default:
deg_v = 512;
break;
}
if( strcmp(btn.c_str(),"反時計回り") == 0){
Serial.println("OK");
deg_v *= -1;
}
Serial.print("deg_v: ");
Serial.println(deg_v);
mstart = true;
str += "<html lang=\"ja\">";
str += "<head>";
str += "<meta http-equiv='refresh' content='1;'>";
str += "<meta charset=\"UTF-8\">";
str += "<title>Sensor graph</title>";
str += "</head>";
str += "<body>";
str += "<h1>モーター制御中</h1>";
str += "</body>";
str += "</html>";
Wserver.send(200, "text/html", str);
}
/* URLが存在しない場合の処理 */
void handleNotFound(void) {
String message = "File Not Found\n\n";
message += "URI: ";
message += Wserver.uri();
message += "\nMethod: ";
message += (Wserver.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += Wserver.args();
message += "\n";
for (uint8_t i = 0; i < Wserver.args(); i++) {
message += " " + Wserver.argName(i) + ": " + Wserver.arg(i) + "\n";
}
Wserver.send(404, "text/plain", message); //テキストファイルであることを示している。
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:RUNTEQ(ランテック )- マイベスト3年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
最後まで、読んでいただきありがとうございました。