※ 以下で行っていることは、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 FIRE(M5GO)のボトムに針金をネジ止め。ユニバーサル基板のホールを通して、ねじって抜けないように。
少々強引な方法ですが、PLEN:bit に M5Stack FIRE を搭載できました! pic.twitter.com/ZbtwBXzwJY— Nochi (@shikarunochi) 2019年6月1日
実行!
PLEN:bit内 のモーションデータを M5Stack で読み込んでモーション再生。
AquesTalkの音声合成と合わせてみました。
さすがに前側が重すぎて、お辞儀モーションでふんばりきれず倒れてしまった。 pic.twitter.com/QrijzWn9Q4— Nochi (@shikarunochi) 2019年6月3日
うまくモーション再生できた!が、M5Stack は重すぎたか…!
M5Stick-C を使えば、軽くできそうですね。Grove 端子から、I2C でコントロールできました。
M5Stick-CのGrove 端子からI2CでPLEN:bit動かせました!
Wire.begin(32,33);です。
ピンソケット(0,26,36)側は、どの組み合わせで接続しても動作できませんでした。残念…。 pic.twitter.com/X3KGCIvLQ5— Nochi (@shikarunochi) 2019年6月4日
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);
}
}
Grove側の接続が必要になるのであればこういう感じか。もしくは縦にするか。M5Stick-Cの電源をピンソケット経由で外部から供給したい場合は、接続スペース的に、縦にするしかなさそうだ。 pic.twitter.com/GzbSgD9LRx
— Nochi (@shikarunochi) 2019年6月4日