8 👨‍🏫 Teach Nybble New Skills

"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. Understand skills in Instinct.h.

One frame of joint angles defines a static posture, while a series of frames defines a periodic motion, usually a gait.

EEPROM has limited (1,000,000) write cycles. So I want to minimize write operations on it.

There are two kinds of skills: Instincts and Newbility. The addresses of both are written to the onboard EEPROM(1KB) as a lookup table, but the actual data is stored at different memory locations:

  • I2C EEPROM (8KB) stores Instincts.

The Instincts are already fine-tuned/fixed skills. You can compare them to “muscle memory”. Multiple Instincts are linearly written to the I2C EEPROM only once with WriteInstinct.ino. Their addresses are generated and saved to the lookup table in onboard EEPROM during the runtime of WriteInstinct.ino.

  • PROGMEM (sharing the 32KB flash with the sketch) stores Newbility.

A Newbility is any new experimental skill that requires a lot of tests. It's not written to the I2C nor onboard EEPROM, but the flash memory in the format of PROGMEM. It has to be uploaded as one part of Arduino sketch. Its address is also assigned during the runtime of the code, though the value rarely changes if the total number of skills (including all Instincts and Newbilities) is unchanged.

8.2. Example Instinct.h

//a short version of Instinct.h as example
#define WalkingDOF 8
#define NUM_SKILLS 6
#define I2C_EEPROM
const char cr[] PROGMEM = {
26, 0, -5,
35, 37,-46,-53,-23,-32, -3, 12,
40, 28,-42,-59,-24,-28, -4, 12,
...
33, 39,-47,-51,-22,-32, -3, 11,
};
const char stair[] PROGMEM = {
54, 0, 30,
44, 90,-39,-38, 10,-32,-10, 32,
45, 90,-32,-46, 16,-38,-16, 38,
43, 90,-44,-32, 6,-26, -6, 26,
};
const char pu1[] PROGMEM = {
1, 0, 0,
0,-30, 0, 0, 0, 0, 0, 0, 20, 20, 60, 60, 60, 60,-55,-55,};
const char pu2[] PROGMEM = {
1, 0, 0,
0, 10, 0, 0, 0, 0, 0, 0, 60, 60, 40, 40,-45,-45,-55,-55,};
const char rest[] PROGMEM = {
1, 0, 0,
-30,-80,-45, 0, -3, -3, 3, 3, 60, 60,-60,-60,-45,-45, 45, 45,};
const char zero[] PROGMEM = {
1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
#if !defined(MAIN_SKETCH) || !defined(I2C_EEPROM)
const char* skillNameWithType[] =
{"crI", "stairN", "pu1I", "pu2I", "restI", "zeroN",};
const char* progmemPointer[] =
{cr, stair, pu1, pu2, rest, zero, };
#else
const char* progmemPointer[] = {stair, zero};
#endif

8.2.1. Defined constants

define WalkingDOF 8

Means the number of DoF (Degree of Freedom) for walking is 8 on Nybble.

define NUM_SKILLS 6

Means the total number of skills is 6. It should be the same as the number of items in list const char* skillNameWithType[].

define I2C_EEPROM

Means there’s an I2C EEPROM on NyBoard to save Instincts.

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.

8.2.2. Data structure of skill array

Observe the following two skills:

const char rest[] PROGMEM = {
1, 0, 0,
-30,-80,-45, 0, -3, -3, 3, 3, 60, 60,-60,-60,-45,-45, 45, 45,};
const char cr[] PROGMEM = {
26, 0, -5,
35, 37,-46,-53,-23,-32, -3, 12,
40, 28,-42,-59,-24,-28, -4, 12,
...
33, 39,-47,-51,-22,-32, -3, 11,
};

They are formatted as:

A posture has only one frame, and a gait has more than one frames.

8.2.3. Suffix for indicating Instinct and Newbility

You must upload WriteConst.ino to have the skills written to EEPROM for the first time. The following information will be used:

const char* skillNameWithType[] =
{"crI", "stairN", "pu1I", "pu2I", "restI", "zeroN",};
const char* progmemPointer[] =
{cr, stair, pu1, pu2, rest, zero, };

Notice the suffix I or N in the skill name strings. They tell the program where to store skill data and when to assign their addresses.

Later, if the uploaded sketch is main sketch Nybble.ino, and if you are using NyBoard that has an I2C EEPROM, the program will only need the pointer to Newbility list

const char* progmemPointer[] = {stair, zero};

to extract the full knowledge of pre-defined skills.

8.3. Define new skills and behaviors

8.3.1 Modify the existing skill template

There’s already a skill called “zeroN” in Instinct.h. It’s a posture at zero state waiting for your new definition.

You can first use command mIndex Offset to move individual joint to your target position, then replace the joint angles (bold fonts) in array at once:

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

Because it’s declared as a Newbility and doesn’t require writing to I2C EEPROM, you can simply upload Nybble.ino everytime you change the array (without uploading WriteInstinct.ino). You can trigger the new posture by pressing 7 on the IR remote, or type kzero in the serial monitor.

You can rename this skill, but remember to update the keymap of IR remote.

8.3.2. Explore more skills hidden in Instinct.h

There are more skills hidden in Instinct.h. They can be called from the serial monitor with token 'k' command. For example, kcd1 will move Nybble to posture "cd1", which means "check down".

You can also write short programs to perform multiple skills sequentially, like the "push up" behavior in Nybble.ino. For example, by connecting posture "cd1" and "cd2" as a looping sequence, you can make Nybble lean forward, look from left to right, then right to left.

8.3.3 Automation

So far Nybble is controlled by the infrared remote. You make decisions for Nybble's behavior.

You can connect Nybble with your computer or smartphone, and let them send out instructions automatically. Nybble will try its best to follow those instructions.

By adding some sensors (like a touch sensor), or some communication modules (like a voice control module), you can bring new perception and decision abilities to Nybble. You can accumulate those automatic behaviors and eventually make Nybble live by itself!