Hairless MIDI with Adafruit Servo Shield

Hey all,

I’ve been bashing my head against this for the last two days and now I’m throwing in the towel.

Here’s the situation. While I’m fascinated by Arduino, this particular project is not a learning thing, but rather a work thing with a deadline rapidly approaching, which quite frankly is taking all the fun out of it. I don’t really have time to learn a new programming language so I could really do with some help.

What I’m trying to do SEEMS like it should be simple, but is apparently a lot more complicated than I thought. All I need is a device that will rotate a servo upon receiving a MIDI note-on, and rotate it back on note-off. 8 notes in total.

The hardware I have is:
Arduino Uno R3 (I am now realizing it would be a lot simpler with a Teensy)
Adafruit 16 Ch Servo Driver (I have literally no idea how the code works for this, I can only stare at it in despair, any attempt to modify it for use with MIDI has failed)
Servo Motors
A 2a 5v PSU

The closest I have come is using the following code (without the Adafruit board and by using Hairless MIDI) however, after spending from 3am to 11am on it, I have realized that the Arduino can only output PWM on 6 pins, and there seems to be some issue with the notes that Hairless is sending, because multiple notes affect the same servo. Back to the drawing board…

/*
* MIDI Servo core code.
*/

#include <Servo.h>

// MIDI Command codes, note, the low order 4 bits are 0
#define MIDI_Note_Off 0x80
#define MIDI_Note_On 0x90

// MIDI in read actions
#define WAIT 1 // Waiting for a valid MIDI command.
#define Note_Off 2 // MIDI_Note_Off was received.
#define Note_On 3 // MIDI_Note_On was received.

// Output pins
#define Servo1 9
#define Servo2 10
#define Servo3 11

Servo myservo1; // create servo object to control a servo
Servo myservo2; // create servo object to control a servo
Servo myservo3; // create servo object to control a servo

int pos = 0; // variable to store the servo position

//
// MIDI Reception variables
byte IncomingByte; // Byte from serial port
byte MIDI_Command; // undecoded MIDI command (0xn0, where n=0x8 to 0xF)
byte MIDI_Channel; // MIDI Channel (0x00 to 0x0F)

int action = WAIT; // Decoded MIDI Command
byte note = 0x80; // Initialised to invalid note.
byte velocity = 0; //

void setup() {
// Configure the serial port for MIDI, which is 31250 bps, 8 bits, no parity, 1 stop.
Serial.begin(115200);

// Configure the output pins
myservo1.attach(Servo1); // attaches the servo on pin 9 to the servo object
myservo2.attach(Servo2); // attaches the servo on pin 9 to the servo object
myservo3.attach(Servo3); // attaches the servo on pin 9 to the servo object

// Initial servo test, full rotation test for each servo
for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo1.write(pos); // tell servo to go to position in variable ‘pos’
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo1.write(pos); // tell servo to go to position in variable ‘pos’
delay(15); // waits 15ms for the servo to reach the position
}

for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo2.write(pos); // tell servo to go to position in variable ‘pos’
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo2.write(pos); // tell servo to go to position in variable ‘pos’
delay(15); // waits 15ms for the servo to reach the position
}

for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo3.write(pos); // tell servo to go to position in variable ‘pos’
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo3.write(pos); // tell servo to go to position in variable ‘pos’
delay(15); // waits 15ms for the servo to reach the position
}
myservo1.write(90);
myservo2.write(90);
myservo3.write(90);
}

void loop() {
if (Serial.available() > 0) {
// read the incoming byte:
IncomingByte = Serial.read();

MIDI_Command = IncomingByte & 0xF0;
MIDI_Channel = IncomingByte & 0x0F;

// Was it a MIDI command (bit 7 = 1)?
if (bitRead(MIDI_Command, 7) == 1) {
// If so, need to save the decoded action & reset note and velocity
switch (MIDI_Command){
case MIDI_Note_On:
action = Note_On;
break;
case MIDI_Note_Off:
action = Note_Off;
break;
}
note = 0x80;
velocity = 0;
}
else {
// Process data byte
if (note == 0x80 && action != WAIT) // note on, wait for note value
{
note = IncomingByte;
}
else if (note != 0x80 && action != WAIT) // velocity
{
velocity = IncomingByte;
// All elements of the command have been received, now process it.
if (action == Note_On) {
Set_Servo(MIDI_Channel, note, velocity);
}
else if (action == Note_Off) {
Reset_Servo(MIDI_Channel, note);
}
note = 0x80;
velocity = 0;
action = WAIT;
}
}
}
}

 
void Set_Servo(byte MIDI_Channel, byte Note, byte Velocity) {
// This function is the one you will most likely need to change to adapt this project to different applications.
//
// As this is test code, I have avoided making it too fancy, so Bit 0 drives servo1, Bit 1 drives servo2 and Bit 2 drives servo3.
// The Velocity value determines the value.
// As the approach is bitwise, a single key may set more than one of the servos
//
pos = map(Velocity, 1, 127, 0, 179);
if((Note & 1)!=0){
myservo1.write(pos);
}
if((Note & 2)!=0){
myservo2.write(pos);
}
if((Note & 4)!=0){
myservo3.write(pos);
}
}

 

void Reset_Servo(byte MIDI_Channel, byte Note) {
// This function is one you will most likely need to change to adapt this project to different applications.
//
// When the midi key is released, this function is called and may be used to reset the servo to it’s home.
//
pos = 89; //By default, home is the servo’s 90degree mark.
if((Note & 1)!=0){
myservo1.write(pos);
}
if((Note & 2)!=0){
myservo2.write(pos);
}
if((Note & 4)!=0){
myservo3.write(pos);
}
}

Apologies for my ignorance. I would usually approach something like this from the ground up, but I just don’t have the time available. If anyone could point me in a better direction, preferably with an example of someone using Hairless with the Adafruit PWM Driver I would be eternally grateful.

Many, many thanks,
MixingWizard

Pretty advanced project with a very short deadline. You may want to click on "Report to moderator" and ask for your thread to be moved to the Gigs and Collaborations section where you can pay someone to write your code. Which is probably the only way you'll come close to meeting your deadline.

If anyone needs more info on the code supplied by the OP, Click Here

One misconception. You don't need that Adafruit thing for only 8 servos. The Servo library does not need PWM pins, it can use normal digital pins and can handle up to 12 servos on a Uno.

O.k. two misconceptions. A 5V 2A power supply is not enough for 8 servos. Even a little SG90 can require up to 650mA. A 5V 5A power supply would be more like it.

Unfortunately that code is far too confusing for me to understand. I've only used the standard MIDI.h library which is much simpler and comes with some useful example code. It might also be worth having a look in the Audio forum which is where most of the MIDI discussions/examples are.

Steve

I did something similar recently. I’ll post the code below. I added the Adafruit Servo code as an example, but I don’t have such a shield, so I can’t test it. You need these libraries:

// Servo setup
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

// Based on: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/blob/master/examples/servo/servo.ino

// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);
// you can also call it with a different address and I2C interface
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire);

// Depending on your servo make, the pulse width min and max may vary, you 
// want these to be as small/large as possible without hitting the hard stop
// for max range. You'll have to tweak them as necessary to match the servos you
// have!
constexpr unsigned SERVOMIN = 150; // This is the 'minimum' pulse length count (out of 4096)
constexpr unsigned SERVOMAX = 600; // This is the 'maximum' pulse length count (out of 4096)
constexpr unsigned long SERVO_FREQ = 60; // Analog servos run at ~60 Hz updates

// MIDI setup
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

#include <Control_Surface.h>

// The MIDI interface to use
HairlessMIDI_Interface midi;

// Class for handling incoming MIDI note events and driving the servos
struct MyServoNoteCallback : SimpleNoteCCValueCallback {
  
  // Constructor: save the servo number
  MyServoNoteCallback(uint8_t servonum) : servonum(servonum) {}

  // Initialize the note callback
  void begin(const INoteCCValue &) override { /* Nothing to initialize */ }

  // Function is called when the note is updated
  void update(const INoteCCValue &note, uint8_t) override {
    uint8_t velocity = note.getValue();
    if (velocity > 0) { 
      // If the note is on (velocity > 0)
      // move the servo to maximum position
      pwm.setPWM(servonum, 0, SERVOMAX);
      // You could use the velocity value here to make the
      // servo move less far
    } else { 
      // If the note is off (velocity = 0)
      // move the servo to the minimum position
      pwm.setPWM(servonum, 0, SERVOMIN);
    }
  }

  private:
    uint8_t servonum;
};

// Declare MIDI notes and servo numbers
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

using namespace MIDI_Notes;
// Array of MIDI input elements that listen to a single (1) note and use the callback
// class declared earlier:
GenericNoteCCRange<MIDIInputElementNote, 1, MyServoNoteCallback> midiServoNotes[] = {
  {{note(C, 4), CHANNEL_1}, 0}, // MIDI Note C4 controls servo 0
  {{note(D, 4), CHANNEL_1}, 1}, // MIDI Note D4 controls servo 1
  {{note(E, 4), CHANNEL_1}, 2},
  {{note(F, 4), CHANNEL_1}, 3},
  // ...  
};

// Setup
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void setup() {
  pwm.begin();
  // In theory the internal oscillator is 25MHz but it really isn't
  // that precise. You can 'calibrate' by tweaking this number till
  // you get the frequency you're expecting!
  pwm.setOscillatorFrequency(27000000);  // The int.osc. is closer to 27MHz  
  pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~60 Hz updates

  Control_Surface.begin();
}

// Loop
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void loop() {
  Control_Surface.loop();
}

Pieter

I would also use the standard midi.h library, (cause that works really well and i don't need to do the parsing and error checking) but maybe your midi code is functional if you correct the BAUD rate

Serial.begin(115200);

to 31250
Oh sorry it's 'Hairless midi' well that is not really midi, then you BAUD rate is correct, i still think you should go for the midi.h, but change the BAUD rate to 115200 it is defined on line 73 of 'midi_settings.h'

I would advise against editing the library, you can set the baud rate in your sketch if you have to:

MIDI library baud rate setting:





```
#include <MIDI.h>

struct MySettings : public midi::DefaultSettings {
  static const long BaudRate = 115200;
};

MIDI_CREATE_CUSTOM_INSTANCE(decltype(Serial), Serial, MIDI, MySettings)

void setupcolor=#000000[/color] {
  MIDI.begincolor=#000000[/color];
}

void loopcolor=#000000[/color] {
  // put your main code here, to run repeatedly:
}
```

|

PieterP:
I would advise against editing the library, you can set the baud rate in your sketch if you have to:

Agreed, it describes how to to do this in the same file 'midi_settings.h'
I figured if you are going to check on how to do this, that information is presented.