Hack a Guitar Hero drumset to use it with any computer over USB, Part 3

 

BitBucket repository is live: https://bitbucket.org/MostThingsWeb/usbdrumming/src
In part 2, we finished up the initial drafts of the hardware and embedded software for the drumset. Now, we’ll make a few modifications to our circuit and software to add some additional features.
 

Improving the circuit

Let’s add three more things to your circuit:

  1. Some kind of indicator, so we know it’s working.
  2. Decoupling capacitors
  3. Circuity to detect when the control box and Arduino are on at the same time (a conflict)

 

Indication

I wanted a visual indicator that would quickly indicate the status of drumset, so I chose to install a big RGB LED right in the back of the drumset. Specifically, I chose this 10mm RGB LED. If you look at the back of your drumset, you can see a little plastic panel where the pedal and MIDI “in” ports are installed. That panel is taken, but if you look opposite the control box (still on the back of the drumset), there’s some open space. That’s where we will eventually install a hole for the USB cable to come out of, as well as the LED.

The LED – image property of SparkFun

Side note: This indicator LED is optional, though highly recommended. It will involve you drilling a hole in your drumset, which is a very delicate operation because of the weak plastic (sawing the hole for the USB cable is far easier). If you choose not to install the LED, you will probably save yourself half an hour, but you will lose an important feature. Plus the LED is really big and shiny. I’d recommend you install it. If you want, you could go for the smaller 5mm version instead of the huge 10mm LED. Just be sure that you get a common cathode LED.
 

Decoupling capacitors

We just need two decoupling capacitors: one on each of the buffer chip power supplies. I tried adding decoupling capacitors on the control box and drumpad controller, but the added capacitance caused a bunch of problems, so don’t do that. Also, you don’t need one on the Arduino’s power supply because it already has decoupling capacitors on the board.
 

Conflict detection

So what happens when the control box and Arduino are running at the same time? Currently, both of them would try to take control of the SPI bus and nothing would really work. In my testing, running the control box and Arduino at the same time put the control box into some kind of transient state that could only be fixed by pulling the batteries and unplugging the Arduino.

While we can’t prevent the control box from going into a transient state, we can at least warn the user when a conflict occurs. Take a look at this snippet of our updated schematic:

Gate IC2A is the important part. It’s OE line is connected to that of the rest of Arduino SPI-driving buffer gates. So when the Arduino is on, IC2A is enabled. The input of the buffer is the control box power, pulled low to ground. What this means is that when the control box is on, we’ll read digital high on pin 4.
 

The new schematic

Here is the new schematic complete with the additions detailed above. Click the image to get a larger version.


 

Improving the software

Now let’s focus our attention on the sketch that gets uploaded to the Arduino. That’s the software that emulates the control box and allows us to talk to the drumpad controller. Below is the full updated listing of the sketch. Note that this is only current at time of writing. You’ll be able to get the most up-to-date version at the BitBucket repository when I make it public soon.

#include <SPI.h>

const int PIN_SS = 2;
const int PIN_MISO = 14;
const int PIN_CLK = 15;
const int PIN_MOSI = 16;

const int PIN_RED_LED = 10;
const int PIN_GREEN_LED = 5;
const int PIN_BLUE_LED = 9;

const int PIN_OE = 3;
const int PIN_FAULT = 4;

// Define some colors
const int RED[] = {
  255, 0, 0};
const int GREEN[] = {
  0, 255, 0};
const int BLUE[] = {
  0, 0, 255};
const int ORANGE[] = {
  255, 128, 0};
const int YELLOW[] = {
  255, 255, 0};
const int WHITE[] = {
  255, 255, 255}; // For the pedal

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

  pinMode(PIN_FAULT, INPUT);
  pinMode(PIN_OE, OUTPUT);
  digitalWrite(PIN_OE, HIGH);

  // Initialize SPI
  SPI.setDataMode(SPI_MODE1);
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  SPI.begin();

  // Set up RBGB led
  pinMode(PIN_RED_LED, OUTPUT);
  pinMode(PIN_GREEN_LED, OUTPUT);
  pinMode(PIN_BLUE_LED, OUTPUT);



  Serial.begin(9600);

  // Show the startup LED pattern
  for (int i = 0; i <= 255; i++){
    setLED(i, 0, 255 - i);
    delay(3);
  }
  for (int i = 0; i <= 255; i++){
    setLED(255 - i, i, 0);
    delay(3);
  }
  for (int i = 0; i <= 255; i++){
    setLED(0, 255 - i, i);
    delay(3);

  }

  setLED(0, 0, 0);

  // Inititalize drumset
  digitalWrite(PIN_SS, LOW);
  delayMicroseconds(27.5);
  SPI.transfer(0xAA);
  delayMicroseconds(8);
  digitalWrite(PIN_SS, HIGH);
  delayMicroseconds(10004.5);

}

bool fault = false;

void loop(){
  if (fault){
    Serial.println("{ "msgType": "error", "error": "Drumset is on!" }");
    delay(2000);
    return;
  }

  if (digitalRead(PIN_FAULT) == HIGH){
    fault = true;
    SPI.end();
    // Release the buffer enable line, isolating
    // the Pro Micro from everything else
    digitalWrite(PIN_OE, LOW);
    return;
  }

  // The client might want to know what we are, so let
  // them ping us
  if (Serial.available()){
    while (Serial.available()){
      Serial.read();
    }

    Serial.println("{ "msgType": "ping" }");
  }


  setLED(0, 0, 0);

  // Select the slave
  digitalWrite(PIN_SS, LOW);
  delayMicroseconds(27.5);

  // Send preamble
  SPI.transfer(0xAA);
  delayMicroseconds(32.625);

  // Read the number of messages that the drumset has to send
  byte hitCount = SPI.transfer(0x55);
  byte pad;
  byte velocity;

  if (hitCount > 0){ 
    delayMicroseconds(50);

    // All messages are encoded as JSON
    Serial.print("{ "msgType": "hits", "hitCount": ");
    Serial.print(hitCount);
    Serial.print(", "hits": [");

    for (int hit = 0; hit < hitCount; hit++){
      SPI.transfer(0); // Returns 153 (0x99)
      delayMicroseconds(23.125);
      pad = SPI.transfer(0);
      delayMicroseconds(23.125);
      velocity = SPI.transfer(0);
      delayMicroseconds(23.125);

      Serial.print("{"pad": ");
      Serial.print(pad);
      Serial.print(", "veloc": ");
      Serial.print(velocity);
      Serial.print("}");
      if (hit != hitCount - 1){
        Serial.print(",");
      }
    }

    Serial.println("]}");
  } 

  delayMicroseconds(48);
  digitalWrite(PIN_SS, HIGH);

  if (hitCount == 1){
    switch (pad){
    case 0x26: 
      setLEDColor(RED);
      break;
    case 0x2E:
      setLEDColor(YELLOW);
      break;
    case 0x30:
      setLEDColor(BLUE);
      break;
    case 0x31:
      setLEDColor(ORANGE);
      break;
    case 0x2D:
      setLEDColor(GREEN);
      break;
    case 0x24:
      setLEDColor(WHITE);
      break;
    } 

    delay(5);
  } 
  else if (hitCount > 1){
    setLEDColor(WHITE);
    delay(5);
  }
}

void setLED(int r, int g, int b){
  analogWrite(PIN_RED_LED, r);
  analogWrite(PIN_GREEN_LED, g);
  analogWrite(PIN_BLUE_LED, b);
}

void setLEDColor(const int* color){
  setLED(color[0], color[1], color[2]);
}

 

Notes about the updated software

Previously, I just wrote simple messages to the serial line to indicate the pads that had been hit. That output isn’t as easy to parse as it could be, so I switched to outputting (homebrew) JSON. JSON parsing libraries exist for practically any programming language you could want, so I knew I wouldn’t be limiting myself later on when it came time to parse the pad hits.

I’ve also added code to detect when pin 4 is high (indicating that the Arduino and control box are running at the same time). When this occurs, SPI communications are ended and the Arduino is isolated from the rest of the circuit by pulling the buffers’ output enable lines low, switching them into high impedance mode. We also send messages every 2 seconds to indicate that a fault detection has occurred.

These lines of code exist in the main loop and respond to ‘pings’ that are received through the serial port:

// The client might want to know what we are, so let
// them ping us
if (Serial.available()){
  while (Serial.available()){
    Serial.read();
  }
    
  Serial.println("{ "msgType": "ping" }");
}

Later on, that feature will let us quickly determine if a serial port leads to our drumset.
 

Part 4

In part 4, we get to the good part: Assembling the circuit and installing it in the drumset!

 

Leave a comment

Leave a Reply