Myo Powered Arduino Sketches and Applications

Today we're gonna cover integrating the Myo armband in our Arduino projects using the MyoBridge library. If you've already played around with the Myo you know that it still lacks some very needed accuracy when it comes to pose recognition, which is to be expected since the developers are trying to provide a for all solution while relying on very minimalistic calibration.

During one of our projects with the Myo we were in a position where we couldn't afford any false positives, missed positives though weren't a problem. So we developed a little library called PoseChainer that goes hand in hand with the MyoBridge library.

With PoseChainer you can basically create custom pose chains, and check when they're fulfilled. For example one pose chain can look like this: {WAVE_IN, WAVE_IN, FINGERS_SPREAD}. PoseChainer keeps a record of the poses that were recently registered, and checks if any of them matches the record either fully or partially. Enough with the talking, let's start doing stuff.

/**
 * @file   readPoseData.ino
 * @author Valentin Roland (webmaster at vroland.de)
 * @date   September-October 2015
 * @brief  Example file demonstrating the pose data reading.
 *
 * This file is part of the MyoBridge Project. For more information, visit https://github.com/vroland/MyoBridge.
 * 
 * @include readPoseData.ino
 */


#include <MyoBridge.h>
#include <SoftwareSerial.h>

//SoftwareSerial connection to MyoBridge
SoftwareSerial bridgeSerial(2,3);

//initialize MyoBridge object with software serial connection
MyoBridge bridge(bridgeSerial);

//declare a function to handle pose data
void handlePoseData(MyoPoseData& data) {

  //convert pose data to MyoPose
  MyoPose pose;
  pose = (MyoPose)data.pose;

  //print the pose
  Serial.println(bridge.poseToString(pose));
}

void setup() {  
  //initialize both serial connections
  Serial.begin(115200);
  bridgeSerial.begin(115200);

  //wait until MyoBridge has found Myo and is connected. Make sure Myo is not connected to anything else and not in standby!
  Serial.println("Searching for Myo...");
  bridge.begin();
  Serial.println("connected!");

  //set the function that handles pose events
  bridge.setPoseEventCallBack(handlePoseData);
  //tell the Myo we want Pose data
  bridge.enablePoseData();
  //make sure Myo is unlocked
  bridge.unlockMyo();

  //You have to perform the sync gesture to receive Pose data!
}

void loop() {  
  //update the connection to MyoBridge
  bridge.update();
}

This is the readPoseData example from the MyoBridge repo, it should be a good place to learn about the workflow and to start hacking. Let's a take a closer look at the code one piece at a time. I'm gonna assume that everything in this sketch other than the Myo related code is clear.

#include <MyoBridge.h>
....
SoftwareSerial bridgeSerial(2,3);  
MyoBridge bridge(bridgeSerial);  

First we include MyoBridge like we do any other library, then we create a SoftwareSerial object called bridgeSerial using PIN 2 for RX and PIN 3 for TX, afterwards we pass bridgeSerial to the constructor of MyoBridge to create an object called bridge which is going to use pins 2 & 3 to communicate with our BLE module.

void handlePoseData(MyoPoseData& data) {

  MyoPose pose;
  pose = (MyoPose)data.pose;

  //do something with pose
}

handlePoseData is a callback function which fires up when ever the Myo armband sends us a registered pose, we'll pass this function later on to our MyoBridge object bridge. You can change the name of the function if you want as long you keep the parameter type MyoPoseData&, but I wouldn't recommend that since the name is very clear as it is.

pose is an integer that represents the registered pose, below is a list of the possible values of pose and what they represent:

/** source: myohw.h - https://github.com/thalmiclabs/myo-bluetooth/**/

typedef enum {  
    myohw_pose_rest           = 0x0000,
    myohw_pose_fist           = 0x0001,
    myohw_pose_wave_in        = 0x0002,
    myohw_pose_wave_out       = 0x0003,
    myohw_pose_fingers_spread = 0x0004,
    myohw_pose_double_tap     = 0x0005,
    myohw_pose_unknown        = 0xffff
} myohw_pose_t;

We can work with those names provided by thalmiclabs since MyoBridge.h includes myohw.h or use MyoBridge's own definitions, which I find more convenient:

/** source: MyoBridge.h - https://github.com/vroland/MyoBridge/**/

typedef enum {  
    MYO_POSE_REST             = myohw_pose_rest,
    MYO_POSE_FIST             = myohw_pose_fist,
    MYO_POSE_WAVE_IN          = myohw_pose_wave_in,
    MYO_POSE_WAVE_OUT         = myohw_pose_wave_out,
    MYO_POSE_FINGERS_SPREAD   = myohw_pose_fingers_spread,
    MYO_POSE_DOUBLE_TAP       = myohw_pose_double_tap,
    MYO_POSE_UNKNOWN          = myohw_pose_unknown
} MyoPose;

Alrighty now that we covered handlePoseData let's keep going and check our setup function.

void setup() {  
  ...

  //wait until MyoBridge has found Myo and is connected. Make sure Myo is not connected to anything else and not in standby!
  Serial.println("Searching for Myo...");
  bridge.begin();
  Serial.println("connected!");

  //set the function that handles pose events
  bridge.setPoseEventCallBack(handlePoseData);
  //tell the Myo we want Pose data
  bridge.enablePoseData();
  //make sure Myo is unlocked
  bridge.unlockMyo();

  //You have to perform the sync gesture to receive Pose data!
}

bridge.begin() starts the connection process, anything after it won't be reached unless a connection was established. bridge.setPoseEventCallBack(handlePoseData) passes our bridge the handlePoseData callback function from earlier so that it would be called every time there's a pose to be handled. The rest in the setup function should be clear with the help of the comments in the code.

void loop() {  
  //update the connection to MyoBridge
  bridge.update();
}

The last part of the example is just updating the connection to the MyoBridge so that we get our data.

Now that we've understood what's going on in the example, let's introduce PoseChainer into the code and change things around. Let's make a sketch that turns the Arduino led on and off based on the pose chain.

#include <MyoBridge.h>
#include <SoftwareSerial.h>
#include <PoseChainer.h>

//poseChainer limits on how many chains allowed and how many poses should be kept in the record
#define CHAIN_LIMIT 2
#define RECORD_LIMIT 3

...
//initialize poseChainer object with the limits
PoseChainer poseChainer(CHAIN_LIMIT, RECORD_LIMIT);  

PoseChainer.h is the header file, CHAIN_LIMIT defines the limit of chains that can be tracked, and RECORD_LIMIT defines the number of recent poses that the record is gonna hold. The last line initializes a PoseChainer object with the predefined limits.

//desired pose chains
uint8_t turn_led_on_chain[2] = {MYO_POSE_WAVE_IN, MYO_POSE_WAVE_IN};  
#define SIZE_TURN_LED_ON_CHAIN 2

uint8_t turn_led_off_chain[3] = {MYO_POSE_WAVE_OUT, MYO_POSE_DOUBLE_TAP, MYO_POSE_DOUBLE_TAP};  
#define SIZE_TURN_LED_OFF_CHAIN 3

Here we define an array for each pose we wanna detect, and then define a constant for the size of the array, which will come in handy later on.

void setup() {  
  ...

  //add the chains we defined earlier to our poseChainer
  poseChainer.addChain(turn_led_on_chain, SIZE_TURN_LED_ON_CHAIN);
  poseChainer.addChain(turn_led_off_chain, SIZE_TURN_LED_OFF_CHAIN);

  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

In the setup() function we add the two chains we defined earlier to the poseChainer object using the function PoseChainer.addChain(uint8_t pose_chain[], uint8_t chain_size); .

void handlePoseData(MyoPoseData& data) {  
   ...

  //poseChainer doesn't care if the pose is MYO_POSE_REST or not, so let's make sure we don't record REST poses
  if (pose != MYO_POSE_REST && pose != MYO_POSE_UNKNOWN) {
    //insert the registered pose to the record
    poseChainer.insertPoseToChainRecord(pose);
    //check if any chain matches our record
    sVibeChain vibeChain = poseChainer.whichChain();

    //check which chain matches or about to match the record
    switch (vibeChain.found_chain) {
      //-1 means no chain, 0 is the first chain we added to the record, 1 the second etc.
      case -1:
        //no match, break and do noting
        break;

      case 0:
        //check if full match happened
        if (vibeChain.vibe_level == SIZE_TURN_LED_ON_CHAIN) {
          //LED ON
          digitalWrite(LED_BUILTIN, HIGH);
          //level 2 myo vibration to signal completion
          bridge.vibrate(2);
          //empty record
          poseChainer.emptyRecord();
          break;
        }

      case 1:
        //check if full match happened
        if (vibeChain.vibe_level == SIZE_TURN_LED_OFF_CHAIN) {
          //LED OFF
          digitalWrite(LED_BUILTIN, LOW);
          //level 2 myo vibration to signal completion 
          bridge.vibrate(2);
          //empty record
          poseChainer.emptyRecord();
          break;
        }
      default:
        //in the past cases we didn't use break when a full match didn't happen
        //switch falls to default, and does a level 1 myo vibration to signal progress
        bridge.vibrate(1);

    }
  }
}

The handlePoseData function is where we'll do most of the work. First we add a conditional statement to ignore both MYOPOSEREST and MYOPOSEUNKNOWN. Now that we got rid of the noise, we append the valid registered pose to the record. After updating the record we check if a certain pose chain was activated or in the process of it, by calling PoseChainer.whichPose() which returns a sVibeChain struct.

/* source: PoseChainer.h - https://github.com/RaquenaTeam/PoseChainer */

struct sVibeChain  
{
    uint8_t vibe_level;
    int8_t found_chain;
};

found_chain should be clear, but WTF is vibe_level? PoseChainer.whichChain() first checks if a certain pose chain matches the record completely, and if it doesn't find any, it eliminates the oldest entry and checks if any chain is close to matching the record. vibe_level returns how many poses in the chain match the record so far. If vibe_level equals the size of a chain, it means the full chain matches the record. The rest should also be clear with the help of the comments, however one important thing to remember is that PoseChainer.whichChain() returns a found_chain int representing the order, in which the chains were added using PoseChainer.addChain(uint8_t pose_chain[], uint8_t chain_size);.

Hope you enjoyed this tutorial! All questions regarding the topic are obviously welcome:)

Ahmed Alsharif

Read more posts by this author.