From d53b0bceffab2f348ae517c132b2a95d3b39d019 Mon Sep 17 00:00:00 2001 From: Michel Pollet Date: Sat, 28 Oct 2023 16:59:08 +0100 Subject: [PATCH] Added joystick support It is limited for now, hard coded to mine. Still need some sort of way to specify which button to map to the emulated joytick. Same with the axis, currently default to 0,1 Signed-off-by: Michel Pollet --- README.md | 2 + nuklear/mii_mish.c | 6 +++ nuklear/mii_thread.c | 95 ++++++++++++++++++++++++++++++++++++++++---- src/mii.c | 5 +++ src/mii_analog.c | 59 +++++++++++++++++++++++++++ src/mii_analog.h | 33 +++++++++++++++ 6 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 src/mii_analog.c create mode 100644 src/mii_analog.h diff --git a/README.md b/README.md index 7fc16b7..3059e4c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ I wanted something: * Adds a small 'attack' filter when playing back to soften the often annoying 'click' of typical audio effects from the apple II. * Mouse Card -- mouse isn't captured like in some other emulators. * No Slot Clock + * Joystick (in a limited way...) * Smartport DMA 'hard drive' card * "Titan Accelerator //e" simulation, to turn on/off fast mode. * Terence's J Boldt [1MB ROM card](https://github.com/tjboldt/ProDOS-ROM-Drive), also because I own a couple! @@ -127,6 +128,7 @@ There are just a few keys that are mapped for anything useful. * A2Desktop PT3 player doesn't see keypresses. * Sometimes the emulator goes in 'slow mode', ie 0.2MHz. Likely the frame scheduler playing up. * Thats' about it really, all the other things I tried work + * Joystick support is a bit limited, no 'mapping' I used a (USB) 8bitdo NES30 Pro, and it works, but it's not perfect. But, I can play choplifter with it, so it's good enough for now... ## What it could do with * Not sure about keeping Nuklear, it does a lot bit it's hard work customizing anything diff --git a/nuklear/mii_mish.c b/nuklear/mii_mish.c index a7226d5..0a9063f 100644 --- a/nuklear/mii_mish.c +++ b/nuklear/mii_mish.c @@ -86,6 +86,12 @@ show_state: } return; } + if (!strcmp(argv[1], "analog")) { + printf("analog: %3d %3d %3d %3d\n", mii->analog.v[0].value, + mii->analog.v[1].value, mii->analog.v[2].value, + mii->analog.v[3].value); + return; + } if (!strcmp(argv[1], "trace")) { mii->trace_cpu = !mii->trace_cpu; printf("trace_cpu %d\n", mii->trace_cpu); diff --git a/nuklear/mii_thread.c b/nuklear/mii_thread.c index 31b14cc..5a57bfd 100644 --- a/nuklear/mii_thread.c +++ b/nuklear/mii_thread.c @@ -12,6 +12,15 @@ #include #include #include +#include +#include +#include + +// probably should wrap these into a HAVE_JOYSTICK define for non-linux +#ifndef HAVE_JOYSTICK +#define HAVE_JOYSTICK 1 +#endif + #include "mii.h" #include "mii_thread.h" @@ -31,19 +40,17 @@ mii_get_time() return time; } - -static pthread_t mii_thread; static bool mii_thread_running = false; static float default_fps = 60; mii_th_fifo_t signal_fifo; static void * -mii_thread_func( +mii_thread_cpu_regulator( void *arg) { mii_t *mii = (mii_t *) arg; mii_thread_running = true; - __uint128_t last_cycles = mii->cycles; + mii_cycles_t last_cycles = mii->cycles; uint32_t running = 1; unsigned long target_fps_us = 1000000 / default_fps; long sleep_time = target_fps_us; @@ -117,8 +124,8 @@ mii_thread_func( last_frame_stamp += target_fps_us; // calculate the MHz - __uint128_t cycles = mii->cycles; - __uint128_t delta_cycles = cycles - last_cycles; + mii_cycles_t cycles = mii->cycles; + mii_cycles_t delta_cycles = cycles - last_cycles; last_cycles = cycles; mii->speed_current = delta_cycles / (float)target_fps_us; } @@ -129,13 +136,87 @@ mii_thread_func( return NULL; } +#if HAVE_JOYSTICK +#include + +static void * +mii_thread_joystick( + void *arg) +{ + int fd = open("/dev/input/js0", O_RDONLY); + if (fd < 0) { + printf("No joystick found\n"); + return NULL; + } + uint8_t axes, buttons; + if (ioctl(fd, JSIOCGAXES, &axes) == -1 || + ioctl(fd, JSIOCGBUTTONS, &buttons) == -1) { + perror(__func__); + return NULL; + } + + struct js_event event; + mii_t *mii = (mii_t *)arg; + mii->analog.v[0].value = 127; + mii->analog.v[1].value = 127; + do { + ssize_t rd = read(fd, &event, sizeof(event)); + if (rd != sizeof(event)) { + perror(__func__); + break; + } + switch (event.type) { + case JS_EVENT_BUTTON: + // printf("button %u %s\n", event.number, event.value ? "pressed" : "released"); + switch (event.number) { + case 2 ... 3: + mii_bank_poke(&mii->bank[MII_BANK_MAIN], + 0xc061 + (event.number - 2), + event.value ? 0x80 : 0); + break; + case 4 ... 5: + mii_bank_poke(&mii->bank[MII_BANK_MAIN], + 0xc061 + (event.number - 4), + event.value ? 0x80 : 0); + break; + } + break; + case JS_EVENT_AXIS: + switch (event.number) { + case 0 ... 1: {// X + uint32_t v = (event.value + 0x8000) / 256; + if (v > 255) + v = 255; + mii->analog.v[event.number ? 1 : 0].value = v; +// printf("axis %u %6d %3dx%3d\n" +// event.number, event.value, +// mii->analog.v[0].value, mii->analog.v[1].value); + } break; + } + break; + default: + /* Ignore init events. */ + break; + } + } while (1); + close(fd); + printf("Joystick thread terminated\n"); + return NULL; +} +#endif + void mii_thread_start( mii_t *mii) { const mii_th_fifo_t zero = {}; signal_fifo = zero; - pthread_create(&mii_thread, NULL, mii_thread_func, mii); + + pthread_t thread; + pthread_create(&thread, NULL, mii_thread_cpu_regulator, mii); +#if HAVE_JOYSTICK + pthread_create(&thread, NULL, mii_thread_joystick, mii); +#endif } struct mii_th_fifo_t* diff --git a/src/mii.c b/src/mii.c index aa83ddf..8ea25c1 100644 --- a/src/mii.c +++ b/src/mii.c @@ -313,6 +313,11 @@ mii_access_soft_switches( res = true; mii_speaker_click(&mii->speaker); break; + case 0xc064 ... 0xc067: // Joystick, buttons + case 0xc070: // Analog reset + res = true; + mii_analog_access(mii, &mii->analog, addr, byte, write); + break; case 0xc068: res = true; // IIgs register, read by prodos tho diff --git a/src/mii_analog.c b/src/mii_analog.c new file mode 100644 index 0000000..e56e4dc --- /dev/null +++ b/src/mii_analog.c @@ -0,0 +1,59 @@ +/* + * mii_analog.c + * + * Copyright (C) 2023 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include "mii.h" +#include "mii_analog.h" + +void +mii_analog_init( + struct mii_t *mii, + mii_analog_t * a ) +{ + memset(a, 0, sizeof(*a)); +} + +/* + * https://retrocomputing.stackexchange.com/questions/15093/how-do-i-read-the-position-of-an-apple-ii-joystick + */ +void +mii_analog_access( + mii_t *mii, + mii_analog_t * a, + uint16_t addr, + uint8_t * byte, + bool write) +{ + if (write) + return; + switch (addr) { + case 0xc070: { + // multiplying by mii->speed allows reading joystick in 'fast' mode, + // this basically simulate slowing down just for the joystick reading + + /* TODO: According to various artivles, the multiplier ought + * to be 11, but we're not making the count here, which means it's + * likely the emulated core is missing a cycle for one instruction + * somewhere... */ + for (int i = 0; i < 4; i++) { + a->v[i].decay = mii->cycles + + ((a->v[i].value * 10.10) * mii->speed); + // printf("joystick %d: %d\n", i, a->v[i].value); + } + } break; + case 0xc064 ... 0xc067: { + addr -= 0xc064; + *byte = mii->cycles <= a->v[addr].decay ? 0x80 : 0x00; + } break; + } +} + diff --git a/src/mii_analog.h b/src/mii_analog.h new file mode 100644 index 0000000..4f4aab8 --- /dev/null +++ b/src/mii_analog.h @@ -0,0 +1,33 @@ +/* + * mii_analog.h + * + * Copyright (C) 2023 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +#include "mii_types.h" + + +typedef struct mii_analog_t { + struct { + uint8_t value; + mii_cycles_t decay; + } v[4]; +} mii_analog_t; + +struct mii_t; + +void +mii_analog_init( + struct mii_t *mii, + mii_analog_t * analog ); + +void +mii_analog_access( + mii_t *mii, + mii_analog_t * analog, + uint16_t addr, + uint8_t * byte, + bool write);