11 Tutorial on Creating New Skills

Preparation

Get familiar with the standard process of assembly, uploading the standard program Opencat.ino, and calibration (refer to chapter 6 of Bittle User Manuals). Validate that the following functions work as expected.
  • Press the button (>>|)which is in the 2nd row, the 2nd column on the IR remote. Later we will use (2, 2) as the index. You can also enter kbalance via serial port. Bittle should stand up and keep balance;
  • Press the button (9)which is in the 7th row, the 3rd column on the IR remote. Later we will use (7, 3) as the index. You can also enter kzero via serial port. Bittle should enter a posture similar to the calibration state, which is the "zero" skill in the program (as shown in the figure below).
A visualization of the perfect zero state
Open the folder WriteInstinct/, create a backup file of instinctBittle.h as instinctBittle.hBAK.
The coordinate system of Bittle is shown in the figure below.
The coordinate system
Yaw: Rotate around the Z-axis.
Pitch: Rotate around the Y-axis (nose up/down).
Roll: Rotate around the X-axis (tilt left/right).
For the legs, on the left side, counterclockwise is positive, clockwise is negative. On the right side, the rotation directions are mirrored. The origin position and rotation direction of a single leg (upper/lower leg) around the joint are shown in the first figure.
The indexing order of all the joints is shown in the figure below:
The indexing order of all the joints
The skill arrays are defined in WriteInstinct/instinctBittle.h, formated as the figure below. Note the index starts from 0.
The format of skill arrays
Total # of Frames defines the number of frames of a certain skill.
For example, rest is a static posture, it has only one frame of 16 joint angles.
crF is the abbreviation for "crawl forward". It has 36 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.

Understand the format of a posture

Zero is a static posture, Find the zero array in instinctBittle.h:
1
const char zero[] PROGMEM = {
2
1, 0, 0, 1,
3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
Copied!
Change some of these values to:
1
const char zero[] PROGMEM = {
2
1, 0, 0, 1,
3
70, 0, 0, 0, 0, 0, 0, 0, -60, 0, 0, 0, 60, 0, 0, 0,};
Copied!
Save the change, and upload the program OpenCat.ino to Bittle (Note: there is no need to upload writeinsict.ino or opencat.h), after upload, press the button(9)which is in the 7th row, the 3rd column on the IR remote to trigger the modified zero skill. You can see the posture is changed.
Explanation why the new zero posture looks in that way. The coordinate system with the new angle data is shown in the figure below.
The first element (1) represents the total number of frames of the skill,1 means it is a static posture.
The 4th element (1) represents the Angle ratio. It means all the following indexed joint angles are actual angles (because each of them multiply by 1).
From the 5th to the 20th elements represent 16 indexed joint angles.
For Bittle, the 5th element (joint index 0) means the servo on Bittle's neck rotates counterclockwise 70 (the unit is in degrees). Bittle's head turn to it's left side.
The 13th element (joint index 8) means Bittle's left upper leg rotates clockwise 60 (the unit is in degrees) around the joint.
The 17th element (joint index 12) means Bittle's left lower leg rotates counterclockwise 60 (the unit is in degrees) around the joint.
All the other indexed joint angles remain 0 (the unit is in degrees).
You can define a new posture and upload it to the robot. Call the new posture with the IR remote or the serial monitor.

Explain exceptions of expected body orientations

Look at the example of:
1
const char balance[] PROGMEM = {
2
1, 0, 0, 1,
3
0, 0, 0, 0, 0, 0, 0, 0, 30, 30, 30, 30, 30, 30, 30, 30,};
Copied!
and
1
const char sit[] PROGMEM = {
2
1, 0, -30, 1,
3
0, 0, -45, 0, -5, -5, 20, 20, 45, 45, 105, 105, 45, 45, -45, -45,};
Copied!
With the gyro activated, rotate the robot and see its adjustments. Explain why the expected angle is needed in the definition.
The 2nd and 3rd elements represent Expected body orientation, corresponding to the roll angle and the pitch angle.
The unit is in degrees.
The sign of the number follows the right-handed spiral rule, look in the direction pointed by the axis arrow, clockwise is positive, counterclockwise is negative.
With the gyro activated, rotate Bittle, when the body is tilted from the expected angles, the balancing algorithm will calculate some adjustments to keep it in this posture.

Explain exceptions of large angles

Look at the example of
1
const char rc[] PROGMEM = {
2
-3, 0, 0, 2,
3
0, 0, 0,
4
0, 0, 0, 0, 0, 0, 0, 0, -88, -43, 67, 87, 42, -35, 42, 42, 15, 0, 0, 0,
5
0, 0, 0, 0, 0, 0, 0, 0, -83, -88, 87, 42, 42, 42, 42, -40, 15, 0, 0, 0,
6
-8, -20, -11, 0, -1, -1, 0, 0, 18, 18, 18, 18, -14, -14, -14, -14, 10, 0, 0, 0,
7
};
Copied!
It's designed for large angles out of the range -128~127.
The 4th element represents the angle ratio. It means all the following all indexed joint angles real values is equal to each of them multiply by the value of this angle ratio.

Understand the format of a gait

A series of frames defines sequential postures, such as a gait. Find the bk array in instinctBittle.h:
1
const char bk[] PROGMEM = {
2
35, 0, 0, 1,
3
46, 54, 46, 54, -5, -23, -5, -23,
4
43, 58, 43, 58, -5, -24, -5, -24,
5
......
6
52, 43, 52, 43, -5, -21, -5, -21,
7
50, 48, 50, 48, -5, -22, -5, -22,
8
47, 53, 47, 53, -5, -23, -5, -23,
9
};
Copied!
bk is the abbreviation for "back".
The first four elements are defined the same as before, The first element (35) means it has 35 frames. Next are 35 frames of 8 indexed joint angles that form a repetitive gait.

Understand the format of a behavior

Modify the zero skill as:
1
const char zero[] PROGMEM = {
2
-1, 0, 0, 1,
3
0, 0, 0,
4
70, 0, 0, 0, 0, 0, 0, 0, -60, 0, 0, 0, 60, 0, 0, 0,4,0,0,0};
Copied!
Upload the new zero skill and see the effect. It should be the same. (Later explanation a posture can be considered as a behavior or gait with one frame.)
Copy the content of hi array to zero:
1
const char zero[] PROGMEM = {
2
-3, 0, -30, 1,
3
1, 2, 3,
4
0, -20, -60, 0, 0, 0, 0, 0, 30, 30, 90, 80, 60, 60, -40, -30, 4, 1, 0, 0,
5
35, -5, -60, 0, 0, 0, 0, 0, -75, 30, 75, 60, 40, 75, -30, 0, 10, 0, 0, 0,
6
40, 0, -35, 0, 0, 0, 0, 0, -60, 30, 75, 60, 60, 75, -30, 0, 10, 0, 0, 0,
7
};
Copied!
Change a few values:
1
const char zero[] PROGMEM = {
2
-3, 0, -30, 1,
3
1, 2, 6,
4
0, -20, -60, 0, 0, 0, 0, 0, 30, 30, 90, 80, 60, 60, -40, -30, 4, 1, 0, 0,
5
35, -5, -60, 0, 0, 0, 0, 0, -75, 30, 75, 60, 40, 75, -30, 0, 10, 0, 0, 0,
6
-40, 0, -35, 0, 0, 0, 0, 0, -60, 30, 75, 60, 60, 75, -30, 0, 20, 0, 0, 0,
7
};
Copied!
Save and upload OpenCat.ino. Call the new zero skill to see the effect.
Explanation on the format of a behavior.
For example, the modified hi behavior:
The first four elements are defined the same as before, except that the number of frames is saved as a negative value (-3) to indicate that it's a behavior.
The 2nd element (0) means the Roll rotation body angle is 0 (The unit is in degrees). The 3rd element (-30) means the Pitch rotation body angle is -30 (The unit is in degrees). If the body is tilted from the expected angles, the balancing algorithm will calculate some adjustments. The 4th element (1) means all the following all indexed joint angles are real values.
The next three elements define the repeating frames in the sequence: starting frame (1), ending frame (2), looping cycles (6). So the 1, 2, 6 in the example means the behavior should loop from the 2nd to the 3rd frame for 6 times (the index starts from 0). The whole behavior array will be executed only once, rather than looping over like the gait.
For behavior, each frame contains 16 indexed joint angles, and the last 4 elements define the speed of the transition, and the delay condition after each transition:
  1. 1.
    The first number represents speed factor. The default speed factor is 4, it can be changed to an integer from 1 (slow) to 127 (fast). The unit is in degrees 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. Here for this example, in the first frame, it is default value (4).
  2. 2.
    The 2nd number represents delay time. The default delay is 0. It can be set from 0 to 127, the unit is 50 ms. Here for this example, in the first frame, it is 1.
  3. 3.
    The 3rd number represents 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. Here for this example, in the first frame, it is 0.
  4. 4.
    The 4th number represents the trigger angle. It can be -128 to 127 degrees. Here for this example, in the first frame, it is 0.

Understand the memory structure

Explanation the locations:
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.
  • Flash (sharing the 32KB flash with the program) 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.
Explanation the implementation code :
1
#if !defined(MAIN_SKETCH) || !defined(I2C_EEPROM)
2
//if it's not the main sketch to save data or there's no external EEPROM,
3
//the list should always contain all information.
4
const char* skillNameWithType[]={"bdFI","bkI","bkLI","bkRI","crFI","crLI","crRI","trFI","trLI","trRI","vtI","wkFI","wkLI","wkRI","balanceI","buttUpI","calibI","droppedI","liftedI","restI","sitI","strI","zeroN","bfI","ckI","hiI","pdI","peeI","puI","rcI","stpI",};
5
const char* progmemPointer[] = {bdF, bk, bkL, bkR, crF, crL, crR, trF, trL, trR, vt, wkF, wkL, wkR, balance, buttUp, calib, dropped, lifted, rest, sit, str, zero, bf, ck, hi, pd, pee, pu, rc, stp, };
6
#else //only need to know the pointers to newbilities, because the intuitions have been saved onto external EEPROM,
7
//while the newbilities on progmem are assigned to new addresses
8
const char* progmemPointer[] = {zero, };
9
#endif
Copied!
The first section is active when uploading WriteInstinct.ino. It contains all the skills' data and pointers. The skill names contain a suffix, "N" or "I" to indicate whether it's a Newbility or Instinct. The Instinct will be saved to the external I2C EEPROM while the Newbility will be saved to the flash. The address of all the skills will be saved to the onboard EEPROM.
The second section is active when uploading OpenCat.ino. Because the Instincts are already saved in the external EEPROM, their data is omitted to save space. The Newbility will be saved to the flash with the upload so you can update them directly with OpenCat.ino. It's very useful if you are still tuning a new skill.
In the example code, only zero is defined as a Newbility so you don't need to re-upload WriteInstinct.ino for experiments.

Create a new behavior

What if we want to add more skills with customized names and button assignments?
Serial commands
Could not load image
Keymap on the IR remote
If you want to add more skills, you can refer to the implementation of serial commands (the table above) and keymap (the figure above) in the program code.
Add an example of a Newbility test and assign it to a button on the IR remote. Upload the skill and call it with both IR remote and serial monitor.
1
#if !defined(MAIN_SKETCH) || !defined(I2C_EEPROM)
2
//if it's not the main sketch to save data or there's no external EEPROM,
3
//the list should always contain all information.
4
const char* skillNameWithType[]={"bdFI","bkI","bkLI","bkRI","crFI","crLI","crRI","trFI","trLI","trRI","vtI","wkFI","wkLI","wkRI","balanceI","buttUpI","calibI","droppedI","liftedI","restI","sitI","strI","zeroN","bfI","ckI","hiI","pdI","peeI","puI","rcI","stpI","testN"};
5
const char* progmemPointer[] = {bdF, bk, bkL, bkR, crF, crL, crR, trF, trL, trR, vt, wkF, wkL, wkR, balance, buttUp, calib, dropped, lifted, rest, sit, str, zero, bf, ck, hi, pd, pee, pu, rc, stp,test};
6
#else //only need to know the pointers to newbilities, because the intuitions have been saved onto external EEPROM,
7
//while the newbilities on progmem are assigned to new addresses
8
const char* progmemPointer[] = {zero, test};
9
#endif
Copied!
Modify a few fields of test to make it an Instinct. Call the behavior with the IR remote or serial monitor.
1
#if !defined(MAIN_SKETCH) || !defined(I2C_EEPROM)
2
//if it's not the main sketch to save data or there's no external EEPROM,
3
//the list should always contain all information.
4
const char* skillNameWithType[]={"bdFI","bkI","bkLI","bkRI","crFI","crLI","crRI","trFI","trLI","trRI","vtI","wkFI","wkLI","wkRI","balanceI","buttUpI","calibI","droppedI","liftedI","restI","sitI","strI","zeroN","bfI","ckI","hiI","pdI","peeI","puI","rcI","stpI","testI"};
5
const char* progmemPointer[] = {bdF, bk, bkL, bkR, crF, crL, crR, trF, trL, trR, vt, wkF, wkL, wkR, balance, buttUp, calib, dropped, lifted, rest, sit, str, zero, bf, ck, hi, pd, pee, pu, rc, stp, test};
6
#else //only need to know the pointers to newbilities, because the intuitions have been saved onto external EEPROM,
7
//while the newbilities on progmem are assigned to new addresses
8
const char* progmemPointer[] = {zero, };
9
#endif
Copied!
Remember to add 1 to the number of skills at the beginning of the header file.
Then you can call this test by entering ktest in the serial monitor. You can also call it from the IR remote if you replace a button definition in OpenCat.h.

Tune skills in realtime

You need to understand the above structure to store a skill on the robot. However, when tuning the skills, things can be easier. To do this, you can connect your computer with the robot through the USB or Bluetooth connector. Then you can send string commands that define the joint angles. The program on the NyBoard will listen and perform the instructions in real-time. In the SerialMaster folder, you can find the ArdSerial.py to play the role of Arduino IDE's serial monitor, but with the ability to write more logic and controls within the script.
Last modified 3mo ago