8 👨‍🏫 Bittleに新しいスキルを教える

"Give a cat a fish and you feed it for a day. Teach a cat to fish and you feed it for a lifetime." 🎣

8.1. InstinctBittle.hのスキルを理解する

EEPROMの書き込み操作回数は1,000,000回と限られています。そのため、EEPROMへの書き込み操作を最小限にしたいと考えています。

スキルには「Instincts」と「Newbility」の2種類あります。両者のアドレスはルックアップテーブルとして搭載されたEEPROM(1KB)に書き込まれていますが、実際のデータは別のメモリに格納されています。

  • InstinctsはI2CのEEPROM(8KB)に格納されています。

Instinctsは、初期搭載されたスキルのことです。例えるなら「マッスルメモリー」のようなものです。複数のInstinctsは、WriteInstinct.inoを使って一度だけI2C EEPROMに書き込まれます。それらのアドレスは、WriteInstinct.ino実行中に生成され、オンボードEEPROMのルックアップテーブルに保存されます。

  • PROGMEM(32KBのフラッシュをスケッチで共有)はNewbilityを格納します。

Newbilityとは、多くのテストを必要とする新たな実験的スキルのことです。I2CやオンボードEEPROMに書き込まれるのではなく、PROGMEM形式のフラッシュメモリーに書き込まれます。Arduinoのスケッチの一部としてアップロードする必要があります。そのアドレスはコードの実行時にも割り当てられますが、スキルの総数(すべてのInstinctsとNewbilitiesを含む)が変わらなければ、その値が変わることはほとんどありません。

8.2. InstinctBittle.hの例

//a short version of InstinctBittle.h as example

#define BITTLE
#define NUM_SKILLS 4
#define I2C_EEPROM

const char rest[] PROGMEM = { 
1, 0, 0, 1,
  -30, -80, -45,   0,  -3,  -3,   3,   3,  75,  75,  75,  75, -55, -55, -55, -55,};
const char zero[] PROGMEM = { 
1, 0, 0, 1,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,};

const char crF[] PROGMEM = { 
36, 0, -3, 1,
  61,  68,  54,  61, -26, -39, -13, -26,
  66,  61,  58,  55, -26, -39, -13, -26,
  ...
  51,  81,  45,  72, -25, -37, -12, -25,
  55,  76,  49,  68, -26, -38, -13, -26,
  60,  70,  53,  62, -26, -39, -13, -26,
};

const char pu[] PROGMEM = { 
-8, 0, -15, 1,
 6, 7, 3, 
    0,   0,   0,   0,   0,   0,   0,   0,  30,  30,  30,  30,  30,  30,  30,  30,	 5, 0,
   15,   0,   0,   0,   0,   0,   0,   0,  30,  35,  40,  29,  50,  15,  15,  15,	 5, 0,
   30,   0,   0,   0,   0,   0,   0,   0,  27,  35,  40,  60,  50,  15,  20,  45,	 5, 0,
   15,   0,   0,   0,   0,   0,   0,   0,  45,  35,  40,  60,  25,  20,  20,  60,	 5, 0,
    0,   0,   0,   0,   0,   0,   0,   0,  50,  35,  75,  60,  20,  30,  20,  60,	 6, 0,
  -15,   0,   0,   0,   0,   0,   0,   0,  60,  60,  70,  70,  15,  15,  60,  60,	 6, 0,
    0,   0,   0,   0,   0,   0,   0,   0,  30,  30,  95,  95,  60,  60,  60,  60,	 6, 1,
   30,   0,   0,   0,   0,   0,   0,   0,  75,  70,  80,  80, -50, -50,  60,  60,	 8, 0,
};

#if !defined(MAIN_SKETCH) || !defined(I2C_EEPROM)
const char* skillNameWithType[] =
{"crI", "puI", "restI", "zeroN",};
const char* progmemPointer[] = 
{cr, pu, rest, zero, };
#else
const char* progmemPointer[] = {zero};
#endif

8.2.1. 定義された定数

define WalkingDOF 8

これは歩行のための自由度(DOF)の数値を定義しており、Bittleでは8となっています。

define NUM_SKILLS 4

スキルの合計数が4であることを定義しています。これはリストの項目数と同じである必要があります。

const char* skillNameWithType[].

define I2C_EEPROM

NyBoardにはInstinctsを保存するためのI2C EEPROMがあることを意味します。

If you are building your own circuit board that doesn’t have it, comment out this line. Then both kinds of skills will be saved to the flash as PROGMEM. Obviously, it will reduce the available flash space for functional codes. If there were too many skills, it may even exceed the size limit for uploading the sketch.

それがない回路基板を自分で作成する場合は、この行をコメントアウトしてください。両方のスキルがPROGMEMとしてフラッシュに保存されます。それによって機能コードのための利用可能なフラッシュのスペースが減ります。スキルの数が多すぎると、スケッチをアップロードする際のサイズ制限を超えてしまうことがあります。

8.2.2. スキル配列のデータ構造について

関節角度の1フレームは静止した姿勢を定義し、一連のフレームは歩行や動作などの連続した姿勢を定義します。次の2つの例をご覧ください:

const char rest[] PROGMEM = { 
1, 0, 0, 1,
  -30, -80, -45,   0,  -3,  -3,   3,   3,  75,  75,  75,  75, -55, -55, -55, -55,};

const char crF[] PROGMEM = { 
36, 0, -3, 1,
  61,  68,  54,  61, -26, -39, -13, -26,
  66,  61,  58,  55, -26, -39, -13, -26,
  ...
  51,  81,  45,  72, -25, -37, -12, -25,
  55,  76,  49,  68, -26, -38, -13, -26,
  60,  70,  53,  62, -26, -39, -13, -26,
};

以下のようなフォーマットとなっています:

restは静止した姿勢で、16個ある関節角のうち、1フレームしかありません。crFは "crawl forward "の略です。8個(歩行DOF数によっては12個)の関節角で36フレームを構成し、反復歩行を形成します。期待される体勢は、ロボットがスキルを行っているときの体の角度を定義します。予想される角度から体が傾いている場合、バランシング・アルゴリズムがいくつかの調整を計算します。角度比は、-128~127の範囲よりも大きな角度を保存したい場合に使用します。比率を2に変更すると、大きな角度を2で割って保存することができます。

姿勢については1つのフレームしかなく、歩行には複数のフレームがあり、ループしていきます。

以下は動作の例です:

const char pu[] PROGMEM = { 
-8, 0, -15, 1,
 6, 7, 3, 
    0,   0,   0,   0,   0,   0,   0,   0,  30,  30,  30,  30,  30,  30,  30,  30,	 5, 0, 0, 0,
   15,   0,   0,   0,   0,   0,   0,   0,  30,  35,  40,  29,  50,  15,  15,  15,	 5, 0, 0, 0,
   30,   0,   0,   0,   0,   0,   0,   0,  27,  35,  40,  60,  50,  15,  20,  45,	 5, 0, 0, 0,
   15,   0,   0,   0,   0,   0,   0,   0,  45,  35,  40,  60,  25,  20,  20,  60,	 5, 0, 0, 0,
    0,   0,   0,   0,   0,   0,   0,   0,  50,  35,  75,  60,  20,  30,  20,  60,	 6, 0, 0, 0,
  -15,   0,   0,   0,   0,   0,   0,   0,  60,  60,  70,  70,  15,  15,  60,  60,	 6, 0, 0, 0,
    0,   0,   0,   0,   0,   0,   0,   0,  30,  30,  95,  95,  60,  60,  60,  60,	 6, 1, 0, 0,
   30,   0,   0,   0,   0,   0,   0,   0,  75,  70,  80,  80, -50, -50,  60,  60,	 8, 0, 0, 0,
};

puは「push up」の略で、「動作」のことです。このデータ構造は、姿勢や歩行よりも多くの情報を含んでいます。

最初の4つの要素は、動作であることを示すためにフレーム数が負の値として保存されていることを除いて、前と同じように定義されています。次の3つの要素では、シーケンス内の繰り返しフレームを定義します:開始フレーム、終了フレーム、ループサイクルです。つまり、例の6, 7, 3は、行動が7番目のフレームから8番目のフレームまでを3回ループすることを意味します(インデックスは0から始まります)。歩行のようにループするのではなく、行動の配列全体が1度だけ実行されます。

各フレームには16個の関節角が含まれており、最後の4つの要素では、トランジションの速度と各トランジション後の遅延条件が定義されています。

  1. デフォルトの速度係数は4ですが、1(遅い)から127(速い)までの整数に変更できます。単位は 1 ステップあたりの度数です。0に設定すると、サーボの最大速度で目標の角度まで回転します(約0.07秒/60度)。リスクを理解していない限り、10 以上の値を使用することはお勧めできません。

  2. 遅延時間の初期値は0ですが、0~127まで設定でき、単位は50msです。

  3. 3つ目の数値はトリガー軸です。0でない場合は、前の遅延時間は無視されます。次のフレームのトリガーは、対応する軸のボディの角度に依存します。ピッチ軸には1、ロール軸には2を指定します。数字の符号は閾値の方向、つまり現在の角度がトリガーの角度よりも小さいか大きいかを定義します。

  4. 4つ目の数値はトリガー角度です。128度から127度の範囲で設定できます。

8.2.3. 本能を示すサフィックスと優れたポイント

初めてEEPROMにスキルを書き込ませるには、WriteInstinct.inoをアップロードする必要があります。以下の情報が使用されます。

const char* skillNameWithType[] =
{"crI", "puI", "restI", "zeroN",};
const char* progmemPointer[] = 
{cr, pu, rest, zero, };

スキル名の文字列には、IまたはNというサフィックスが付いていることに注意してください。これは、スキルデータをどこに保存するか、いつそのアドレスを割り当てるかをプログラムに伝えるものです。

その後、アップロードされたスケッチがメインスケッチのOpenCat.inoであり、I2C EEPROMを搭載したNyBoardを使用している場合、プログラムはNewbilityリストへのポインタのみを必要とします。

const char* progmemPointer[] = {zero};

を使って、あらかじめ定義されたスキルの完全な知識を引き出すことができます。

8.3. 新しいスキルや行動を定義する

8.3.1既存のスキルテンプレートの変更

すでにInstinctBittle.hには "zeroN "というスキルが存在しています。

まずmndex Offsetというコマンドで個々の関節を目標の位置に移動させてから、配列の中の関節角度(太字フォント)を一度に置き換えることができます。

const char zero[] PROGMEM = { 
1, 0, 0, 1,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,};

Newbilityとして宣言されており、I2C EEPROMに書き込む必要がないため、配列を変更するたびにOpenCat.inoをアップロードするだけでよい(WriteInstinct.inoをアップロードしなくてもよい)。IRリモコンの7>を押すか、シリアルモニタにkzeroと入力することで、新しい姿勢を引き起こすことができます。

このスキルの名前は変更できますが、IRリモートのキーマップを更新することを忘れないでください。

8.3.2. Add more skills in InstinctBittle.h

InstinctBittle.hでスキルを増やすことができます。ファイルの最初にスキル番号を増やし、スキルリスト配列に対応するスキル名とポインタを追加することを忘れないでください。

スキルはシリアルモニタからトークンの「k」コマンドで呼び出すことができます。例えば、ksitはBittleを姿勢「sit」に動かします。

また、新しいスケッチをアップロードしなくても、m、i、lトークンを使ってシリアルポートから姿勢フレームを送ることで、新しいスキルをチューニングすることができます。スキルを微調整した後は、instinctBittle.hに保存し、newbilityやinstinctとしてボードにアップロードすることができます。

使用可能なスキル名については、必ず実際のコードを確認してください。ソフトウェアを繰り返し開発する中で、スキルセットを変更することがあります。

このgit repoは、カスタマイズされた歩行を開発したい場合の良い出発点となります。逆運動学の計算を行う場合は、以下の主要なディメンションを使用してモデルを構築することができます。

8.3.3. Automation

これまでBittleは赤外線リモコンで操作していました。Bittleの行動を決めるのはあなたです。 パソコンやスマートフォンと接続して、自動的に指示を出すことができます。そうすると「Bittle」はその指示に一生懸命従います。

また、タッチセンサーなどのセンサーや、ボイスコントロールなどの通信モジュールを追加することで、新しい知覚や判断力を持たせることができます。その自動行動を積み重ねて、最終的には自分で生きていくことができるのです。

最終更新