DreamerDreamのブログ

夢想家の夢です。〜揚げたてのモヤっとしたものをラフレシアと共に〜

ESP32でカメラ搭載ラジコンを作る ②データの改造

前回、スマホからのタップ情報を得られたのでそれを元にモーターを動かします。

dreamerdream.hateblo.jp

 

WebSocket経由でデータを取得している部分が、app_httpd.cppタブの中のここですね。

さっぱり解らないのでChatGPTさんに聞いてみました。

つまり、ws_pktに値が格納されているので、それをいい感じで処理すればよさそうですね。

 

C++は書き方がさっぱりなのでネットで調べながら進めますと、.cpp本体と.hのヘッダファイルがセットで使われるそうです。

ということで、モーターを扱うように新規タブを作ります。

 

こんな感じで.cppと.hファイルを作成しました。

ヘッダファイルはこんな感じでOK。?

これをデータを受信するコードにincludeすれば使えるはずですね。

 

 

camera_index.hタブに記載のJavaScriptのmove( [X軸の値], [Y軸の値] )で取得値を送信しているようです。

サンプルコードではこれを受信してからAという[ヘッダ文字]+[中身]+[改行]となっているようなのですが、JavaScript側で既にA文字を付けて送ってしまうことにします。

こうすれば、後でBを追加でコントロールしたい!となったときにJavaScriptのここだけ変更すれば同じコントローラーが使い回せるので楽だろうという考えです。

 

受信側はこのようにヘッダ文字によって制御関数を分けることにしました。

 

さて、問題はステッピングモーターの制御です。

ESP32はパワフルなマイコンなので画像を送信しながらサーボモーターのPWM制御ぐらいは余裕ですが、DUTY比(一定周波数でON−OFFの長さ)を変えるのは得意でも、ステッピングモーターの制御に必要なDUTY比を50%のままで周波数を好きに変えるような制御はどうも苦手なようです。周波数も変えられるようですが任意に変えられるわけでは無さそうです。

 

そういった制御をするに手動で別スレッドを立てて制御する必要がありそうです。

<参考>

タスク(task) - M5StickC非公式日本語リファレンス

 

別スレッドを動かすには、タスクハンドルを用意し、スレッドを初期化し、別ファイルのコードを実行するにはプロトタイプ宣言も必要なようです。

あとステッピングモータ用の制御ピンの宣言です。

ピンはENABLE(LOWで有効)、DIR(回転方向制御)、STEP(ステップパルス入力)が必要で、ENABLEピンは共用可能です。

ヘッダファイルにピン番号を書いて宣言しておけば分かりやすいですね。

無事に制御できたのがコチラ(片方だけ)


www.youtube.com

 

ひとまずトライアンドエラーでうまくステッピング制御できたコードを備忘録として貼っておきます。
motion.cpp

#include "Arduino.h"
#include "motion.h"


/*STEPM_ENABLE 13  ,STEPM_RIGHT_DIR 0 , STEPM_LEFT_DIR 15 , STEPM_RIGHT_STEP 2 , STEPM_LEFT_STEP 12 */

// クローラ制御範囲の設定
const int controlMin = -100;  // 入力最小値
const int controlMax = 100;   // 入力最大値
const int clr_BackForMaxSpeed = -30;   // 制御最小値
const int clr_FrontForMaxSpeed = 30;   // 制御最大値

int speed_clr_left = 0;
int speed_clr_right = 0;


/* 別スレッドでモーターを動かす
 * 安全対策 :スマホ制御時に突如電源OFFになった場合、その値のまま暴走するので指定時間以上同じ値の場合は走行停止にする
 */
void steppingM(void *args){
  int now_wait_l = 0;
  int now_wait_r = 0;
  int wait_l = 0;
  int wait_r = 0;
  int cnt_l = 0;
  int cnt_r = 0;
  int cnt_watch = 0;
  while(1){
    
    while( now_wait_l != wait_l || now_wait_r != wait_r || speed_clr_left != 0 ||  speed_clr_right != 0){
      digitalWrite(STEPM_ENABLE, LOW );
      cnt_l++;
      cnt_r++;
      //set DIR
      if( speed_clr_left < 0 ){
        wait_l = -(clr_BackForMaxSpeed - speed_clr_left);
        digitalWrite(STEPM_LEFT_DIR, LOW );
      }else{
        wait_l = clr_FrontForMaxSpeed - speed_clr_left;
        digitalWrite(STEPM_LEFT_DIR, HIGH );
      }
      if( speed_clr_right < 0 ){
        wait_r = -(clr_BackForMaxSpeed - speed_clr_right);
        digitalWrite(STEPM_RIGHT_DIR, LOW );
      }else{
        wait_r = clr_FrontForMaxSpeed - speed_clr_right;
        digitalWrite(STEPM_RIGHT_DIR, HIGH );
      }

      if(wait_l < 3) wait_l = 3;
      if(wait_r < 3) wait_r = 3;

      if( now_wait_l  < cnt_l ){
        digitalWrite( STEPM_LEFT_STEP , !digitalRead(STEPM_LEFT_STEP) );

        if( now_wait_l > wait_l ){
          now_wait_l--;
        }else if( now_wait_l < wait_l ){
          now_wait_l++;
        }
        cnt_l = 0;
      }
     
      if( now_wait_r  < cnt_r ){
        digitalWrite( STEPM_RIGHT_STEP , !digitalRead(STEPM_RIGHT_STEP) );
        if( now_wait_r > wait_r ){
          now_wait_r--;
        }else if( now_wait_r < wait_r ){
          now_wait_r++;
        }
        cnt_r = 0;
      }
      
      if( wait_r != 0 && wait_l != 0 && now_wait_r == wait_r && now_wait_l == wait_l ){
        cnt_watch++;
        if( 50000 < cnt_watch ){  //10s = 0.0002s * 50000; 
          speed_clr_left = 0;
          speed_clr_right = 0;
        }
      }else{
        cnt_watch = 0;
      }

      delayMicroseconds(200);
    }
    digitalWrite(STEPM_ENABLE, HIGH );

    delay(100); 
  }
}

void motion_1( int xAxis, int yAxis ){

  //Y軸の-+入れ替え
  yAxis = -yAxis;
  // 左右の車輪の速度を計算 ひとまずY軸の値 
  int leftSpeed = yAxis;
  int rightSpeed = yAxis;

  int turningSpeed = xAxis;
  //double ratio = sin(yAxis * 3.14159 / 200.0);
  //int turningSpeed = static_cast<int>(xAxis * ratio * 10);
 
  if( yAxis < 0 ){
    leftSpeed -= turningSpeed;
    rightSpeed += turningSpeed;    
  }else{
    leftSpeed += turningSpeed;
    rightSpeed -= turningSpeed;     
  }

  // 制御値を範囲にまとめる
  leftSpeed = map(leftSpeed, controlMin, controlMax, clr_BackForMaxSpeed, clr_FrontForMaxSpeed);
  rightSpeed = map(rightSpeed, controlMin, controlMax, clr_BackForMaxSpeed, clr_FrontForMaxSpeed);
  if( clr_FrontForMaxSpeed < leftSpeed) leftSpeed = clr_FrontForMaxSpeed;
  else if( leftSpeed < clr_BackForMaxSpeed ) leftSpeed = clr_BackForMaxSpeed;
  if( clr_FrontForMaxSpeed < rightSpeed) rightSpeed = clr_FrontForMaxSpeed;
  else if( rightSpeed < clr_BackForMaxSpeed ) rightSpeed = clr_BackForMaxSpeed;
  
    speed_clr_left = leftSpeed;
    speed_clr_right = rightSpeed;
}

//他のモーターとか制御用
void motion_2( int x, int y ){

Serial.print("motion_2 -- X:");
Serial.print(x);
Serial.print(" Y:");
Serial.println(y);

}

 

motion_1()に入力されたXY軸の値を元に、左右のクローラーの速度を算出して共用関数に保存、スレッドで保存された値を参考に待ち時間を算出してHIGH/LOWの入れ替えでステップ出力をしています。

スピード=待ち時間ではない。というところがコードをややこしくしています。

また、スレッドでdelayMicroseconds(200);を使っていますが、これを動かすコアはcore0はNG!

core0を使う場合、wait(1)以下にするとウォッチドッグリセットがなくなるらしいので、3秒ぐらいでマイコンが再起動しちゃいます。なのでcore1で実行させる必要があります。

 

あと、映像取得しながらグリグリ動かし続けていると通信が追いつかず、フリーズしてリセットが必要になることがありました。

どうもCPU速度が追いつかなくなるとフリーズしてしまうようなので80MHz -> 160MHzに変更しました。

推奨速度はわかりませんが、CPU速度と映像の解像度は関係しているようです。必要以上に高くしなければOK!なようです。

 

 

dreamerdream.hateblo.jp

 

 

 

 

kampa.me