PLEN:bit:M5Stackからのモーションコントロール

※ 以下で行っていることは、PLEN:bit の基本的な使い方とは外れていると思いますので、そのあたり理解した上でお読みください。


M5Stack との I2C 通信方法と、PLEN:blt のモーションデータフォーマットが分かったので、M5Stack でモーションデータを取得して、それを再生してみます。せっかくなので、モーション再生時に、AquesTalkを用いてしゃべらせてみました。

AquesTalk ESP32
https://www.a-quest.com/products/aquestalk_esp32.html

ソースはこちら。

#include <M5Stack.h>
const int LED_PIN = 2;

int servoSetInit[] = {1000, 630, 300, 600, 240, 600, 1000, 720};
int servoAngle[] =  {1000, 630, 300, 600, 240, 600, 1000, 720};
void setup(){
  M5.begin();
  Wire.begin();

  pinMode(LED_PIN,OUTPUT);
  digitalWrite( LED_PIN, HIGH );
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextSize(4);
  delay( 1000 );
  secretIncantation();
}

void loop(){
  M5.update();
  if (M5.BtnA.wasReleased()){
    delay(5000);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("Hello, World!");
    servoWrite(6,0);
    delay(1000);
    servoWrite(6,100);
    delay(500);
    M5.Lcd.println("");
    M5.Lcd.println("PLEN:M5Stack");

    String motionData = reep(0x32 + 4 * 860);
    playMotion(motionData);
  }
  if (M5.BtnC.wasReleased()){
    servoInitialSet();
  }
  delay(10);
}

void write8(int addr, int val){
  Wire.beginTransmission(0x6A);
  Wire.write(addr);
  Wire.write(val);
  Wire.endTransmission();
  //0: 成功
  //1: 送信バッファ溢れ
  //2: アドレス送信時にNACKを受信
  //3: データ送信時にNACKを受信
  //4: その他エラー
}

void secretIncantation() {
  Serial.println("secretIncantation");
    write8(0xFE, 0x85);//PRE_SCALE
    write8(0xFA, 0x00);//ALL_LED_ON_L
    write8(0xFB, 0x00);//ALL_LED_ON_H
    write8(0xFC, 0x66);//ALL_LED_OFF_L
    write8(0xFD, 0x00);//ALL_LED_OFF_H
    write8(0x00, 0x01);
    servoInitialSet();
}

void servoWrite(int num, int degrees) {
  //Serial.printf("ServoWrite:%d,%d\n",num,degrees);
  int servoNum = 0x08;
  bool highByte = false;
  int pwmVal = degrees * 100 * 226 / 10000;
  pwmVal = pwmVal + 0x66;
  if (pwmVal > 0xFF) {
    highByte = true;
  }
  write8(servoNum + num * 4, pwmVal);
  if (highByte) {
      write8(servoNum + num * 4 + 1, 0x01);
  } else {
      write8(servoNum + num * 4 + 1, 0x00);
  }
}

void servoInitialSet() {
  for(int i = 0;i < 8;i++){ 
    servoWrite(i, servoSetInit[i] / 10);
  }
}

String reep(int eepAdr) { //0xFFが出るまで読み、文字列として返す String result = ""; int romAdr1 = 0x56; boolean dataEnd = false; int readAddress = eepAdr; int readSize = 43; while(dataEnd == false){ Wire.beginTransmission(romAdr1); Wire.write(readAddress >> 8);
  Wire.write(readAddress & 0xFF);
  Wire.endTransmission();
  Wire.requestFrom(romAdr1, readSize);
  for(int index = 0;index < readSize;index++){
    if(Wire.available()){
      char c = Wire.read();      // 1バイトを受信
      if(c == 0xFF){
        dataEnd = true;
        break;
      }
      //Serial.print(c);
      result = result + String(c);
    }else{
      dataEnd = true;
      break;
    }
  }
    readAddress = readAddress + readSize;
  }
  return result;
}

void playMotion(String motionData){
  //Serial.println("PlayMotion");
  int offset = 0;
  while(offset < motionData.length()){ //>MF
    String checkData;
    checkData = motionData.substring(offset, offset + 3);
    if(checkData.equals(">MF")==false){
      Serial.printf("ERROR:OFFSET=%d\n",offset);
      return;
    }
    offset = offset + 3;
    offset = offset + 4;

    checkData = motionData.substring(offset, offset + 4);
    int msec = strtol(checkData.c_str() , NULL , 16);
    offset = offset + 4;
    int angle[8];
    //Serial.printf("SetAngel:%d:",msec);
    for(int servoNo = 0;servoNo < 8;servoNo++){ checkData = motionData.substring(offset, offset + 4); angle[servoNo] = strtol(checkData.c_str() , NULL , 16); if(angle[servoNo] > 0x7FFF){
        angle[servoNo] = angle[servoNo] - 0x10000;
      }
      //Serial.printf(":%d", angle[servoNo] );
      offset = offset + 4;
    }
    //Serial.println();
    setAngle(angle,msec);
  }
}
void setAngle(int angle[], int msec){
  int step[] = {0,0,0,0,0,0,0,0};
  int realMsec = msec / 20;
  
  for(int val = 0;val < 8;val++){
    int target = servoSetInit[val] - angle[val];
    if (target != servoAngle[val]) { // Target != Present
      step[val] = (target - servoAngle[val]) / (realMsec);
    }
  }
  //Serial.println();
  for (int i = 0; i <= realMsec; i++) {
    for (int val = 0; val < 8; val++) {
      servoAngle[val] += step[val];
      servoWrite(val, (servoAngle[val] / 10));
      //Serial.printf("Servo:%d:%d\n",val,servoAngle[val] );
    }
  } 
}

Aボタンを押すと、右手を上げ下げした後に、モーション番号4(おじぎ)を再生するようにしています。

サーボ制御の部分は「PLEN:bit:M5Stackと接続」と同じです。

・void write8(int addr, int val):アドレスを指定して値を書き込む
・void secretIncantation():サーボ初期化
・void servoWrite(int num, int degrees):サーボ番号と角度を指定してサーボ動作

PLEN:bit:モーションデータ」のTypeScript を移植する感じで、モーションデータ読み込み関数を作成。

・String reep(int eepAdr):アドレスを指定してモーションデータを読みこみ。

モーションデータに従って、モーション再生する関数を作成。

・void playMotion(String motionData):reepで読み込んだモーションデータを引数にもらい、その順にサーボ状態を設定。

playMotion の中で、各サーボモータの状態を設定しているのは setAngle 関数です。

・void setAngle(int angle[], int msec):時間msecをかけて、各サーボを angle[] 状態にする。

msec は、現状だと秒指定というわけではないです。なんとなくの動作時間ぐらいに思ってもらえれば…。


せっかくなので、M5Stack を PLEN:bit と合体させました。


実行!

うまくモーション再生できた!が、M5Stack は重すぎたか…!


M5Stick-C を使えば、軽くできそうですね。Grove 端子から、I2C でコントロールできました。

HOMEボタンを押したら腕を上げ下げするようにしてます。

#include <M5StickC.h>
int servoSetInit[] = {1000, 630, 300, 600, 240, 600, 1000, 720};
int servoAngle[] = {1000, 630, 300, 600, 240, 600, 1000, 720};
void setup() {
  M5.begin();
  Wire.begin(32,33);
  //Wire.begin(36,0);//ピンソケット側はどの組み合わせでもダメだった
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("PLEN:");
  M5.Lcd.println("M5Stick-C");
  pinMode(M5_BUTTON_HOME, INPUT);
  secretIncantation();
}

void loop() {
  if(digitalRead(M5_BUTTON_HOME) == LOW){
    M5.Lcd.println("HOME BUTTON");
    servoWrite(6,0);
    delay(1000);
    servoWrite(6,100);
    delay(500);
  }
}

void write8(int addr, int val){
  Wire.beginTransmission(0x6A);
  Wire.write(addr);
  Wire.write(val);
  Wire.endTransmission();
  //Serial.print("Write:");
  //0: 成功
  //1: 送信バッファ溢れ
  //2: アドレス送信時にNACKを受信
  //3: データ送信時にNACKを受信
  //4: その他エラー
}

void secretIncantation() {
  Serial.println("secretIncantation");
    write8(0xFE, 0x85);//PRE_SCALE
    write8(0xFA, 0x00);//ALL_LED_ON_L
    write8(0xFB, 0x00);//ALL_LED_ON_H
    write8(0xFC, 0x66);//ALL_LED_OFF_L
    write8(0xFD, 0x00);//ALL_LED_OFF_H
    write8(0x00, 0x01);
    servoInitialSet();
}

void servoWrite(int num, int degrees) {
  //Serial.printf("ServoWrite:%d,%d\n",num,degrees);
  int servoNum = 0x08;
  bool highByte = false;
  int pwmVal = degrees * 100 * 226 / 10000;
  pwmVal = pwmVal + 0x66;
  if (pwmVal > 0xFF) {
    highByte = true;
  }
  write8(servoNum + num * 4, pwmVal);
  if (highByte) {
      write8(servoNum + num * 4 + 1, 0x01);
  } else {
      write8(servoNum + num * 4 + 1, 0x00);
  }
}

void servoInitialSet() {
  for(int i = 0;i < 8;i++){
    servoWrite(i, servoSetInit[i] / 10);
  }
}