Nybble User Manual
Searchโ€ฆ
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 the 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

1
//a short version of Instinct.h as example
2
โ€‹
3
#define WalkingDOF 8
4
#define NUM_SKILLS 4
5
#define I2C_EEPROM
6
โ€‹
7
const char rest[] PROGMEM = {
8
1, 0, 0, 1,
9
-30,-80,-45, 0, -3, -3, 3, 3, 60, 60,-60,-60,-45,-45, 45, 45,};
10
const char zero[] PROGMEM = {
11
1, 0, 0, 1,
12
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
13
โ€‹
14
const char cr[] PROGMEM = {
15
26, 0, -5, 1,
16
35, 37,-46,-53,-23,-32, -3, 12,
17
40, 28,-42,-59,-24,-28, -4, 12,
18
...
19
33, 39,-47,-51,-22,-32, -3, 11,
20
};
21
โ€‹
22
const char pu[] PROGMEM = {
23
-5, 0, -15, 1,
24
1, 2, 3,
25
30, 30, 0, 0, 0, 0, 0, 0, 60, 60, 70, 70, 15, 15, -70, -70, 6, 0,
26
0, -40, 0, 0, 0, 0, 0, 0, 30, 30, 95, 95, 60, 60, -70, -70, 6, 1,
27
5, 5, 0, 0, 0, 0, 0, 0, 75, 75, 55, 55, -50, -50, -75, -75, 8, 0,
28
5, 5, 0, 0, 0, 0, 0, 0, 75, -70, 55, 55, -50, 70, -75, -75, 8, 0,
29
60, -30, -45, 0, 0, 0, 0, 0, 70, -70, 55, 0, -30, -45, -75, -45, 8, 1,
30
};
31
โ€‹
32
#if !defined(MAIN_SKETCH) || !defined(I2C_EEPROM)
33
const char* skillNameWithType[] =
34
{"crI", "puI", "restI", "zeroN",};
35
const char* progmemPointer[] =
36
{cr, pu, rest, zero, };
37
#else
38
const char* progmemPointer[] = {zero};
39
#endif
Copied!

8.2.1. Defined constants

define WalkingDOF 8
defines the number of DoF (Degree of Freedom) for walking is 8 on Nybble.
define NUM_SKILLS 4
defines the total number of skills is 4. It should be the same as the number of items in the 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 sequential postures, such as a gait or a behavior. Observe the following two examples:
1
const char rest[] PROGMEM = { //posture
2
1, 0, 0, 1,
3
-30,-80,-45, 0, -3, -3, 3, 3, 60, 60,-60,-60,-45,-45, 45, 45,};
4
โ€‹
5
const char cr[] PROGMEM = { //gait
6
26, 0, -5, 1,
7
35, 37,-46,-53,-23,-32, -3, 12,
8
40, 28,-42,-59,-24,-28, -4, 12,
9
...
10
33, 39,-47,-51,-22,-32, -3, 11,
11
};
Copied!
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 repetitive 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:
1
const char pu[] PROGMEM = { //behavior
2
-5, 0, -15, 1,
3
1, 2, 3,
4
30, 30, 0, 0, 0, 0, 0, 0, 60, 60, 70, 70, 15, 15, -70, -70, 6, 0, 0๏ผŒ0๏ผŒ
5
0, -40, 0, 0, 0, 0, 0, 0, 30, 30, 95, 95, 60, 60, -70, -70, 6, 1, 0๏ผŒ0๏ผŒ
6
5, 5, 0, 0, 0, 0, 0, 0, 75, 75, 55, 55, -50, -50, -75, -75, 8, 0, 0๏ผŒ0๏ผŒ
7
5, 5, 0, 0, 0, 0, 0, 0, 75, -70, 55, 55, -50, 70, -75, -75, 8, 0, 0๏ผŒ0๏ผŒ
8
60, -30, -45, 0, 0, 0, 0, 0, 70, -70, 55, 0, -30, -45, -75, -45, 8, 1, 0๏ผŒ0๏ผŒ
9
};
Copied!
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 define 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 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 16 joint angles, and the last 4 elements define the speed of the transition, and the delay condition after each transition:
    1.
    The default speed factor is 4, it can be changed to an integer from 1 (slow) to 127 (fast). The unit is degree per step. If it's set to 0, the servo will rotate to the target angle by its maximal speed (about 0.07sec/60 degrees). It's not recommended to use a value larger than 10 unless you understand the risks.
    2.
    The default delay is 0. It can be set from 0 to 127, the unit is 50 ms.
    3.
    The 3rd number is the trigger axis. If it's not 0, the previous delay time will be ignored. The trigger of the next frame will depend on the body angle on the corresponding axis. 1 for the pitch axis, and 2 for the roll axis. The sign of the number defines the direction of the threshold, i.e. if the current angle is smaller or larger than the trigger angle.
    4.
    The 4th number is the trigger angle. It can be -128 to 127 degrees.

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:
1
const char* skillNameWithType[] =
2
{"crI", "puI", "restI", "zeroN",};
3
const char* progmemPointer[] =
4
{cr, pu, rest, zero, };
Copied!
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 the main sketch OpenCat.ino, and if you are using NyBoard that has an I2C EEPROM, the program will only need the pointer to the 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 the zero state waiting for your new definition.
You can first use the command mIndex Offset to move an individual joint to your target position, then replace the joint angles (bold fonts) in array at once:
1
const char zero[] PROGMEM = {
2
1, 0, 0,
3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
Copied!
Because itโ€™s declared as a Newbility and doesnโ€™t require writing to I2C EEPROM, you can simply upload OpenCat.ino every time 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 the 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 the 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.
This git repo is a good starting point if you want to develop a customized gait.
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!
Last modified 6mo ago