Raspberry Pi Pico WにWebサーバーを搭載してステッピングモータをWebブラウザから操作する実験(Arduino IDE使用)


Raspberry Pi Pico WにWebサーバ機能を持たせてパソコンとWi-Fi接続して、Webブラウザでステッピングモータを操作する実験を行いましたので紹介します。

プログラミングツールとしてはArduino IDEを使用しています。
作成したプログラムはArduino IDEでスケッチ例として見ることが出来るHellowServerを参考にしています。

ステッピングモータの駆動はArduino言語のStepperライブラリを使用しています。

1.動作動画

以下動画は、今回作成した実験装置を動作させる様子です。
Raspberry Pi Pico Wにステッピングモータを接続して、パソコンとWi-Fi接続してWebブラウザでモータを操作しています。

2.実験装置

ステッピングモータは28BYJ-48とそのドライバーボードのセットを使用しました。このステッピングモータとドライバーボードは複数のネット通販から購入することが出来ます。
コントローラとしてはRaspberry Pi Pico Wを使用して実験装置を組みました。(ステッピングモータ駆動の詳細に関してはこちらを参照して下さい)

28BYJ-48ステッピングモータとドライバ
28BYJ-48ステッピングモータ裏面
実験装置

今回使用したステッピングモータの主な仕様は表1の通りです。このモータは1/64のギアで減速されていて、1-2相励磁の場合にステップ角は5.625°/64なので4096ステップでモータ軸が1回転します。最大自起動周波数が500ppsなので、500パルス/sec以下で起動する必要があります。起動後は徐々に加速を行った場合に、最大で1000パルス/secまで仕様上は可能と思われます。
ただし、Arduino IDEのStepperライブラリーのstep()関数を使用した場合の励磁方式は2相励磁のようです。そのため2048ステップでモータ1回転となります。

項目仕様
相数4相
励磁方式1-2相励磁方式
ステップ角5.625°/64   (減速比1/64)
電圧5VDC
相抵抗22Ω ± 7% 25°C
最大応答周波数1000pps
最大自起動周波数500pps
引き込みトルク800gf.cm / 5VDC 400pps
表1.28BYJ-48ステッピングモータの主な仕様

3.実験装置回路図

図1に実験装置の回路図を示します。1個のステッピングモータをRaspberry Pi Pico Wに接続しています。

28BYJ-48ステッピングモータのドライバーボードはテキサスインスツルメンツのULN2003ANと言うダーリントントランジスタアレイが使用されています。ドライバーボード自体の資料が無かったので、テスタと基板のパターンを見て回路を調べました。

ステッピングモータのドライバーボードの入力信号IN1~IN4をRaspberry Pi Pico WのGP0~GP3に接続しました。

Wi-Fiルータを介してパソコンと接続します。パソコンのWebブラウザでステッピングモータを操作します。

図1.実験装置の回路図

4.プログラムに関して

今回作成したプログラムは、パソコンからRaspberry Pi Pico WをWi-Fi接続して、パソコンのブラウザでhttp://picow/にアクセスすると図2の様なホームページ画面が表示されます。プログラミングツールとしてはArduino IDEを使用しています。

画面のCWボタンをクリックしている間ステッピングモータがCW方向に回転し、CCWボタンをクリックしている間ステッピングモータがCCW方向に回転します。また、モータの現在位置が表示されます。以下で作成したプログラムに関して説明します。

図2.ホームページ画面

4-1.プログラムで使用したWebサーバ関係の関数の説明

最初に今回作成したプログラムで使用したWi-FiとWebServer関係の関数の概要を表2に示します。
作成したプログラムの説明用として記載したので、各関数のクラス部分はプログラム中で使用しているインスタンス名のまま記載しています。
関数の引数のバリエーションもプログラムで使用したものに限り記載しています。全てのバリエーションを記載した厳密なものではありませんので、ご注意お願い致します。

関数等説明
#include <WiFi.h>Wi-Fi関係のヘッダファイルのinclude
#include <WebServer.h>Webサーバ関係のヘッダファイルのinclude
#include <LEAmDNS.h>マルチキャストDNS関係のヘッダファイルのinclude
WiFi.mode(WiFiMode_t m)Wi-Fiのモードを設定する。
引数: WiFiMode_t
WIFI_OFF: (0) WiFi off
WIFI_STA: (1) WiFi stationモード
WIFI_AP: (2) WiFi APモード
WIFI_AP_STA: (3) WiFi stationモードかつAPIモード
戻り値: void
WiFi.begin(const char *ssid, const char *passphrase)Wi-Fi接続を開始する。
引数:
const char *ssid: SSIDの文字列のポインタ
const char *passphrase: パスワードの文字列のポインタ
戻り値: int
WL_CONNECTED: (3) ネットワークに接続されている
WL_IDLE_STATUS: (0) 電源入っているがネットワークに接続されていない
WiFi.status()現在のWi-Fiの接続状態をリターン。
戻り値: uint8_t (wl_status_tで定義)
WL_NO_SHIELD: (255)
WL_STOPPED: (254)
WL_IDLE_STATUS: (0)
WL_NO_SSID_AVAIL: (1)
WL_SCAN_COMPLETED: (2)
WL_CONNECTED: (3)
WL_CONNECT_FAILED: (4)
WL_CONNECTION_LOST: (5)
WL_DISCONNECTED: (6)
WiFi.localIP()Wi-Fiルータから現在割り当てられているIPアドレスを読み出す。
戻り値: IPAddress
MDNS.begin(const char *pcHostname)ホスト名を設定して マルチキャストDNSのレスポンダーを起動する。すべての「ループ」で MDNS.update() を呼び出すことによって、mDNSの処理が実行される。
引数: const char *pcHostname: ホストネームのポインタ
戻り値: bool
true: OK, false; NG
MDNS.update()MDNS.begin()関数で、マルチキャストDNSのレスポンダーを起動後、すべての「ループ」でMDNS.update()を呼び出すことで、mDNSの処理が実行される。
戻り値: bool
ture: OK, false: NG
WebServer webserver(int port);WebServerクラスのコンストラクタ。
引数:int
port: ポート番号
webserver.on(const Uri &uri, HTTPServer::ThandlerFunction fn)指定したURIにリクエストがあった時に呼び出される関数を登録する。
引数:
uri: URIテキストのアドレス
HTTPServer::ThandlerFunction fn: URIにリクエストがあった時に呼び出される関数名
webserver.begin()Webサーバを開始する。
戻り値: void
webserver.handleClient()Webサーバ処理を実行する。すべての「ループ」で呼び出すことによってWebサーバ処理が行われる。
戻り値: void
webserver.uri()クライアントからアクセスされたURIを返す。
戻り値: String URI値
webserver.method()クライアントからアクセスされたメソッドを返す。(GET,POST等)
戻り値: HTTPMethod
(例)
HTTP_DELETE: (0)
HTTP_GET: (1)
HTTP_HEAD: (2)
HTTP_POST: (3)
webserver.args()Argumentの数を呼び出す。
戻り値: int
webserver.arg(int i)Argumentを呼び出す。
引数: int i
戻り値: String
webserver.send(int code, const char *content_type, const arduino::String &content)クライアントに応答を送信する。
引数:
int code: HTTP 応答コード。200 (OK) 、 404 (Not Found)等
const char *content_type: HTTP コンテンツ タイプ。”text/plain” や “image/png” 等。
String &content: 実際のコンテンツ本体のアドレス (テキスト等)
戻り値: void
表2.Arduino IDE Webサーバ関係関数説明

4-2.ステッピングモータ駆動関係関数の説明 (Stepperライブラリ)


表3にステッピングモータを駆動するStepperライブラリの概略を表3に示します。
作成したプログラムの説明用として記載したので、各関数のクラス部分はプログラム中で使用しているインスタンス名のまま記載しています。

関数等説明
#include <Stepper.h>ステッピングモータ関係のヘッダファイルのインクルード。
Stepper stepper(steps, pin1, pin2, pin3, pin4)Stepperクラスのコンストラクター。
引数:
steps: 1回転あたりのステップ数(int)
pin1,pin2: モータに接続されているピンの番号
pin3,pin4: 4ピンのモータの場合のピン番号
戻り値: 作成されたインスタンス
stepper.setSpeed(rpms)ステッピングモータの回転速度の設定。
rpms:モータの回転速度。1分間あたり何回転するかを正の数で設定。(long)
step()をコールしたときのスピードをセットする。
stepper.step(steps)ステッピングモータを回転させる。
steps:モータが回転する量(ステップ数)。逆回転させる場合は負の値を設定。(int)
回転が終了するまで関数を抜け出ない。
表3.Arduino IDEのStepperライブラリーの関数説明

4-3.プログラム説明

今回作成したプログラムに関して4つの部分に分割して説明します。

①初期処理 共通変数とsetup()関数

初期処理を行うsetup()関数等のソースコードを以下に示します。

#include <WiFi.h>
#include <WebServer.h>
#include <LEAmDNS.h>
#include <Stepper.h>

const char* ssid = "ssid_data";
const char* password = "password";

WebServer webserver(80);

//---ステッピングモータ関係変数-----
#define RPM_SPEED 10      //モータ回転速度(rpm)
#define STEPS 2048        //モータの1回転のステップ数

//経過時間判断用データ
unsigned long last_time = 0;       //前回の時間記憶
unsigned long delay_time = 0;      //処理待ち時間(次の処理をするまでの時間)

//回転指令フラグ
bool cw_mov_flag = false;   //CW回転指令フラグ
bool ccw_mov_flag = false;  //CCW回転指令フラグ

//軸の現在位置記憶用データ(符号付きステップ位置)
long current_pos = 0;

//ステッピングモータ初期設定
Stepper stepper(STEPS, 0, 2, 1, 3);//ステッピングモータ設定(1回転2048ステップ)

void setup(void) {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);  //WiFiステーションモード
  WiFi.begin(ssid, password); //WiFiスタート
  Serial.println("");

  //Wifi接続待ち
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("Connected to WiFi");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  //mDNSのURLをhttp://picowとする
  if (MDNS.begin("picow")) {
    Serial.println("MDNS responder started");
  }

  //http://picow/に対するハンドル設定
  webserver.on("/", handleRoot);
  //http://picow/cwに対するハンドル設定
  webserver.on("/cw", handleCw);
  //http://picow/ccwに対するハンドル設定
  webserver.on("/ccw", handleCcw);
  //http://picow/stopに対するハンドル設定
  webserver.on("/stop", handleStop);
  //http://picow/posに対するハンドル設定
  webserver.on("/pos", handlePos);
  //該当URL無の場合のハンドル設定
  webserver.onNotFound(handleNotFound);

  webserver.begin();   //Webサーバースタート
  Serial.println("HTTP webserver started");

  //----ステッピングモータ関係セットアップ-----
  pinMode(0, OUTPUT);       //ステッピングモータ(青線)
  pinMode(1, OUTPUT);       //ステッピングモータ(桃線)
  pinMode(2, OUTPUT);       //ステッピングモータ(黄線)
  pinMode(3, OUTPUT);       //ステッピングモータ(橙線)

  //ステッピングモータ回転速度設定
  stepper.setSpeed(500);    //ステッピングモータの回転速度500rpm

  //処理待ち時間の設定
  delay_time = 60 * 1000 * 1000 / STEPS / RPM_SPEED;
}

【説明】
1行目~4行目:
各種includeです。
#include <WiFi.h> Wi-Fi関係のヘッダファイルのインクルード
#include <WebServer.h> Webサーバ関係のヘッダファイルのインクルード
#include <LEAmDNS.h> マルチキャストDNS関係のヘッダファイルのインクルード
#include <Stepper.h> ステッピングモータ駆動関係のヘッダファイルのインクルード

6行目、7行目:
const char* ssidとconst char* passwordに使用するWi-FiルータのSSIDとパスワードをセットします。

32行目~40行目:
Wi-Fi接続に到るまでの処理です。33行目のWiFi.begin()関数でWi-FiルータのSSIDとパスワードを指定してWi-Fi接続を行います。37行目のWiFi.status()関数でWi-Fi接続の状態を確認してWi-Fiが接続されるまで待ちます。

48行目~50行目
MDNS.begin(“picow”)関数で、ホスト名を”picow”としてマルチキャストDNS (mDNS)をスタートします。mDNSはローカルエリアネットワーク内でホスト名からIPアドレスを割り出す機能です。具体的にはブラウザから「http://picow/」とアクセスした時に、Wi-Fiルータによって割り当てられたRaspberry Pi Pico WのIPアドレスが割り出され、そのIPアドレスに対してアクセスされます。

53行目~65行目
webserver.on()関数は、指定したURIにリクエストがあった時に呼び出される関数を登録する関数です。ここでは表4の様に関数を登録しています。
これらの登録を行った後にwebserver.begin()関数でWebサーバをスタートしています。

URIリクエスト呼び出される関数
http://picow/ブラウザで表示するホームページのHTMLデータの呼び出し要求handleRoot()
http://picow/cwモータをCW方向に回転するhandleCw()
http://picow/ccwモータをCCW方向に回転するhandleCcw()
http://picow/stopモータを停止するhandleStop()
http://picow/posモータの現在位置の呼び出し要求handlePos()
表4.URIアドレスに対する呼出し関数登録

69行目~78行目:
ステッピングモータ関係の設定部分です。

69行目~72行目:
pinMode(0, OUTPUT);~pinMode(3, OUTPUT);の処理で、I/OのGP0~GP3を出力ピンとしてステッピングモータ線に割り当てています。

75行目:
stepper.setSpeed(500)でステッピングモータの回転速度を500rpmに設定しています。但し、今回のプログラムではステッピングモータを駆動するstepper.step(steps)関数のstepsの値は「1」か「-1」で1ステップずつの駆動しかしていません。これはstepper.step()関数から出来るだけ早く抜け出して他の処理を行うためです。
よって、stepper.setSpeed()関数で回転速度は決まりません。stepper.step()関数からは出来るだけ速く抜け出るようにstepper.setSpeed(rpms)のrpmsの値は大きめに設定しています。

78行目:
delay_time = 60 * 1000 * 1000 / STEPS / RPM_SPEED;
はステッピングモータを1ステップ駆動した後に次に1ステップ駆動するまでの待ち時間をモータの回転速度(rpm)とモータの1回転のステップ数から計算しています。時間の単位はμsecです。

②ループ処理 loop()関数

繰り返し処理を行うloop()関数のソースコードを以下に示します。この部分でWebサーバ処理、mDNS処理、ステッピングモータの駆動処理を行っています。

void loop(void) {
  //----Webサーバ関係処理--------------------
  webserver.handleClient(); //Webサーバ処理
  MDNS.update();            //mDNS処理

  //-----モータ回転処理----------------------
  //前回処理からの経過時間計算
  unsigned long pass_time;
  unsigned long now_time = micros();    //現在時刻読出し(usec単位)
  if (now_time >= last_time){
    pass_time = now_time - last_time;   //経過時間計算
  }else{
    //オーバフローを加味して経過時間計算
    pass_time = now_time - (0xffffffffUL - last_time + 1);
  }
  
  //モータ回転処理
  if (pass_time >= delay_time){     //時間到達?
    last_time = now_time;
    if (cw_mov_flag == true){       //CW回転?
      stepper.step(-1);             //-1ステップ回転
      current_pos--;                //現在位置マイナス
    }
    else if (ccw_mov_flag == true){ //CCW回転?
      stepper.step(1);              //1ステップ回転
      current_pos++;                //現在位置プラス
    }
  }
}

【説明】
3行目:
webserver.handleClient()をloop関数内で繰り返し実行することによって、Webサーバのクライアントとの通信処理が行われます。

4行目:
MDNS.update()をloop関数内で繰り返し実行することによって、マルチキャストDNS (mDNS)の処理が行われます。

8行目~28行目:
ステッピングモータの駆動処理を行っています。

8行目~15行目:
今回のプログラムでは、一定時間間隔毎にステッピングモータを1ステップずつ駆動させています。このステップ駆動間の時間を取るのに、現在時間を読み込むmicros()関数を使用しています。
micros()関数はプログラム実行を開始した時から現在までの時間をマイクロ秒単位で返す関数です。この関数で現在の時間を得て、前回処理した時の時間との差が予定した一定時間になった時にステッピングモータを1ステップ駆動することによって一定速度の回転が可能となります。

但し、注意が必要なのは、micros()関数のリターン値はunsigned longの32bitでオーバーフローすると零に戻ります。実際には0xffffffff (4294967295)を超えると零に戻ります。これは71.6分に相当します。
よって、micros()関数で得た現在時間から、前回の処理時にmicros()関数で得た時間を引いて経過時間を計算すると、前回処理した時の時間が71.6分近くの場合に、次の時間読込が0付近の値となってしまい経過時間が正しく計算出来ません。

今回作成したプログラムでは、現在時間と前回の時間を比較して、
現在時間 < 前回の時間
の場合は途中でオーバフローが発生したと判断して、次の様に経過時間を計算しています。
経過時間 = 現在時間 – (0xffffffffUL – 前回の時間 + 1)

18行目~28行目:
if (pass_time >= delay_time)で経過時間が予め予定した時間以上になっているか判断します。
経過時間が予定時間以上の場合に、cw_mov_flagがtureの場合はCW方向に1ステップ、ccw_mov_flagがtureの場合はCCW方向に1ステップステッピングモータを駆動しています。(cw_mov_flagはhttp://picow/cwにアクセスがあった時にtureにセットされています。ccw_mov_flagはhttp://picow/ccwにアクセスがあった時にtureにセットされています。)

ステッピングモータの駆動は、stepper.step(steps)関数をしています。これはStepperライブラリの関数でステッピングモータをstepsで指定したステップ数駆動します。この関数での処理待ちを避けるために1ステップの送りとしています。

③ホームページ画面表示(http://picow/)に対する処理(handleRoot関数)

本プログラムでは、パソコンのWebブラウザから「http://picow/」のアドレスにリクエストが要求された場合、Raspberry Pi Pico WはパソコンへHTMLデータを送信します。これによってWebブラウザにホームページが表示されます。①で示したように、webserver.on()関数を使用して「http://picow/」のアドレスにリクエストがあった場合は、handleRoot()関数が呼び出されるように設定しています。handleRoot()関数でHTMLデータを作成してクライアントであるパソコンに送信します。このHTMLデータでWebブラウザに表示される画面を図3に示します。

図3.ホームページ表示画面

以下にhandleRoot()関数のソースコードを示します。HTMLのテキストを作成してクライアントに送信しています。

//HTMLページ表示(http://picow/)
void handleRoot() {
  String html = "<html>\n";
  html += "<head>\n";
  html += "<meta charset=\"utf-8\">\n";
  html += "<title>ステッピングモータテスト</title>";
  //JavaScript
  html += "<script language=\"JavaScript\">\n";
  html += " function cw_motor() {\n";     //モータCW回転関数
  html += "   fetch('/cw');\n";
  html += " }\n";
  html += " function ccw_motor() {\n";    //モータCCW回転関数
  html += "   fetch('/ccw');\n";
  html += " }\n";
  html += " function stop_motor() {\n";   //モータ停止関数
  html += "   fetch('/stop');\n";
  html += " }\n";
  html += " function pos_load() {\n";     //現在位置読込関数
  html += "   fetch('/pos')\n";             
  html += "     .then(response => response.text())\n";
  html += "     .then(data => document.posform.pos.value=data);\n";
  html += " }\n";
  html += " </script>\n";
  //スタイルシート
  html +=  "<style type=\"text/css\">\n";
  html += " .button {\n";           //押し釦スイッチ用
  html += "   width: 300px;\n";
  html += "   height: 150px;\n";
  html += "   font-size: 400%;\n";
  html += " }\n";
  html += " .textbox {\n";          //位置表示テキスト用
  html += "   width: 300px;\n";
  html += "   height: 60px;\n";
  html += "   font-size: 300%;\n";
  html += " }\n";
  html += "</style>\n";
  html += "</head>\n";
  //Body
  html += "<body leftmargin=\"80\"topmargin=\"55\">\n";
  html += "<font size=\"7\">ステッピングモータテスト</font>\n";
  html += "<h1> <br></h1>\n";
  //現在位置表示フォーム
  html += "<form name=\"posform\">\n";
  html += "<font size=\"8\">現在位置</font>\n";
  html += "<input type=\"text\"name=\"pos\"value=\"0\"class=\"textbox\">\n";
  html += "</form>\n";
  //CW押し釦スイッチ
  html += "<input type=\"button\"value=\"CW\"\n";
  html += " ontouchstart=\"cw_motor()\"\n";
  html += " ontouchend=\"stop_motor()\"\n";
  html += " onmousedown=\"cw_motor()\"\n";
  html += " onmouseup=\"stop_motor()\"\n";
  //CCW押し釦スイッチ
  html += " class=\"button\">\n";
  html += "<input type=\"button\"value=\"CCW\"\n";
  html += " ontouchstart=\"ccw_motor()\"\n";
  html += " ontouchend=\"stop_motor()\"\n";
  html += " onmousedown=\"ccw_motor()\"\n";
  html += " onmouseup=\"stop_motor()\"\n";
  html += " class=\"button\">\n";
  //現在位置読込スクリプト
  html += "<script language=\"JavaScript\">\n";
  html += " setInterval(\"pos_load()\",100);\n";
  html += "</script>\n";
  html += "</body>\n";
  html += "</html>";

  //HTMLデータをクライアントに送る
  webserver.send(200, "text/html", html);
}

【説明】
9行目~22行目:
JavaScriptの関数の定義を行っています。それぞれの関数は以下の場合に呼び出されます。

関数呼び出される条件処理
cw_motor()CWボタンがマウスダウンされた時fetch()関数を使用してhttp://picow/cwにアクセス。
ccw_motor()CCWボタンがマウスダウンされた時fetch()関数を使用してhttp://picow/ccwにアクセス。
stop_motor()CWボタンまたはCCWボタンがマウスアップされた時fetch()関数を使用してhttp://picow/stopにアクセス。
pos_load()setInterval()関数を使用して、100ms毎に呼び出されるように設定(63行目)fetch()関数を使用してhttp://picow/posにアクセス、モータの現在位置を呼び出す。
.thenで得られたデータをテキストに変換して現在位置表示用のフォームに表示。
表5.JavaScript関数内容

47行目~60行目:
CWボタンとCCWボタンの定義をしているHTMLのタグです。押し釦を操作した時に呼び出されるJavaScriptの関数を定義しています。パソコンのブラウザとスマホのブラウザでは、ボタン操作で発生するイベントが表6の様に異なるので、両方に対応するようにしています。

操作パソコンスマホ
押し釦上のマウスのボタンを押した時onmousedownイベント
押し釦上のマウスのボタンを離した時onmouseupイベント
押し釦に触れた時ontouchstartイベント
押し釦から指を離した時ontouchendイベント
表6.押し釦操作に関する発生イベント

69行目:
webserver.send()関数で、作成したHTMLテキストをクライアント(パソコン)に送ります。

④その他URIに対するハンドル処理関数

その他のURIアドレスアクセスに対するハンドル関数のソースコードを以下に記載します。

//CW回転要求(http://picow/cw)
void handleCw() {
  ccw_mov_flag = false;                 //CCW回転フラグリセット
  cw_mov_flag = true;                   //CW回転フラグセット

  //OKレスポンスをクライアントに送る
  webserver.send(200, "text/plain", "OK"); // レスポンスを送信
}

//CCW回転要求(http://picow/ccw)
void handleCcw(){
  cw_mov_flag = false;                  //CW回転フラグリセット
  ccw_mov_flag = true;                  //CCW回転フラグセット

  //OKレスポンスをクライアントに送る     
  webserver.send(200, "text/plain", "OK"); // レスポンスを送信
}

//回転停止要求(http://picow/stop)
void handleStop(){
  cw_mov_flag = false;                  //CW回転フラグリセット
  ccw_mov_flag = false;                 //CCW回転フラグリセット

  //OKレスポンスをクライアントに送る    
  webserver.send(200, "text/plain", "OK"); // レスポンスを送信
}

//現在位置要求(http://picow/pos)
void handlePos(){
  String text = String(current_pos);
  //現在位置送信
  webserver.send(200, "text/plain", text);
}

//該当URL無
void handleNotFound() {
  digitalWrite(LED_BUILTIN, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += webserver.uri();
  message += "\nMethod: ";
  message += (webserver.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += webserver.args();
  message += "\n";
  for (uint8_t i = 0; i < webserver.args(); i++) {
    message += " " + webserver.argName(i) + ": " + webserver.arg(i) + "\n";
  }

  //URL無のメッセージをクライアントに送る
  webserver.send(404, "text/plain", message);
  digitalWrite(LED_BUILTIN, 0);
}

【説明】
2行目:handleCw()関数
「http://picow/cw」にアクセスがあった時に呼び出される関数です。cw_move_flagをtrueにしています。このcw_move_flagはloop()関数内でステッピングモータをCW方向に回転するための判断に使用しています。

11行目:handleCcw()関数
「http://picow/ccw」にアクセスがあった時に呼び出される関数です。ccw_move_flagをtrueにしています。このccw_move_flagはloop()関数内でステッピングモータをCCW方向に回転するための判断に使用しています。

20行目:handleStop()関数
「http://picow/stop」にアクセスがあった時に呼び出される関数です。cw_move_flagとccw_move_flagを共にfalseにしています。これらのフラグははloop()関数内でステッピングモータの回転を停止するための判断に使用しています。

29行目:handlePos()関数
「http://picow/pos」にアクセスがあった時に呼び出される関数です。ステッピングモータの現在位置データをwebserver.send()関数を使用してクライアント(パソコン)に送信しています。クライアント側はJavaScriptのfetch()関数を使用して「http://picow/pos」にアクセスし、送られてきた現在位置データを受信して表示しています。

36行目:handleNotFound()関数
「http://picow/—-」にアクセスがあって該当アドレスが存在しないときに呼び出される関数です。websever.uri()関数で呼び出されたURI、webserver.method()関数で使用されたメソッド、webserver.args()関数でArgument数、webserver.arg()関数でArgumentを呼び出し、これらを使用してメッセージテキストを作成して、クライアント(パソコン)へ送信しています。
クライアント側のブラウザはこのメッセージを表示します。

4-4.全プログラム

全プログラムを以下に示します。

#include <WiFi.h>
#include <WebServer.h>
#include <LEAmDNS.h>
#include <Stepper.h>

const char* ssid = "ssid_data";
const char* password = "password";

WebServer webserver(80);

//---ステッピングモータ関係変数-----
#define RPM_SPEED 10      //モータ回転速度(rpm)
#define STEPS 2048        //モータの1回転のステップ数

//経過時間判断用データ
unsigned long last_time = 0;       //前回の時間記憶
unsigned long delay_time = 0;      //処理待ち時間(次の処理をするまでの時間)

//回転指令フラグ
bool cw_mov_flag = false;   //CW回転指令フラグ
bool ccw_mov_flag = false;  //CCW回転指令フラグ

//軸の現在位置記憶用データ(符号付きステップ位置)
long current_pos = 0;

//ステッピングモータ初期設定
Stepper stepper(STEPS, 0, 2, 1, 3);//ステッピングモータ設定(1回転2048ステップ)

void setup(void) {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);  //WiFiステーションモード
  WiFi.begin(ssid, password); //WiFiスタート
  Serial.println("");

  //Wifi接続待ち
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("Connected to WiFi");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  //mDNSのURLをhttp://picowとする
  if (MDNS.begin("picow")) {
    Serial.println("MDNS responder started");
  }

  //http://picowに対するハンドル設定
  webserver.on("/", handleRoot);
  //http://picow/cwに対するハンドル設定
  webserver.on("/cw", handleCw);
  //http://picow/ccwに対するハンドル設定
  webserver.on("/ccw", handleCcw);
  //http://picow/stopに対するハンドル設定
  webserver.on("/stop", handleStop);
  //http://picow/posに対するハンドル設定
  webserver.on("/pos", handlePos);
  //該当URL無の場合のハンドル設定
  webserver.onNotFound(handleNotFound);

  webserver.begin();   //Webサーバースタート
  Serial.println("HTTP webserver started");

  //----ステッピングモータ関係セットアップ-----
  pinMode(0, OUTPUT);       //ステッピングモータ(青線)
  pinMode(1, OUTPUT);       //ステッピングモータ(桃線)
  pinMode(2, OUTPUT);       //ステッピングモータ(黄線)
  pinMode(3, OUTPUT);       //ステッピングモータ(橙線)

  //ステッピングモータ回転速度設定
  stepper.setSpeed(500);    //ステッピングモータの回転速度500rpm

  //処理待ち時間の設定
  delay_time = 60 * 1000 * 1000 / STEPS / RPM_SPEED;
}

void loop(void) {
  //----Webサーバ関係処理--------------------
  webserver.handleClient(); //Webサーバ処理
  MDNS.update();            //mDNS処理

  //-----モータ回転処理----------------------
  //前回処理からの経過時間計算
  unsigned long pass_time;
  unsigned long now_time = micros();    //現在時刻読出し(usec単位)
  if (now_time >= last_time){
    pass_time = now_time - last_time;   //経過時間計算
  }else{
    //オーバフローを加味して経過時間計算
    pass_time = now_time - (0xffffffffUL - last_time + 1);
  }
  
  //モータ回転処理
  if (pass_time >= delay_time){     //時間到達?
    last_time = now_time;
    if (cw_mov_flag == true){       //CW回転?
      stepper.step(-1);             //-1ステップ回転
      current_pos--;                //現在位置マイナス
    }
    else if (ccw_mov_flag == true){ //CCW回転?
      stepper.step(1);              //1ステップ回転
      current_pos++;                //現在位置プラス
    }
  }
}

//HTMLページ表示(http://picow/)
void handleRoot() {
  String html = "<html>\n";
  html += "<head>\n";
  html += "<meta charset=\"utf-8\">\n";
  html += "<title>ステッピングモータテスト</title>";
  //JavaScript
  html += "<script language=\"JavaScript\">\n";
  html += " function cw_motor() {\n";     //モータCW回転関数
  html += "   fetch('/cw');\n";
  html += " }\n";
  html += " function ccw_motor() {\n";    //モータCCW回転関数
  html += "   fetch('/ccw');\n";
  html += " }\n";
  html += " function stop_motor() {\n";   //モータ停止関数
  html += "   fetch('/stop');\n";
  html += " }\n";
  html += " function pos_load() {\n";     //現在位置読込関数
  html += "   fetch('/pos')\n";             
  html += "     .then(response => response.text())\n";
  html += "     .then(data => document.posform.pos.value=data);\n";
  html += " }\n";
  html += " </script>\n";
  //スタイルシート
  html +=  "<style type=\"text/css\">\n";
  html += " .button {\n";           //押し釦スイッチ用
  html += "   width: 300px;\n";
  html += "   height: 150px;\n";
  html += "   font-size: 400%;\n";
  html += " }\n";
  html += " .textbox {\n";          //位置表示テキスト用
  html += "   width: 300px;\n";
  html += "   height: 60px;\n";
  html += "   font-size: 300%;\n";
  html += " }\n";
  html += "</style>\n";
  html += "</head>\n";
  //Body
  html += "<body leftmargin=\"80\"topmargin=\"55\">\n";
  html += "<font size=\"7\">ステッピングモータテスト</font>\n";
  html += "<h1> <br></h1>\n";
  //現在位置表示フォーム
  html += "<form name=\"posform\">\n";
  html += "<font size=\"8\">現在位置</font>\n";
  html += "<input type=\"text\"name=\"pos\"value=\"0\"class=\"textbox\">\n";
  html += "</form>\n";
  //CW押し釦スイッチ
  html += "<input type=\"button\"value=\"CW\"\n";
  html += " ontouchstart=\"cw_motor()\"\n";
  html += " ontouchend=\"stop_motor()\"\n";
  html += " onmousedown=\"cw_motor()\"\n";
  html += " onmouseup=\"stop_motor()\"\n";
  //CCW押し釦スイッチ
  html += " class=\"button\">\n";
  html += "<input type=\"button\"value=\"CCW\"\n";
  html += " ontouchstart=\"ccw_motor()\"\n";
  html += " ontouchend=\"stop_motor()\"\n";
  html += " onmousedown=\"ccw_motor()\"\n";
  html += " onmouseup=\"stop_motor()\"\n";
  html += " class=\"button\">\n";
  //現在位置読込スクリプト
  html += "<script language=\"JavaScript\">\n";
  html += " setInterval(\"pos_load()\",100);\n";
  html += "</script>\n";
  html += "</body>\n";
  html += "</html>";

  //HTMLデータをクライアントに送る
  webserver.send(200, "text/html", html);
}

//CW回転要求(http://picow/cw)
void handleCw() {
  ccw_mov_flag = false;                 //CCW回転フラグリセット
  cw_mov_flag = true;                   //CW回転フラグセット

  //OKレスポンスをクライアントに送る
  webserver.send(200, "text/plain", "OK"); // レスポンスを送信
}

//CCW回転要求(http://picow/ccw)
void handleCcw(){
  cw_mov_flag = false;                  //CW回転フラグリセット
  ccw_mov_flag = true;                  //CCW回転フラグセット

  //OKレスポンスをクライアントに送る     
  webserver.send(200, "text/plain", "OK"); // レスポンスを送信
}

//回転停止要求(http://picow/stop)
void handleStop(){
  cw_mov_flag = false;                  //CW回転フラグリセット
  ccw_mov_flag = false;                 //CCW回転フラグリセット

  //OKレスポンスをクライアントに送る    
  webserver.send(200, "text/plain", "OK"); // レスポンスを送信
}

//現在位置要求(http://picow/pos)
void handlePos(){
  String text = String(current_pos);
  //現在位置送信
  webserver.send(200, "text/plain", text);
}

//該当URL無
void handleNotFound() {
  digitalWrite(LED_BUILTIN, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += webserver.uri();
  message += "\nMethod: ";
  message += (webserver.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += webserver.args();
  message += "\n";
  for (uint8_t i = 0; i < webserver.args(); i++) {
    message += " " + webserver.argName(i) + ": " + webserver.arg(i) + "\n";
  }

  //URL無のメッセージをクライアントに送る
  webserver.send(404, "text/plain", message);
  digitalWrite(LED_BUILTIN, 0);
}

5.動作テスト

作成した実験装置は、パソコンとRaspberry Pi Pico WをWi-Fiルータを介してイーサネット接続して、パソコンのブラウザで「http://picow/」にアクセスすると、ステッピングモータの操作画面のホームページが表示されます。
その画面のCWボタンを押している間、モータがCW方向に回転します。また、CCWボタンを押している間、モータがCCW方向に回転します。モータの現在位置が画面上部に表示されます。以下にテスト手順を記載します。

5-1.テスト手順

①パソコンとRaspberry Pi Pico WをUSBケーブルで接続してArduino IDEを起動します。今回作成したプログラム(スケッチ)をArduino IDEで開きます。

②プログラム(スケッチ)のSSIDとパスワードの部分を、使用するWi-FiルータのSSIDとパスワードに変えます。

③ツール→シリアルモニタをクリックすると画面の下側にシリアルモニタ画面が表示されます。

④画面左上の→をクリックするとプログラムのコンパイルが行われ、その後プログラムがRaspberry Pi Pico Wに書き込まれます。

⑤Raspberry Pi Pico Wへのプログラムの書き込みが終了すると、プログラムの実行が開始されます。
プログラムはWi-Fiルータへの接続を行い、接続完了後にシリアルモニタにConnect to WiFiと表示され、Wi-Fiルータから割り当てられたIPアドレスが表示されます。

本例では、IP address:の後に表示される192.168.1.7がWi-Fiルータから割り当てられたRaspberry Pi Pico WのIPアドレスです。
この場合パソコンのWebブラウザからhttp://192.168.1.7/にアクセスすれば、ステッピングモータ操作用の画面が表示されます。
但し、今回作成したプログラムではmDNS機能が働いていますので、http://picow/にアクセスすれば、picowから192.168.1.7のIPアドレスが割り出されステッピングモータ操作用の画面が表示されます。

⑥Raspberry Pi Pico Wと同じWi-Fiルータに接続されているパソコンでブラウザを起動して、http://picow/を呼び出すと以下の様な操作画面が表示されます。(尚、Androidスマホのブラウザでアクセスした場合は、mDNSがサポートされて無くてhttp://picow/のアドレスでアクセスできません。その場合はhttp://192.168.1.7/の様にIPアドレスでアクセスします)

この画面でCWボタンを押している間、モータがCW方向に回転します。また、CCWボタンを押している間、モータがCCW方向に回転します。
また、モータ現在位置が現在位置表示欄に表示されます。

6.最後に

パソコンとRaspberry Pi Pico WとをWi-Fiルータを介したイーサネット通信で接続してブラウザからステッピングモータの操作をすることが出来ました。
パソコン側のソフトウェア作成が不要で操作画面が出来るので、いろいろな用途に使用できると思います。


PAGE TOP