// Read from USB joystick and buttons, send to Vectrex. // This was originally usbjoy.cpp but has been cleaned up to remove any dependency on C++. // This is very preliminary software, although it will output data from // a joystick such as the Microsoft Sidewinder Pro to both Vectrex joystick // ports, it has not yet been customised to handle two separate joystick // devices simultaneously. (The sidewinder has > 8 buttons and both an // analogue stick and a digital thumbstick, so can behave as if it were // two independent Vectrex controllers) // gcc -I. -o usbjoy usbjoy.c -lwiringPi `sdl2-config --cflags --libs` -lm // To do: https://gist.github.com/uobikiemukot/457338b890e96babf60b - get the extra mouse parameters // To do: request exclusive access when using mouse, so it is not also active // on the desktop. See https://forums.raspberrypi.com/viewtopic.php?t=213801 // for details. May need to use different mouse device than currently. // Need to only open mouse when no joystick, and close again when joystick // replaced - otherwise won't ever be able to use desktop because mouse will // always be suppressed. // WII: although it would be possible to build the Wii software into this // program, it will be cleaner to make it a joystick device in its own // right - see vec2linux.c and wiitest.c for code that could be modified // to implement that with. #include "wiringPi.h" typedef int byte; #include <errno.h> #include <fcntl.h> #include <linux/joystick.h> #include <math.h> #include <stdio.h> #include <stdlib.h> // for exit() #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> // usleep static int CS_LEFT, CS_RIGHT, CLK, MOSI; static void transferSPI(byte data) { byte i; for (i=1; i<=8; i++) { digitalWrite(MOSI, (data >> (8-i)) & 1 ? HIGH : LOW); digitalWrite(CLK, HIGH); // There is no delay here unless digitalWrite has an implicit delay? digitalWrite(CLK, LOW); // In any case, it works, for whatever reason. } } // Sets the given pot (1 or 2) on the given controller (left or right) to a given value (0-255) void setPot(byte chip, byte pot, byte value) { digitalWrite(chip, LOW); transferSPI(pot); transferSPI(value); digitalWrite(chip, HIGH); } //--------- // This program uses the joystick hotplug monitor which detects when joystick // devices are added or removed: /* BSD Zero Clause License Copyright (c) 2022 Rupert Carmichael Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //--------- int open_joystick(const char *devname) { if (devname == NULL) return -1; return open(devname, O_RDONLY | O_NONBLOCK); } static int mouse_fd, mouse_bytes; static signed char mouse_data[3]; static int mouse_attached = 0; void open_mouse(void) { const char *mouse_device = "/dev/input/mice"; // Open Mouse: mouse_fd = open(mouse_device, O_RDWR /* | O_RDONLY */ | O_NONBLOCK); if (mouse_fd == -1) { mouse_attached = 0; printf("ERROR Opening %s - mouse not available.\n", mouse_device); } else mouse_attached = 1; } int axes = 0, buttons = 0; void print_device_info(int fd) { char name[128]; ioctl(fd, JSIOCGAXES, &axes); ioctl(fd, JSIOCGBUTTONS, &buttons); ioctl(fd, JSIOCGNAME(sizeof(name)), &name); printf("Your joystick is a \"%s\"\nIt supports %d Axes and %d Buttons\n", name, axes, buttons); // Not that I use these yet :-/ } const char *Pressed[2] = {"released", "pressed"}; static int LeftJoyX = 0, LeftJoyY = 0, RightJoyX = 0, RightJoyY = 0, LinButtons = 0; #define ONE_SECOND 1000000 // 13 is a pwm pin which we'll reserve for generating sound later. const int gpio_a[4] = {17, 27, 22, 23}; // Vectrex buttons 1 2 3 4 on secondary controller const int gpio_b[4] = {25, 5, 6, 16}; // Vectrex buttons 1 2 3 4 on primary controller #define PRESSED 1 #define RELEASED 0 void cleanup(void) { int i; for (i = 0; i <= 3; i++) { digitalWrite(gpio_a[i], RELEASED); digitalWrite(gpio_b[i], RELEASED); } } void process_event(struct js_event jse) { if (jse.type == 2) { //printf("Axis %d Value %d\n", jse.number, jse.value); if (jse.number == 0) RightJoyX = jse.value >> 8; else if (jse.number == 1) RightJoyY = jse.value >> 8; else if (jse.number == 4) LeftJoyY = jse.value >> 8; else if (jse.number == 5) LeftJoyX = jse.value >> 8; } else if (jse.type == 1) { //printf("Button %d %s\n", jse.number, Pressed[jse.value]); if (jse.value == 0) { if ((0 <= jse.number) && (jse.number <= 3)) { LinButtons &= ~(1 << jse.number); digitalWrite(gpio_b[jse.number], RELEASED); } else if ((4 <= jse.number) && (jse.number <= 7)) { LinButtons &= ~(1 << jse.number); digitalWrite(gpio_a[jse.number-4], RELEASED); } } else if (jse.value == 1) { if ((0 <= jse.number) && (jse.number <= 3)) { LinButtons |= 1 << jse.number; digitalWrite(gpio_b[jse.number], PRESSED); } else if ((4 <= jse.number) && (jse.number <= 7)) { LinButtons |= 1 << jse.number; digitalWrite(gpio_a[jse.number-4], PRESSED); } } else { } } else if (jse.type == 130) { printf("Axis %d Supported\n", jse.number); } else if (jse.type == 129) { printf("Button %d Supported\n", jse.number); } else { printf("Type=%d Value=%d\n", jse.type, jse.number); } } #include <SDL.h> #define MAXPORTS 16 static SDL_Joystick *joystick[MAXPORTS]; static SDL_Haptic *haptic[MAXPORTS]; static int jsports[MAXPORTS]; static int jsiid[MAXPORTS]; #ifdef NEVER typedef short int fp14; // 16 bits on Vectrex (fp2.14 not fp18.14) typedef unsigned char uint8_t; typedef signed char int8_t; //---------------------------------------------- // // 2.14 fixed point sine and cosine tables. // // These tables assume that there are 256 angles // in a circle. // #define int2fp(x) ((x) << 14) #define fp2int(x) ((x) >> 14) const fp14 qsine[65] = { 0, 402, 803, 1205, 1605, 2005, 2404, 2801, 3196, 3589, 3980, 4369, 4756, 5139, 5519, 5896, 6269, 6639, 7005, 7366, 7723, 8075, 8423, 8765, 9102, 9434, 9759, 10079, 10393, 10701, 11002, 11297, 11585, 11866, 12139, 12406, 12665, 12916, 13159, 13395, 13622, 13842, 14053, 14255, 14449, 14634, 14810, 14978, 15136, 15286, 15426, 15557, 15678, 15790, 15892, 15985, 16069, 16142, 16206, 16260, 16305, 16339, 16364, 16379, 16384, }; static inline fp14 hsine(uint8_t x) { return x>=64 ? qsine[128-x] : qsine[x]; } static inline fp14 sine(uint8_t x) { return x&0x80 ? -hsine(x&0x7f) : hsine(x); } static inline fp14 fpSin(uint8_t x) { return sine((uint8_t)(x)); } static inline fp14 fpCos(uint8_t x) { return sine((uint8_t)((x)+64)); } static inline int8_t sin127(int8_t x) { return 63-(int8_t)(sine((uint8_t)x&255U)>>8); } static inline int8_t cos127(int8_t x) { return 63-(int8_t)(sine(((uint8_t)x+64U)&255U)>>8); } #endif // NEVER int main(int argc, char **argv) { int tick = 0; int x, y; int fd; char device_name[128]; // Sidewinder Pro for now on my machine. Unfortunately // button numbers are different from Vectrex controller // unless remapped struct js_event jse; SDL_Event event; int joystick_attached = 0; int running = 1; SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC); if (strrchr(argv[0], '/')) argv[0] = strrchr(argv[0], '/') + 1; if (argc == 2) sprintf(device_name, "%s", argv[1]); if (argc > 2) { printf("syntax: %s {optional joystick device such as /dev/input/js0}\n", argv[0]); exit(1); } wiringPiSetupGpio(); // These are the pins used to connect to the two digipots. // Originally I intended to use daisy-chain mode, but finding // accurate documentation on that proved impossible, so I // used 2 GPIO pins to address each pot independently, which // works just fine. CS_RIGHT = 8; CS_LEFT = 26; CLK = 11; MOSI = 10; pinMode(CS_LEFT, OUTPUT); pinMode(CS_RIGHT, OUTPUT); pinMode(CLK, OUTPUT); pinMode(MOSI, OUTPUT); digitalWrite(CLK, LOW); // We must still clean up even if the user exits with ^C atexit(cleanup); /* Release all buttons, stop DMA, release resources */ /* Set GPIO modes to OUTPUT for all 4 buttons on each controller */ { int i; for (i = 0; i <= 3; i++) { // Init state is no buttons pressed. pinMode(gpio_a[i], OUTPUT); digitalWrite(gpio_a[i], RELEASED); pinMode(gpio_b[i], OUTPUT); digitalWrite(gpio_b[i], RELEASED); } } int mouse_x = 127, mouse_y = 127; wiringPiSetupGpio(); // These are the pins used to connect to the daisy-chained digipots: CS_RIGHT = 8; CS_LEFT = 26; CLK = 11; MOSI = 10; pinMode(CS_LEFT, OUTPUT); pinMode(CS_RIGHT, OUTPUT); pinMode(CLK, OUTPUT); pinMode(MOSI, OUTPUT); digitalWrite(CLK, LOW); open_mouse(); //circle(); // maybe want some sort of feedback to indicate that USB converter is active now. for (;;) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: { fprintf(stderr, "SDL_QUIT\n"); running = 0; break; } // case SDL_QUIT case SDL_JOYDEVICEADDED: { int port = 0; joystick_attached += 1; fprintf(stderr, "SDL_JOYDEVICEADDED\n"); // Choose next unplugged port for (int i = 0; i < MAXPORTS; ++i) { if (!jsports[i]) { joystick[i] = SDL_JoystickOpen(event.jdevice.which); SDL_JoystickSetPlayerIndex(joystick[i], i); jsports[i] = 1; jsiid[i] = SDL_JoystickInstanceID(joystick[i]); port = i; break; } } fprintf(stderr, "Joystick Connected: %s\n" "\tInstance ID: %d, Player: %d\n" "\tPort /dev/input/js%0d\n", SDL_JoystickName(joystick[port]), jsiid[port], SDL_JoystickGetPlayerIndex(joystick[port]), port); sprintf(device_name, "/dev/input/js%0d", port); if (SDL_JoystickIsHaptic(joystick[port])) { haptic[port] = SDL_HapticOpenFromJoystick(joystick[port]); SDL_HapticRumbleInit(haptic[port]) < 0 ? fprintf(stderr, "Force Feedback Enable Failed: %s\n", SDL_GetError()) : fprintf(stderr, "Force Feedback Enabled\n"); } else { fprintf(stderr, "Device is not haptic\n"); } fprintf(stderr, "Currently connected joystick devices: %s%s%s%s\n\n", jsports[0] ? "/dev/input/js0 " : "", jsports[1] ? "/dev/input/js1 " : "", jsports[2] ? "/dev/input/js2 " : "", jsports[3] ? "/dev/input/js3 " : ""); fd = open_joystick(device_name); if (fd < 0) { printf("Could not locate joystick device %s\n", device_name); exit(1); } print_device_info(fd); break; } // case SDL_JOYDEVICEADDED case SDL_JOYDEVICEREMOVED: { int id = event.jdevice.which; joystick_attached -= 1; fprintf(stderr, "SDL_JOYDEVICEREMOVED\n"); fprintf(stderr, "Instance ID: %d\n", id); for (int i = 0; i < MAXPORTS; ++i) { // If it's the one that got disconnected... if (jsiid[i] == id) { if (SDL_JoystickIsHaptic(joystick[i])) SDL_HapticClose(haptic[i]); jsports[i] = 0; // Notify that this is unplugged fprintf(stderr, "Joystick /dev/input/js%0d Disconnected\n", i); SDL_JoystickClose(joystick[i]); break; } } if (!jsports[0] && !jsports[1] && !jsports[2] && !jsports[3]) { fprintf(stderr, "Waiting for a USB Joystick to be attached.\n"); joystick_attached = 0; } else { fprintf(stderr, "Currently connected joystick devices: %s%s%s%s\n\n", jsports[0] ? "/dev/input/js0 " : "", jsports[1] ? "/dev/input/js1 " : "", jsports[2] ? "/dev/input/js2 " : "", jsports[3] ? "/dev/input/js3 " : ""); } // go quiescent until another joystick is attached. break; } // case SDL_JOYDEVICEREMOVED } // switch (event.type) } // loop while (SDL_PollEvent(&event)) if (!running) break; if (joystick_attached) { int vx, vy; usleep(1000000 / 120); // 120 polls per second // read joysticks, buttons ... while (read(fd, &jse, sizeof(jse)) > 0) { process_event(jse); // sets JoyXY values } x = (LeftJoyX ^ 128) & 255; // currentJoy1X; y = (LeftJoyY ^ 128) & 255; // currentJoy1Y; // whether these are inverted or not depends on order of +5V 0V -5V pins on PCB. // First voltage divider has +5V <-> 0V inputs, second has 0V <-> -5V. vx = x; vy = 255-y; setPot(CS_LEFT, 0B00010001, vx); setPot(CS_LEFT, 0B00010010, vy); //fprintf(stderr, "Setting left potentiometer values vx=%d vy=%d \r", vx, vy); x = (RightJoyX ^ 128) & 255; // currentJoy1X; y = (RightJoyY ^ 128) & 255; // currentJoy1Y; vx = 255-x; vy = y; setPot(CS_RIGHT, 0B00010001, vx); setPot(CS_RIGHT, 0B00010010, vy); //fprintf(stderr, "Setting right potentiometer values vx=%d vy=%d \r", vx, vy); } else if (mouse_attached) { int left, middle, right; // Read Mouse mouse_bytes = read(mouse_fd, mouse_data, sizeof(mouse_data)); if (mouse_bytes > 0) { left = mouse_data[0] & 0x1; right = mouse_data[0] & 0x2; middle = mouse_data[0] & 0x4; mouse_x -= mouse_data[1]; if (mouse_x > 255) mouse_x = 255; if (mouse_x < 0) mouse_x = 0; mouse_y -= mouse_data[2]; if (mouse_y > 255) mouse_y = 255; if (mouse_y < 0) mouse_y = 0; setPot(CS_RIGHT, 0B00010001, mouse_x); setPot(CS_RIGHT, 0B00010010, mouse_y); fprintf(stderr, "dx=%d, dy=%d, x=%d, y=%d, left=%d, middle=%d, right=%d\n", mouse_data[1], mouse_data[2], mouse_x, mouse_y, left, middle, right); digitalWrite(gpio_b[0], (left != 0)&1); digitalWrite(gpio_b[2], (middle != 0)&1); digitalWrite(gpio_b[3], (right != 0)&1); } } else { usleep(1000000 / 120); // set rate at which the joysticks are polled. } } // while running exit(0); return 0; }