Guitar controller on a Pro Micro
Five fret buttons, a two-switch strum bar, a whammy knob and a tilt switch for star power, sent as a gamepad report through Joystick.h. The whole build, a SparkFun Pro Micro and 10 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate Runs in your browser. Free, and no account needed.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Rhythm game guitar controller on a Pro Micro.
//
// Five fret buttons, a strum bar, a whammy bar, a start button and a tilt
// switch for star power: the inputs a DIY or rebuilt guitar for games like
// Clone Hero has. The board is a USB gamepad through the Arduino Joystick
// library's API (Joystick.h).
//
// Wiring. Every switch has one leg on GND and the other on a pin with the
// internal pull-up on, so a pressed switch reads LOW and needs no resistor.
//
// frets green 2 red 3 yellow 4 blue 5 orange 6
// strum up 7 down 8 (two microswitches under one bar)
// start 9 tilt 10 whammy: a 10k pot, wiper on A0
//
// In Mokxi: click and hold the arcade buttons for the frets (the white one is
// orange), click the two lever switches to strum, drag the knob for the
// whammy and click the tilt switch to tip it over, which is the neck going up.
//
// What the Serial Monitor shows. Each strum prints the chord it hit:
//
// Strum down: G R . . .
//
// and each change of the whole report prints a [Joystick] line, which is what
// the simulator shows in place of the USB report it would send.
//
// Not simulated: the USB connection to a PC or console. Mokxi models this
// chip's USB as a serial pipe, so the reports become those [Joystick] lines.
// Flash the same sketch to a real Pro Micro and it enumerates as a gamepad.
// Which game accepts it, and how you map the buttons there, is set up on the
// PC.
#include <Joystick.h>
// ---------------------------------------------------------------------------
// Make it yours
// ---------------------------------------------------------------------------
// Pins, in the order green, red, yellow, blue, orange.
const uint8_t FRET_PINS[5] = {2, 3, 4, 5, 6};
const uint8_t STRUM_UP_PIN = 7;
const uint8_t STRUM_DOWN_PIN = 8;
const uint8_t START_PIN = 9;
const uint8_t TILT_PIN = 10;
const uint8_t WHAMMY_PIN = A0;
// A left-handed player turns the guitar over, so up and down swap.
const bool LEFTY = false;
// A ball tilt switch is closed while it stands upright. Mount it so it is
// upright while the guitar is level, and it opens when the neck goes up:
// true means open is star power. Set false for a switch that closes on tilt.
const bool TILT_OPENS_FOR_STAR_POWER = true;
// A strum switch ignores its pin for this long after a change is accepted.
// Microswitches bounce; the lockout throws the bounce away without delaying
// the strum. Raise it if one strum ever prints twice.
const unsigned long STRUM_LOCK_MS = 15;
const unsigned long FRET_LOCK_MS = 5;
// A tactile start button chatters for longer, and a tilt switch's ball rolls
// and rattles for tens of milliseconds. Neither needs to be fast.
const unsigned long START_LOCK_MS = 30;
const unsigned long TILT_LOCK_MS = 80;
// The whammy pot's reading with the bar at rest and fully pushed. Turn on
// WHAMMY_DEBUG, read both off the Serial Monitor and type them in here.
const int WHAMMY_REST = 0;
const int WHAMMY_FULL = 1023;
// Readings this close to rest count as rest, so a worn pot does not wobble.
const int WHAMMY_DEADZONE = 20;
const bool WHAMMY_DEBUG = false;
// ---------------------------------------------------------------------------
// The inputs in one list: five frets, strum up, strum down, start, tilt.
const uint8_t INPUTS = 9;
enum { FRET0 = 0, STRUM_UP = 5, STRUM_DOWN = 6, START = 7, TILT = 8 };
uint8_t pins[INPUTS];
bool held[INPUTS];
unsigned long changedAt[INPUTS];
const char FRET_NAMES[5] = {'G', 'R', 'Y', 'B', 'O'};
// A gamepad with 7 buttons (five frets, start, star power), one hat switch
// for the strum bar, as console guitars send it, and the Rx axis for the
// whammy.
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 7, 1,
false, false, false, true, false, false,
false, false, false, false, false);
int lastWhammy = -1;
int whammy() {
int raw = analogRead(WHAMMY_PIN);
if (WHAMMY_DEBUG) {
Serial.print("whammy raw ");
Serial.println(raw);
}
long span = (long)WHAMMY_FULL - WHAMMY_REST;
long pushed = (long)raw - WHAMMY_REST;
if (span < 0) { // a pot wired the other way around
span = -span;
pushed = -pushed;
}
if (pushed < WHAMMY_DEADZONE) {
return 0;
}
long out = (pushed - WHAMMY_DEADZONE) * 1023 / (span - WHAMMY_DEADZONE);
return out > 1023 ? 1023 : (int)out;
}
// True when input i changed this time round.
bool readInput(uint8_t i, unsigned long now) {
bool down = digitalRead(pins[i]) == LOW;
if (i == TILT && TILT_OPENS_FOR_STAR_POWER) {
down = !down; // the neck is up when the switch has opened
}
if (down == held[i]) {
return false;
}
unsigned long lock = FRET_LOCK_MS;
if (i == STRUM_UP || i == STRUM_DOWN) lock = STRUM_LOCK_MS;
if (i == START) lock = START_LOCK_MS;
if (i == TILT) lock = TILT_LOCK_MS;
if (now - changedAt[i] < lock) {
return false; // inside the lockout: this edge is contact bounce
}
held[i] = down;
changedAt[i] = now;
return true;
}
void printStrum(bool up) {
Serial.print(up ? "Strum up: " : "Strum down: ");
for (uint8_t f = 0; f < 5; f++) {
Serial.print(held[FRET0 + f] ? FRET_NAMES[f] : '.');
Serial.print(' ');
}
Serial.println();
}
void setup() {
for (uint8_t f = 0; f < 5; f++) {
pins[FRET0 + f] = FRET_PINS[f];
}
pins[STRUM_UP] = LEFTY ? STRUM_DOWN_PIN : STRUM_UP_PIN;
pins[STRUM_DOWN] = LEFTY ? STRUM_UP_PIN : STRUM_DOWN_PIN;
pins[START] = START_PIN;
pins[TILT] = TILT_PIN;
for (uint8_t i = 0; i < INPUTS; i++) {
pinMode(pins[i], INPUT_PULLUP);
}
Serial.begin(115200);
Joystick.setRxAxisRange(0, 1023);
Joystick.begin(false); // one report per pass, sent by sendState()
Serial.println("Guitar controller ready: hold frets, then strum.");
Joystick.sendState();
}
void loop() {
unsigned long now = millis();
bool changed = false;
for (uint8_t i = 0; i < INPUTS; i++) {
if (readInput(i, now)) {
changed = true;
if (held[i] && i == STRUM_UP) printStrum(true);
if (held[i] && i == STRUM_DOWN) printStrum(false);
if (i == TILT) Serial.println(held[i] ? "Star power!" : "Neck down");
}
}
for (uint8_t f = 0; f < 5; f++) {
Joystick.setButton(f, held[FRET0 + f]);
}
Joystick.setButton(5, held[START]);
Joystick.setButton(6, held[TILT]);
// The strum bar as the hat: up is 0 degrees, down 180, neither released.
int hat = JOYSTICK_HATSWITCH_RELEASE;
if (held[STRUM_UP]) hat = 0;
if (held[STRUM_DOWN]) hat = 180;
Joystick.setHatSwitch(0, hat);
// Report the whammy when it has moved a little, not on every count.
int w = whammy();
if (lastWhammy < 0 || w > lastWhammy + 8 || w < lastWhammy - 8 || (w == 0 && lastWhammy != 0)) {
lastWhammy = w;
changed = true;
}
Joystick.setRxAxis(lastWhammy);
if (changed) {
Joystick.sendState();
}
}
Parts list
12 parts, plus the jumper wires. Every one is in the editor's parts bin.
How it is wired
13 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Ground: SparkFun Pro Micro pin GND; Limit switch, lever microswitch (1) pin COM; Limit switch, lever microswitch (2) pin COM; Pushbutton pin 1a; Arcade button (1) pin 1; Arcade button (2) pin 1; Arcade button (3) pin 1; Arcade button (4) pin 1; Arcade button (5) pin 1
- SparkFun Pro Micro pin 2; Arcade button (1) pin 2
- SparkFun Pro Micro pin 3; Arcade button (2) pin 2
- SparkFun Pro Micro pin 4; Arcade button (3) pin 2
- SparkFun Pro Micro pin 5; Arcade button (4) pin 2
- SparkFun Pro Micro pin 6; Arcade button (5) pin 2
- SparkFun Pro Micro pin 7; Limit switch, lever microswitch (1) pin NO
- SparkFun Pro Micro pin 8; Limit switch, lever microswitch (2) pin NO
- SparkFun Pro Micro pin 9; Pushbutton pin 2a
- Ground: SparkFun Pro Micro pin GND; Potentiometer pin 1; Tilt switch pin 2
- SparkFun Pro Micro pin VCC; Potentiometer pin 3
- SparkFun Pro Micro pin A0; Potentiometer pin 2
- SparkFun Pro Micro pin 10; Tilt switch pin 1
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.