diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fb24cf..da76cd7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: working-directory: ${{github.workspace}}/build run: | mkdir -p $OUTPUT_DIR - cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="GUS" -DUSE_LTO= + cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="GUS" -DUSE_LTO=1 -DUSB_JOYSTICK=1 cmake --build . --config $BUILD_TYPE --parallel $(nproc) cp picogus.uf2 $OUTPUT_DIR/pg-gus.uf2 @@ -58,7 +58,7 @@ jobs: working-directory: ${{github.workspace}}/build shell: bash run: | - cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="OPL" -DUSE_LTO= + cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="OPL" -DUSE_LTO=1 -DUSB_JOYSTICK=1 cmake --build . --config $BUILD_TYPE --parallel $(nproc) cp picogus.uf2 $OUTPUT_DIR/pg-adlib.uf2 @@ -66,7 +66,7 @@ jobs: working-directory: ${{github.workspace}}/build shell: bash run: | - cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="MPU" -DUSE_LTO= + cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="MPU" -DUSE_LTO=1 -DUSB_JOYSTICK=1 cmake --build . --config $BUILD_TYPE --parallel $(nproc) cp picogus.uf2 $OUTPUT_DIR/pg-mpu.uf2 @@ -74,7 +74,7 @@ jobs: working-directory: ${{github.workspace}}/build shell: bash run: | - cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="TANDY" -DUSE_LTO= + cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="TANDY" -DUSE_LTO=1 -DUSB_JOYSTICK=1 cmake --build . --config $BUILD_TYPE --parallel $(nproc) cp picogus.uf2 $OUTPUT_DIR/pg-tandy.uf2 @@ -82,18 +82,10 @@ jobs: working-directory: ${{github.workspace}}/build shell: bash run: | - cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="CMS" -DUSE_LTO= + cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="CMS" -DUSE_LTO=1 -DUSB_JOYSTICK=1 cmake --build . --config $BUILD_TYPE --parallel $(nproc) cp picogus.uf2 $OUTPUT_DIR/pg-cms.uf2 - - name: Build Joy Firmware - working-directory: ${{github.workspace}}/build - shell: bash - run: | - cmake $GITHUB_WORKSPACE/sw -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DPROJECT_TYPE="JOY" -DUSE_LTO= - cmake --build . --config $BUILD_TYPE --parallel $(nproc) - cp picogus.uf2 $OUTPUT_DIR/pg-joy.uf2 - # will generate PicoGUS Firmwares.zip as downloadable artifact with all .uf2 files - name: Upload All Firmwares uses: actions/upload-artifact@v3 diff --git a/.gitmodules b/.gitmodules index 0002ae5..a354889 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "sw/rp2040-psram"] path = sw/rp2040-psram url = https://github.com/polpo/rp2040-psram.git +[submodule "sw/tinyusb"] + path = sw/tinyusb + url = https://github.com/hathach/tinyusb +[submodule "sw/tusb_xinput"] + path = sw/tusb_xinput + url = https://github.com/RobertDaleSmith/tusb_xinput diff --git a/pgusinit/pgusinit.c b/pgusinit/pgusinit.c index 29a511b..35ba40d 100755 --- a/pgusinit/pgusinit.c +++ b/pgusinit/pgusinit.c @@ -30,7 +30,7 @@ typedef enum { } card_mode_t; void banner(void) { - printf("PicoGUSinit v1.3.0 (c) 2023 Ian Scott - licensed under the GNU GPL v2\n\n"); + printf("PicoGUSinit v1.3.0-beta0 (c) 2023 Ian Scott - licensed under the GNU GPL v2\n\n"); } const char* usage_by_card[] = { @@ -47,11 +47,12 @@ void usage(char *argv0, card_mode_t mode) { fprintf(stderr, "Usage:\n"); fprintf(stderr, " %s [/?] | [/f fw.uf2]", argv0); if (mode <= CMS_MODE) { - fprintf(stderr, " | %s", usage_by_card[mode]); + fprintf(stderr, " | [/j] %s", usage_by_card[mode]); } fprintf(stderr, "\n\n"); - fprintf(stderr, " /? - show this message\n"); - fprintf(stderr, " /f fw.uf2 - Program the PicoGUS with the firmware file fw.uf2.\n"); + fprintf(stderr, " /? - show this message\n"); + fprintf(stderr, " /f fw.uf2 - program the PicoGUS with the firmware file fw.uf2\n"); + fprintf(stderr, " /j - enable USB joystick support\n"); if (mode != GUS_MODE) { fprintf(stderr, "AdLib, MPU-401, Tandy, CMS modes only:\n"); fprintf(stderr, " /p x - set the (hex) base port address of the emulated card. Defaults:\n"); @@ -209,6 +210,8 @@ int write_firmware(const char* fw_filename, uint8_t protocol) { // Put card into programming mode outp(CONTROL_PORT, 0xCC); // Knock on the door... outp(CONTROL_PORT, 0xFF); // Select firmware programming mode + // Wait a bit for 2nd core on Pico to restart + delay(100); if (inp(DATA_PORT_HIGH) != PICO_FIRMWARE_IDLE) { fprintf(stderr, "ERROR: Card is not in programming mode?\n"); return 13; @@ -275,6 +278,7 @@ int main(int argc, char* argv[]) { card_mode_t mode; uint8_t mpu_delaysysex = 0; uint8_t mpu_fakeallnotesoff = 0; + uint8_t enable_joystick = 0; banner(); // Get magic value from port on PicoGUS that is not on real GUS @@ -295,6 +299,8 @@ int main(int argc, char* argv[]) { if (stricmp(argv[i], "/?") == 0) { usage(argv[0], mode); return 0; + } else if (stricmp(argv[i], "/j") == 0) { + enable_joystick = 1; } else if (stricmp(argv[i], "/a") == 0) { if (i + 1 >= argc) { usage(argv[0], mode); @@ -390,6 +396,10 @@ int main(int argc, char* argv[]) { port = inpw(DATA_PORT_LOW); // Get port } + outp(CONTROL_PORT, 0x0f); // Select joystick enable register + outp(DATA_PORT_HIGH, enable_joystick); + printf("USB joystick support %s\n", enable_joystick ? "enabled" : "disabled"); + switch(mode) { case GUS_MODE: init_gus(argv[0]); diff --git a/sw/CMakeLists.txt b/sw/CMakeLists.txt index 53246c6..935a582 100644 --- a/sw/CMakeLists.txt +++ b/sw/CMakeLists.txt @@ -25,6 +25,10 @@ set(CMAKE_BUILD_TYPE "Release") set(PICO_COPY_TO_RAM 1) #set(SKIP_PICO_MALLOC 1) +# Tell the Pico SDK to use our local tinyusb instead of the one included in the SDK +# This is needed to support tusb_xinput +set(PICO_TINYUSB_PATH ${CMAKE_CURRENT_LIST_DIR}/tinyusb) + if(NOT PROJECT_TYPE) set(PROJECT_TYPE "GUS") message("PROJECT_TYPE not set, defaulting to ${PROJECT_TYPE}") @@ -33,7 +37,7 @@ endif() # Initialise the Raspberry Pi Pico SDK pico_sdk_init() -if(DEFINED USE_LTO) +if(USE_LTO) # Pico SDK must be patched to allow for LTO so it is not enabled by default. # To patch to Pico SDK, run: # sed -i 's/WRAPPER_FUNC(x) __wrap_/WRAPPER_FUNC(x) __attribute__((used)) __wrap_/' "$PICO_SDK_PATH"/src/rp2_common/pico_platform/include/pico/platform.h @@ -103,11 +107,18 @@ if(PROJECT_TYPE STREQUAL "GUS") endif() pico_enable_stdio_usb(picogus 0) pico_enable_stdio_semihosting(picogus 0) + # Joystick stuff + target_sources(picogus PRIVATE joy_hid/joy_hid.c) + target_link_libraries(picogus tinyusb_host tinyusb_board hardware_pwm) + target_include_directories(picogus PUBLIC ${CMAKE_CURRENT_LIST_DIR}/joy_hid) + target_compile_definitions(picogus PRIVATE USB_JOYSTICK=1) + endif() if(PROJECT_TYPE STREQUAL "OPL") pico_set_program_name(picogus "picogus-adlib") target_sources(picogus PRIVATE adlibplay.cpp) + target_link_libraries( picogus opl @@ -167,7 +178,7 @@ if(PROJECT_TYPE STREQUAL "CMS") pico_pic.c square/square.cpp ) - target_sources(picogus PRIVATE cmsplay.cpp) + pico_enable_stdio_uart(picogus 1) pico_enable_stdio_usb(picogus 0) pico_enable_stdio_semihosting(picogus 0) @@ -175,25 +186,28 @@ endif() if(PROJECT_TYPE STREQUAL "JOY") pico_set_program_name(picogus "picogus-joy") + set(USB_JOYSTICK=1) target_compile_definitions(picogus PRIVATE - USB_JOYSTICK=1 + USB_JOYSTICK_ONLY=1 ) - + target_sources(picogus PRIVATE - joy_hid/joy_hid.c usbplay.cpp ) - pico_enable_stdio_uart(picogus 1) - pico_enable_stdio_usb(picogus 0) - pico_enable_stdio_semihosting(picogus 0) + pico_enable_stdio_uart(picogus 1) + pico_enable_stdio_usb(picogus 0) + pico_enable_stdio_semihosting(picogus 0) +endif() - # Make sure TinyUSB can find tusb_config.h - target_include_directories(picogus PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/joy_hid) - - # In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_host - # for TinyUSB device support and tinyusb_board for the additional board support library used by the example - target_link_libraries(picogus tinyusb_host tinyusb_board hardware_pwm) +if(USB_JOYSTICK) + # Joystick stuff + target_sources(picogus PRIVATE joy_hid/joy_hid.c) + target_link_libraries(picogus hardware_pwm) + target_include_directories(picogus PUBLIC ${CMAKE_CURRENT_LIST_DIR}/joy_hid) + target_link_libraries(picogus tinyusb_host) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/tusb_xinput) + target_link_libraries(picogus xinput_host_app_driver) + target_compile_definitions(picogus PRIVATE USB_JOYSTICK=1) endif() target_sources(picogus PRIVATE M62429/M62429.cpp) diff --git a/sw/adlibplay.cpp b/sw/adlibplay.cpp index 994322a..6300814 100644 --- a/sw/adlibplay.cpp +++ b/sw/adlibplay.cpp @@ -20,6 +20,8 @@ #include "opl.h" +#include "tusb.h" + #if PICO_ON_DEVICE #include "pico/binary_info.h" bi_decl(bi_3pins_with_names(PICO_AUDIO_I2S_DATA_PIN, "I2S DIN", PICO_AUDIO_I2S_CLOCK_PIN_BASE, "I2S BCK", PICO_AUDIO_I2S_CLOCK_PIN_BASE+1, "I2S LRCK")); @@ -69,11 +71,16 @@ void play_adlib() { puts("starting core 1"); uint32_t start, end; + // Init TinyUSB for joystick support + tuh_init(BOARD_TUH_RHPORT); + struct audio_buffer_pool *ap = init_audio(); for (;;) { struct audio_buffer *buffer = take_audio_buffer(ap, true); OPL_Pico_Mix_callback(buffer); // putchar((unsigned char)buffer->buffer->bytes[1]); give_audio_buffer(ap, buffer); + // Service TinyUSB events + tuh_task(); } } diff --git a/sw/cmsplay.cpp b/sw/cmsplay.cpp index b76f983..eca28cc 100644 --- a/sw/cmsplay.cpp +++ b/sw/cmsplay.cpp @@ -19,6 +19,8 @@ #include "pico/audio_i2s.h" +#include "tusb.h" + #ifdef MAME_CMS #include "saa1099/saa1099.h" saa1099_device *saa0, *saa1; @@ -72,6 +74,9 @@ struct audio_buffer_pool *init_audio() { void play_cms() { puts("starting core 1 CMS"); + + // Init TinyUSB for joystick support + tuh_init(BOARD_TUH_RHPORT); #ifdef MAME_CMS puts("Creating SAA1099 1"); saa0 = new saa1099_device(7159090); @@ -155,5 +160,7 @@ void play_cms() { buffer->sample_count = buffer->max_sample_count; give_audio_buffer(ap, buffer); + // Service TinyUSB events + tuh_task(); } } diff --git a/sw/gusplay.cpp b/sw/gusplay.cpp index c0ced06..ecec6f3 100644 --- a/sw/gusplay.cpp +++ b/sw/gusplay.cpp @@ -20,6 +20,8 @@ #include "pico_pic.h" #endif +#include "tusb.h" + #ifdef PSRAM #include "psram_spi.h" extern psram_spi_inst_t psram_spi; @@ -112,6 +114,8 @@ void play_gus() { #endif #endif GUS_Setup(); + // Init TinyUSB for joystick support + tuh_init(BOARD_TUH_RHPORT); struct audio_buffer_pool *ap = init_audio(); for (;;) { @@ -143,5 +147,7 @@ void play_gus() { */ // gpio_xor_mask(1u << PICO_DEFAULT_LED_PIN); give_audio_buffer(ap, buffer); + // Service TinyUSB events + tuh_task(); } } diff --git a/sw/joy_hid/joy_hid.c b/sw/joy_hid/joy_hid.c index ae0882c..198442c 100644 --- a/sw/joy_hid/joy_hid.c +++ b/sw/joy_hid/joy_hid.c @@ -25,6 +25,7 @@ /* #include "bsp/board_api.h" */ #include "tusb.h" +#include "xinput_host.h" #include "joy.h" joystate_struct_t joystate_struct; @@ -107,44 +108,29 @@ typedef struct TU_ATTR_PACKED } sony_ds4_report_t; -typedef struct TU_ATTR_PACKED { - // First 16 bits set what data is pertinent in this structure (1 = set; 0 = not set) - uint8_t set_rumble : 1; - uint8_t set_led : 1; - uint8_t set_led_blink : 1; - uint8_t set_ext_write : 1; - uint8_t set_left_volume : 1; - uint8_t set_right_volume : 1; - uint8_t set_mic_volume : 1; - uint8_t set_speaker_volume : 1; - uint8_t set_flags2; +typedef struct TU_ATTR_PACKED +{ + uint8_t x_axis, y_axis, z_axis, rz_axis; // joystick - uint8_t reserved; + struct { + uint8_t dpad : 4; // (hat format, 0x08 is released, 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW) + uint8_t a : 1; // west + uint8_t b : 1; // south + uint8_t x : 1; // east + uint8_t y : 1; // north + }; - uint8_t motor_right; - uint8_t motor_left; - - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; - uint8_t lightbar_blink_on; - uint8_t lightbar_blink_off; - - uint8_t ext_data[8]; - - uint8_t volume_left; - uint8_t volume_right; - uint8_t volume_mic; - uint8_t volume_speaker; - - uint8_t other[9]; -} sony_ds4_output_report_t; - -static bool ds4_mounted = false; -static uint8_t ds4_dev_addr = 0; -static uint8_t ds4_instance = 0; -static uint8_t motor_left = 0; -static uint8_t motor_right = 0; + struct { + uint8_t l1 : 1; + uint8_t r1 : 1; + uint8_t l2 : 1; + uint8_t r2 : 1; + uint8_t start : 1; + uint8_t back : 1; + uint8_t l3 : 1; + uint8_t r3 : 1; + }; +} generic_report_t; // check if device is Sony DualShock 4 static inline bool is_sony_ds4(uint8_t dev_addr) @@ -159,31 +145,34 @@ static inline bool is_sony_ds4(uint8_t dev_addr) ); } -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -void hid_app_task(void) +static inline bool is_generic(uint8_t dev_addr) { - /* - if (ds4_mounted) - { - const uint32_t interval_ms = 200; - static uint32_t start_ms = 0; + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); - uint32_t current_time_ms = board_millis(); - if ( current_time_ms - start_ms >= interval_ms) - { - start_ms = current_time_ms; + return ( (vid == 0x2e24 && pid == 0x16fa ) // Hyperkin thing + ); +} - sony_ds4_output_report_t output_report = {0}; - output_report.set_rumble = 1; - output_report.motor_left = motor_left; - output_report.motor_right = motor_right; - tuh_hid_send_report(ds4_dev_addr, ds4_instance, 5, &output_report, sizeof(output_report)); - } - } - */ + +static inline bool is_xbox(uint8_t dev_addr) +{ + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); + + return ( (vid == 0x2563 && pid == 0x0575 ) // Clone Xbox 360 controller + || (vid == 0x045E && (pid == 0x028E || pid == 0x028f || pid == 0x0291)) // Official Xbox 360 wired controller + || (vid == 0x046d && (pid == 0xc21d || pid == 0xc21e || pid == 0xc21f || pid == 0xc216)) // Official Xbox 360 wired controller + ); +} + +static inline bool is_ps_classic(uint8_t dev_addr) +{ + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); + + return ( (vid == 0x054c && pid == 0x0cda) // PS Classic USB controller + ); } //--------------------------------------------------------------------+ @@ -206,18 +195,11 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re printf("VID = %04x, PID = %04x\r\n", vid, pid); // Sony DualShock 4 [CUH-ZCT2x] - if ( is_sony_ds4(dev_addr) ) + if ( is_sony_ds4(dev_addr) | is_xbox(dev_addr) | is_generic(dev_addr) ) { - if (!ds4_mounted) - { - ds4_dev_addr = dev_addr; - ds4_instance = instance; - motor_left = 0; - motor_right = 0; - ds4_mounted = true; - } // request to receive report // tuh_hid_report_received_cb() will be invoked when report is available + printf("requesting report from it\n"); if ( !tuh_hid_receive_report(dev_addr, instance) ) { printf("Error: cannot request to receive report\r\n"); @@ -229,10 +211,6 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); - if (ds4_mounted && ds4_dev_addr == dev_addr && ds4_instance == instance) - { - ds4_mounted = false; - } } // check if different than 2 @@ -256,25 +234,9 @@ bool diff_report(sony_ds4_report_t const* rpt1, sony_ds4_report_t const* rpt2) return result; } -void process_sony_ds4(uint8_t const* report, uint16_t len) -{ - const char* dpad_str[] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW", "none" }; - - // previous report used to compare for changes - static sony_ds4_report_t prev_report = { 0 }; - - uint8_t const report_id = report[0]; - report++; - len--; - - // all buttons state is stored in ID 1 - if (report_id == 1) - { - sony_ds4_report_t ds4_report; - memcpy(&ds4_report, report, sizeof(ds4_report)); - - joystate_struct.button_mask = (!ds4_report.cross << 4) | (!ds4_report.circle << 5) | (!ds4_report.square << 6) | (!ds4_report.triangle << 7); - switch (ds4_report.dpad) { +void update_joystate(uint8_t dpad, uint8_t x, uint8_t y, uint8_t z, uint8_t rz, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4) { + joystate_struct.button_mask = (!b1 << 4) | (!b2 << 5) | (!b3 << 6) | (!b4 << 7); + switch (dpad) { case 0: // N joystate_struct.joy1_x = 127; joystate_struct.joy1_y = 0; @@ -308,61 +270,109 @@ void process_sony_ds4(uint8_t const* report, uint16_t len) joystate_struct.joy1_y = 0; break; case 8: // None - use analog stick instead - joystate_struct.joy1_x = ds4_report.x; - joystate_struct.joy1_y = ds4_report.y; + joystate_struct.joy1_x = x; + joystate_struct.joy1_y = y; break; } - joystate_struct.joy2_x = ds4_report.z; - joystate_struct.joy2_y = ds4_report.rz; + joystate_struct.joy2_x = z; + joystate_struct.joy2_y = rz; +} +inline void process_generic(uint8_t const* report, uint16_t len) +{ + uint8_t const report_id = report[0]; + report++; + len--; + + // all buttons state is stored in ID 1 + if (report_id == 1) + { + generic_report_t generic_report; + memcpy(&generic_report, report, sizeof(generic_report)); + + update_joystate(generic_report.dpad, generic_report.x_axis, generic_report.y_axis, generic_report.z_axis, generic_report.rz_axis, + generic_report.a, generic_report.b, generic_report.x, generic_report.y); return; - - // counter is +1, assign to make it easier to compare 2 report - prev_report.counter = ds4_report.counter; - // only print if changes since it is polled ~ 5ms - // Since count+1 after each report and x, y, z, rz fluctuate within 1 or 2 - // We need more than memcmp to check if report is different enough - if ( diff_report(&prev_report, &ds4_report) ) - { - printf("(x, y, z, rz) = (%u, %u, %u, %u)\r\n", ds4_report.x, ds4_report.y, ds4_report.z, ds4_report.rz); - printf("DPad = %s ", dpad_str[ds4_report.dpad]); + const char* dpad_str[] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW", "none" }; - if (ds4_report.square ) printf("Square "); - if (ds4_report.cross ) printf("Cross "); - if (ds4_report.circle ) printf("Circle "); - if (ds4_report.triangle ) printf("Triangle "); + printf("(x, y, z, rz) = (%u, %u, %u, %u)\r\n", generic_report.x_axis, generic_report.y_axis, generic_report.z_axis, generic_report.rz_axis); + printf("DPad = %s ", dpad_str[generic_report.dpad]); - if (ds4_report.l1 ) printf("L1 "); - if (ds4_report.r1 ) printf("R1 "); - if (ds4_report.l2 ) printf("L2 "); - if (ds4_report.r2 ) printf("R2 "); + if (generic_report.a ) printf("A "); + if (generic_report.b ) printf("B "); + if (generic_report.x ) printf("X "); + if (generic_report.y ) printf("Y "); - if (ds4_report.share ) printf("Share "); - if (ds4_report.option ) printf("Option "); - if (ds4_report.l3 ) printf("L3 "); - if (ds4_report.r3 ) printf("R3 "); + if (generic_report.l1 ) printf("L1 "); + if (generic_report.r1 ) printf("R1 "); + if (generic_report.l2 ) printf("L2 "); + if (generic_report.r2 ) printf("R2 "); - if (ds4_report.ps ) printf("PS "); - if (ds4_report.tpad ) printf("TPad "); + if (generic_report.start ) printf("Share "); + if (generic_report.back ) printf("Option "); + if (generic_report.l3 ) printf("L3 "); + if (generic_report.r3 ) printf("R3 "); - printf("\r\n"); - } + printf("\r\n"); + } +} - // The left and right triggers control the intensity of the left and right rumble motors - motor_left = ds4_report.l2_trigger; - motor_right = ds4_report.r2_trigger; +inline void process_sony_ds4(uint8_t const* report, uint16_t len) +{ + uint8_t const report_id = report[0]; + report++; + len--; - prev_report = ds4_report; + // all buttons state is stored in ID 1 + if (report_id == 1) + { + sony_ds4_report_t ds4_report; + memcpy(&ds4_report, report, sizeof(ds4_report)); + + update_joystate(ds4_report.dpad, ds4_report.x, ds4_report.y, ds4_report.z, ds4_report.rz, + ds4_report.cross, ds4_report.circle, ds4_report.square, ds4_report.triangle); + return; + + const char* dpad_str[] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW", "none" }; + + printf("(x, y, z, rz) = (%u, %u, %u, %u)\r\n", ds4_report.x, ds4_report.y, ds4_report.z, ds4_report.rz); + printf("DPad = %s ", dpad_str[ds4_report.dpad]); + + if (ds4_report.square ) printf("Square "); + if (ds4_report.cross ) printf("Cross "); + if (ds4_report.circle ) printf("Circle "); + if (ds4_report.triangle ) printf("Triangle "); + + if (ds4_report.l1 ) printf("L1 "); + if (ds4_report.r1 ) printf("R1 "); + if (ds4_report.l2 ) printf("L2 "); + if (ds4_report.r2 ) printf("R2 "); + + if (ds4_report.share ) printf("Share "); + if (ds4_report.option ) printf("Option "); + if (ds4_report.l3 ) printf("L3 "); + if (ds4_report.r3 ) printf("R3 "); + + if (ds4_report.ps ) printf("PS "); + if (ds4_report.tpad ) printf("TPad "); + + printf("\r\n"); } } // Invoked when received report from device via interrupt endpoint void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) { + /* putchar('.'); */ if ( is_sony_ds4(dev_addr) ) { process_sony_ds4(report, len); + /* process_generic(report, len); */ + } else if ( is_generic(dev_addr) ) + { + process_generic(report, len); + /* process_generic(report, len); */ } // continue to request to receive report @@ -371,3 +381,68 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons printf("Error: cannot request to receive report\r\n"); } } + +inline void update_joystate_xinput(uint16_t wButtons, int16_t sThumbLX, int16_t sThumbLY, int16_t sThumbRX, int16_t sThumbRY, uint8_t bLeftTrigger, uint8_t bRightTrigger) { + uint8_t dpad = wButtons & 0xf; + if (!dpad) { + joystate_struct.joy1_x = ((int32_t)sThumbLX + 32768) >> 8; + joystate_struct.joy1_y = ((-(int32_t)sThumbLY) + 32767) >> 8; + } else { + joystate_struct.joy1_x = (dpad & XINPUT_GAMEPAD_DPAD_RIGHT) ? 255 : ((dpad & XINPUT_GAMEPAD_DPAD_LEFT) ? 0 : 127); + joystate_struct.joy1_y = (dpad & XINPUT_GAMEPAD_DPAD_DOWN) ? 255 : ((dpad & XINPUT_GAMEPAD_DPAD_UP) ? 0 : 127); + } + if (bLeftTrigger) { + joystate_struct.joy1_y = 127u + (bLeftTrigger >> 1); + } else if (bRightTrigger) { + joystate_struct.joy1_y = 127u - (bRightTrigger >> 1); + } + joystate_struct.joy2_x = ((int32_t)sThumbRX + 32768) >> 8; + joystate_struct.joy2_y = ((-(int32_t)sThumbRY) + 32767) >> 8; + joystate_struct.button_mask = (~(wButtons >> 12)) << 4; + /* printf("%04x %04x\n", wButtons, joystate_struct.button_mask); */ +} + +void tuh_xinput_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) +{ + xinputh_interface_t *xid_itf = (xinputh_interface_t *)report; + xinput_gamepad_t *p = &xid_itf->pad; + /* const char* type_str; */ + /* switch (xid_itf->type) */ + /* { */ + /* case 1: type_str = "Xbox One"; break; */ + /* case 2: type_str = "Xbox 360 Wireless"; break; */ + /* case 3: type_str = "Xbox 360 Wired"; break; */ + /* case 4: type_str = "Xbox OG"; break; */ + /* default: type_str = "Unknown"; */ + /* } */ + + if (xid_itf->connected && xid_itf->new_pad_data) + { + /* printf("[%02x, %02x], Type: %s, Buttons %04x, LT: %02x RT: %02x, LX: %d, LY: %d, RX: %d, RY: %d\n", */ + /* dev_addr, instance, type_str, p->wButtons, p->bLeftTrigger, p->bRightTrigger, p->sThumbLX, p->sThumbLY, p->sThumbRX, p->sThumbRY); */ + + update_joystate_xinput(p->wButtons, p->sThumbLX, p->sThumbLY, p->sThumbRX, p->sThumbRY, p->bLeftTrigger, p->bRightTrigger); + } + tuh_xinput_receive_report(dev_addr, instance); +} + +void tuh_xinput_mount_cb(uint8_t dev_addr, uint8_t instance, const xinputh_interface_t *xinput_itf) +{ + printf("XINPUT MOUNTED %02x %d\n", dev_addr, instance); + // If this is a Xbox 360 Wireless controller we need to wait for a connection packet + // on the in pipe before setting LEDs etc. So just start getting data until a controller is connected. + if (xinput_itf->type == XBOX360_WIRELESS && xinput_itf->connected == false) + { + tuh_xinput_receive_report(dev_addr, instance); + return; + } + tuh_xinput_set_led(dev_addr, instance, 0, true); + tuh_xinput_set_led(dev_addr, instance, 1, true); + tuh_xinput_set_rumble(dev_addr, instance, 0, 0, true); + tuh_xinput_receive_report(dev_addr, instance); +} + +void tuh_xinput_umount_cb(uint8_t dev_addr, uint8_t instance) +{ + printf("XINPUT UNMOUNTED %02x %d\n", dev_addr, instance); +} diff --git a/sw/joy_hid/tusb_config.h b/sw/joy_hid/tusb_config.h index e1869d5..6660603 100644 --- a/sw/joy_hid/tusb_config.h +++ b/sw/joy_hid/tusb_config.h @@ -33,6 +33,9 @@ //--------------------------------------------------------------------+ // Board Specific Configuration //--------------------------------------------------------------------+ +#define CFG_TUSB_MCU OPT_MCU_RP2040 + +#define CFG_TUH_XINPUT 1 #if CFG_TUSB_MCU == OPT_MCU_RP2040 // change to 1 if using pico-pio-usb as host controller for raspberry rp2040 diff --git a/sw/mpuplay.cpp b/sw/mpuplay.cpp index 1061595..79b4780 100644 --- a/sw/mpuplay.cpp +++ b/sw/mpuplay.cpp @@ -4,18 +4,25 @@ #include "pico_pic.h" #endif +#include "tusb.h" + #include "mpu401/export.h" void play_mpu() { - puts("starting core 1"); + puts("starting core 1 MPU"); + + // Init TinyUSB for joystick support + tuh_init(BOARD_TUH_RHPORT); #ifdef USE_ALARM // Init PIC on this core so it handles timers PIC_Init(); + puts("pic inited on core 1"); #endif - puts("pic inited"); for (;;) { send_midi_byte(); // see if we need to send a byte + // Service TinyUSB events + tuh_task(); } } diff --git a/sw/picogus.cpp b/sw/picogus.cpp index c6a619c..6671258 100644 --- a/sw/picogus.cpp +++ b/sw/picogus.cpp @@ -85,8 +85,10 @@ cms_buffer_t cms_buffer = { {0}, 0, 0 }; #endif #ifdef USB_JOYSTICK -static uint16_t basePort = 0x201u; +static uint16_t joyPort = 0xffffu; +#ifdef USB_JOYSTICK_ONLY void play_usb(void); +#endif #include "joy_hid/joy.h" extern "C" joystate_struct_t joystate_struct; uint8_t joystate_bin; @@ -127,6 +129,8 @@ __force_inline void select_picogus(uint8_t value) { case 0x04: // Base port basePort_tmp = 0; break; + case 0x0f: // enable joystick + break; #ifdef SOUND_GUS case 0x10: // Audio buffer size case 0x11: // DMA interval @@ -158,12 +162,16 @@ __force_inline void write_picogus_low(uint8_t value) { __force_inline void write_picogus_high(uint8_t value) { switch (sel_reg) { case 0x04: // Base port +#if defined(SOUND_GUS) || defined(SOUND_OPL) || defined(SOUND_MPU) || defined(SOUND_TANDY) || defined(SOUND_CMS) basePort = (value << 8) | basePort_tmp; +#endif #ifdef SOUND_GUS gus_port_test = basePort >> 4 | 0x10; // GUS_SetPort(basePort); #endif break; + case 0x0f: // enable joystick + joyPort = value ? 0x201u : 0xffff; #ifdef SOUND_GUS case 0x10: // Audio buffer size // Value is sent by pgusinit as the size - 1, so we need to add 1 back to it @@ -241,6 +249,8 @@ __force_inline uint8_t read_picogus_high(void) { return 0xff; #endif break; + case 0x0f: // enable joystick + return joyPort == 0x201u; #ifdef SOUND_MPU case 0x20: // Wavetable mixer volume if (BOARD_TYPE == PICOGUS_2) { @@ -372,14 +382,22 @@ __force_inline void handle_iow(void) { } #endif // SOUND_CMS #ifdef USB_JOYSTICK - if (port == basePort) { + if (port == joyPort) { pio_sm_put(pio0, iow_sm, IO_END); // Set times in # of cycles (affected by clkdiv) for each PWM slice to count up and wrap back to 0 // TODO better calibrate this - pwm_set_wrap(0, 2000 + (joystate_struct.joy1_x << 6)); - pwm_set_wrap(1, 2000 + (joystate_struct.joy1_y << 6)); - pwm_set_wrap(2, 2000 + (joystate_struct.joy2_x << 6)); - pwm_set_wrap(3, 2000 + (joystate_struct.joy2_y << 6)); + // GUS w/ gravis gamestick - + // sbdiag: 322/267 16/18 572/520 + // gravutil 34/29 2/2 61/56 + // checkit 556/451 23/25 984/902 + // Noname w/ gravis gamestick + // sbdiag: 256/214 7/6 495/435 + // gravutil 28/23 1/1 53/47 + // checkit 355/284 6/5 675/585 + pwm_set_wrap(0, 2000 + ((uint16_t)joystate_struct.joy1_x << 6)); + pwm_set_wrap(1, 2000 + ((uint16_t)joystate_struct.joy1_y << 6)); + pwm_set_wrap(2, 2000 + ((uint16_t)joystate_struct.joy2_x << 6)); + pwm_set_wrap(3, 2000 + ((uint16_t)joystate_struct.joy2_y << 6)); // Convince PWM to run as one-shot by immediately setting wrap to 0. This will take effect once the wrap // times set above hit, so after wrapping the counter value will stay at 0 instead of counting again pwm_set_wrap(0, 0); @@ -470,7 +488,7 @@ __force_inline void handle_ior(void) { } #endif // SOUND_CMS #ifdef USB_JOYSTICK - if (port == basePort) { + if (port == joyPort) { pio_sm_put(pio0, ior_sm, IO_WAIT); uint8_t value = // Proportional bits: 1 if counter is still counting, 0 otherwise @@ -694,7 +712,9 @@ int main() pwm_init(1, &pwm_c, true); pwm_init(2, &pwm_c, true); pwm_init(3, &pwm_c, true); +#ifdef USB_JOYSTICK_ONLY multicore_launch_core1(&play_usb); +#endif // USB_JOYSTICK_ONLY #endif // USB_JOYSTICK for(int i=AD0_PIN; i<(AD0_PIN + 10); ++i) { diff --git a/sw/tandyplay.cpp b/sw/tandyplay.cpp index 3565147..d3938d8 100644 --- a/sw/tandyplay.cpp +++ b/sw/tandyplay.cpp @@ -23,6 +23,8 @@ #include "cmd_buffers.h" extern tandy_buffer_t tandy_buffer; +#include "tusb.h" + #include #if PICO_ON_DEVICE @@ -69,6 +71,9 @@ struct audio_buffer_pool *init_audio() { void play_tandy() { puts("starting core 1 tandy"); + // Init TinyUSB for joystick support + tuh_init(BOARD_TUH_RHPORT); + tandysound_t tandysound; struct audio_buffer_pool *ap = init_audio(); #ifdef SQUARE_FLOAT_OUTPUT @@ -102,5 +107,7 @@ void play_tandy() { buffer->sample_count = SAMPLES_PER_BUFFER; give_audio_buffer(ap, buffer); + // Service TinyUSB events + tuh_task(); } } diff --git a/sw/tinyusb b/sw/tinyusb new file mode 160000 index 0000000..f84eafc --- /dev/null +++ b/sw/tinyusb @@ -0,0 +1 @@ +Subproject commit f84eafcaa2138a360e0baf8271b28f45cf120961 diff --git a/sw/tusb_xinput b/sw/tusb_xinput new file mode 160000 index 0000000..4a2882b --- /dev/null +++ b/sw/tusb_xinput @@ -0,0 +1 @@ +Subproject commit 4a2882bffa304c11d5b62ccb8c6f74fb42b2eb75 diff --git a/sw/usbplay.cpp b/sw/usbplay.cpp index 96c7af3..d1c1a01 100644 --- a/sw/usbplay.cpp +++ b/sw/usbplay.cpp @@ -1,6 +1,5 @@ #include -#include "bsp/board.h" #include "tusb.h" // #include