Browse Source

Initial commit

master
Yessiest 2 years ago
commit
d6daf1194d
  1. 3
      .gitignore
  2. 7
      LICENSE
  3. 18
      Makefile
  4. 28
      README.md
  5. 61
      controller_firmware.ino
  6. 35
      sega_pinout.txt
  7. 207
      uinput-sega-genesis.c

3
.gitignore

@ -0,0 +1,3 @@
*.out
driver
driver_kb

7
LICENSE

@ -0,0 +1,7 @@
Copyright © 2022 Yessiest
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.

18
Makefile

@ -0,0 +1,18 @@
CC=gcc
OUTPUT=driver
OUTPUT_KB=driver_kb
CONTROLLER=-DCONTROLLER_MODE=1
DEBUG=-DDEBUG_MODE=1
all: controller keyboard
controller:
${CC} ${CONTROLLER} uinput-sega-genesis.c -o ${OUTPUT} ${CFLAGS}
keyboard:
${CC} uinput-sega-genesis.c -o ${OUTPUT_KB} ${CFLAGS}
debug:
${CC} ${DEBUG} uinput-sega-genesis.c -o ${OUTPUT} ${CFLAGS}
clean:
${RM} driver driver_kb

28
README.md

@ -0,0 +1,28 @@
# genesis2pc - connect your genesis controller to a linux box
This small project was one of the silly ideas I once had about connecting one of the fake sega genesis controllers I had to a Raspberry Pi. Obviously there were easier ways to do this, but here we are.
# How to run the project
You will need a sega genesis controller, an Arduino Nano/UNO chip, a linux kernel with the uinput module, and a way to flash the .ino file to the arduino (by whichever means you prefer)
The process is done in the following order:
1. Connect the pins of the controllers port to the pins on the board, as specified in sega\_pinout.txt
2. Flash the controller\_firmware.ino file to the board
3. Compile the driver part (simply run `make`)
4. Connect the arduino board to your PC via a usb cable
5. Run the driver as root and specify the serial device for your arduino
There are 2 variations of the driver portion - `driver` and `driver_kb`. The difference is that `driver` attempts to simulate an actual joystick, while `driver_kb` maps to keyboard buttons.
# Credits
Big thanks to [tldp.org](https://tldp.org/HOWTO/Serial-Programming-HOWTO) for the Serial Programming manual, thanks to the Arduino community for documenting the specifics of the communication between a genesis controller and the console, and to the people behind the uinput module for the linux kernel.
# License
```
Copyright © 2022 Yessiest
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.
```

61
controller_firmware.ino

@ -0,0 +1,61 @@
#define SELECT 8
#define IN_SC 7
#define IN_AB 6
#define IN_LEFT 5
#define IN_RIGHT 4
#define IN_UP 3
#define IN_DOWN 2
#define STATUS 13
#define GETSTATE(pin,bitpos,buttons) digitalRead(pin) ? (buttons | (1<<bitpos) ) : (buttons & (buttons ^ (1<<bitpos)))
byte buttons = 0;
byte buttons_prev = 0;
byte buttons_other = 240;
byte buttons_other_prev = 0;
bool rmode = 1;
long int count = 0;
void setup() {
Serial.begin(9600);
digitalWrite(SELECT,HIGH);
pinMode(SELECT, OUTPUT);
pinMode(IN_SC, INPUT);
pinMode(IN_AB, INPUT);
pinMode(IN_LEFT, INPUT);
pinMode(IN_RIGHT, INPUT);
pinMode(IN_UP, INPUT);
pinMode(IN_DOWN, INPUT);
pinMode(STATUS, OUTPUT);
}
void loop() {
rmode != rmode;
count++;
digitalWrite(SELECT,HIGH); //Pulse 1
delay(5);
buttons = GETSTATE(IN_AB,2,buttons); //B
buttons = GETSTATE(IN_SC,3,buttons); //C
buttons = GETSTATE(IN_LEFT,4,buttons); //Left
buttons = GETSTATE(IN_RIGHT,5,buttons); //Right
buttons = GETSTATE(IN_DOWN,6,buttons); //Down
buttons = GETSTATE(IN_UP,7,buttons); //Up
digitalWrite(SELECT,LOW);
buttons = GETSTATE(IN_SC,0,buttons); //Start
buttons = GETSTATE(IN_AB,1,buttons); //A
digitalWrite(SELECT,HIGH); //Pulse 2
digitalWrite(SELECT,LOW);
digitalWrite(SELECT,HIGH); //Pulse 3
digitalWrite(SELECT,LOW); //Pulse 4 (XYZ)
digitalWrite(SELECT,HIGH); //XYZ Mode
buttons_other = GETSTATE(IN_RIGHT,0,buttons_other); //Mode
buttons_other = GETSTATE(IN_LEFT,1,buttons_other); //X
buttons_other = GETSTATE(IN_DOWN,2,buttons_other); //Y
buttons_other = GETSTATE(IN_UP,3,buttons_other); //Z
digitalWrite(SELECT,LOW); //snussy mode
//output only on change
if (rmode && ((buttons_prev != buttons) || (buttons_other_prev != buttons_other))) {
Serial.write(buttons);
Serial.write(buttons_other);
//Serial.println(buttons | buttons_other<<8,BIN);
//Serial.println(,BIN);
}
buttons_other_prev = buttons_other;
buttons_prev = buttons;
}

35
sega_pinout.txt

@ -0,0 +1,35 @@
This file describes how controller wires map to arduino pins
Format is: <Arduino pin> - <controller wire>
GND - gnd
5V - vdd (+5V)
2 - down
3 - up
4 - right
5 - left
6 - A (B in SELECT +5V)
7 - Start (C in SELECT +5V)
8 - select (NOT to be confused with the select/mode button)
The female connector for sega genesis should roughly map out like this
___________________
\ /
\ 5 4 3 2 1 /
\ 9 8 7 6 /
\_____________/
1 - Up
2 - Down
3 - Left
4 - Right
5 - vdd (+5V)
6 - A (B in SELECT +5V)
7 - select
8 - GND
9 - Start (C in Select +5V)
P.S: The select pin on the controller doesn't manage the select button - it manages the set of buttons to be read by the controller. Be sure to check that it's actually connected to the arduino board.
A similar project with more details on how the controller works:
https://www.raspberryfield.life/2019/03/25/sega-mega-drive-genesis-6-button-xyz-controller/

207
uinput-sega-genesis.c

@ -0,0 +1,207 @@
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/uinput.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <termios.h>
//List of button definitons and whatnot
#define START 0x01
#define WEST 0x02
#define EAST 0x08
#define SOUTH 0x04
#define DPAD_LEFT 0x10
#define DPAD_RIGHT 0x20
#define DPAD_DOWN 0x40
#define DPAD_UP 0x80
#define SELECT 0x100
#define LEFT_TRIGGER 0x200
#define NORTH 0x400
#define RIGHT_TRIGGER 0x800
//#define DEBUG_MODE 0x1337
//#define CONTROLLER_MODE 0x420
//A list that will hold the button offsets in a sequential order
int buttons_list[] = {
START,WEST,EAST,SOUTH, // 0 - 3
DPAD_LEFT,DPAD_RIGHT,DPAD_DOWN,DPAD_UP, // 4 - 7
SELECT,LEFT_TRIGGER,NORTH,RIGHT_TRIGGER // 8 - 11
};
#ifdef DEBUG_MODE
//1-letter names for buttons
char debug_names[] = "TWESLRDUMZNC";
#endif
//Actual mappings for the buttons, in the same order as in buttons_list
#ifdef CONTROLLER_MODE
//Mappings that are binding the controller as a joystick instead of a keyboard
int mappings[] = {
BTN_START,BTN_WEST,BTN_EAST,BTN_SOUTH,
BTN_DPAD_LEFT,BTN_DPAD_RIGHT,BTN_DPAD_DOWN,BTN_DPAD_UP,
BTN_SELECT,BTN_TL,BTN_NORTH,BTN_TR
};
#else
//Mappings that correspond to a particular subset of keyboard keys
int mappings[] = {
KEY_ESC,KEY_A,KEY_D,KEY_S,
KEY_LEFT,KEY_RIGHT,KEY_DOWN,KEY_UP,
KEY_X,KEY_Z,KEY_W,KEY_C
};
#endif
int input;
int uinput_fd;
//a bit of stolen code to make things work
void emit(int fd, int type, int code, int val) {
struct input_event ie;
ie.type = type;
ie.code = code;
ie.value = val;
/* timestamp values below are ignored */
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
write(fd, &ie, sizeof(ie));
}
void bye(int signum) {
//wait some more just because we want all the events that are happening to happen before we close everything.
sleep(1);
printf("Exit signal received - destroying the uinput device.\n");
close(input);
ioctl(uinput_fd, UI_DEV_DESTROY);
sleep(1);
close(uinput_fd);
exit(0);
};
int set_interface_attribs(int fd, int speed, int parity) {
struct termios tty;
if (tcgetattr(fd, &tty) != 0) {
printf("Error %d from tcget",errno);
return -1;
}
cfsetospeed(&tty, speed);
cfsetispeed(&tty, speed);
tty.c_lflag = 0;
tty.c_cflag |= CRTSCTS | CS8 | CLOCAL | CREAD;
tty.c_iflag = IGNPAR;
tty.c_oflag = 0;
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 0;
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error %d when setting up tty", errno);
return -1;
}
return 0;
}
int set_blocking(int fd, int should_block) {
struct termios tty;
memset (&tty, 0, sizeof(tty));
if(tcgetattr(fd, &tty) != 0) {
printf("Error %d from tcget", errno);
return -1;
}
tty.c_cc[VMIN] = should_block ? 1 : 0;
tty.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error %d from tcset", errno);
return -1;
}
return 0;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: driver <input device>\n");
return 1;
}
//Open tty device for reading
input = open(argv[1], O_RDWR | O_NOCTTY | O_SYNC);
if (!input) {
printf("Unable to open device %s for reading: %s\n",argv[1],strerror(errno));
return errno;
}
set_interface_attribs(input, B9600, 0);
set_blocking(input, 1);
//Create uinput configuration struct and open /dev/uinput
struct uinput_setup usetup;
uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
//Add this to gracefully close uinput_fd and input
signal(SIGINT, bye);
ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY);
for (int k = 0; k < 12; k++) {
ioctl(uinput_fd, UI_SET_KEYBIT, mappings[k]);
}
//Clear struct, set vendor and product.
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x041e;
usetup.id.product = 0x1003;
strcpy(usetup.name,"Macrohard YBox 2pi LPT2USB controller");
//Call setup
if (ioctl(uinput_fd, UI_DEV_SETUP, &usetup)) {
perror("UI_DEV_SETUP");
printf("Note: This might have been the result of not running the program as root\n");
return 1;
}
if (ioctl(uinput_fd, UI_DEV_CREATE)) {
perror("UI_DEV_CREATE");
return 1;
}
//Wait for some time so that uinput has time to setup stuff
sleep(1);
short unsigned int buffer[2];
short unsigned int prev_state = 0x0;
short unsigned int changes = 0x0;
short unsigned int current_state = 0x0;
//Main loop
while(1) {
read(input, buffer, 2);
current_state = ~buffer[0];
changes = prev_state ^ current_state;
if (changes != 0) {
#ifdef DEBUG_MODE
printf("State hash: %u\n",current_state);
printf("Recieved message: %u\n",buffer[0]);
#endif
for (int k = 0; k < 12; k++) {
if (changes & buttons_list[k]) {
emit(uinput_fd, EV_KEY, mappings[k], (current_state & buttons_list[k]) > 0);
emit(uinput_fd, EV_SYN, SYN_REPORT, 0);
#ifdef DEBUG_MODE
printf("Internal key name: %c\n",debug_names[k]);
printf("Key state: %u\n",(current_state & buttons_list[k]) > 0);
printf("Key mapping: %u\n",mappings[k]);
#endif
}
}
#ifdef DEBUG_MODE
printf("-------\n");
fflush(stdout);
#endif
}
prev_state = current_state;
}
//haha what a loser can't even get here because while true do loop
return 0;
}
Loading…
Cancel
Save