Skip to contentSkip to author details

electronics

A 19-post collection

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

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).

Strange VGA Effects on an FPGA

Written by Michael Earls
 electronics  FPGA

When I assembled my VGA implementation on my Mojo FPGA, I did so with only a single wire for each of the color signals (Red, Green, and Blue). This limited my color choices to 8 colors. I read about using Pulse width modulation to send other values to a wire, so I gave it a try. The results were interesting, but not what I was looking for.

Here is a video of the results:

Here is my VHDL code:

----------------------------------------------------------------------------------  
-- Company: 
-- Engineer: 
-- 
-- Create Date:    04:37:23 05/27/2017 
-- Design Name: 
-- Module Name:    img_gen - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;  
use IEEE.STD_LOGIC_1164.ALL;  
use IEEE.STD_LOGIC_ARITH.ALL;  
use IEEE.STD_LOGIC_UNSIGNED.ALL;


entity img_gen is  
     Port ( clk : in STD_LOGIC;
                x_control : in STD_LOGIC_VECTOR(9 downto 0);
                paddle : in STD_LOGIC_VECTOR(9 downto 0);
                y_control : in STD_LOGIC_VECTOR(9 downto 0);
                video_on : in STD_LOGIC;
                rgb : out STD_LOGIC_VECTOR(2 downto 0));
end img_gen;

architecture Behavioral of img_gen is

signal  PWM_R_Accumulator : std_logic_vector(8 downto 0);  
signal  PWM_G_Accumulator : std_logic_vector(8 downto 0);  
signal  PWM_B_Accumulator : std_logic_vector(8 downto 0);

--wall
constant wall_l:integer :=10;--the distance between wall and left side of screen  
constant wall_t:integer :=10;--the distance between wall and top side of screen  
constant wall_k:integer :=10;--wall thickness  
signal wall_on:std_logic;  
signal rgb_wall:std_logic_vector(23 downto 0); 

--bar
signal bar_l, bar_l_next: integer:=100;  
constant bar_t:integer :=420;--the distance between bar and top side of screen  
constant bar_k:integer :=10;--bar thickness  
constant bar_w:integer:=120;--bar width  
constant bar_v:integer:=10;--velocity of the bar  
signal bar_on:std_logic;  
signal rgb_bar:std_logic_vector(23 downto 0); 

--ball
signal ball_l,ball_l_next:integer :=100;--the distance between ball and left side of screen  
signal ball_t,ball_t_next:integer :=100; --the distance between ball and top side of screen  
constant ball_w:integer :=20;--ball Height  
constant ball_u:integer :=20;--ball width  
constant x_v,y_v:integer:=3;-- horizontal and vertical speeds of the ball  
signal ball_on:std_logic;  
signal rgb_ball:std_logic_vector(23 downto 0); 

--refreshing(1/60)
signal refresh_reg,refresh_next:integer;  
constant refresh_constant:integer:=830000;  
signal refresh_tick:std_logic;

--ball animation
signal xv_reg,xv_next:integer:=3;--variable of the horizontal speed  
signal yv_reg,yv_next:integer:=3;--variable of the vertical speed

--x,y pixel cursor
signal x,y:integer range 0 to 650;

--mux
signal vdbt:std_logic_vector(3 downto 0);

--buffer
signal rgb_reg:std_logic_vector(2 downto 0);  
signal rgb_next:std_logic_vector(23 downto 0);

begin

--x,y pixel cursor
x <=conv_integer(x_control);  
y <=conv_integer(y_control );

--refreshing
process(clk)  
begin  
     if clk'event and clk='1' then
          refresh_reg<=refresh_next;
     end if;
end process;  
refresh_next<= 0 when refresh_reg= refresh_constant else  
refresh_reg+1;  
refresh_tick<= '1' when refresh_reg = 0 else  
                           '0';
--register part
process(clk)  
begin  
     if clk'event and clk='1' then
         ball_l<=ball_l_next;
         ball_t<=ball_t_next;
         xv_reg<=xv_next;
         yv_reg<=yv_next;
         bar_l<=bar_l_next;
      end if;
end process;

--bar animation
process(refresh_tick,paddle)  
begin

    if refresh_tick= '1' then
       bar_l_next<= conv_integer(paddle);
    end if;
end process;

--ball animation
process(refresh_tick,ball_l,ball_t,xv_reg,yv_reg)  
begin  
     ball_l_next <=ball_l;
     ball_t_next <=ball_t;
     xv_next<=xv_reg;
     yv_next<=yv_reg;
     if refresh_tick = '1' then
        if ball_t> 400 and ball_l > (bar_l -ball_u) and ball_l < (bar_l +120) then --the ball hits the bar
           yv_next<= -y_v ;
       elsif ball_t< 35 then--The ball hits the wall
           yv_next<= y_v;
       end if;
       if ball_l < 10 then --The ball hits the left side of the screen
          xv_next<= x_v;
       elsif ball_l> 600 then 
          xv_next<= -x_v ; --The ball hits the right side of the screen
       end if; 
       ball_l_next <=ball_l +xv_reg;
       ball_t_next <=ball_t+yv_reg; 
    end if;
end process;

--wall object
wall_on <= '1' when x > wall_l and x < (640-wall_l) and y> wall_t and y < (wall_t+ wall_k) else  
                      '0'; 
rgb_wall<=x"010101";--Black  
--bar object
bar_on <= '1' when x > bar_l and x < (bar_l+bar_w) and y> bar_t and y < (bar_t+ bar_k) else  
                    '0'; 
rgb_bar<=x"0000F1";--blue

--ball object
ball_on <= '1' when x > ball_l and x < (ball_l+ball_u) and y> ball_t and y < (ball_t+ ball_w) else  
                     '0'; 
rgb_ball<=x"00F100"; --Green


--buffer
process(clk)  
begin  
    if clk'event and clk='1' then      
      PWM_R_Accumulator  <=  ("0" & PWM_R_Accumulator(7 downto 0)) + ("0" & rgb_next(23 downto 16));
        PWM_G_Accumulator  <=  ("0" & PWM_G_Accumulator(7 downto 0)) + ("0" & rgb_next(15 downto 8));
        PWM_B_Accumulator  <=  ("0" & PWM_B_Accumulator(7 downto 0)) + ("0" & rgb_next(7 downto 0));
    end if;
     if clk'event and clk='1' then
         --rgb_reg<=rgb_next;

     end if;
end process;

--mux
vdbt<=video_on & wall_on & bar_on &ball_on;  
with vdbt select  
     rgb_next <= x"FFAAAA" when "1000",--Background of the screen is red 
     rgb_wall when "1100",
     rgb_wall when "1101",
     rgb_bar when "1010",
     rgb_bar when "1011",
     rgb_ball when "1001",
      x"000000" when others;
--output

rgb<=PWM_R_Accumulator(8) & PWM_G_Accumulator(8) & PWM_B_Accumulator(8);

end Behavioral;

I added three new signals for the PWMs. One PWM per color. Those are defined starting on line 37. Once those were defined, I then set the colors on the objects on the screen (the wall, the ball, the paddle, and the background). That code starts on line 149 (colors are defined in hexadecimal).

I then defaulted the color to red for things that didn't have a setting (line 178).

On line 187, I set the output of the VGA wires to the most significant bit of the PWM accumulators.

The idea was that the PWM would send a different color to the monitor. I really don't know how (or even if) I can fix it, but the results are interesting.

After watching the video below, I learned that this is a hardware issue. I need to build out the different resistance values to handle each of the bits with a gpio pin on the FPGA.

Here is a 12-bit schematic that I'm going to try next.

12-bit color VGA schematic (image source: https://electronics.stackexchange.com/questions/228825/programming-pattern-to-generate-vga-signal-with-micro-controller)

A Saturday morning puzzle on my FPGA

Written by Michael Earls
 FPGA  electronics  hobbies

I woke up this morning with a desire to solve a puzzle. I decided that I'd make a simple LED chaser on a 7-segment LED display using my Mojo FPGA (now that I have everything working in Windows 10 again thanks to the awesome customer support from Justin at Embedded Micro).

Here's a video of the results:

Here is a breadboard image showing how I've wired up the circuit:

Mojo 7-segment chaser breadboard circuit

Here's my constraints file that shows the wiring of the 7-segment display:

NET "seg<0>" LOC = P51 | IOSTANDARD = LVTTL;  
NET "seg<1>" LOC = P50 | IOSTANDARD = LVTTL;  
NET "seg<2>" LOC = P41 | IOSTANDARD = LVTTL;  
NET "seg<3>" LOC = P40 | IOSTANDARD = LVTTL;  
NET "seg<4>" LOC = P35 | IOSTANDARD = LVTTL;  
NET "seg<5>" LOC = P34 | IOSTANDARD = LVTTL;

Update (8:00 PM): I was able to reach my goal and pull this off using VHDL instead of Lucid. Here is the entire program (no dependencies required). This is much more precise. I really like VHDL.

library IEEE;  
use IEEE.STD_LOGIC_1164.ALL;  
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity circle_chaser is  
    Port ( clk : in  STD_LOGIC;
             rst_n : in STD_LOGIC;            -- reset button (active low)
             led : out STD_LOGIC_VECTOR (7 downto 0); --8 user controllable LEDs
             cclk : in STD_LOGIC;             -- configuration clock, AVR ready when high
             spi_miso : out STD_LOGIC;       -- AVR SPI MISO
             spi_ss : in STD_LOGIC;           -- AVR SPI Slave Select
             spi_mosi : in STD_LOGIC;         -- AVR SPI MOSI
             spi_sck : in STD_LOGIC;          -- AVR SPI Clock
             spi_channel : in STD_LOGIC_VECTOR (3 downto 0); --AVR general purpose pins (used by default to select ADC channel)
             avr_tx : in STD_LOGIC;           -- AVR TX (FPGA RX)
             avr_rx : out STD_LOGIC;          -- AVR RX (FPGA TX)
             avr_rx_busy : in STD_LOGIC;
             seg : out  STD_LOGIC_VECTOR (5 downto 0)
     );
end circle_chaser;

architecture Behavioral of circle_chaser is

signal clk_div   : std_logic_vector(21 downto 0);  
signal shift_reg : std_logic_vector(5 downto 0) := "000001";

begin  
    led <= X"00";
    spi_miso <= '0';
    avr_rx <= '0';

    -- clock divider
    process (clk)
    begin
      if (clk'Event and clk = '1') then
            clk_div <= clk_div + '1';
      end if;
    end process;

    -- LED chaser
    process (clk_div(21))
    begin
      if (clk_div(21)'Event and clk_div(21) = '1') then
            shift_reg <= shift_reg(4 downto 0) & '0';
            if (shift_reg(5) = '1') then
                shift_reg <= "000001";
            end if;
      end if;
    end process;

    -- display the result on the LEDs
    seg <= shift_reg;

end Behavioral;  

Update (6/18 10:00 AM): I completed the HDL trilogy this morning and implemented this chaser in Verilog. Here is the code:

module mojo_top(  
    // 50MHz clock input
    input clk,
    // Input from reset button (active low)
    input rst_n,
    // cclk input from AVR, high when AVR is ready
    input cclk,
    // Outputs to the 8 onboard LEDs
    output[7:0]led,
    // AVR SPI connections
    output spi_miso,
    input spi_ss,
    input spi_mosi,
    input spi_sck,
    // AVR ADC channel select
    output [3:0] spi_channel,
    // Serial connections
    input avr_tx, // AVR Tx => FPGA Rx
    output avr_rx, // AVR Rx => FPGA Tx
    input avr_rx_busy, // AVR Rx buffer full
     output [5:0] seg
    );

wire rst = ~rst_n; // make reset active high  
reg [22:0] clk_div = 22'b0;

// these signals should be high-z when not used
assign spi_miso = 1'bz;  
assign avr_rx = 1'bz;  
assign spi_channel = 4'bzzzz;

assign led = 8'b0;

parameter REG_INIT = 7'b0000001;

    reg [6:0] shift_reg = REG_INIT; // register for led output

    always @ (posedge clk) 
    begin
        clk_div = clk_div + 1;
        if (clk_div[22] == 1) begin
            clk_div = 0;
            shift_reg = shift_reg << 1;
            if (shift_reg[6] == 1) begin
                shift_reg = REG_INIT;
            end
        end
    end

    assign seg = shift_reg[5:0]; // wire output and leds register

endmodule

I had to take a different approach with the shift register to allow for an additional bit to check to see when to reset the shift register (and mirrored LED segments) back to the start. I made the shift register 1 bit larger (line 36) than the LED segments so I could check to see when the MSB was high and then reset. When the shift register was the same size as the LED segments, it would skip lighting one because it got reset before it could be lit up.

Update: November 17, 2017: I got some feedback on the Embedded Micro (the makers of the Mojo FPGA) forums about my code. It seems I was assigning the value of the shift register incorrectly by using the = instead of the <= assignment operator (line 43). That's what was causing me to have to add the extra bit to the shift register to correctly trigger the reset.

I rewrote my Verilog code using what I learned from the forums and it works perfectly.

So, instead of:

shift_reg = shift_reg << 1;

line 43 would be

shift_reg <= shift_reg << 1

this causes the changes to all happen at once so that the evaluation of the shift happens at the same time as the output instead of the shift happening instantly (just before the output).

However, Justin had another shortcut that allowed me to do the shift at the same time as the assignment by using the following code:

shift_reg <= {shift_reg[4:0], shift_reg[5]};

Here's the optimized code based on the feedback that I received on the forums:

assign led = 8'b0;

reg [5:0] shift_reg = 6'b000001; // register for led output

always @ (posedge clk)  
begin  
  clk_div = clk_div + 1;
  if (clk_div[22] == 1) begin
        clk_div = 0;
        shift_reg <= {shift_reg[4:0], shift_reg[5]};
  end
end

assign seg = shift_reg[5:0]; // wire output and leds register

Justin from Embedded Micro is very responsive on his forums and I've learned as much on the forums as I did from the great tutorials on the Mojo website.

Here is the Lucid code (the Mojo-specific HDL) for the same LED chaser:

  
.clk(clk) {
  .rst(rst) {
    circleChase chase(#SPEED(22));
  }
}

always {  
  seg = chase.out;
}

The chaser was a lot easier to implement than I expected...

module circleChase #(  
    MAX_OUT = 6: MAX_OUT > 2,
    SPEED = 25: SPEED > 0 // the lower the SPEED, the faster the counter
  )(
    input clk,  // clock
    input rst,  // reset
    output out[6]
  ) {

  .clk(clk) {   
    .rst(rst) {    
      slowCount count(#SPEED(SPEED));
    }
  }

  const TOP = MAX_OUT;
  counter outCount(.rst(rst), .clk(count.q), #TOP(TOP - 1));

  always {

    out = 6h0; 

    if(count.q) {
        out[outCount.value] = 1;
    }
  }
}

Notice how the clock to the outCount module on line 17 is coming from the slowCount's .q trigger. This goes high each time the slowCount reaches it's limit (the code for this is at the bottom of this post).

I've covered it before, but for completeness, here is the code for the slowCount module (in Lucid, the Mojo-specific HDL):

module slowCount #(  
      SPEED = 25 : SPEED > 0
    )(
    input clk,  // clock
    input rst,  // reset
    output value[8],
    output q
  ) {

  .clk(clk), .rst(rst) {
    dff flip[SPEED];
  }

  const LIMIT = SPEED - 1;
  counter ctr(.rst(rst));

  always {
    ctr.clk = flip.q[LIMIT];

    // toggle the q bit when the counter reaches each end
    q = flip.q[LIMIT] == 0 ? 0 : 1;

    flip.d = flip.q + 1;
    value = ctr.value;
  }
}

Pong on the Mojo Revisited

Written by Michael Earls
 FPGA  electronics  programming

In my previous post, I briefly discussed the implementation of a pseudo-pong game on my Mojo FPGA developer board.

I was able to implement the VHDL version that I linked to later in the post and it works much better.

The biggest challenge I had with it was converting the two-button input to a potentiometer input. It turns out all I had to do was convert an array to an integer as input to the paddle's left position.

Here is a link to the article describing the implementation that I used:

FPGA Pong From FPGACenter.com.

The VHDL was a big challenge for me because I had to learn it as I went along. It works just like Verilog, but the syntax is so different.

Also, I modified my User Constraints File to make the RGB an array for compatibility with this particular implementation.

Here are the new custom constraints:

NET "hsync" LOC = P50 | IOSTANDARD = LVTTL;  
NET "vsync" LOC = P51 | IOSTANDARD = LVTTL;  
NET "rgb<2>" LOC = P41 | IOSTANDARD = LVTTL;  
NET "rgb<1>" LOC = P40 | IOSTANDARD = LVTTL;  
NET "rgb<0>" LOC = P35 | IOSTANDARD = LVTTL;

Here is the code containing my changes (in the vga_control and img_gen modules):

vga_control:

library IEEE;  
use IEEE.STD_LOGIC_1164.ALL;  
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity vga_control is  
     Port ( clk : in STD_LOGIC;
                start : in STD_LOGIC;
                reset : in STD_LOGIC;
                paddle : IN STD_LOGIC_VECTOR(9 downto 0);
                rgb : out STD_LOGIC_VECTOR (2 downto 0);
                h_s : out STD_LOGIC;
                v_s : out STD_LOGIC);
end vga_control;

architecture Behavioral of vga_control is

COMPONENT img_gen  
       PORT( clk : IN std_logic;
                     x_control : IN std_logic_vector(9 downto 0);
                     paddle : IN std_logic_vector(9 downto 0);
                     y_control : IN std_logic_vector(9 downto 0);
                     video_on : IN std_logic; 
                     rgb : OUT std_logic_vector(2 downto 0) );
END COMPONENT;

COMPONENT sync_mod  
       PORT( clk : IN std_logic;
                     reset : IN std_logic;
                     start : IN std_logic; 
                     y_control : OUT std_logic_vector(9 downto 0);
                     x_control : OUT std_logic_vector(9 downto 0);
                     h_s : OUT std_logic;
                     v_s : OUT std_logic;
                     video_on : OUT std_logic );
END COMPONENT;

signal x,y:std_logic_vector(9 downto 0);  
signal video:std_logic;

begin  
          U1: img_gen PORT MAP( clk =>clk , x_control => x, paddle => paddle , 
                                                         y_control => y, video_on =>video , rgb => rgb );

           U2: sync_mod PORT MAP( clk => clk, reset => reset, start => start, y_control => y, x_control =>x ,
                                                             h_s => h_s , v_s => v_s, video_on =>video );
end Behavioral;

My changes are on lines 9, 20, and 41. This sends the output from the potentiometer hooked up to the ADC on the Mojo to the img_gen module where it's converted to position data for the paddle control.

Here's the modified img_gen code:

library IEEE;  
use IEEE.STD_LOGIC_1164.ALL;  
use IEEE.STD_LOGIC_ARITH.ALL;  
use IEEE.STD_LOGIC_UNSIGNED.ALL;


entity img_gen is  
     Port ( clk : in STD_LOGIC;
                x_control : in STD_LOGIC_VECTOR(9 downto 0);
                paddle : in STD_LOGIC_VECTOR(9 downto 0);
                y_control : in STD_LOGIC_VECTOR(9 downto 0);
                video_on : in STD_LOGIC;
                rgb : out STD_LOGIC_VECTOR(2 downto 0));
end img_gen;

architecture Behavioral of img_gen is

--wall
constant wall_l:integer :=10;--the distance between wall and left side of screen  
constant wall_t:integer :=10;--the distance between wall and top side of screen  
constant wall_k:integer :=10;--wall thickness  
signal wall_on:std_logic;  
signal rgb_wall:std_logic_vector(2 downto 0); 

--bar
signal bar_l, bar_l_next: integer:=100;  
constant bar_t:integer :=420;--the distance between bar and top side of screen  
constant bar_k:integer :=10;--bar thickness  
constant bar_w:integer:=120;--bar width  
constant bar_v:integer:=10;--velocity of the bar  
signal bar_on:std_logic;  
signal rgb_bar:std_logic_vector(2 downto 0); 

--ball
signal ball_l,ball_l_next:integer :=100;--the distance between ball and left side of screen  
signal ball_t,ball_t_next:integer :=100; --the distance between ball and top side of screen  
constant ball_w:integer :=20;--ball Height  
constant ball_u:integer :=20;--ball width  
constant x_v,y_v:integer:=3;-- horizontal and vertical speeds of the ball  
signal ball_on:std_logic;  
signal rgb_ball:std_logic_vector(2 downto 0); 

--refreshing(1/60)
signal refresh_reg,refresh_next:integer;  
constant refresh_constant:integer:=830000;  
signal refresh_tick:std_logic;

--ball animation
signal xv_reg,xv_next:integer:=3;--variable of the horizontal speed  
signal yv_reg,yv_next:integer:=3;--variable of the vertical speed

--x,y pixel cursor
signal x,y:integer range 0 to 650;

--mux
signal vdbt:std_logic_vector(3 downto 0);

--buffer
signal rgb_reg,rgb_next:std_logic_vector(2 downto 0);

begin

--x,y pixel cursor
x <= conv_integer(x_control);  
y <= conv_integer(y_control );

--refreshing
process(clk)  
begin  
     if clk'event and clk='1' then
          refresh_reg<=refresh_next; 
     end if;
end process;  
refresh_next <= 0 when refresh_reg= refresh_constant else  
refresh_reg+1;  
refresh_tick <= '1' when refresh_reg = 0 else  
                           '0';
--register part
process(clk)  
begin  
     if clk'event and clk='1' then
         ball_l <= ball_l_next;
         ball_t <= ball_t_next;
         xv_reg <= xv_next;
         yv_reg <= yv_next;
         bar_l <= bar_l_next;
      end if;
end process;

--bar animation
process(refresh_tick,paddle)  
begin

    if refresh_tick= '1' then
       bar_l_next <= conv_integer(paddle);
    end if;
end process;

--ball animation
process(refresh_tick,ball_l,ball_t,xv_reg,yv_reg)  
begin  
     ball_l_next <=ball_l;
     ball_t_next <=ball_t;
     xv_next <= xv_reg;
     yv_next <= yv_reg;
     if refresh_tick = '1' then
        if ball_t > 400 and ball_l > (bar_l -ball_u) and ball_l < (bar_l +120) then --the ball hits the bar
           yv_next <= -y_v ;
       elsif ball_t < 35 then--The ball hits the wall
           yv_next <= y_v;
       end if;
       if ball_l < 10 then --The ball hits the left side of the screen
          xv_next <= x_v;
       elsif ball_l> 600 then 
          xv_next <= -x_v ; --The ball hits the right side of the screen
       end if; 
       ball_l_next <= ball_l + xv_reg;
       ball_t_next <= ball_t + yv_reg; 
    end if;
end process;

--wall object
wall_on <= '1' when x > wall_l and x < (640-wall_l) and y> wall_t and y < (wall_t+ wall_k) else  
                      '0'; 
rgb_wall <= "000";--Black  
--bar object
bar_on <= '1' when x > bar_l and x < (bar_l+bar_w) and y> bar_t and y < (bar_t+ bar_k) else  
                    '0'; 
rgb_bar <= "001";--blue

--ball object
ball_on <= '1' when x > ball_l and x < (ball_l+ball_u) and y> ball_t and y < (ball_t+ ball_w) else  
                     '0'; 
rgb_ball <= "010"; --Green

--buffer
process(clk)  
begin  
     if clk'event and clk = '1' then
         rgb_reg <= rgb_next;
     end if;
end process;

--mux
vdbt<=video_on & wall_on & bar_on &ball_on;  
with vdbt select  
     rgb_next <= "100" when "1000",--Background of the screen is red 
     rgb_wall when "1100",
     rgb_wall when "1101",
     rgb_bar when "1010",
     rgb_bar when "1011",
     rgb_ball when "1001",
      "000" when others;
--output
rgb <= rgb_reg;

end Behavioral;

My changes are on lines 10 and 95 to make the potentiometer control the paddle.

Creating PONG on the Mojo FPGA

Written by Michael Earls
 FPGA  electronics  programming

I recently found an old VGA monitor on the side of the road and it inspired me to see if I could get my Mojo FPGA board to output a VGA signal. I took the monitor apart and wired its VGA cable directly to my Mojo.

I read up on the VGA specification and learned that getting it to work was as easy as adding resistors to the Red, Green, and Blue outputs.

VGA Connector

I decided to try my hand at implementing the PONG game that is outlined on fpga4fun.com.

There were some differences between that FPGA that they were using and my Mojo board, so I had to go through some trial-and-error.

For instance, the Verilog code that they have on the site depends on a 25MHz clock and my Mojo uses a 50MHz clock. I tried various ways of dividing the clock without success.

I finally found a great example using a 50MHz clock, but it wasn't quite right because it used 8-bits to encode the RGB values, 3 bits for red and green, and 2 bits for blue. I couldn't get this to work, so I found the code that it was based on and found 1 bit per color implementation. I was finally able to get a signal.

The rest of the time was spent implementing the Pong code from the original article. However, the article used a mouse input. I wanted to use a regular potentiometer (dial) to make it more like the "good old days". So, I wired up a pot to the Mojo and altered the Verilog to use it.

Honestly, my code turned out to be spaghetti code because I haven't quite learned how to modularize Verilog to my satisfaction.

I was able to get the paddle working and responding to movement of the potentiometer, but for some reason, the code for the ball just didn't work in my implementation. I'm guessing it has to do with the timing. I still haven't been able to get the ball working, but I was pretty happy that the paddle works.

The Mojo wired to the VGA cable and the paddle The Mojo wired to the VGA cable and the paddle "controller"

The pong paddle The paddle "controller" - a simple 10k potentiometer

Here is the Verilog definition for the Pong module:

`timescale 1ns / 1ps  
module pong(clk, red, green, blue, hsync, vsync, pot_sample);

// Input clk, 50 MHz Oscillator
input clk;  
input [9:0]pot_sample;

// VGA outputs
output red;  
output green;  
output blue;  
output hsync;  
output vsync;     

reg [9:0] hcount;     // VGA horizontal counter  
reg [9:0] vcount;     // VGA vertical counter  
reg [2:0] data;          // RGB data

wire hcount_ov;  
wire vcount_ov;  
wire inDisplayArea;  
wire hsync;  
wire vsync;  
reg  vga_clk;

// VGA mode parameters
parameter hsync_end   = 10'd95,  
   hdat_begin  = 10'd143,
   hdat_end  = 10'd783,
   hpixel_end  = 10'd799,
   vsync_end  = 10'd1,
   vdat_begin  = 10'd34,
   vdat_end  = 10'd514,
   vline_end  = 10'd524;


always @(posedge clk)  
begin  
 vga_clk = ~vga_clk;
end

always @(posedge vga_clk)  
begin  
 if (hcount_ov)
  hcount <= 10'd0;
 else
  hcount <= hcount + 10'd1;
end

assign hcount_ov = (hcount == hpixel_end);

always @(posedge vga_clk)  
begin  
 if (hcount_ov)
 begin
  if (vcount_ov)
   vcount <= 10'd0;
  else
   vcount <= vcount + 10'd1;
 end
end  
assign  vcount_ov = (vcount == vline_end);

////////////////////////////////////////////////////////////////
// Pong
////////////////////////////////////////////////////////////////
// Handle paddle position
reg [9:0] PaddlePosition;

always @(posedge vga_clk) begin

        PaddlePosition <= hdat_begin + pot_sample;

    if(0)
    begin
      if(0)
      begin
         if(~&PaddlePosition)        // make sure the value doesn't overflow
            PaddlePosition <= PaddlePosition + 1;
      end 
      else
      begin
         if(|PaddlePosition)        // make sure the value doesn't underflow
            PaddlePosition <= PaddlePosition - 1;
      end
    end
end

wire paddle = (hcount >= PaddlePosition + 8) && (hcount <= PaddlePosition+120) && ((vcount <= vdat_end - 15) && (vcount > vdat_end - 25));

// Draw a border around the screen
wire border = ((hcount >=  hdat_begin) && (hcount <  hdat_begin + 10)) || ((hcount >  hdat_end - 10) && (hcount <=  hdat_end))  
    || ((vcount >= vdat_begin) && (vcount < vdat_begin + 10)) || ((vcount > vdat_end - 10) && (vcount <= vdat_end));

//////////////////////////////////////////////////////////////////
reg [9:0] ballX;  
reg [9:0] ballY;  
reg ball_inX, ball_inY;

always @(posedge vga_clk)  
if(ball_inX==0) ball_inX <= ((hcount>ballX) && (hcount<=ballX+16)) & ball_inY; else ball_inX <= 0;//!(hcount==ballX+16);

always @(posedge vga_clk)  
if(ball_inY==0) ball_inY <= ((vcount>ballY) && (vcount<=ballY+16)); else ball_inY <= 0;//!(vcount==ballY+16);

wire ball = ball_inX & ball_inY;

/////////////////////////////////////////////////////////////////
wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself

reg ResetCollision;  
always @(posedge vga_clk) ResetCollision <= (vcount==0) & (hcount==0);  // active only once for every video frame

reg CollisionX1, CollisionX2, CollisionY1, CollisionY2;  
always @(posedge vga_clk) if(ResetCollision) CollisionX1<=0; else if(BouncingObject & (hcount==ballX   ) & (vcount==ballY+ 8)) CollisionX1<=1;  
always @(posedge vga_clk) if(ResetCollision) CollisionX2<=0; else if(BouncingObject & (hcount==ballX+16) & (vcount==ballY+ 8)) CollisionX2<=1;  
always @(posedge vga_clk) if(ResetCollision) CollisionY1<=0; else if(BouncingObject & (hcount==ballX+ 8) & (vcount==ballY   )) CollisionY1<=1;  
always @(posedge vga_clk) if(ResetCollision) CollisionY2<=0; else if(BouncingObject & (hcount==ballX+ 8) & (vcount==ballY+16)) CollisionY2<=1;

/////////////////////////////////////////////////////////////////
wire UpdateBallPosition = ResetCollision;  // update the ball position at the same time that we reset the collision detectors

reg ball_dirX, ball_dirY;  
always @(posedge vga_clk)  
if(UpdateBallPosition)  
begin  
    if(~(CollisionX1 & CollisionX2))        // if collision on both X-sides, don't move in the X direction
    begin
        ballX <= ballX + (ball_dirX ? -1 : 1);
        if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0;
    end

    if(~(CollisionY1 & CollisionY2))        // if collision on both Y-sides, don't move in the Y direction
    begin
        ballY <= ballY + (ball_dirY ? -1 : 1);
        if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0;
    end
end 

/////////////////////////////////////////////////////////////////

// display area calculation
assign inDisplayArea = ((hcount >= hdat_begin) && (hcount < hdat_end))  
     && ((vcount >= vdat_begin) && (vcount < vdat_end));

assign hsync = (hcount > hsync_end);  
assign vsync = (vcount > vsync_end);

reg vga_R, vga_G, vga_B;

always @(posedge vga_clk)  
begin  
  vga_R <= BouncingObject | ball;
  vga_G <= BouncingObject | ball;
  vga_B <= BouncingObject | ball;
end

assign red = (inDisplayArea) ?  vga_R : 0;  
assign green = (inDisplayArea) ?  vga_G : 0;  
assign blue = (inDisplayArea) ?  vga_B : 0;      


// generate "image"
always @(posedge vga_clk)  
begin  
  data <= (vcount[2:0] ^ hcount[2:0]); 
end

endmodule

Line #6 is the direct input of the potentiometer from the Mojo's on-board Analog to Digital Converter (ADC). Here is the top file showing how it's all wired together:

module mojo_top(  
    // 50MHz clock input
     input clk,
    // Input from reset button (active low)
    input rst_n,
    // cclk input from AVR, high when AVR is ready
    input cclk,
    // Outputs to the 8 onboard LEDs
    output[7:0]led,
    // AVR SPI connections
    output spi_miso,
    input spi_ss,
    input spi_mosi,
    input spi_sck,
    // AVR ADC channel select
    output [3:0] spi_channel,
    // Serial connections
    input avr_tx, // AVR Tx => FPGA Rx
    output avr_rx, // AVR Rx => FPGA Tx
    input avr_rx_busy, // AVR Rx buffer full
     output  red,
    output  green,
    output  blue,
    output hsync, vsync
    );

wire rst = ~rst_n; // make reset active high

// these signals should be high-z when not used
assign spi_miso = 1'bz;  
assign avr_rx = 1'bz;  
assign spi_channel = 4'bzzzz;

assign led = 8'b0;

wire [3:0] channel;  
wire new_sample;  
wire [9:0] sample;  
wire [3:0] sample_channel;  
wire [9:0] pot_sample;

avr_interface avr_interface (  
 .clk(clk),
 .rst(rst),
 .cclk(cclk),
 .spi_miso(spi_miso),
 .spi_mosi(spi_mosi),
 .spi_sck(spi_sck),
 .spi_ss(spi_ss),
 .spi_channel(spi_channel),
 .tx(avr_rx),
 .rx(avr_tx),
 .channel(channel),
 .new_sample(new_sample),
 .sample(sample),
 .sample_channel(sample_channel),
 .tx_data(8'h00),
 .new_tx_data(1'b0),
 .tx_busy(),
 .tx_block(avr_rx_busy),
 .rx_data(),
 .new_rx_data()
);

 input_capture input_capture (
 .clk(clk),
 .rst(rst),
 .channel(channel),
 .new_sample(new_sample),
 .sample(sample),
 .sample_channel(sample_channel),
 .sample_out(pot_sample)
);

pong pongBoard(  
    .clk(clk), 
    .red(red),
    .green(green),
    .blue(blue),
    .hsync(hsync),
    .vsync(vsync),
    .pot_sample(pot_sample)
);

endmodule

And, finally, here are the custom user constraints that I used to wire the output pins up to the VGA connector:

NET "hsync" LOC = P50 | IOSTANDARD = LVTTL;  
NET "vsync" LOC = P51 | IOSTANDARD = LVTTL;  
NET "red" LOC = P41 | IOSTANDARD = LVTTL;  
NET "green" LOC = P40 | IOSTANDARD = LVTTL;  
NET "blue" LOC = P35 | IOSTANDARD = LVTTL;

I'd really like to get the ball working with this. My scoring will be different than traditional Pong as I am not yet ready to tackle the "AI" of the computer player. I'd like the player to score 1 point each time the ball hits the paddle and lose a point each time the ball hits the bottom wall. If the ball hits the bottom wall while the score is 0, then the game is over. I'll have to add in a reset button, but that shouldn't be much trouble.

I just found another implementation of Pong in VHDL that I may try later. It divides the 50MHz clock into a 25MHz clock using a mod 2 counter. I discuss my implementation of that code in another post.

I haven't written any VHDL, yet, so I'm still trying to wrap my head around it. It was hard enough switching from Mojo's Lucid language to Verilog as Lucid made things a bit easier. However, I felt that since there were a lot more people out there using Verilog that I would be better off learning that. Now I'm learning that there are even more people using VHDL.

Lucid, Verilog, and VHDL are a far cry from C#, C, and JavaScript; three languages I'm very familiar with. This has been a very beneficial lesson in parallel "programming".

Custom Fritzing part for Mojo v3

Written by Michael Earls
 electronics  FPGA  Illustrator

There is a great tool called Fritzing that allows you to create a breadboard view of circuits that you can use for documentation. I really wanted to use it to document the breadboard for my post on setting up a reversible counter, but no one had created a part for it yet. You can also use Fritzing to create schematic diagrams from your breadboard as well as create PC boards.

So, I took a side journey down a very deep rabbit hole. Here's what I learned along the way.

The final part is available on GitHub in my fritzing-parts repository.

Fritzing example with new Mojo part

Fritzing Community

The first thing I noticed immediately was how helpful everyone is in the Fritzing community. The online forums are full of helpful people willing to pitch in. In fact, my part would not have been completed without the enormous effort of @vanepp from the Fritzing forums. I had spent three weeks of my spare time getting everything laid out in Illustrator and Fritzing before I was brave enough to "release" the part onto the Fritzing site. As soon as I did, I received instant feedback on what needed improvement on my part. From there, I've had the assistance of a very experienced part maker working with my source files (which I had converted from the Eagle CAD software to SVG since Eagle is the format that the Mojo manufacturer used to create the board).

Fritzing does not like Adobe Illustrator SVG

Adobe illustrator has the ability to export to SVG. It retains the layer and group names, but some things just weren't translating correctly, so it kept causing trouble. It turns out that the community is mostly using Inkscape (an open-source (GPL) alternative to Illustrator) to make their SVG documents.

I am going to learn how to use Inkscape and I will likely cancel my Adobe Creative Cloud membership. I already know how to use GIMP (the open-source Photoshop alternative). I'm not a graphics professional, but I'm paying a pro price to keep these tools.

The only other product that I use from Creative Cloud is Premiere Pro, but I can just buy Premiere Elements to replace it. That will save me a lot of money each month.

Part creation is hard

I jumped into this thinking "this should be easy, there are so many online tutorials". I was wrong. Even with the tutorials, there were some things I had to learn about creating parts. I accidentally deleted the first part that I created after wiring up 110 pins to three different views (that was over 600 mouse clicks after it was all over).

There are lots of resources, but I'm a hard-knocks kind of learner, so my stubbornness cost me extra pain. The Illustrator part took me over two weeks of spare time to get right. I started with a high-resolution image of the board from the Embedded Micro website and edited it in Photoshop. I prepared it (changed color balance and contrast) for Illustrator. Once I had it in Illustrator, I used the image trace tool to create the beginnings of a long task. I replaced each individual component with hand drawn basic shapes (the image trace created complex paths that would have made an enormous SVG file).

Once I had exported it to SVG, I then had to make sure that the pins lined up. It took a lot of work between myself and @vanepp from the forums

Summary

I enjoyed the part creation overall and I'd do it again. I think Fritzing is a great tool and I hope more people add more parts to it.