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 InstinctNybble.h.

EEPROM has limited (1,000,000) write cycles. So I want to minimize the 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 InstinctNybble.h

//a short version of Instinct.h as example
#define WalkingDOF 8
#define NUM_SKILLS 4
#define I2C_EEPROM
const char rest[] PROGMEM = {
1, 0, 0, 1,
-30,-80,-45, 0, -3, -3, 3, 3, 60, 60,-60,-60,-45,-45, 45, 45,};
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 cr[] PROGMEM = {
26, 0, -5, 1,
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 pu[] PROGMEM = {
-5, 0, -15, 1,
1, 2, 3,
30, 30, 0, 0, 0, 0, 0, 0, 60, 60, 70, 70, 15, 15, -70, -70, 6, 0,
0, -40, 0, 0, 0, 0, 0, 0, 30, 30, 95, 95, 60, 60, -70, -70, 6, 1,
5, 5, 0, 0, 0, 0, 0, 0, 75, 75, 55, 55, -50, -50, -75, -75, 8, 0,
5, 5, 0, 0, 0, 0, 0, 0, 75, -70, 55, 55, -50, 70, -75, -75, 8, 0,
60, -30, -45, 0, 0, 0, 0, 0, 70, -70, 55, 0, -30, -45, -75, -45, 8, 1,
};
#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. Defined constants

define WalkingDOF 8

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

define NUM_SKILLS 4

Means the total number of skills is 4. 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

One frame of joint angles defines a static posture, while a series of frames defines a sequencial postures, such as a gait or a behavior. Observe the following two examples:

const char rest[] PROGMEM = { //posture
1, 0, 0, 1,
-30,-80,-45, 0, -3, -3, 3, 3, 60, 60,-60,-60,-45,-45, 45, 45,};
const char cr[] PROGMEM = { //gait
26, 0, -5, 1,
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:

rest is a static posture, it has only one frame of 16 joint angles. cr is the abbreviation for "crawl". It has 26 frames of 8 (or 12, depending on the number of walking DOF) joint angles that form a repeatitive gait. Expected body orientation defines the body angle when the robot is conducting the skill. If the body is tilted from the expected angles, the balancing algorithm will calculate some adjustments. Angle ratio is used when you want to store angles larger than the range of -128 to 127. Change the ratio to 2 so that you can save those large angles by dividing 2.

A posture has only one frame, and a gait has more than one frames and will be looped over.

The following example is a behavior:

const char pu[] PROGMEM = { //behavior
-5, 0, -15, 1,
1, 2, 3,
30, 30, 0, 0, 0, 0, 0, 0, 60, 60, 70, 70, 15, 15, -70, -70, 6, 0,
0, -40, 0, 0, 0, 0, 0, 0, 30, 30, 95, 95, 60, 60, -70, -70, 6, 1,
5, 5, 0, 0, 0, 0, 0, 0, 75, 75, 55, 55, -50, -50, -75, -75, 8, 0,
5, 5, 0, 0, 0, 0, 0, 0, 75, -70, 55, 55, -50, 70, -75, -75, 8, 0,
60, -30, -45, 0, 0, 0, 0, 0, 70, -70, 55, 0, -30, -45, -75, -45, 8, 1,
};

pu is short for "push up", and is a "behavior". Its data structure contains more information than posture and gait.

The first four elements are defined the same as before, except that the number of frames is saved as a negative value to indicate that it's a behavior. The next three elements defines the repeating frames in the sequence: starting frame, ending frame, looping cycles. So the 1, 2, 3 in the example means the behavior should loop from the second to the third frame for three times (the index starts from zero). The whole behavior array will be executed only once, rather than looping over like the gait.

Each frame contains the 16 joint angles, and the last two elements defines the speed of the transition, and the delay time after each transition. The default speed factor is 4, it can be changed to an integer from 1 (slow) to 10(fast). The default delay is 0, it can be set from (0 to 100), the unit is 100ms.

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", "puI", "restI", "zeroN",};
const char* progmemPointer[] =
{cr, pu, 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[] = {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 InstinctNybble.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. Add more skills in InstinctNybble.h

You can add more skills in InstinctNybble.h. Remember to increase the skill number at the beginning of the file and add the corresponding skill name and pointer in the skill list array.

A skill can be called from the serial monitor with token 'k' command. For example, ksit will move Nybble to posture "sit".

You can also tune new skills by sending posture frames through the serial port, using the m, i, l tokens without uploading a new sketch. After fine-tuning the skill, you can save it in the instinctNybble.h and upload it to the board as a newbility or instinct.

Always check the actual code for the available skill names. We may alter the skill set as we iterate the software.

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!