shirox22
@shirox22 (Shiro Shiba)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

m5stick/BalaC(MPU6886)をbluetooth joystickで操作

m5stick/BalaC(MPU6886)をbluetooth joystickで操作したいのですが、bluetooth接続できるのですが、そこでloopが切れてしまうようです。 解決方法を教えてください。

本来ならloop();から、demo();で倒立振子動作が実行されるはずなんですが、まったく反応しません。しかし、Bluetooth接続が切れると、倒立振子動作が実行されます。
static void notifyCallbackにjoystickの動作を書くと、動くのは動きますが、自動の倒立振子動作が実行されないので、倒れて前進するのみです。bluetoothで接続された後に、自動倒立制御がなぜ実行されないのか、わかりません。どなたかご教授よろしくお願いいたします。(自動制御しないプログラム、例えば、BugCなら問題なく実行されます。)プログラムの流れが悪いとおもうのですが、何かが足りないと思うのですが・・・

arduino

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include "M5StickC.h"
//
// BalaC balancing robot (IMU:MPU6886)
// by Kiraku Labo
//
// 1. Lay the robot flat, and power on.
// 2. Wait until Gal-1 (Pitch Gyro calibration) completes.
// 3. Hold still the robot upright in balance until Cal-2 (Accel & Yaw Gyro cal) completes.
//
// short push of power button: Gyro calibration
// long push (>1sec) of power button: switch mode between standig and demo(circle)
//

#include <M5StickC.h>

#define LED 10
#define N_CAL1 100
#define N_CAL2 100
#define LCDV_MID 60

boolean serialMonitor=true;
boolean standing=false;
int16_t counter=0;
uint32_t time0=0, time1=0;
int16_t counterOverPwr=0, maxOvp=20;
float power, powerR, powerL, yawPower;
float varAng, varOmg, varSpd, varDst, varIang;
float gyroXoffset, gyroYoffset, gyroZoffset, accXoffset;
float gyroXdata, gyroYdata, gyroZdata, accXdata, accZdata;
float aveAccX=0.0, aveAccZ=0.0, aveAbsOmg=0.0;
float cutoff=0.1; //~=2 * pi * f (Hz)
const float clk = 0.01; // in sec,
const uint32_t interval = (uint32_t) (clk*1000);  // in msec
float  Kang, Komg, KIang, Kyaw, Kdst, Kspd;
int16_t maxPwr;
float yawAngle=0.0;
float moveDestination, moveTarget;
float moveRate=0.0;
const float moveStep=0.2*clk;
int16_t fbBalance=0;
int16_t motorDeadband=0;
float mechFactR, mechFactL;
int8_t motorRDir=0, motorLDir=0;
bool spinContinuous=false;
float spinDest, spinTarget, spinFact=1.0;
float spinStep=0.0; //deg per 10msec
int16_t ipowerL=0, ipowerR=0;
int16_t motorLdir=0, motorRdir=0; //0:stop 1:+ -1:-
float vBatt, voltAve=3.7;
int16_t punchPwr, punchPwr2, punchDur, punchCountL=0, punchCountR=0;
byte demoMode=1;


// UUID HID
static BLEUUID serviceUUID("1812");
// UUID Report Charcteristic
static BLEUUID charUUID("2a4d");

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;

static BLEAdvertisedDevice *myDevice;

std::vector<BLEAdvertisedDevice*> myDevices;


static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic,uint8_t *pData,size_t length, bool isNotify) {
  // ボタンを押すなどしてNotifyが実行されると呼び出される

//joystickの動作


}

class MyClientCallback: public BLEClientCallbacks {
  void onConnect(BLEClient *pclient) {



  }

  void onDisconnect(BLEClient *pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {

  Serial.printf("myDevices=%d\n", myDevices.size());
  for (int i = 0; i < myDevices.size(); i++) {
    Serial.print("Forming a connection to ");
    Serial.println(myDevices.at(i)->getAddress().toString().c_str());

    BLEClient *pClient = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevices.at(i));  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
    Serial.println(" - Connected to server");


    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");

    // 複数のCharacteristicを持つHIDの事は考慮されていないので
    // 複数のCharacteristicに対応した処理をする
    std::map<uint16_t, BLERemoteCharacteristic*> *pCharacteristicMap;
    pCharacteristicMap = pRemoteService->getCharacteristicsByHandle();
    // 引数なしのgetCharacteristics()は複数のCharacteristicを返してこないのでこちらを利用する
    if (pCharacteristicMap == nullptr) {
      Serial.println("pCharacteristicMap=null");
      return false;
    }

    for (auto itr = pCharacteristicMap->begin(); itr != pCharacteristicMap->end(); ++itr) {
      Serial.print("UUID: ");
      Serial.println(itr->second->getUUID().toString().c_str());
      if (itr->second->getUUID().equals(charUUID)) {
        Serial.print("CharUUID matched: ");
        Serial.println(itr->second->getUUID().toString().c_str());
        if (itr->second->canNotify()) {
          // ReportCharacteristicsはNotifyCallbackでデータを通信するため
          // Notify属性を持っているCharacteristicをすべて登録する
          Serial.print("Notify registerd: ");
          Serial.println(itr->second->getUUID().toString().c_str());
          itr->second->registerForNotify(notifyCallback);
        }
      }
    }


  }
  connected = true;
  return true;
}
/**
 * Scan for BLE servers and find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

//      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      myDevices.push_back(myDevice);
      doConnect = true;
      doScan = true;

    } // Found our server
  } // onResult
};


void setup() {

  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);
  Serial.begin(115200);
  M5.begin();
  Wire.begin(0, 26); //SDA,SCL
  imuInit();
  M5.Axp.ScreenBreath(11);
  M5.Lcd.setRotation(2);
  M5.Lcd.setTextFont(4);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(1);
  resetMotor();
  resetPara();
  resetVar();
  calib1();
  #ifdef DEBUG
  debugSetup();
  #else
  setMode(true);
  #endif

 BLEDevice::init("");
 BLEScan *pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);

}

void loop() {

 checkButtonP();
  #ifdef DEBUG
  if (debugLoop1()) return;
  #endif
  getGyro();
  if (!standing) {
    dispBatVolt();
    aveAbsOmg = aveAbsOmg * 0.9 + abs(varOmg) * 0.1;
    aveAccZ = aveAccZ * 0.9 + accZdata * 0.1;
    M5.Lcd.setCursor(10,130);
    M5.Lcd.printf("%5.2f   ", -aveAccZ);
    if (abs(aveAccZ)>0.9 && aveAbsOmg<1.5) {
      calib2();
      if (demoMode==1) startDemo();
      standing=true;
    }
  }
  else {
    if (abs(varAng)>30.0 || counterOverPwr>maxOvp) {
      resetMotor();
      resetVar();
      standing=false;
      setMode(false);
    }
    else {
      drive();
    }
  }
  counter += 1;
  if (counter >= 100) {
    counter = 0;
    dispBatVolt();
    if (serialMonitor) sendStatus();
  }
  do time1 = millis();
  while (time1 - time0 < interval);
  time0 = time1;


  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    }
    else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {

    // String newValue = "Time since boot: " + String(millis()/1000);
    // Serial.println("Setting new characteristic value to \"" + newValue + "\"");

    // // Set the characteristic's value to be the array of bytes that is actually a string.
    // pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
  }
  else if (doScan) {
    BLEDevice::getScan()->start(0);  // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino
  }

}

void calib1() {
  calDelay(30);
  digitalWrite(LED, LOW);
  calDelay(80);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, LCDV_MID);
  M5.Lcd.print(" Cal-1  ");
  gyroYoffset=0.0;
  for (int i=0; i <N_CAL1; i++){
    readGyro();
    gyroYoffset += gyroYdata;
    delay(9);
  }
  gyroYoffset /= (float)N_CAL1;
  M5.Lcd.fillScreen(BLACK);
  digitalWrite(LED, HIGH);
}

void calib2() {
  resetVar();
  resetMotor();
  digitalWrite(LED, LOW);
  calDelay(80);
  M5.Lcd.setCursor(0, LCDV_MID);
  M5.Lcd.println(" Cal-2  ");
  accXoffset=0.0;
  gyroZoffset=0.0;
  for (int i=0; i <N_CAL2; i++){
    readGyro();
    accXoffset += accXdata;
    gyroZoffset += gyroZdata;
    delay(9);
  }
  accXoffset /= (float)N_CAL2;
  gyroZoffset /= (float)N_CAL2;
  M5.Lcd.fillScreen(BLACK);
  digitalWrite(LED, HIGH);
}

void checkButtonP() {
  byte pbtn=M5.Axp.GetBtnPress();
  if (pbtn==2) calib1(); //short push
  else if (pbtn==1) setMode(true); //long push
}

void calDelay(int n) {
  for (int i=0; i<n; i++) {
    getGyro();
    delay(9);
  }
}

void setMode(bool inc) {
  if (inc) demoMode=++demoMode%2;
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(5, 5);
  if (demoMode==0) M5.Lcd.print("Stand ");
  else if (demoMode==1) M5.Lcd.print("Demo ");

}

void startDemo() {
  moveRate=0.0;
  spinContinuous=false;
  spinStep=0.0*clk;

}

void resetPara() {
  Kang=37.0;
  Komg=0.84;
  KIang=800.0;
  Kyaw=4.0;
  Kdst=85.0;
  Kspd=2.7;
  mechFactL=0.45;
  mechFactR=0.45;
  punchPwr=20;
  punchDur=1;
  fbBalance=-3;
  motorDeadband=10;
  maxPwr=120;
  punchPwr2=max(punchPwr, motorDeadband);
}

void getGyro() {
  readGyro();
  varOmg = (gyroYdata-gyroYoffset); //unit:deg/sec
  yawAngle += (gyroZdata-gyroZoffset) * clk; //unit:g
  varAng += (varOmg + ((accXdata-accXoffset) * 57.3 - varAng) * cutoff ) * clk; //complementary filter
}

void readGyro() {
  float gX, gY, gZ, aX, aY, aZ;
  M5.Imu.getGyroData(&gX,&gY,&gZ);
  M5.Imu.getAccelData(&aX,&aY,&aZ);
  gyroYdata=gX;
  gyroZdata=-gY;
  gyroXdata=-gZ;
  accXdata=aZ;
  accZdata=aY;
}

void drive() {
  #ifdef DEBUG
  debugDrive();
  #endif
  if (abs(moveRate)>0.1) spinFact=constrain(-(powerR+powerL)/10.0, -1.0, 1.0); //moving
  else spinFact=1.0; //standing
  if (spinContinuous) spinTarget += spinStep * spinFact;
  else {
     if (spinTarget < spinDest) spinTarget += spinStep;
     if (spinTarget > spinDest) spinTarget -= spinStep;
  }
  moveTarget += moveStep * (moveRate +(float)fbBalance/100.0);
  varSpd += power * clk;
  varDst += Kdst * (varSpd * clk -moveTarget);
  varIang += KIang * varAng * clk;
  power = varIang + varDst + (Kspd * varSpd) + (Kang * varAng) + (Komg * varOmg);
  if (abs(power) > 1000.0) counterOverPwr += 1;
  else counterOverPwr =0;
  if (counterOverPwr > maxOvp) return;
  power = constrain(power, -maxPwr, maxPwr);
  yawPower = (yawAngle - spinTarget) * Kyaw;
  powerR = power - yawPower;
  powerL = power + yawPower;

  ipowerL = (int16_t) constrain(powerL * mechFactL, -maxPwr, maxPwr);
  int16_t mdbn=-motorDeadband;
  int16_t pp2n=-punchPwr2;
  if (ipowerL > 0) {
    if (motorLdir == 1) punchCountL = constrain(++punchCountL, 0, 100);
    else punchCountL=0;
    motorLdir = 1;
    if (punchCountL<punchDur) drvMotorL(max(ipowerL, punchPwr2));
    else drvMotorL(max(ipowerL, motorDeadband));
  }
  else if (ipowerL < 0) {
    if (motorLdir == -1) punchCountL = constrain(++punchCountL, 0, 100);
    else punchCountL=0;
    motorLdir = -1;
    if (punchCountL<punchDur) drvMotorL(min(ipowerL, pp2n));
    else drvMotorL(min(ipowerL, mdbn));
  }
  else {
    drvMotorL(0);
    motorLdir = 0;
  }

  ipowerR = (int16_t) constrain(powerR * mechFactR, -maxPwr, maxPwr);
  if (ipowerR > 0) {
    if (motorRdir == 1) punchCountR = constrain(++punchCountR, 0, 100);
    else punchCountR=0;
    motorRdir = 1;
    if (punchCountR<punchDur) drvMotorR(max(ipowerR, punchPwr2));
    else drvMotorR(max(ipowerR, motorDeadband));
  }
  else if (ipowerR < 0) {
    if (motorRdir == -1) punchCountR = constrain(++punchCountR, 0, 100);
    else punchCountR=0;
    motorRdir = -1;
    if (punchCountR<punchDur) drvMotorR(min(ipowerR, pp2n));
    else drvMotorR(min(ipowerR, mdbn));
  }
  else {
    drvMotorR(0);
    motorRdir = 0;
  }
}

void drvMotorL(int16_t pwm) {
  drvMotor(0, (int8_t)constrain(pwm, -127, 127));
}

void drvMotorR(int16_t pwm) {
  drvMotor(1, (int8_t)constrain(-pwm, -127, 127));
}

void drvMotor(byte ch, int8_t sp) {
  Wire.beginTransmission(0x38);
  Wire.write(ch);
  Wire.write(sp);
  Wire.endTransmission();
}

void resetMotor() {
 drvMotorR(0);
 drvMotorL(0);
 counterOverPwr=0;
}

void resetVar() {
  power=0.0;
  moveTarget=0.0;
  moveRate=0.0;
  spinContinuous=false;
  spinDest=0.0;
  spinTarget=0.0;
  spinStep=0.0;
  yawAngle=0.0;
  varAng=0.0;
  varOmg=0.0;
  varDst=0.0;
  varSpd=0.0;
  varIang=0.0;
}

void sendStatus () {
  Serial.print(millis()-time0);
  Serial.print(" stand="); Serial.print(standing);
  Serial.print(" accX="); Serial.print(accXdata);
  Serial.print(" power="); Serial.print(power);
  Serial.print(" ang=");Serial.print(varAng);
  Serial.print(", ");
  Serial.print(millis()-time0);
  Serial.println();
}

void imuInit() {
  M5.Imu.Init();
  if (M5.Imu.imuType=M5.Imu.IMU_MPU6886) {
    M5.Mpu6886.SetGyroFsr(M5.Mpu6886.GFS_250DPS); //250DPS 500DPS 1000DPS 2000DPS
    M5.Mpu6886.SetAccelFsr(M5.Mpu6886.AFS_4G); //2G 4G 8G 16G
    if (serialMonitor) Serial.println("MPU6886 found");
  }
  else if (serialMonitor) Serial.println("MPU6886 not found");
}

void dispBatVolt() {
  M5.Lcd.setCursor(5, LCDV_MID);
  vBatt= M5.Axp.GetBatVoltage();
  M5.Lcd.printf("%4.2fv ", vBatt);
}



参考にさせていただいたページ

木楽ラボさんのページ
https://kirakulabo2.blogspot.com/2021/05/m5stickcble.html

Hiroyuki ITO さんのページ
https://intellectualcuriosity.hatenablog.com/entry/2020/11/03/041837

@coppercele さんの投稿
https://qiita.com/coppercele/items/57354f3e5a50ec7e0573

1

1Answer

ざっと見た感じなんですけど、恐らくBLEの処理が走ることで倒立振子側の処理が止まってるんじゃないかと思います
なのでloop()内のBLEの処理をマルチスレッドで動かしたらよいかもしれません

[M5Stack] マルチスレッドで並列処理を行う - Qiita
https://qiita.com/kotai2003/items/6a4d620a51ca0f1535dc

void bleTask() {

while (true) {
if (doConnect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the BLE Server.");
}
else {
Serial.println("We have failed to connect to the server; there is nothin more we will do.");
}
doConnect = false;
}

if (connected) {

}
else if (doScan) {
BLEDevice::getScan()->start(0);
}
}

setup() {

xTaskCreatePinnedToCore(bleTask, "bleTask", 4096, NULL, 1, NULL, 1)

}

このような感じにするとタスクが別になるのでBLEのタスクが止まっても
loop()内の処理は止まらずに動きます

1Like

Comments

  1. @shirox22

    Questioner

    返信大変ありがとうございます。マルチタスクなるほど!と思って、いろいろとやってみたのですが、今度はなぜか、bluetoothにつながりません。何かが間違っているのか、もう少しやってみます。何か情報ありましたら、よろしくお願い致します。
  2. 今Arduino-ESP32同梱のbluetoothを使っていますが、
    NimBLEのライブラリを使ってみたらどうでしょうか?
    こちらの方がメモリ使用量が少なくて動作も安定しているので
    自分はこちらをメインで使っています

    NimBLEでESP32(M5StickC)にBLE HIDデバイスを接続する(複数デバイス対応)
    https://qiita.com/coppercele/items/4ce0e8858a92410c81e3
  3. @shirox22

    Questioner

    返信ありがとうございます。教えて頂いたマルチスレッドを使って、中身を、BLEのloopじゃなく、MPU6886のloopの方にしたら、なぜか上手くいきました!!。本当にありがとうございました。しかし、動作が不安定なので、やはり、NimBLEのライブラリを使ってみようと思います。また、質問させてください。お忙しいところ本当にすみません、よろしくお願いします。
  4. マルチスレッドでうまく動いたようでよかったです

    また何かありましたらコメントしてください:D

Your answer might help someone💌