Initial commit
This commit is contained in:
commit
d6daf1194d
|
@ -0,0 +1,3 @@
|
|||
*.out
|
||||
driver
|
||||
driver_kb
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
||||
```
|
|
@ -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;
|
||||
}
|
|
@ -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/
|
|
@ -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…
Reference in New Issue