Skip to content

Instantly share code, notes, and snippets.

@mnkhouri
Last active October 12, 2022 04:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mnkhouri/e6ac28bc48560b31890ddb61cc7f7a87 to your computer and use it in GitHub Desktop.
Save mnkhouri/e6ac28bc48560b31890ddb61cc7f7a87 to your computer and use it in GitHub Desktop.
Arduino sketch for I2C communication with a Guitar Hero 5 neck
/*
Copyright (c) 2022 Marc Khouri
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <Wire.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
// Toggles
#define SERIAL_EN false // Enable serial output
#define VERBOSE false // Verbose serial output
#define ENABLE_SLEEP true // Go to sleep eventually
#define SLEEP_TIMEOUT_MS 600000UL // Idle timeout before sleep. 600,000 == 10 minutes (ardwiino uses 10min).
// Pin definitions
#define PIN_GREEN 9
#define PIN_RED 8
#define PIN_YELLOW 7
#define PIN_BLUE 6
#define PIN_ORANGE 5
#define PIN_I2C_POWER 4 // guitar neck only uses 3mA, we can source 40mA
#define PIN_WAKEUP 2
// Constants / structs
#define I2C_ADDRESS 0x0D
const char * hex = "0123456789ABCDEF";
typedef struct {
bool green;
bool red;
bool yellow;
bool blue;
bool orange;
} Buttons;
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
pinMode(PIN_GREEN, OUTPUT);
pinMode(PIN_RED, OUTPUT);
pinMode(PIN_YELLOW, OUTPUT);
pinMode(PIN_BLUE, OUTPUT);
pinMode(PIN_ORANGE, OUTPUT);
pinMode(PIN_I2C_POWER, OUTPUT);
digitalWrite(PIN_I2C_POWER, HIGH);
pinMode(PIN_WAKEUP, INPUT_PULLUP);
if (SERIAL_EN) {
Serial.begin(115200);
Serial.println("Setup complete");
}
}
void wakeIsr() {
detachInterrupt(digitalPinToInterrupt(PIN_WAKEUP));
digitalWrite(PIN_I2C_POWER, HIGH);
if (SERIAL_EN) { Serial.println("Waking up"); }
}
void goToSleep() {
// for (i = 2; i < 20; i++) { // all pins to one rail or the other - power saving
// pinMode(i,OUTPUT);
// digitalWrite(i,LOW);
// }
digitalWrite(PIN_I2C_POWER, LOW);
ADCSRA = 0; // disable ADC for power saving
wdt_disable(); // disable WDT for power saving
set_sleep_mode (SLEEP_MODE_PWR_DOWN); // Deep sleep
sleep_enable();
sleep_bod_disable(); // disable brownout detector during sleep
attachInterrupt(digitalPinToInterrupt(PIN_WAKEUP), wakeIsr, LOW); // wakeup on signal
sleep_cpu(); // now go to sleep
}
void diagnoseTransmissionError(byte code) {
// Docs for wire.endtransmission: https://docs.particle.io/cards/firmware/wire-i2c/endtransmission/
switch(code) {
case 0:
Serial.println("success");
break;
case 1:
Serial.println("busy timeout upon entering endTransmission()");
break;
case 2:
Serial.println("START bit generation timeout");
break;
case 3:
Serial.println("end of address transmission timeout");
break;
case 4:
Serial.println("data byte transfer timeout");
break;
case 5:
Serial.println("data byte transfer succeeded, busy timeout immediately after");
break;
case 6:
Serial.println("timeout waiting for peripheral to clear stop bit");
break;
default:
Serial.print("Unknown return from EndTransmission: ");
Serial.println(code);
}
}
unsigned int readFromSerial(uint8_t* arr, unsigned int expectedByteCount) {
unsigned int readCount = 0;
Wire.requestFrom(I2C_ADDRESS, expectedByteCount); // request N bytes from peripheral device
while (Wire.available() && (readCount < expectedByteCount)) { // peripheral may send less than requested
arr[readCount] = Wire.read(); // receive a byte as character
readCount++;
}
return readCount;
}
void printByteArray(uint8_t* arr, unsigned int len) {
Serial.print("Read ");
Serial.print(len);
Serial.print(" bytes:");
for(unsigned int i = 0; i < len; i++) {
Serial.print(" 0x");
Serial.print(hex[(arr[i]>>4) & 0xF]);
Serial.print(hex[arr[i] & 0xF]);
}
Serial.println("");
}
Buttons twoBytesToButton(uint8_t* arr) {
Buttons buttons = {.green = false, .red = false, .yellow = false, .blue = false, .orange = false};
char topButtons = arr[0];
if (topButtons & 0x10) {
buttons.green = true;
}
if (topButtons & 0x20) {
buttons.red = true;
}
if (topButtons & 0x80) {
buttons.yellow = true;
}
if (topButtons & 0x40) {
buttons.blue = true;
}
if (topButtons & 0x01) {
buttons.orange = true;
}
// The remaining array on the real guitar 5 bytes long. It varies in a stable
// way when pressing on the touch pad. However, we can get away with just
// looking at the first byte of the five, since it has a unique value for
// each combination of touchpad presses.
// There's gotta be a pattern here, but I'm too tired to spot it...
switch (arr[1]) {
case 0x00:
// no buttons
break;
case 0x95:
buttons.green = true; break;
case 0xCD:
buttons.red = true; break;
case 0x1A:
buttons.yellow = true; break;
case 0x49:
buttons.blue = true; break;
case 0x7F:
buttons.orange = true; break;
case 0xB0:
buttons.green = true; buttons.red = true; break;
case 0x19:
buttons.green = true; buttons.yellow = true; break;
case 0x47:
buttons.green = true; buttons.blue = true; break;
case 0x7B:
buttons.green = true; buttons.orange = true; break;
case 0xE6:
buttons.red = true; buttons.yellow = true; break;
case 0x48:
buttons.red = true; buttons.blue = true; break;
case 0x7D:
buttons.red = true; buttons.orange = true; break;
case 0x2F:
buttons.yellow = true; buttons.blue = true; break;
case 0x7E:
buttons.yellow = true; buttons.orange = true; break;
case 0x66:
buttons.blue = true; buttons.orange = true; break;
case 0x65:
buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
case 0x64:
buttons.red = true; buttons.blue = true; buttons.orange = true; break;
case 0x7C:
buttons.red = true; buttons.yellow = true; buttons.orange = true; break;
case 0x2E:
buttons.red = true; buttons.yellow = true; buttons.blue = true; break;
case 0x62:
buttons.green = true; buttons.blue = true; buttons.orange = true; break;
case 0x7A:
buttons.green = true; buttons.yellow = true; buttons.orange = true; break;
case 0x2D:
buttons.green = true; buttons.yellow = true; buttons.blue = true; break;
case 0x79:
buttons.green = true; buttons.red = true; buttons.orange = true; break;
case 0x46:
buttons.green = true; buttons.red = true; buttons.blue = true; break;
case 0xE5:
buttons.green = true; buttons.red = true; buttons.yellow = true; break;
case 0x63:
buttons.red = true; buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
case 0x61:
buttons.green = true; buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
case 0x60:
buttons.green = true; buttons.red = true; buttons.blue = true; buttons.orange = true; break;
case 0x78:
buttons.green = true; buttons.red = true; buttons.yellow = true; buttons.orange = true; break;
case 0x2C:
buttons.green = true; buttons.red = true; buttons.yellow = true; buttons.blue = true; break;
case 0x5F:
buttons.green = true; buttons.red = true; buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
default:
if (SERIAL_EN) {
Serial.print("Unrecognized pattern! ");
printByteArray(&arr[1], 1);
}
}
return buttons;
}
void printButtons(Buttons buttons) {
if (VERBOSE) { Serial.print("Green: "); }
Serial.print(buttons.green);
if (VERBOSE) { Serial.print(" Red: "); }
Serial.print(buttons.red);
if (VERBOSE) { Serial.print(" Yellow: "); }
Serial.print(buttons.yellow);
if (VERBOSE) { Serial.print(" Blue: "); }
Serial.print(buttons.blue);
if (VERBOSE) { Serial.print(" Orange: "); }
Serial.println(buttons.orange);
}
void buttonsToDigitalOut (Buttons buttons) {
if (buttons.green) {
digitalWrite(PIN_GREEN, HIGH);
} else {
digitalWrite(PIN_GREEN, LOW);
}
if (buttons.red) {
digitalWrite(PIN_RED, HIGH);
} else {
digitalWrite(PIN_RED, LOW);
}
if (buttons.yellow) {
digitalWrite(PIN_YELLOW, HIGH);
} else {
digitalWrite(PIN_YELLOW, LOW);
}
if (buttons.blue) {
digitalWrite(PIN_BLUE, HIGH);
} else {
digitalWrite(PIN_BLUE, LOW);
}
if (buttons.orange) {
digitalWrite(PIN_ORANGE, HIGH);
} else {
digitalWrite(PIN_ORANGE, LOW);
}
}
bool isInitialized = false;
unsigned long lastChange = 0;
unsigned int loopCounter = 0; // Only used to control how often debug output is printed
void loop() {
if (ENABLE_SLEEP && (millis() - lastChange > SLEEP_TIMEOUT_MS)) {
if (SERIAL_EN) {
Serial.println("Going to sleep");
}
isInitialized = false;
delay(1000); // give time for any serial printing to finish
goToSleep();
if (SERIAL_EN) { Serial.println("Resetting sleep timer"); }
lastChange = millis(); // Reset sleep timer after wakeup
}
if (!isInitialized) {
delay(1000);
if (SERIAL_EN) {
Serial.println("Not yet initialized");
}
Wire.beginTransmission(I2C_ADDRESS); // Transmit to device
Wire.write(0x00); // Sends value byte
byte error = Wire.endTransmission(); // Stop transmitting
if (error != 0) {
if (SERIAL_EN) { diagnoseTransmissionError(error); }
return;
}
unsigned int expectedInitBytes = 7;
uint8_t values[expectedInitBytes];
unsigned int readCount = readFromSerial(values, expectedInitBytes);
if (SERIAL_EN && VERBOSE) { printByteArray(values, readCount); }
if (readCount == expectedInitBytes) {
if (SERIAL_EN) { Serial.println("Initialized"); }
isInitialized = true;
} else {
if (SERIAL_EN) { Serial.println("Wrong byte count read"); }
}
return;
}
if (SERIAL_EN && VERBOSE) {
delay(500); // read every N ms
} else {
delay(0.5); // The guitar leaves a 9ms gap between reads, but it seems like we can go lower
}
Wire.beginTransmission(0x0D); // Transmit to device
Wire.write(0x12); // Sends value byte to set the memory address we want to read from
byte error = Wire.endTransmission(); // Stop transmitting
if (error != 0) {
if (SERIAL_EN) { diagnoseTransmissionError(error); }
return;
}
/* Note: the real guitar body sets the memory address for the read to be `0x10` instead of
* `0x12`. Then, the real guitar body reads two bytes from the neck. These two bytes seem
* to always be identical and useless. sanjay900 suggested that we can instead set the
* memory address to be `0x12`, and skip the initial two byte read!
// This read of two bytes seems to always contain the same information. Not clear why it needs to happen.
unsigned int expectedInitialByteCount = 2;
uint8_t valuesInitial[expectedInitialByteCount];
unsigned int readCountInitial = readFromSerial(valuesInitial, expectedByteCountInitial);
if (SERIAL_EN && VERBOSE) { printByteArray(valuesInitial, readCountInitial); }
if (readCountInitial != expectedByteCountInitial) {
if (SERIAL_EN) { Serial.println("Wrong byte count read"); }
return;
}
*/
// The guitar will send up to 6 bytes in this response, but we only need the first
// one for the buttons and the second byte to decode the touchpad. The remaining
// bytes seems useless.
unsigned int expectedByteCount = 2;
uint8_t values[expectedByteCount];
unsigned int readCount = readFromSerial(values, expectedByteCount);
if (SERIAL_EN && VERBOSE) { printByteArray(values, readCount); }
if (readCount != expectedByteCount) {
if (SERIAL_EN) { Serial.println("Wrong byte count read"); }
return;
}
Buttons buttons = twoBytesToButton(values);
buttonsToDigitalOut(buttons);
if (buttons.green || buttons.red || buttons.yellow || buttons.blue || buttons.orange) {
lastChange = millis();
}
if (SERIAL_EN) {
loopCounter++;
// Only print out the button state periodically, so polling stays fast
if (loopCounter % 25 == 0) {
printButtons(buttons);
}
}
}
#include <Wire.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
// Toggles
#define SERIAL_EN false // Enable serial output
#define VERBOSE false // Verbose serial output
#define ENABLE_SLEEP true // Go to sleep eventually
#define SLEEP_TIMEOUT_MS 10000UL // Idle timeout before sleep
// Pin definitions
#define PIN_GREEN 9
#define PIN_RED 8
#define PIN_YELLOW 7
#define PIN_BLUE 6
#define PIN_ORANGE 5
#define PIN_I2C_POWER 4 // guitar neck only uses 3mA, we can source 40mA
#define PIN_WAKEUP 2
// Constants / structs
#define I2C_ADDRESS 0x0D
const char * hex = "0123456789ABCDEF";
typedef struct {
bool green;
bool red;
bool yellow;
bool blue;
bool orange;
} Buttons;
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
pinMode(PIN_GREEN, OUTPUT);
pinMode(PIN_RED, OUTPUT);
pinMode(PIN_YELLOW, OUTPUT);
pinMode(PIN_BLUE, OUTPUT);
pinMode(PIN_ORANGE, OUTPUT);
pinMode(PIN_I2C_POWER, OUTPUT);
digitalWrite(PIN_I2C_POWER, HIGH);
pinMode(PIN_WAKEUP, INPUT_PULLUP);
if (SERIAL_EN) {
Serial.begin(115200);
Serial.println("Setup complete");
}
}
void wakeIsr() {
detachInterrupt(digitalPinToInterrupt(PIN_WAKEUP));
digitalWrite(PIN_I2C_POWER, HIGH);
if (SERIAL_EN) { Serial.println("Waking up"); }
}
void goToSleep() {
// for (i = 2; i < 20; i++) { // all pins to one rail or the other - power saving
// pinMode(i,OUTPUT);
// digitalWrite(i,LOW);
// }
digitalWrite(PIN_I2C_POWER, LOW);
ADCSRA = 0; // disable ADC for power saving
wdt_disable(); // disable WDT for power saving
set_sleep_mode (SLEEP_MODE_PWR_DOWN); // Deep sleep
sleep_enable();
sleep_bod_disable(); // disable brownout detector during sleep
attachInterrupt(digitalPinToInterrupt(PIN_WAKEUP), wakeIsr, LOW); // wakeup on signal
sleep_cpu(); // now go to sleep
}
void diagnoseTransmissionError(byte code) {
// Docs for wire.endtransmission: https://docs.particle.io/cards/firmware/wire-i2c/endtransmission/
switch(code) {
case 0:
Serial.println("success");
break;
case 1:
Serial.println("busy timeout upon entering endTransmission()");
break;
case 2:
Serial.println("START bit generation timeout");
break;
case 3:
Serial.println("end of address transmission timeout");
break;
case 4:
Serial.println("data byte transfer timeout");
break;
case 5:
Serial.println("data byte transfer succeeded, busy timeout immediately after");
break;
case 6:
Serial.println("timeout waiting for peripheral to clear stop bit");
break;
default:
Serial.print("Unknown return from EndTransmission: ");
Serial.println(code);
}
}
unsigned int readFromSerial(uint8_t* arr, unsigned int expectedByteCount) {
unsigned int readCount = 0;
Wire.requestFrom(I2C_ADDRESS, expectedByteCount); // request N bytes from peripheral device
while (Wire.available() && (readCount < expectedByteCount)) { // peripheral may send less than requested
arr[readCount] = Wire.read(); // receive a byte as character
readCount++;
}
return readCount;
}
void printByteArray(uint8_t* arr, unsigned int len) {
Serial.print("Read ");
Serial.print(len);
Serial.print(" bytes:");
for(unsigned int i = 0; i < len; i++) {
Serial.print(" 0x");
Serial.print(hex[(arr[i]>>4) & 0xF]);
Serial.print(hex[arr[i] & 0xF]);
}
Serial.println("");
}
Buttons sixBytesToButton(uint8_t* arr) {
Buttons buttons = {.green = false, .red = false, .yellow = false, .blue = false, .orange = false};
char topButtons = arr[0];
if (topButtons & 0x10) {
buttons.green = true;
}
if (topButtons & 0x20) {
buttons.red = true;
}
if (topButtons & 0x80) {
buttons.yellow = true;
}
if (topButtons & 0x40) {
buttons.blue = true;
}
if (topButtons & 0x01) {
buttons.orange = true;
}
// The remaining array is 5 bytes long. It varies in a stable way
// when pressing on the touch pad.
// There's gotta be a pattern here, but I'm too tired to spot it...
// We can get away with just looking at the first byte because they're unique.
switch (arr[1]) {
case 0x00:
// no buttons
break;
case 0x95:
buttons.green = true; break;
case 0xCD:
buttons.red = true; break;
case 0x1A:
buttons.yellow = true; break;
case 0x49:
buttons.blue = true; break;
case 0x7F:
buttons.orange = true; break;
case 0xB0:
buttons.green = true; buttons.red = true; break;
case 0x19:
buttons.green = true; buttons.yellow = true; break;
case 0x47:
buttons.green = true; buttons.blue = true; break;
case 0x7B:
buttons.green = true; buttons.orange = true; break;
case 0xE6:
buttons.red = true; buttons.yellow = true; break;
case 0x48:
buttons.red = true; buttons.blue = true; break;
case 0x7D:
buttons.red = true; buttons.orange = true; break;
case 0x2F:
buttons.yellow = true; buttons.blue = true; break;
case 0x7E:
buttons.yellow = true; buttons.orange = true; break;
case 0x66:
buttons.blue = true; buttons.orange = true; break;
case 0x65:
buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
case 0x64:
buttons.red = true; buttons.blue = true; buttons.orange = true; break;
case 0x7C:
buttons.red = true; buttons.yellow = true; buttons.orange = true; break;
case 0x2E:
buttons.red = true; buttons.yellow = true; buttons.blue = true; break;
case 0x62:
buttons.green = true; buttons.blue = true; buttons.orange = true; break;
case 0x7A:
buttons.green = true; buttons.yellow = true; buttons.orange = true; break;
case 0x2D:
buttons.green = true; buttons.yellow = true; buttons.blue = true; break;
case 0x79:
buttons.green = true; buttons.red = true; buttons.orange = true; break;
case 0x46:
buttons.green = true; buttons.red = true; buttons.blue = true; break;
case 0xE5:
buttons.green = true; buttons.red = true; buttons.yellow = true; break;
case 0x63:
buttons.red = true; buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
case 0x61:
buttons.green = true; buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
case 0x60:
buttons.green = true; buttons.red = true; buttons.blue = true; buttons.orange = true; break;
case 0x78:
buttons.green = true; buttons.red = true; buttons.yellow = true; buttons.orange = true; break;
case 0x2C:
buttons.green = true; buttons.red = true; buttons.yellow = true; buttons.blue = true; break;
case 0x5F:
buttons.green = true; buttons.red = true; buttons.yellow = true; buttons.blue = true; buttons.orange = true; break;
default:
if (SERIAL_EN) {
Serial.print("Unrecognized pattern! ");
printByteArray(&arr[1], 1);
}
}
return buttons;
}
void printButtons(Buttons buttons) {
if (VERBOSE) { Serial.print("Green: "); }
Serial.print(buttons.green);
if (VERBOSE) { Serial.print(" Red: "); }
Serial.print(buttons.red);
if (VERBOSE) { Serial.print(" Yellow: "); }
Serial.print(buttons.yellow);
if (VERBOSE) { Serial.print(" Blue: "); }
Serial.print(buttons.blue);
if (VERBOSE) { Serial.print(" Orange: "); }
Serial.println(buttons.orange);
}
void buttonsToDigitalOut (Buttons buttons) {
if (buttons.green) {
digitalWrite(PIN_GREEN, HIGH);
} else {
digitalWrite(PIN_GREEN, LOW);
}
if (buttons.red) {
digitalWrite(PIN_RED, HIGH);
} else {
digitalWrite(PIN_RED, LOW);
}
if (buttons.yellow) {
digitalWrite(PIN_YELLOW, HIGH);
} else {
digitalWrite(PIN_YELLOW, LOW);
}
if (buttons.blue) {
digitalWrite(PIN_BLUE, HIGH);
} else {
digitalWrite(PIN_BLUE, LOW);
}
if (buttons.orange) {
digitalWrite(PIN_ORANGE, HIGH);
} else {
digitalWrite(PIN_ORANGE, LOW);
}
}
bool isInitialized = false;
unsigned long lastChange = 0;
void loop() {
if (ENABLE_SLEEP && (millis() - lastChange > SLEEP_TIMEOUT_MS)) {
if (SERIAL_EN) {
Serial.println("Going to sleep");
}
isInitialized = false;
delay(1000); // give time for any serial printing to finish
goToSleep();
if (SERIAL_EN) { Serial.println("Resetting sleep timer"); }
lastChange = millis(); // Reset sleep timer after wakeup
}
if (!isInitialized) {
delay(1000);
if (SERIAL_EN) {
Serial.println("Not yet initialized");
}
Wire.beginTransmission(I2C_ADDRESS); // Transmit to device
Wire.write(0x00); // Sends value byte
byte error = Wire.endTransmission(); // Stop transmitting
if (error != 0) {
if (SERIAL_EN) { diagnoseTransmissionError(error); }
return;
}
unsigned int expectedInitBytes = 7;
uint8_t values[expectedInitBytes];
unsigned int readCount = readFromSerial(values, expectedInitBytes);
if (SERIAL_EN && VERBOSE) { printByteArray(values, readCount); }
if (readCount == expectedInitBytes) {
if (SERIAL_EN) { Serial.println("Initialized"); }
isInitialized = true;
} else {
if (SERIAL_EN) { Serial.println("Wrong byte count read"); }
}
return;
}
if (SERIAL_EN && VERBOSE) {
delay(500); // read every N ms
} else {
delay(2); // The guitar leaves a 9ms gap between reads, but it seems like we can go lower
}
Wire.beginTransmission(0x0D); // Transmit to device
Wire.write(0x10); // Sends value byte
byte error = Wire.endTransmission(); // Stop transmitting
if (error != 0) {
if (SERIAL_EN) { diagnoseTransmissionError(error); }
return;
}
unsigned int expectedByteCount = 2;
uint8_t values[expectedByteCount];
unsigned int readCount = readFromSerial(values, expectedByteCount);
if (SERIAL_EN && VERBOSE) { printByteArray(values, readCount); }
if (readCount == expectedByteCount) {
// Serial.println("First read done");
} else {
if (SERIAL_EN) { Serial.println("Wrong byte count read"); }
return;
}
unsigned int expectedByteCount2 = 6;
uint8_t values2[expectedByteCount2];
unsigned int readCount2 = readFromSerial(values2, expectedByteCount2);
if (SERIAL_EN && VERBOSE) { printByteArray(values2, readCount2); }
if (readCount2 == expectedByteCount2) {
// Serial.println("Second read done");
} else {
if (SERIAL_EN) { Serial.println("Wrong byte count read"); }
return;
}
Buttons buttons = sixBytesToButton(values2);
buttonsToDigitalOut(buttons);
if (buttons.green || buttons.red || buttons.yellow || buttons.blue || buttons.orange) {
lastChange = millis();
}
if (SERIAL_EN) { printButtons(buttons); }
}

Worklog

2022-04-05

I open up the guitar to find that the neck connection on this one is 4 pins, unlike the previous guitar I've worked with which had 6 pins (5 buttons + ground). The previous guitar was an easy conversion: each button gets a digital input on the Arduino.

However, this guitar has pins labelled V, C, D, G. I2C is a common protocol with this number of lines: Vcc, SCL, SDA, and Ground. I attached my Saleae logic analyzer to the lines and powered up the guitar, hoping to sniff the communication, but all that happens were that the lines were driven high. I suspect I need to pair the controller to a receiver to get the microcontroller to activate the buttons, but I don't have the receiver (or the Playstation2 Console for the receiver). Time to get more creative.

First, let's check what voltage the guitar runs at. I plug in some batteries and poke at some of the VCCs internally to find they're 3.3V.

I get my Sparkfun Qwiic Pro Micro and set it up:

void setup()
{
    softwarei2c.begin(3, 2);       // sda, scl
    Serial.begin(9600);
    while (!Serial); // Wait for serial monitor
    Serial.println("---I2C Scanner---");
}

void loop() {
    for (unsigned char i = 1; i <= 127; i++) {
        if (softwarei2c.beginTransmission(i)) {
            Serial.print("0x");
            Serial.println(i, HEX);

            while (1);
        }
        softwarei2c.endTransmission();
    }
    Serial.println("find nothing");
    delay(5000);           // wait 5 seconds for next scan
}

Hoorah! The peripheral is on address 0xD!

However, trying to read from it only gets me 0xFF in reply...

void loop() {
    softwarei2c.requestFrom(13, 1);    // request bytes from peripheral device #13
    while (softwarei2c.available()) { // peripheral may send less than requested
        char c = softwarei2c.read(); // receive a byte as character
        Serial.println(c, HEX);         // print the character
    }
    delay(1000);           // wait N ms
}

Time to get a dongle and sniff the real controller, I guess...

2022-04-12

The dongle ($100 on Ebay) has arrived. I toy with the idea of just using the dongle, but each dongle only pairs to one guitar at a time, and I have two guitars.

I plug the dongle in and pair the guitar to it, and use the logic analyzer to read the lines -- and there's activity! The microcontroller in the guitar is attempting to write to 0xD. So I had the address right, but I was trying to read from it, and clearly there's some kind of setup necessary.

  • image of guitar guts connected to Saleae
  • screen cap of guitar coming to life when I plug in adapter (also detail view of write)

Ok, so I need to actually plug in the neck too. This is annoying because the internal guitar connection are 2mm DuPont connectors, rather than the standard 2.54mm. To keep the existing headers, we can use a Pitch Changer:

I get the neck plugged in, and we're off to the races

  • image of neck + guitar connected to analyzer
  • screen cap 3

As we can see in the screen cap, there's communication every 10ms. After clicking the buttons a few times, I find that the data is:

  • one initial write with data 0x00
  • a read (perhaps some data about the neck?)
  • a repetition every 10ms:
    • a write with data 0x10
    • a read of 2 bytes, with one changing byte then 0x01. This is maybe a cycling counter of some sort?
    • a read of 6 bytes. The first byte is bitmask representing which of the buttons is held. The remaining 5 bytes vary when touching the touchpad

The bitmask is [yellow, blue, red, green, n/a, n/a, n/a, orange].

I can't see the pattern for the remaining 5 bytes, but at least they're consistent....

Green pad: 0x95 0x94 0x10 0x04 0x15 Red pad: 0xCD 0xCC 0x08 0x0A 0x4D Green+red: 0xB0 0xAF 0x18 0x06 0x30 Yellow pad:0x1A 0x1A 0x04 0x12 0x9A ...

2022-04-13

I write an Arduino sketch to communicate with the neck. For the 5 bytes that change with pad input, I can't spot a pattern, so I manually go through each possible pattern and record the byte values in the program in a giant if/else. There's only 31 possibilities (32 counting all-open), so it's not too bad, and we actually only need to consider the first of those 5 bytes.

  • photo of setup
  • video of setup

Next steps: transfer this to a Arduino Pro Mini, then finish the conversion in the guitar.

2022-04-14

I transferred the code to the Pro Mini and set it up for low power consumption. I'm powering the neck off a digital output pin, so that I can turn it off entirely during sleep. And I'm listening on an interrupt pin for signals to power up / down from the other microcontroller. After removing the power LED, the sleep current is only 0.7mA, pretty good! There's definitely more gains to be eked out (e.g. like this blog post), but that's good enough for tonight. Now to get it wired up!

TODO: maybe just sleep after a delay (10m is used in guitart configurator), instead. Not sure if I can control a pin from the other uC, we'll see.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment