Leverless SOCD cleaning on a SparkFun Pro Micro
Four direction buttons that can be held two at a time, cleaned by the neutral or last-input rule a slide switch picks, with the cleaned direction on four LEDs. 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.
// Leverless SOCD: four direction buttons, cleaned the way controller firmware
// cleans them.
//
// On a leverless (all-button) controller the four directions are separate
// buttons, so nothing stops a player holding left and right, or up and down,
// at the same time. That is a Simultaneous Opposing Cardinal Direction, SOCD
// for short, and a game has no sensible way to read it. So the firmware
// decides what the pair means before it reports anything. This sketch does
// the same with two of the common rules:
//
// NEUTRAL left + right is neither, up + down is neither
// LAST WIN of two opposed directions, the one pressed last wins
//
// Set UP_PRIORITY to true and up + down gives up in NEUTRAL mode, which is the
// other rule leverless firmware commonly offers.
//
// 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.
//
// UP 2 DOWN 3 LEFT 4 RIGHT 5 button A 6 button B 7
// mode switch pole on 8: slid toward GND is NEUTRAL, the other way LAST WIN
// LEDs showing the cleaned direction: up A3, down A2, left A1, right A0
//
// In Mokxi the four buttons are the console pad with its rocker turned off,
// so they act as four separate switches. The arrow keys press them, and you
// can hold two opposite ones at once. Z and X are A and B.
//
// The Serial Monitor shows what was held and what the rule made of it. On a
// real Pro Micro the cleaned direction is what you would hand to a HID
// library; Mokxi models this chip's USB as a serial pipe only, so the report
// goes to the Serial Monitor instead.
const bool UP_PRIORITY = false;
const uint8_t UP_PIN = 2;
const uint8_t DOWN_PIN = 3;
const uint8_t LEFT_PIN = 4;
const uint8_t RIGHT_PIN = 5;
const uint8_t A_PIN = 6;
const uint8_t B_PIN = 7;
const uint8_t MODE_PIN = 8;
const uint8_t LED_UP = A3;
const uint8_t LED_DOWN = A2;
const uint8_t LED_LEFT = A1;
const uint8_t LED_RIGHT = A0;
// For LAST WIN: which of each pair was pressed most recently, -1 or +1.
int lastX = 0;
int lastY = 0;
bool wasLeft = false;
bool wasRight = false;
bool wasUp = false;
bool wasDown = false;
// What was printed last, so a line only goes out when something changed.
uint16_t lastState = 0xFFFF;
bool pressed(uint8_t pin) {
return digitalRead(pin) == LOW;
}
// One axis. `last` is the direction of the pair pressed most recently.
int clean(bool negative, bool positive, int last, bool lastWin) {
if (negative && positive) {
return lastWin ? last : 0;
}
return (positive ? 1 : 0) - (negative ? 1 : 0);
}
void setup() {
const uint8_t inputs[] = {UP_PIN, DOWN_PIN, LEFT_PIN, RIGHT_PIN, A_PIN, B_PIN, MODE_PIN};
for (uint8_t i = 0; i < sizeof(inputs); i++) {
pinMode(inputs[i], INPUT_PULLUP);
}
const uint8_t leds[] = {LED_UP, LED_DOWN, LED_LEFT, LED_RIGHT};
for (uint8_t i = 0; i < sizeof(leds); i++) {
pinMode(leds[i], OUTPUT);
}
Serial.begin(115200);
Serial.println("Leverless SOCD: hold two opposite arrow keys and watch the rule.");
Serial.println("dir is numpad notation: 7 8 9 / 4 5 6 / 1 2 3, 5 is neutral.");
}
void loop() {
bool up = pressed(UP_PIN);
bool down = pressed(DOWN_PIN);
bool left = pressed(LEFT_PIN);
bool right = pressed(RIGHT_PIN);
bool a = pressed(A_PIN);
bool b = pressed(B_PIN);
bool lastWin = !pressed(MODE_PIN);
// Remember the newest press on each axis, from the moment it went down.
if (left && !wasLeft) lastX = -1;
if (right && !wasRight) lastX = 1;
if (down && !wasDown) lastY = -1;
if (up && !wasUp) lastY = 1;
wasLeft = left;
wasRight = right;
wasUp = up;
wasDown = down;
int x = clean(left, right, lastX, lastWin);
int y = clean(down, up, lastY, lastWin);
if (!lastWin && UP_PRIORITY && up && down) {
y = 1;
}
int direction = 5 + x + 3 * y;
digitalWrite(LED_UP, y > 0 ? HIGH : LOW);
digitalWrite(LED_DOWN, y < 0 ? HIGH : LOW);
digitalWrite(LED_LEFT, x < 0 ? HIGH : LOW);
digitalWrite(LED_RIGHT, x > 0 ? HIGH : LOW);
uint16_t state = (uint16_t)((up ? 1 : 0) | (down ? 2 : 0) | (left ? 4 : 0) | (right ? 8 : 0) |
(a ? 16 : 0) | (b ? 32 : 0) | (lastWin ? 64 : 0) |
((uint16_t)direction << 8));
if (state == lastState) {
return;
}
lastState = state;
Serial.print(lastWin ? "[last win] " : "[neutral] ");
Serial.print("held");
if (!(up || down || left || right)) Serial.print(" -");
if (up) Serial.print(" U");
if (down) Serial.print(" D");
if (left) Serial.print(" L");
if (right) Serial.print(" R");
Serial.print(" -> dir ");
Serial.print(direction);
if (a) Serial.print(" A");
if (b) Serial.print(" B");
if ((left && right) || (up && down)) Serial.print(" (SOCD cleaned)");
Serial.println();
}
Parts list
12 parts, plus the jumper wires. Every one is in the editor's parts bin.
- 1 × SparkFun Pro Micro
- 1 × Full-size breadboard
- 1 × Game console buttons
- 1 × Slide switch
- 2 × LED, green
- 4 × Resistor, 220 Ω
- 2 × LED, blue
How it is wired
16 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; Game console buttons pin GND; Slide switch pin 1; Resistor, 220 Ω (1) pin 2; Resistor, 220 Ω (2) pin 2; Resistor, 220 Ω (3) pin 2; Resistor, 220 Ω (4) pin 2
- SparkFun Pro Micro pin 2; Game console buttons pin UP
- SparkFun Pro Micro pin 3; Game console buttons pin DOWN
- SparkFun Pro Micro pin 4; Game console buttons pin LEFT
- SparkFun Pro Micro pin 5; Game console buttons pin RIGHT
- SparkFun Pro Micro pin 6; Game console buttons pin A
- SparkFun Pro Micro pin 7; Game console buttons pin B
- SparkFun Pro Micro pin 8; Slide switch pin 2
- SparkFun Pro Micro pin A3; LED, green (1) pin A
- SparkFun Pro Micro pin A2; LED, green (2) pin A
- SparkFun Pro Micro pin A1; LED, blue (1) pin A
- SparkFun Pro Micro pin A0; LED, blue (2) pin A
- LED, green (1) pin C; Resistor, 220 Ω (1) pin 1
- LED, green (2) pin C; Resistor, 220 Ω (2) pin 1
- LED, blue (1) pin C; Resistor, 220 Ω (3) pin 1
- LED, blue (2) pin C; Resistor, 220 Ω (4) 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.