Created
January 15, 2025 13:15
-
-
Save TheGammaSqueeze/97ff17c6798a940d1d302eda3c6dd459 to your computer and use it in GitHub Desktop.
TrimUI smart pro android joypad userspace driver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /******************************************************************************* | |
| * File: trimui_corrected.c | |
| * | |
| * Changes vs previous: | |
| * - #define VENDOR_ID, PRODUCT_ID, VERSION_ID => 0x0000,0x0000,0x0001 | |
| * - Use ABS_Z, ABS_RZ instead of ABS_RX, ABS_RY | |
| * - If L2 or R2 pressed => emit additional "gas" or "brake" events | |
| * - Clamping from ±900 => ±32760 | |
| * - No repeated SYN if values are unchanged | |
| ******************************************************************************/ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stdint.h> | |
| #include <stdbool.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| #include <pthread.h> | |
| #include <termios.h> | |
| #include <linux/input.h> | |
| #include <linux/uinput.h> | |
| #include <sys/ioctl.h> | |
| /* -------------------------------------------------------------------------- | |
| Constants / defines | |
| -------------------------------------------------------------------------- */ | |
| #define UINPUT_PATH "/dev/uinput" | |
| #define VENDOR_ID 0x0000 | |
| #define PRODUCT_ID 0x0000 | |
| #define VERSION_ID 0x0001 | |
| #define DEVICE_NAME "retrogame_joypad" | |
| // For 8-byte Trimui frames | |
| #define MAGIC_START 0xFF | |
| #define MAGIC_END 0xFE | |
| #define FRAME_LEN 8 | |
| // We'll read from: | |
| #define SERIAL_LEFT "/dev/ttyS4" | |
| #define SERIAL_RIGHT "/dev/ttyS3" | |
| // We calibrate using 50 frames | |
| #define CAL_FRAMES 50 | |
| // Axis range in uinput | |
| #define AXIS_MIN (-32760) | |
| #define AXIS_MAX ( 32760) | |
| // We'll clamp ±900 -> ±32760 | |
| static const int RAW_MAX = 900; | |
| // 10% radial deadzone | |
| static float g_deadzone = 0.10f * (float)AXIS_MAX; | |
| // Additional definitions for gas/brake if not in your headers | |
| #ifndef ABS_BRAKE | |
| #define ABS_BRAKE 0x0115 | |
| #endif | |
| #ifndef ABS_GAS | |
| #define ABS_GAS 0x0116 | |
| #endif | |
| // Button bits (for left side & right side) | |
| static unsigned char old_btn_left = 0; | |
| static unsigned char old_btn_right = 0; | |
| // We'll keep track of the HAT states | |
| static int old_hat_x = 0; | |
| static int old_hat_y = 0; | |
| /* -------------------------------------------------------------------------- | |
| We'll store last axis states to avoid re-emitting the same values | |
| -------------------------------------------------------------------------- */ | |
| static int last_left_x = 999999; | |
| static int last_left_y = 999999; | |
| static int last_right_x = 999999; | |
| static int last_right_y = 999999; | |
| // We'll store last brake/gas for L2/R2 | |
| static int last_brake = -1; // 0 or 255 | |
| static int last_gas = -1; | |
| // We also want to store last EV_KEY states to avoid spamming | |
| // But we already do this by comparing old_btn_left, old_btn_right | |
| /* -------------------------------------------------------------------------- | |
| Data structure for simple calibration | |
| We'll accumulate sums of rawX, rawY for 50 frames => average => offset | |
| -------------------------------------------------------------------------- */ | |
| typedef struct { | |
| long sum_x; | |
| long sum_y; | |
| int count; | |
| int offset_x; | |
| int offset_y; | |
| bool done; | |
| } CalData; | |
| static CalData calLeft = {0}; | |
| static CalData calRight = {0}; | |
| // The uinput fd | |
| static int g_uinput_fd = -1; | |
| /* -------------------------------------------------------------------------- | |
| (ADDED) Initialize power GPIO pins for the Trimui sticks. | |
| This must run BEFORE opening /dev/ttyS4 or /dev/ttyS3 so the sticks are powered. | |
| -------------------------------------------------------------------------- */ | |
| static void trimui_init_power_gpio(void) | |
| { | |
| // Make sure these exist and are set to output = 1, so the joysticks are powered | |
| system("echo 110 > /sys/class/gpio/export"); | |
| system("echo -n out > /sys/class/gpio/gpio110/direction"); | |
| system("echo -n 1 > /sys/class/gpio/gpio110/value"); | |
| system("echo 114 > /sys/class/gpio/export"); | |
| system("echo -n out > /sys/class/gpio/gpio114/direction"); | |
| system("echo -n 1 > /sys/class/gpio/gpio114/value"); | |
| system("echo 243 > /sys/class/gpio/export"); | |
| system("echo -n in > /sys/class/gpio/gpio243/direction"); | |
| system("echo 107 > /sys/class/gpio/export"); | |
| system("echo -n out > /sys/class/gpio/gpio107/direction"); | |
| system("echo -n 1 > /sys/class/gpio/gpio107/value"); | |
| // short delay to let hardware settle | |
| usleep(100 * 1000); // 100ms | |
| } | |
| /* -------------------------------------------------------------------------- | |
| Setup uinput | |
| We'll map: | |
| - Left stick => ABS_X, ABS_Y | |
| - Right stick => ABS_Z, ABS_RZ | |
| - D-Pad => ABS_HAT0X, ABS_HAT0Y | |
| - Buttons => L1=BTN_TL, L2=BTN_TL2, R1=BTN_TR, R2=BTN_TR2, A=BTN_A, ... | |
| We'll also set ABS_BRAKE, ABS_GAS => range 0..255 | |
| -------------------------------------------------------------------------- */ | |
| static int setup_uinput(void) | |
| { | |
| int fd = open(UINPUT_PATH, O_WRONLY | O_NONBLOCK); | |
| if(fd < 0) { | |
| fprintf(stderr, "Cannot open %s: %s\n", UINPUT_PATH, strerror(errno)); | |
| return -1; | |
| } | |
| // Key bits | |
| ioctl(fd, UI_SET_EVBIT, EV_KEY); | |
| ioctl(fd, UI_SET_KEYBIT, BTN_TL); // L1 | |
| ioctl(fd, UI_SET_KEYBIT, BTN_TL2); // L2 | |
| ioctl(fd, UI_SET_KEYBIT, BTN_TR); // R1 | |
| ioctl(fd, UI_SET_KEYBIT, BTN_TR2); // R2 | |
| ioctl(fd, UI_SET_KEYBIT, BTN_MODE); // Left side mode | |
| ioctl(fd, UI_SET_KEYBIT, BTN_A); | |
| ioctl(fd, UI_SET_KEYBIT, BTN_B); | |
| ioctl(fd, UI_SET_KEYBIT, BTN_X); | |
| ioctl(fd, UI_SET_KEYBIT, BTN_Y); | |
| ioctl(fd, UI_SET_KEYBIT, BTN_SELECT); | |
| ioctl(fd, UI_SET_KEYBIT, BTN_START); | |
| // Abs bits | |
| ioctl(fd, UI_SET_EVBIT, EV_ABS); | |
| // We'll define these codes: ABS_X, ABS_Y => left stick | |
| // ABS_Z, ABS_RZ => right stick | |
| ioctl(fd, UI_SET_ABSBIT, ABS_X); | |
| ioctl(fd, UI_SET_ABSBIT, ABS_Y); | |
| ioctl(fd, UI_SET_ABSBIT, ABS_Z); | |
| ioctl(fd, UI_SET_ABSBIT, ABS_RZ); | |
| // D-pad => hat | |
| ioctl(fd, UI_SET_ABSBIT, ABS_HAT0X); | |
| ioctl(fd, UI_SET_ABSBIT, ABS_HAT0Y); | |
| // Gas/Brake => 0..255 | |
| ioctl(fd, UI_SET_ABSBIT, ABS_BRAKE); | |
| ioctl(fd, UI_SET_ABSBIT, ABS_GAS); | |
| // Helper for setting up ranges | |
| struct uinput_abs_setup abs_setup; | |
| memset(&abs_setup, 0, sizeof(abs_setup)); | |
| #define SET_ABS(_code, _min, _max) do { \ | |
| memset(&abs_setup, 0, sizeof(abs_setup)); \ | |
| abs_setup.code = (_code); \ | |
| abs_setup.absinfo.minimum = (_min); \ | |
| abs_setup.absinfo.maximum = (_max); \ | |
| abs_setup.absinfo.fuzz = 0; \ | |
| abs_setup.absinfo.flat = 0; \ | |
| if(ioctl(fd, UI_ABS_SETUP, &abs_setup) < 0) { \ | |
| fprintf(stderr, "UI_ABS_SETUP code %d failed\n", (int)(_code)); \ | |
| } \ | |
| } while(0) | |
| // left stick + right stick => [-32760..32760] | |
| SET_ABS(ABS_X, AXIS_MIN, AXIS_MAX); | |
| SET_ABS(ABS_Y, AXIS_MIN, AXIS_MAX); | |
| SET_ABS(ABS_Z, AXIS_MIN, AXIS_MAX); | |
| SET_ABS(ABS_RZ, AXIS_MIN, AXIS_MAX); | |
| // d-pad => -1..+1 | |
| SET_ABS(ABS_HAT0X, -1, 1); | |
| SET_ABS(ABS_HAT0Y, -1, 1); | |
| // gas/brake => [0..255] | |
| SET_ABS(ABS_BRAKE, 0, 255); | |
| SET_ABS(ABS_GAS, 0, 255); | |
| // finalize device | |
| struct uinput_setup uset; | |
| memset(&uset, 0, sizeof(uset)); | |
| uset.id.bustype = BUS_USB; | |
| uset.id.vendor = VENDOR_ID; | |
| uset.id.product = PRODUCT_ID; | |
| uset.id.version = VERSION_ID; | |
| strncpy(uset.name, DEVICE_NAME, UINPUT_MAX_NAME_SIZE); | |
| if(ioctl(fd, UI_DEV_SETUP, &uset) < 0) { | |
| fprintf(stderr, "UI_DEV_SETUP failed\n"); | |
| close(fd); | |
| return -1; | |
| } | |
| if(ioctl(fd, UI_DEV_CREATE) < 0) { | |
| fprintf(stderr, "UI_DEV_CREATE failed\n"); | |
| close(fd); | |
| return -1; | |
| } | |
| printf("Created uinput device: %s\n", DEVICE_NAME); | |
| return fd; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| We'll only emit an EV_KEY or EV_ABS if the new value differs from old value | |
| We'll handle the SYN manually when a change occurs. | |
| This avoids spamming repeated events. | |
| -------------------------------------------------------------------------- */ | |
| static void emit_key_if_changed(int code, int newVal, int oldVal) | |
| { | |
| if(newVal == oldVal) return; // no change | |
| struct input_event ev; | |
| memset(&ev, 0, sizeof(ev)); | |
| ev.type = EV_KEY; | |
| ev.code = code; | |
| ev.value = newVal; | |
| write(g_uinput_fd, &ev, sizeof(ev)); | |
| // then a SYN | |
| memset(&ev, 0, sizeof(ev)); | |
| ev.type = EV_SYN; | |
| ev.code = SYN_REPORT; | |
| ev.value = 0; | |
| write(g_uinput_fd, &ev, sizeof(ev)); | |
| } | |
| static void emit_abs_if_changed(int code, int newVal, int oldVal) | |
| { | |
| if(newVal == oldVal) return; // no change | |
| struct input_event ev; | |
| memset(&ev, 0, sizeof(ev)); | |
| ev.type = EV_ABS; | |
| ev.code = code; | |
| ev.value = newVal; | |
| write(g_uinput_fd, &ev, sizeof(ev)); | |
| // SYN | |
| memset(&ev, 0, sizeof(ev)); | |
| ev.type = EV_SYN; | |
| ev.code = SYN_REPORT; | |
| ev.value = 0; | |
| write(g_uinput_fd, &ev, sizeof(ev)); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| clamp from ±900 => ±32760 | |
| -------------------------------------------------------------------------- */ | |
| static int clamp_scale(int raw) | |
| { | |
| if(raw < -RAW_MAX) raw = -RAW_MAX; | |
| if(raw > RAW_MAX) raw = RAW_MAX; | |
| // scale | |
| float ratio = (float)AXIS_MAX / (float)RAW_MAX; // ~32.76 | |
| float valf = (float)raw * ratio; | |
| int vali = (int)valf; | |
| if(vali < AXIS_MIN) vali = AXIS_MIN; | |
| if(vali > AXIS_MAX) vali = AXIS_MAX; | |
| return vali; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| radial deadzone | |
| -------------------------------------------------------------------------- */ | |
| static void do_deadzone(int *px, int *py) | |
| { | |
| int x = *px; | |
| int y = *py; | |
| long long mag = (long long)x*x + (long long)y*y; | |
| long long dz2 = (long long)g_deadzone * (long long)g_deadzone; | |
| if(mag <= dz2) { | |
| *px = 0; | |
| *py = 0; | |
| } | |
| } | |
| /* -------------------------------------------------------------------------- | |
| handle left bits: | |
| B0 => L1 => BTN_TL | |
| B1 => L2 => BTN_TL2 => also brake | |
| B2 => DPad Up => hatY=-1 | |
| B3 => DPad Left => hatX=-1 | |
| B4 => DPad Right => hatX=+1 | |
| B5 => DPad Down => hatY=+1 | |
| B7 => Mode => BTN_MODE | |
| -------------------------------------------------------------------------- */ | |
| static void handle_left_buttons(unsigned char newB) | |
| { | |
| // L1 => bit0 | |
| bool currL1 = (newB & (1<<0))?true:false; | |
| bool oldL1 = (old_btn_left & (1<<0))?true:false; | |
| if(currL1 != oldL1) { | |
| // 1 => pressed, 0 => released | |
| emit_key_if_changed(BTN_TL, currL1?1:0, oldL1?1:0); | |
| } | |
| // L2 => bit1 => also brake | |
| bool currL2 = (newB & (1<<1))?true:false; | |
| bool oldL2 = (old_btn_left & (1<<1))?true:false; | |
| if(currL2 != oldL2) { | |
| emit_key_if_changed(BTN_TL2, currL2?1:0, oldL2?1:0); | |
| } | |
| // also brake => 255 if pressed else 0 | |
| int newBrake = currL2?255:0; | |
| emit_abs_if_changed(ABS_BRAKE, newBrake, last_brake); | |
| last_brake = newBrake; | |
| // Mode => bit7 => BTN_MODE | |
| bool currMode = (newB & (1<<7))?true:false; | |
| bool oldMode = (old_btn_left & (1<<7))?true:false; | |
| if(currMode != oldMode) { | |
| emit_key_if_changed(BTN_MODE, currMode?1:0, oldMode?1:0); | |
| } | |
| // DPad => bits2/3/4/5 => up/left/right/down | |
| int hatx=0, haty=0; | |
| if(newB & (1<<2)) { // up | |
| haty = -1; | |
| } | |
| if(newB & (1<<5)) { // down | |
| haty = 1; | |
| } | |
| if(newB & (1<<3)) { // left | |
| hatx = -1; | |
| } | |
| if(newB & (1<<4)) { // right | |
| hatx = 1; | |
| } | |
| emit_abs_if_changed(ABS_HAT0X, hatx, old_hat_x); | |
| emit_abs_if_changed(ABS_HAT0Y, haty, old_hat_y); | |
| old_hat_x = hatx; | |
| old_hat_y = haty; | |
| old_btn_left = newB; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| handle right bits: | |
| B0 => R1 => BTN_TR | |
| B1 => R2 => BTN_TR2 => also gas | |
| B2 => X => BTN_X | |
| B3 => Y => BTN_Y | |
| B4 => A => BTN_A | |
| B5 => B => BTN_B | |
| B6 => Select => BTN_SELECT | |
| B7 => Start => BTN_START | |
| -------------------------------------------------------------------------- */ | |
| static void handle_right_buttons(unsigned char newB) | |
| { | |
| // R1 => bit0 | |
| bool currR1 = (newB & (1<<0))?true:false; | |
| bool oldR1 = (old_btn_right & (1<<0))?true:false; | |
| if(currR1 != oldR1) { | |
| emit_key_if_changed(BTN_TR, currR1?1:0, oldR1?1:0); | |
| } | |
| // R2 => bit1 => also gas | |
| bool currR2 = (newB & (1<<1))?true:false; | |
| bool oldR2 = (old_btn_right & (1<<1))?true:false; | |
| if(currR2 != oldR2) { | |
| emit_key_if_changed(BTN_TR2, currR2?1:0, oldR2?1:0); | |
| } | |
| // gas => 255 if pressed else 0 | |
| int newGas = currR2?255:0; | |
| emit_abs_if_changed(ABS_GAS, newGas, last_gas); | |
| last_gas = newGas; | |
| // X => bit2 => BTN_X | |
| bool curX = (newB & (1<<2))?true:false; | |
| bool oldX = (old_btn_right & (1<<2))?true:false; | |
| if(curX != oldX) { | |
| emit_key_if_changed(BTN_X, curX?1:0, oldX?1:0); | |
| } | |
| // Y => bit3 => BTN_Y | |
| bool curY = (newB & (1<<3))?true:false; | |
| bool oldY = (old_btn_right & (1<<3))?true:false; | |
| if(curY != oldY) { | |
| emit_key_if_changed(BTN_Y, curY?1:0, oldY?1:0); | |
| } | |
| // A => bit4 => BTN_A | |
| bool curA = (newB & (1<<4))?true:false; | |
| bool oldA = (old_btn_right & (1<<4))?true:false; | |
| if(curA != oldA) { | |
| emit_key_if_changed(BTN_A, curA?1:0, oldA?1:0); | |
| } | |
| // B => bit5 => BTN_B | |
| bool curB = (newB & (1<<5))?true:false; | |
| bool oldB = (old_btn_right & (1<<5))?true:false; | |
| if(curB != oldB) { | |
| emit_key_if_changed(BTN_B, curB?1:0, oldB?1:0); | |
| } | |
| // Select => bit6 => BTN_SELECT | |
| bool curSel = (newB & (1<<6))?true:false; | |
| bool oldSel = (old_btn_right & (1<<6))?true:false; | |
| if(curSel != oldSel) { | |
| emit_key_if_changed(BTN_SELECT, curSel?1:0, oldSel?1:0); | |
| } | |
| // Start => bit7 => BTN_START | |
| bool curSt = (newB & (1<<7))?true:false; | |
| bool oldSt = (old_btn_right & (1<<7))?true:false; | |
| if(curSt != oldSt) { | |
| emit_key_if_changed(BTN_START, curSt?1:0, oldSt?1:0); | |
| } | |
| old_btn_right = newB; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| We'll do 50 frames calibration: | |
| sum raw x,y => offset_x,y | |
| -------------------------------------------------------------------------- */ | |
| static void calibrate_port(int fd, CalData *cal) | |
| { | |
| printf("Calibrating port ... keep stick centered for 50 frames\n"); | |
| unsigned char tmp[8]; | |
| int frames=0; | |
| while(frames < CAL_FRAMES) { | |
| ssize_t n = read(fd, tmp, 8); | |
| if(n<8) { | |
| usleep(5000); | |
| continue; | |
| } | |
| if(tmp[0]==MAGIC_START && tmp[7]==MAGIC_END) { | |
| int16_t rx = (tmp[3]<<8) | tmp[4]; | |
| int16_t ry = (tmp[5]<<8) | tmp[6]; | |
| cal->sum_x += rx; | |
| cal->sum_y += ry; | |
| cal->count++; | |
| frames++; | |
| } | |
| } | |
| int ox = (int)(cal->sum_x / cal->count); | |
| int oy = (int)(cal->sum_y / cal->count); | |
| cal->offset_x = ox; | |
| cal->offset_y = oy; | |
| cal->done = true; | |
| printf("cal done => offsetX=%d offsetY=%d\n", ox, oy); | |
| } | |
| /* -------------------------------------------------------------------------- | |
| left side thread => parse frames => handle buttons => handle axes => emit | |
| We'll fix left-right swap by negating X | |
| -------------------------------------------------------------------------- */ | |
| static void *thread_left(void *arg) | |
| { | |
| int fd = *(int*)arg; | |
| calibrate_port(fd, &calLeft); | |
| while(1) { | |
| unsigned char buf[8]; | |
| ssize_t n = read(fd, buf, 8); | |
| if(n<8) { | |
| usleep(2000); | |
| continue; | |
| } | |
| if(buf[0]==MAGIC_START && buf[7]==MAGIC_END) { | |
| // parse | |
| unsigned char b = buf[2]; | |
| handle_left_buttons(b); | |
| int16_t rx = (buf[3]<<8)|buf[4]; | |
| int16_t ry = (buf[5]<<8)|buf[6]; | |
| // offset | |
| int ax = rx - calLeft.offset_x; | |
| int ay = ry - calLeft.offset_y; | |
| // fix left-right => negate X | |
| ax = -ax; | |
| // clamp & scale | |
| ax = clamp_scale(ax); | |
| ay = clamp_scale(ay); | |
| // deadzone | |
| do_deadzone(&ax, &ay); | |
| // invert Y => up negative | |
| ay = -ay; | |
| // only emit if changed | |
| // store new => compare last_left_x, last_left_y | |
| emit_abs_if_changed(ABS_X, ax, last_left_x); | |
| emit_abs_if_changed(ABS_Y, ay, last_left_y); | |
| last_left_x = ax; | |
| last_left_y = ay; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| right side thread => parse frames => handle buttons => handle axes => emit | |
| also fix left-right => negate X | |
| -------------------------------------------------------------------------- */ | |
| static void *thread_right(void *arg) | |
| { | |
| int fd = *(int*)arg; | |
| calibrate_port(fd, &calRight); | |
| while(1) { | |
| unsigned char buf[8]; | |
| ssize_t n = read(fd, buf, 8); | |
| if(n<8) { | |
| usleep(2000); | |
| continue; | |
| } | |
| if(buf[0]==MAGIC_START && buf[7]==MAGIC_END) { | |
| unsigned char b = buf[2]; | |
| handle_right_buttons(b); | |
| int16_t rx = (buf[3]<<8)|buf[4]; | |
| int16_t ry = (buf[5]<<8)|buf[6]; | |
| int ax = rx - calRight.offset_x; | |
| int ay = ry - calRight.offset_y; | |
| // also fix left-right => negate X | |
| ax = -ax; | |
| ax = clamp_scale(ax); | |
| ay = clamp_scale(ay); | |
| do_deadzone(&ax, &ay); | |
| // invert y | |
| ay = -ay; | |
| // but now we map to ABS_Z, ABS_RZ | |
| emit_abs_if_changed(ABS_Z, ax, last_right_x); | |
| emit_abs_if_changed(ABS_RZ, ay, last_right_y); | |
| last_right_x = ax; | |
| last_right_y = ay; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| Setup the serial device at 19200 8N1, raw | |
| -------------------------------------------------------------------------- */ | |
| static int setup_serial(const char *dev) | |
| { | |
| int fd = open(dev, O_RDWR | O_NOCTTY | O_NDELAY); | |
| if(fd<0) { | |
| fprintf(stderr, "Cannot open %s: %s\n", dev, strerror(errno)); | |
| return -1; | |
| } | |
| fcntl(fd, F_SETFL, 0); // blocking | |
| struct termios tty; | |
| memset(&tty, 0, sizeof(tty)); | |
| if(tcgetattr(fd, &tty)<0) { | |
| fprintf(stderr, "tcgetattr fail on %s\n", dev); | |
| close(fd); | |
| return -1; | |
| } | |
| cfsetispeed(&tty, B19200); | |
| cfsetospeed(&tty, B19200); | |
| tty.c_cflag = (tty.c_cflag & ~CSIZE)|CS8; | |
| tty.c_cflag &= ~PARENB; | |
| tty.c_cflag &= ~CSTOPB; | |
| tty.c_cflag |= (CLOCAL|CREAD); | |
| tty.c_cflag &= ~CRTSCTS; | |
| tty.c_iflag &= ~(IXON|IXOFF|IXANY); | |
| tty.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG); | |
| tty.c_oflag &= ~OPOST; | |
| tty.c_cc[VMIN] = 1; | |
| tty.c_cc[VTIME] = 0; | |
| tcflush(fd, TCIOFLUSH); | |
| if(tcsetattr(fd, TCSANOW, &tty)!=0) { | |
| fprintf(stderr, "tcsetattr fail on %s\n", dev); | |
| close(fd); | |
| return -1; | |
| } | |
| printf("Opened serial: %s\n", dev); | |
| return fd; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| main | |
| -------------------------------------------------------------------------- */ | |
| int main(int argc, char** argv) | |
| { | |
| // *** IMPORTANT ***: Power on the joystick hardware | |
| trimui_init_power_gpio(); | |
| // 1) setup uinput | |
| g_uinput_fd = setup_uinput(); | |
| if(g_uinput_fd < 0) { | |
| return 1; | |
| } | |
| // 2) open left | |
| int fdL = setup_serial(SERIAL_LEFT); | |
| if(fdL<0) { | |
| ioctl(g_uinput_fd, UI_DEV_DESTROY); | |
| close(g_uinput_fd); | |
| return 1; | |
| } | |
| // 3) open right | |
| int fdR = setup_serial(SERIAL_RIGHT); | |
| if(fdR<0) { | |
| close(fdL); | |
| ioctl(g_uinput_fd, UI_DEV_DESTROY); | |
| close(g_uinput_fd); | |
| return 1; | |
| } | |
| // 4) spawn threads | |
| pthread_t tidL, tidR; | |
| pthread_create(&tidL, NULL, thread_left, &fdL); | |
| pthread_create(&tidR, NULL, thread_right, &fdR); | |
| // 5) just loop forever | |
| while(1) { | |
| sleep(1); | |
| } | |
| // not reached | |
| close(fdL); | |
| close(fdR); | |
| ioctl(g_uinput_fd, UI_DEV_DESTROY); | |
| close(g_uinput_fd); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment