Skip to contentSkip to author details

The Hand-MIDI Interface Project - Adafruit Feather 32u4 with Magnetometer

Written by Michael Earls
 electronics

A few months ago, I had an idea to create a new musical instrument that mounted on a glove. I used a cycling glove that I had from that time that I bought the Trek bike and rode it twice (long story).

I bought the following parts from Adafruit:

When the parts arrived, I started working on my "instrument". The idea was that I'd mount a Raspberry Pi to the back of my hand and wire up the 9-DOF sensor to one hand and the accelerometer to the other. I quickly learned that the Raspberry Pi is too big to mount on the back of a hand and ended up going a different direction. So, I ordered the following parts to try again:

I also ordered the following from ebay:

Once these new parts arrived, I got busy with my project. I first started by soldering some 4-conductor twisted pair phone wire to the I2C interface of the 9-DOF sensor. I made the wire about 8 inches long and then terminated it with one of the RJ-11 connectors.

9-DOF Sensor with RJ-11 connector 9-DOF Sensor with RJ-11 Connector

I then connected the Cat-3 keystone jack to the Feather's I2C port so that the wires would match how I connected them to the 9-DOF sensor. This would allow me to "plug-and-play" my I2C sensors. I originally tried to sew the sensor onto the palm of the glove using the conductive thread, but I never could get it to solder onto the sensor properly, so I ended up eliminating the conductive thread altogether.

Adafruit Feather with Cat-3 Keystone Jack Adafruit Feather with Cat-3 Keystone Jack

Ultimately, I decided to forego the glove completely until I could prove my concept.

Once I had the hardware wired up, it was time to write the sketch for the Feather. The Adafruit Feather that I bought is based on the Arduino 32u4 processor, so I opened up the Arduino IDE and started modifying the sample code for the 9-DOF sensor (and merged in sample code from the OLED display, as well).

I was able to get my custom cerkit.com logo (the older one) on the display and that made me happy.

cerkit.com Logo on the Adafruit OLED Display Featherwing

Here is the code that displays the output of the 9-DOF sensor to the serial monitor of the Arduino IDE:

#include <Adafruit_SSD1306.h>

/*********************************************************************
  This is an example for our Monochrome OLEDs based on SSD1306 drivers

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/category/63_98

  This example is for a 128x32 size display using I2C to communicate
  3 pins are required to interface (2 I2C and one reset)

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada  for Adafruit Industries.
  BSD license, check license.txt for more information
  All text above, and the splash screen must be included in any redistribution
*********************************************************************/

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM9DS0.h>

using namespace std;

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define BUTTON_A 9
#define BUTTON_B 6
#define BUTTON_C 5

#define CHANNEL 1

bool _invertText = false;

/* Assign a unique base ID for this sensor */
Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(1000);  // Use I2C, ID #1000  
char _xBuf [10] = { 0 };  
char _yBuf [10] = { 0 };  
char _zBuf [10] = { 0 };  
char _lsmData [128] = { 0 };


/***************************************************************************************************
   Splash screen image generated by LCD Assistant
   http://en.radzio.dxp.pl/bitmap_converter/
   See https://learn.adafruit.com/monochrome-oled-breakouts/arduino-library-and-examples
   for more details
 **************************************************************************************************/

static const unsigned char PROGMEM cerkit_splash [] = {  
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x7F, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x8F, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x02, 0x1F, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x04, 0x1F, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x08, 0x1F, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x10, 0x1F, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x38, 0x1F, 0x0F, 0xE4, 0x00, 0x00, 0x00, 0x02, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x3C, 0x0F, 0x1F, 0xE6, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x4E, 0x03, 0x3F, 0x8E, 0x0E, 0x07, 0x02, 0xC2, 0x31, 0xC1, 0xF8, 0x00, 0x07, 0x07, 0x07, 0x60,
  0x47, 0x00, 0xFF, 0x1E, 0x11, 0x08, 0x83, 0x22, 0x40, 0x40, 0x40, 0x00, 0x08, 0x88, 0x84, 0x90,
  0xC3, 0xC0, 0xFC, 0x3F, 0x21, 0x10, 0x42, 0x22, 0x80, 0x40, 0x40, 0x00, 0x10, 0x90, 0x44, 0x90,
  0x81, 0xFE, 0xE0, 0xFF, 0x20, 0x10, 0x42, 0x02, 0x80, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x7F, 0x03, 0xFF, 0x20, 0x1F, 0xC2, 0x03, 0x00, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x07, 0x1F, 0xFF, 0x20, 0x10, 0x02, 0x02, 0x80, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x00, 0xFF, 0xFF, 0x20, 0x10, 0x02, 0x02, 0x40, 0x40, 0x4C, 0x10, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x00, 0xFF, 0xFF, 0x11, 0x08, 0x42, 0x02, 0x20, 0x40, 0x48, 0x38, 0x08, 0x88, 0x84, 0x90,
  0xC0, 0x07, 0x1F, 0xFF, 0x0E, 0x07, 0x82, 0x02, 0x10, 0x40, 0x30, 0x10, 0x07, 0x07, 0x04, 0x90,
  0x40, 0x3F, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x41, 0xFE, 0xC0, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x43, 0xE0, 0xFC, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x2F, 0x80, 0xFF, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x1E, 0x00, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x1C, 0x00, 0xFF, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x0C, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x02, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x80, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x70, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

void setup()  
{
  Serial.begin(9600);
  Serial.println(F("cerkit.com warbly hand controller")); Serial.println("");

  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  // init done

  // Show image buffer on the display hardware.
  // Since the buffer is initialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(2000);

  display.clearDisplay();
  display.display();

  showCustomSplashScreen();

  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(BUTTON_C, INPUT_PULLUP);

  /* Initialize the sensor */
  lsm.begin();

  /* Display some basic information on this sensor */
  displaySensorDetails();

  /* Setup the sensor gain and integration time */
  configureSensor();

  Serial.println(F("Found LSM9DS0 9DOF"));
}


void loop() {  
  performSensorSweep();
}

void showCustomSplashScreen()  
{
  // compensate for original image being inverted
  display.invertDisplay(true);
  display.drawBitmap(0, 0, cerkit_splash, 128, 32, WHITE);
  display.display();
  delay(2000);

  // put the display back to normal
  display.invertDisplay(false);

  display.clearDisplay();
  display.display();
}

void updateScreen(char* msg, int x, int y)  
{
  display.setCursor(x, y);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.print(msg);
  display.display();
}

void printMessage(char* msg)  
{
  printMessage(msg, 2);
}

void printMessage(char* msg, int textSize)  
{
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(textSize);
  if (_invertText)
  {
    display.setTextColor(BLACK, WHITE); // 'inverted' text
  }
  else
  {
    display.setTextColor(WHITE);
  }

  display.print(msg);
  display.display();
}

void performSensorSweep()  
{
  /* Get a new sensor event */
  sensors_event_t accel, mag, gyro, temp;

  lsm.getEvent(&accel, &mag, &gyro, &temp);

  // print out accelerometer data
  Serial.print("Accel X: "); Serial.print(accel.acceleration.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(accel.acceleration.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(accel.acceleration.z);     Serial.println("  \tm/s^2");
  Serial.println("**********************\n");

  // print out magnetometer data
  Serial.print("Magn. X: "); Serial.print(mag.magnetic.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(mag.magnetic.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(mag.magnetic.z);     Serial.println("  \tgauss");
  Serial.println("**********************\n");

  // print out gyroscopic data
  Serial.print("Gyro  X: "); Serial.print(gyro.gyro.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(gyro.gyro.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(gyro.gyro.z);     Serial.println("  \tdps");

  Serial.println("**********************\n");

  delay(250);
}

/**************************************************************************/
/*
    Displays some basic information on this sensor from the unified
    sensor API sensor_t type (see Adafruit_Sensor for more information)
*/
/**************************************************************************/
void displaySensorDetails(void)  
{
  sensor_t accel, mag, gyro, temp;

  lsm.getSensor(&accel, &mag, &gyro, &temp);

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(accel.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(accel.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(accel.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(accel.max_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Min Value:    ")); Serial.print(accel.min_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Resolution:   ")); Serial.print(accel.resolution); Serial.println(F(" m/s^2"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(mag.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(mag.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(mag.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(mag.max_value); Serial.println(F(" uT"));
  Serial.print  (F("Min Value:    ")); Serial.print(mag.min_value); Serial.println(F(" uT"));
  Serial.print  (F("Resolution:   ")); Serial.print(mag.resolution); Serial.println(F(" uT"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(gyro.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(gyro.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(gyro.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(gyro.max_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Min Value:    ")); Serial.print(gyro.min_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Resolution:   ")); Serial.print(gyro.resolution); Serial.println(F(" rad/s"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(temp.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(temp.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(temp.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(temp.max_value); Serial.println(F(" C"));
  Serial.print  (F("Min Value:    ")); Serial.print(temp.min_value); Serial.println(F(" C"));
  Serial.print  (F("Resolution:   ")); Serial.print(temp.resolution); Serial.println(F(" C"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  delay(500);
}

/**************************************************************************/
/*
    Configures the gain and integration time for the TSL2561
*/
/**************************************************************************/
void configureSensor(void)  
{
  // 1.) Set the accelerometer range
  lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_2G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_4G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_6G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_8G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_16G);

  // 2.) Set the magnetometer sensitivity
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_2GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_4GAUSS);
  lsm.setupMag(lsm.LSM9DS0_MAGGAIN_8GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_12GAUSS);

  // 3.) Setup the gyroscope
  lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_245DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_500DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_2000DPS);
}

Here's a screenshot of the serial monitor output:

Serial Monitor Output for the 9-DSO Sensor on the Adafruit Feather

Once I had this data, I could start to interface with MIDI to send note and other data to my computer.

This is where things started to fall apart. I'm not sure what exactly went wrong, but nothing sounded the way I intended. I tried to code a note threshold for a particular range of values of the magnetometer. The idea was that as the sensor got closer to a magnet that was fixed to my desk, it would raise or lower the note being played by my music software. It worked...sort of, but not at the level of detail that I imagined. I looked around for ways to come up with the correct combination of notes to data mapping, but I never really got anything that satisfied me.

After a few hours of working on the code, I gave up and put the project away. As a matter of fact, I'm writing this many months after I stopped working on it. I was so frustrated by my failure to get it working to match my imagination that I didn't bother documenting my efforts.

Update: November 19, 2017 - I decided to complete this project and get it up and running. After examining the code, I realized that I was calling the note on and note off MIDI messages in the wrong way. After a bit of twiddling with my timing, I was able to get it to work (mostly). It still stops sending the messages after a few seconds, but I was able to get it to run long enough to create a short video of the results.

#include <frequencyToNote.h>  
#include <MIDIUSB.h>
#include <pitchToFrequency.h>
#include <pitchToNote.h>

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM9DS0.h>

// Simple tutorial on how to receive and send MIDI messages.
// Here, when receiving any message on channel 4, the Arduino
// will blink a led and play back a note for 1 second.

/* Assign a unique base ID for this sensor */   
Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(1000);  // Use I2C, ID #1000

static const unsigned ledPin = 13;      // LED pin on Arduino Uno

void setup()  
{
    pinMode(ledPin, OUTPUT);

    lsm.begin();

    /* Display some basic information on this sensor */
    displaySensorDetails();

    /* Setup the sensor gain and integration time */
    configureSensor();

    Serial.println(F("Found LSM9DS0 9DOF"));
}

void loop()  
{
    performSensorSweep();
}

void performSensorSweep()  
{
  /* Get a new sensor event */ 
  sensors_event_t accel, mag, gyro, temp;

  lsm.getEvent(&accel, &mag, &gyro, &temp);

  // print out accelerometer data
  Serial.print("Accel X: "); Serial.print(accel.acceleration.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(accel.acceleration.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(accel.acceleration.z);     Serial.println("  \tm/s^2");

  // send a MIDI note...
  int zAccelNote = map(gyro.gyro.z, -128, 80, 20, 127);
  int xAccelVelocity = map(gyro.gyro.x, -144, 144, 64, 127);

  // print out magnetometer data
  Serial.print("Magn. X: "); Serial.print(mag.magnetic.x); Serial.print(" ");

  // send a MIDI note...
  int zNote = map(mag.magnetic.z, -3.03, 0.28, 20, 100);
  //int xVelocity = map(mag.magnetic.x, -6.0, 6.0, 64, 127);
  int xVelocity = 127;

  digitalWrite(ledPin, HIGH);
  noteOn(3, zNote, xVelocity);
  yield;
  digitalWrite(ledPin, LOW);

  Serial.print("  \tY: "); Serial.print(mag.magnetic.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(mag.magnetic.z);     Serial.println("  \tgauss");

  Serial.println("**********************\n");

// set acceleration
//int accelDelay = map(accel.acceleration.x, 
 delay(5);
}

// First parameter is the event type (0x09 = note on, 0x08 = note off).
// Second parameter is note-on/note-off, combined with the channel.
// Channel can be anything between 0-15. Typically reported to the user as 1-16.
// Third parameter is the note number (48 = middle C).
// Fourth parameter is the velocity (64 = normal, 127 = fastest).

void noteOn(byte channel, byte pitch, byte velocity) {  
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {  
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

/**************************************************************************/
/*
    Displays some basic information on this sensor from the unified
    sensor API sensor_t type (see Adafruit_Sensor for more information)
*/
/**************************************************************************/
void displaySensorDetails(void)  
{
  sensor_t accel, mag, gyro, temp;

  lsm.getSensor(&accel, &mag, &gyro, &temp);

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(accel.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(accel.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(accel.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(accel.max_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Min Value:    ")); Serial.print(accel.min_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Resolution:   ")); Serial.print(accel.resolution); Serial.println(F(" m/s^2"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(mag.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(mag.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(mag.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(mag.max_value); Serial.println(F(" uT"));
  Serial.print  (F("Min Value:    ")); Serial.print(mag.min_value); Serial.println(F(" uT"));
  Serial.print  (F("Resolution:   ")); Serial.print(mag.resolution); Serial.println(F(" uT"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(gyro.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(gyro.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(gyro.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(gyro.max_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Min Value:    ")); Serial.print(gyro.min_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Resolution:   ")); Serial.print(gyro.resolution); Serial.println(F(" rad/s"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(temp.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(temp.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(temp.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(temp.max_value); Serial.println(F(" C"));
  Serial.print  (F("Min Value:    ")); Serial.print(temp.min_value); Serial.println(F(" C"));
  Serial.print  (F("Resolution:   ")); Serial.print(temp.resolution); Serial.println(F(" C"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  delay(500);
}

// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).

void controlChange(byte channel, byte control, byte value) {  
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
}

/**************************************************************************/
/*
    Configures the gain and integration time for the TSL2561
*/
/**************************************************************************/
void configureSensor(void)  
{
  // 1.) Set the accelerometer range
  lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_2G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_4G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_6G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_8G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_16G);

  // 2.) Set the magnetometer sensitivity
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_2GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_4GAUSS);
  lsm.setupMag(lsm.LSM9DS0_MAGGAIN_8GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_12GAUSS);

  // 3.) Setup the gyroscope
  lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_245DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_500DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_2000DPS);
}

Overall, this was a fun project, and I enjoyed getting to hear the sounds it created.

Armchair Engineering - Ignorance is bliss

Written by Michael Earls
 FPGA  electronics

I'm totally into my FPGA. Embedded Micro is supposed to be announcing a new FPGA based on the new Artix 7 FPGA chip that will allow me to run the new Vivado design suite from Xilinx. I'm currently using the very outdated ISE tool (it's like using Microsoft Front Page to do modern web development). ISE works (with modifications to the binaries to make it work on Windows 10), but I'd like to try out some new (to me) stuff like System Verilog (which is unsupported in ISE).

I love the idea of an FPGA and I'm about to dip my feet into writing my own CPU based on the Basic CPU example project on Embedded Micro's website. I'd like to expand the instruction set to larger than 4-bits. It's probably going to be completely bizarre to anyone who actually really knows what they're doing (real-life engineers), but I don't care, I'm doing it for fun.

I thought about having a register that would write its data directly to a MIDI interface so I could have instructions that would send MIDI signals. I'd also like to figure out the best way to interface with the CPU so I can load programs into it without uploading a new bin file each time. The example requires me to use a custom-built Assembler to make the Lucid code required for running my programs on the CPU. It would be cool to have a loader on the PC that could upload the program to the Mojo using the USB connection and the on-board microprocessor.

I also thought about adding a 12-bit VGA connector and seeing if I could learn how to interface with it using a custom CPU. The main issue I'm running into is I am quickly hitting my wall of interest. The best way to describe it is that if the concepts are too deep (i.e. - they require an advanced degree of some sort to make sense of them), then I tend to veer away from the topic altogether and move on to something a bit more amateur. However, I keep getting drawn back to CPU design, even though it's a very complicated topic.

I've been watching Ben Eater's awesome series about his breadboard computer on YouTube and learning a lot about how computers work at the most basic level (logic gates, timer-driven clocks, and data buses).

Another topic I've been exploring is how to build a "pocket computer" or similar homebrew computer based on the Zilog Z-80 processor from the 80's (70's?). I have a big interest in this particular processor because it's relatively simple as far as CPU's go.

I'd like to take all of this knowledge and apply it to my custom CPU project. I think it would be cool to come up with an amalgamation of Ben Eater's breadboard computer and the Mojo CPU such that I can control and interact with the FPGA CPU using buttons, switches, 7-segment displays, and possibly even LCD displays.

It has taken me over a year to get comfortable enough with electronics to feel somewhat confident in my abilities to pull some of this off. It's a slow road because it is only a hobby, after all, and my day job takes priority. If there's a topic that I need to study at home to help me on the job, I tend to learn those things first, then spend my extra time learning the electronics and FPGA knowledge.

When I first got into this, it was so that I could play with making my own music "synthesizers" using CMOS logic chips, but that rapidly advanced to the idea of "chips on a chip" that the FPGA represents. I can never just do anything the easy way. I like to take my problems and abstract them away until they are almost unrecognizable, then find the solution that covers the most ground. It serves me well at work when solving large-scale enterprise computing problems, but it makes hobbies a bit more complicated than they should be.

Ultimately, I see myself doing a lot more playing around with the FPGA in the future and less time with actual silicon chips and discreet components. I might make plug-in boards for my FPGA to accomplish some task like making a VGA interface, but even that may not be necessary when Embedded Micro releases their HDMI shield for the Mojo.

When it comes down to it, I think that the fact that I don't really know what I'm doing when it comes to FPGA coding is a good thing. It's that kind of spirit that allowed the microprocessor and home computer movement to sprout from the garages and basements of amateurs back when room-sized computers ruled the computing world and IBM thought the home computer was a toy and (luckily) ignored it. I'm not going to cause any real damage and I might actually stumble upon something fun along the way. Sure, I'm doing this the hard way, but I think it's too late for me to go to a university and get a degree in electrical or electronics engineering. Besides, I think this is a lot more fun...

C# Introduction to Expression Trees

Written by Jeff Tyler

Recently I was in a situation where I needed to write my own expression trees to be consumed by Entity Framework. I thought that it would be a good opportunity to pass what I learned along.

Along the way I came up with three realizations that helped me understand what I was doing.

When dealing with Expression Trees you are dynamically building lamda expressions

The power of an Expression Tree is not in it's ability to be used in C# code but in it's ability to write code for another system.

The questions that you are asking when dealing with expression trees are different.

When you write person.Name == "Jeff" you asking if the two are equals. With a lamda your asking what the result of a method is.
With expression trees you are asking how do I build this Lamda in code. So you have to create the parameters and comparison steps separately.

When you consume an expression. You are less concerned with the result of the expression and more concerned with the steps required to get to the end result.

So an explanation of what an expression tree is. An expression tree is used so that you can interpret the code and generate something else. Entity Framework takes an expression tree and uses it to build SQL.
So here is how I finally get my head wrapped around them.

    var person = new Person(){ Name = "Jeff"};
    var isJeff = person.Name == "Jeff";

This is regular code. It is compiled down and when it gets to this line it creates a new person and then sets isJeff to true. This can be used in if statements to change the flow.

var person = new Person { Name = "Jeff" };  
            Func<Person, bool> isJeff = (p) => p.Name == "Jeff";
            var list = new List<Person> { person };
            var jeffs = list.Where(isJeff);
           // calling invoke makes it behave like a regular method.
           var result = isJeff.Invoke(person);

This is a lamda. It creates a function delegate that can be used on any Person to see if they are a Jeff. This can be passed along to LINQ to be used as a filter of a list. This code behaves like a method when it is invoked.

var person = new Person { Name = "Jeff" };  
var param = Expression.Parameter(typeof(Person));  
var property = Expression.Property(param,nameof(Person.Name));  
var constant = Expression.Constant("Jeff");  
var equals = Expression.Equal(property, constant);  

The equals expression gives us three important pieces of information. By knowing it's type we know how to compare it. It is an Equal Expression so we know that it is the same as doing an ==. With equals.Left we can get the property access from the param. It will read param_0.Name.
With equals.right we can see the constant that we added. Which will be "Jeff".

Let's say that we knew an identical object existed in a JavaScript application. we could take this and write something like this

Console.WriteLine(equals.Left + "===" + equals.Right);  

This code would wind up looking like this
param0.Name === "Jeff";
if you happened to have a param
0 variable this would validate it. Not very useful at the moment but it does demonstrate one important thing.

Were going to take it one more step and see if we can't get a more useful condition check.

// this code will only handle a single condition. 
public static string CreateJavaScriptMethod(Expression<Func<Person, bool>> expression)  
        {
            var jsCode = $"function validation({expression.Parameters[0]}) {{";
            var body = expression.Body as BinaryExpression;
            var comparer = "";
            switch (body.NodeType)
            {
                case ExpressionType.Equal:
                    {
                        comparer = "===";
                        break;
                    }
                case ExpressionType.NotEqual:
                    {
                        comparer = "!==";
                        break;
                    }
                case ExpressionType.GreaterThan:
                    {
                        comparer = ">";
                        break;
                    }
                case ExpressionType.LessThan:
                    {
                        comparer = "<";
                        break;
                    }
            }
            jsCode += $"return {body.Left} {comparer} {body.Right}";

            jsCode += "}";

            return jsCode;
        }

This is a method created to handle a simple validation scenario. You write some validation in C# and it generates a javascript method for validation.
The method takes Expression function.
expression.paramters holds all of the paramters for the function. expression.Body is a binary expression. It has a Left, a Right, and it's type will tell you how it is comparing the two values. The switch statement checks the expression type and creates the correct JavaScript equality check.

So now we are going to use the method.
This is the normal use of it.

var script = CreateJavaScriptMethod(p => p.Name == "Jeff");  

This is just a plain old lamda expression that does the same comparison as the one we built. Like you would use for LINQ or Enity Framework. Only this one will give you a JavaScript method.

We can make it a bit more dynamic by building the expression using the expression trees.

Expression<Func<Person, bool>> expression = Expression.Lambda<Func<Person, bool>>(equals, param);

CreateJavaScriptMethod(expression);  

After calling Expression.Lamda on the expression that we created in the beginning it then can be used exactly like the lamda expression.

Emulating a Lunetta circuit with an FPGA is a flawed idea

Written by Michael Earls
 synthesizers  Lunetta  electronics

Last night, I decided to explore an idea that I have been formulating over the past several months, but haven't actually taken the time to explore. I had a thought that I could emulate a Lunetta circuit (a CMOS 40106 integrated circuit with a potentiometer and a capacitor to create an oscillator) using my FPGA and Verilog.

After all, the 40106 is simply a hex inverter (6 inverter circuits on one chip), so the code was this simple:

assign pin1 = ~pin0;

That simply set the value of pin 1 to be the opposite of pin 2 (inverted signal) at every clock cycle. I plugged in the capacitor and potentiometer according to the regular "Lunetta" arrangement (as seen in the schematic below), but it didn't work.

Lunetta circuit

Replace the U1A IC in the schematic with the FPGA and you'll understand how I set it up.

I think I have a fundamental misunderstanding of the nature of CMOS and FPGA's. While you can emulate the functionality of an inverter chip using an FPGA, I don't think it's possible to emulate the physics of the chip itself. The Lunettas work off of the actual physical reaction and timings of the chip itself, not off of a clock cycle such as the one that the FPGA is running under. Emulating a physical feedback circuit will not render the same results when using an FPGA. I looked at the datasheet for the 40106 and saw that it has ~140ns delay. I'm not sure if I can emulate that kind of timing.

I enjoyed the few hours I spent hooking the circuit up and playing around, though, and it got me into Pulse Width Modulation and other audio generation techniques for FPGAs. I'll be exploring more about audio processing in the future, but I've put away the idea of emulating discreet components for their "out of band" uses.

It's fun to play around with this stuff, but when the theory gets too thick, I lose interest and move onto more playful aspects of the technology. I think it's why I have a cursory understanding of a lot of technology, but only a deep understanding of a limited number of topics (like C#, HTML, and other Web Development).

Welcome our newest author on cerkit.com - Jeff Tyler

Written by Michael Earls

We have a new author on the Cerebral Kitchen - Jeff Tyler. Jeff is a professional software developer and will be contributing to the site with more of the same topics you're already used to.

Jeff's first two articles have been posted:

Creating a calendar control with Aurelia part 1

Creating a calendar control with Aurelia part 2

Please welcome Jeff to the site. I look forward to his contributions.

I will still be writing for the site, so look for more from me, as well.

Have a great day.

Michael Earls

Creating an Aurelia Bootstrap Calendar Of Events - Part 2 Create a custom element

Written by Jeff Tyler

This is part two of a multi-part tutorial on how to build a bootstrap calendar with Aurelia
Aurelia Calendar Part 1

Aurelia has a built in custom element modeled after web components. This will allow us to create a calendar widget that is isolated and reusable.

First we are going to create a folder called elements
Inside of the folder we will create another folder called calendar and a file called index.ts.
Inside of the calendar folder we will create two files
calendar.html and calendar.ts

We will start with the calendar.ts file. This file is the backing class for the custom element.

we'll break down the pieces and explain what it all is doing.

import { customElement, bindable, inject } from "aurelia-framework";  
import * as moment from 'moment';  
import * as $ from 'jQuery'  
@customElement("Calendar")
@inject(Element)
export class Calendar {

}

import tells it what extra classes we are going to need. If it's typescript then we can just name the class. Most things related to Aurelia is found in the aurelia-framework namespace. We are going to be using the customElement, bindable, and inject classes from aurelia-framework.

IF you are calling a pure JavaScript library then you import it by calling import * as (alias) from where you are getting it. We will be using the Javascript libraries from moment.js and jQuery.

@customElement('calendar') let's Aurelia know that this is going to be a customElement and that when we create an calendar element that we mean this.

@inject(Element) @inject is the Aurelia way of doing dependency injection. Anything in there will be automatically injected into the constructor. Element tells Aurelia to inject the actual Dom element into our constructor.

import { customElement, bindable, inject } from "aurelia-framework";  
import * as moment from 'moment';  
import * as $ from 'jQuery'  
@customElement("Calendar")
@inject(Element)
export class Calendar {  
    private today: moment.Moment;

    @bindable
    currentDate: Date;

    private element: Element
    constructor(element: Element) {
        this.today = moment();
        this.currentDate = this.today.toDate();
        this.element = element;
    }
}

today will hold today's date, so that we could style it differently if we so choose. @bindable tells aurelia to expose the property as an attribute and to allow them to bind to it. currentDate is the date that we will use for creating the calendar. It's basically a placeholder for the month and year.

element is the property to hold the Dom element.
constructor(element:Element) is a type script constructor and gives Aurelia a place to inject the Dom element.
inside the constructor we set today to moment() which creates a moment.Moment object for today's date.
It defaults currentDate to today by getting the date from the moment version. It will be overridden if someone binds the current-date attribute.

Here comes all of the code.

import { customElement, bindable, inject } from "aurelia-framework";  
import * as moment from 'moment';  
import * as $ from 'jQuery'  
@customElement("Calendar")
@inject(Element)
export class Calendar {  
    private today: moment.Moment;

    @bindable()
    currentDate: Date;

    element: Element
    constructor(element: Element) {
        this.today = moment();
        this.currentDate = this.today.toDate();
        this.element = element;
    }

    private getDisplayDates(): Array> {
        let dates = new Array>();
        let beginning = moment(this.currentDate).startOf('month').startOf('week');
        let currentMonth = this.currentDate.getMonth();
        for (let r = 0; r < 6; r++) {
            let week = new Array();
            for (let i = 0; i < 7; i++) {
                let date = {
                    dayOfWeek: beginning.format('dddd'),
                    date: beginning.date(),
                    darken: beginning.month() != currentMonth,
                    events: []
                }
//                if ((i * r) % 4 == 0) {
//                    date.events.push({ name: i.toString() //+ '-' + r.toString() + 'event', amount: i * r + 10 })
//                }
                week.push(date);
                beginning.add(1, 'days');
            }
            dates.push(week);
        }
        return dates
    }

    attached() {
        let that = this;
        var test = $(".calendarDay", $(this.element)).on('click', function (event) {
            let clickEvent = new CustomEvent('day-click', {
                detail: { value: event.target },
                bubbles: true
            })
            that.element.dispatchEvent(clickEvent);
        });
    }
}

Here we added two new methods. getDisplayDates and attached.

getDisplayDates creates an array of arrays of objects used to render the calendar.

beginning turns takes current date and gets the Sunday of the week that the 1st of the month falls on. So for instance for the month of August 2017 it would start at July 30 2017 in the US. If your are localized in a country that has a different start of the week then it will start there instead.

it loops through 6 weeks of 7 days creating an object that holds the date(30,31,1...), the day of week (Monday, Tuesday...) and darken which is a flag for whether the date is within the month held by currentDate.
It also holds an events array used to hold the events to display for that day. Eventually this would be populated by an ajax call for the data.

The commented out section basically creates some dummy data to see what it might look like with events. Uncomment it if you want to get an idea.

attached()

Attached is part of the Aurelia life cycle. It is the last chance to act upon an element. Here we are creating a custom event to be bound to when the a particular day is clicked.
let that = this allows us to access the typescript class from withing the jQuery event.

We add a jQuery event onto all of the calendarDays within this element. Then we create a customEvent and dispatch it so that any listeners can act upon it. It passes the Dom element that was clicked to the event.

Creating an Aurelia Bootstrap Calendar Of Events - Part 1 Getting Started and Overriding Bootstrap

Written by Jeff Tyler

I needed a calendar as a centerpiece for an application that I am working on. After looking through the internet for something that I liked and not finding it. I decided to do what all sane programmers do. Build my own. So let's get started. YeeHaw!!
Here are the requirements that I have set for myself, and how I plan to fix it.

  • Must be responsive: for this I am going to use bootstrap. 7 days on a week for a full screen and 2 for small.
  • Two Headers: header 1: Monday 31. Header 2: Useful Information
  • Must be able to display multiple events on a given day.

Few events truly span multiple days. There is usually a beginning and end of the first day and a beginning and end of the second day. So this is how i intend to treat it.

Getting Started.

I like being explicit so I am going to list my assumptions and what I have done to meet them. If you have the same set up as me, then you can follow exactly, if yours is different then do your own thing where we deviate.

Assumptions
  1. You have an Aurelia Application set up and running.
  2. you have bootstrap 3 less files
  3. You have a LESS compiler set up
  4. You have moment.js
  5. You are using TypeScript

My set up
1. .netcore C# aurelia app by running dotnet new aurelia
2. dotnet automatically includes bootstrap through npm when you create a new aurelia app.
3. less-loader through webpack
4. npm install moment -save

Steps involved
  1. Change bootstrap from a grid of 12 to a grid of 14.
  2. Create an custom element for aurelia.
  3. Create grids and rows to hold the dates
  4. Populate the grids with dates
  5. Remove padding for rows inside of the calendar.
1. Change bootstrap from a grid of 12 to a grid of 14.

There are seven days in a week. So it would make since for seven columns to exist in a row. A seven column bootstrap doesn't give much flexibility for the rest of the application to use for layouts. If we treat the rows and columns inside of the calendar differently then you have a lot of repeated CSS to handle the special case. Twelve columns don't work well when you need seven columns. However with fourteen columns the rest of the application is a little more flexible and you have one grid system throughout everything so there isn't a special case. So we are going to increase the number of columns for bootstrap using LESS.

So first we are going to create a file called myLess.less

open the file and add these three lines of code.

@import '~bootstrap/less/variables.less';  
@grid-columns: 14;
@import '~bootstrap/less/bootstrap.less';

Your path may be different depending on what you use to compile the less files. The tilda in this case tells webpack to look in node_modules.

@import is a less command that allows us to reference other less files.

First we bring in the variables file for bootstrap. This file holds all of the settings bootstrap uses to set it's self up. It's worth looking at the file and seeing what all is there that you could change.

For our purposes we only need to change the @grid-columns variable from the default of 12 to 14

next we import the main bootstrap.less file. This file is a wrapper file around all of the bootstrap components and will give you everything that you would have gotten if you just used the bootstrap.css file directly except now it uses a 14 column grid instead.

Continue on with Aurelia Calendar Part 2

ASP.NET Core, JWT Tokens, and the User.Identity.Name property - A Discovery

Written by Michael Earls
 ASP.NET  programming  .NET Core

I've been working on creating a token-based auth system and I wanted to write about a discovery that I made. I've been following the excellent ASP.NET Core Token Authentication Guide.

I was able to get everything up and running as suggested in the guide, but when I accessed the User.Identity.Name property, it was null. I was hoping to find the Name of the user there, but it wasn't. After some exploration, I was able to determine the solution. You simply add the following code in Startup.cs. I added this to the TokenValidationParameters area as outlined in the Guide.

var tokenValidationParameters = new TokenValidationParameters
{
    // Ensure that User.Identity.Name is set correctly after login
    NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",

    ... Existing code here ...

};

If you want to use a different claim as your User.Identity.Name, then use that claim name instead of the XmlSoap schema above. We're actually not using username, we're using an Id number that identifies the user.