From d57396edf0bb49b3c30ed3dd369a8d9af56b3950 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Sun, 18 Dec 2022 06:33:38 +1100 Subject: bus/sms_ctrl: Added support for Denpa XE-1AP controller. This is the "horsehoe crab" game pad version of the Denpa Micom Soft Analog/Digital Intelligent Controller System (the other version is the desktop "cyber stick"). Mega Drive games only support this controller in analog mode (the default). Digital mode is designed to be used for games on computers with MSX-style controller ports that don't support analog controls. --- scripts/src/bus.lua | 2 + src/devices/bus/sms_ctrl/controllers.cpp | 4 +- src/devices/bus/sms_ctrl/controllers.h | 1 + src/devices/bus/sms_ctrl/xe1ap.cpp | 310 +++++++++++++++++++++++++++++++ src/devices/bus/sms_ctrl/xe1ap.h | 19 ++ 5 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 src/devices/bus/sms_ctrl/xe1ap.cpp create mode 100644 src/devices/bus/sms_ctrl/xe1ap.h diff --git a/scripts/src/bus.lua b/scripts/src/bus.lua index e2d290b55d3..d25711e9585 100644 --- a/scripts/src/bus.lua +++ b/scripts/src/bus.lua @@ -3579,6 +3579,8 @@ if (BUSES["SMS_CTRL"]~=null) then MAME_DIR .. "src/devices/bus/sms_ctrl/sportsjp.h", MAME_DIR .. "src/devices/bus/sms_ctrl/teamplayer.cpp", MAME_DIR .. "src/devices/bus/sms_ctrl/teamplayer.h", + MAME_DIR .. "src/devices/bus/sms_ctrl/xe1ap.cpp", + MAME_DIR .. "src/devices/bus/sms_ctrl/xe1ap.h", } end diff --git a/src/devices/bus/sms_ctrl/controllers.cpp b/src/devices/bus/sms_ctrl/controllers.cpp index 43e7c63dcc7..77b2925f98e 100644 --- a/src/devices/bus/sms_ctrl/controllers.cpp +++ b/src/devices/bus/sms_ctrl/controllers.cpp @@ -8,7 +8,6 @@ * Mega Modem (connects to EXP port on Mega Drive) * Sega Menacer (infrared wireless lightgun) * Konami Justifier (dual wired lightguns) - * Dempa XE-1AP (3-axis analog "Cyber Stick" pad) * EA 4-Play (impractical - connects to both CTRL1 and CTRL2) **********************************************************************/ @@ -31,6 +30,7 @@ #include "sports.h" #include "sportsjp.h" #include "teamplayer.h" +#include "xe1ap.h" char const *const SMS_CTRL_OPTION_DIY_PADDLE = "diypaddle"; @@ -49,6 +49,7 @@ char const *const SMS_CTRL_OPTION_SEGA_MOUSE = "mouse"; char const *const SMS_CTRL_OPTION_SPORTS = "sports"; char const *const SMS_CTRL_OPTION_SPORTS_JP = "sportsjp"; char const *const SMS_CTRL_OPTION_TEAM_PLAYER = "teamplay"; +char const *const SMS_CTRL_OPTION_XE1AP = "xe1ap"; @@ -70,6 +71,7 @@ void sms_control_port_devices(device_slot_interface &device) device.option_add(SMS_CTRL_OPTION_SPORTS, SMS_SPORTS_PAD); device.option_add(SMS_CTRL_OPTION_SPORTS_JP, SMS_SPORTS_PAD_JP); device.option_add(SMS_CTRL_OPTION_TEAM_PLAYER, SMS_TEAM_PLAYER); + device.option_add(SMS_CTRL_OPTION_XE1AP, SMS_XE1AP); } diff --git a/src/devices/bus/sms_ctrl/controllers.h b/src/devices/bus/sms_ctrl/controllers.h index 67868790831..a6cdbc14ed8 100644 --- a/src/devices/bus/sms_ctrl/controllers.h +++ b/src/devices/bus/sms_ctrl/controllers.h @@ -27,6 +27,7 @@ extern char const *const SMS_CTRL_OPTION_SEGA_MOUSE; extern char const *const SMS_CTRL_OPTION_SPORTS; extern char const *const SMS_CTRL_OPTION_SPORTS_JP; extern char const *const SMS_CTRL_OPTION_TEAM_PLAYER; +extern char const *const SMS_CTRL_OPTION_XE1AP; void sms_control_port_devices(device_slot_interface &device); void sms_control_port_passive_devices(device_slot_interface &device); diff --git a/src/devices/bus/sms_ctrl/xe1ap.cpp b/src/devices/bus/sms_ctrl/xe1ap.cpp new file mode 100644 index 00000000000..ee97bf1e8d0 --- /dev/null +++ b/src/devices/bus/sms_ctrl/xe1ap.cpp @@ -0,0 +1,310 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/********************************************************************** + + Dempa Micom Soft Analog/Digital Controller XE-1AP emulation + + PC pin Name MD pin Name Dir Signal + 1 Up 1 Up In D0 + 2 Down 2 Down In D1 + 3 Left 3 Left In D2 + 4 Right 4 Right In D3 + 6 TRIG1 6 TL In L/H + 7 TRIG2 9 TR In Ack + 8 STROBE 7 TH Out Req + + In analog mode, data is shifted out as eleven nybbles: + + _ ____________________________________________ + Req \_________/ + ____ __ __ __ __ __ __ __ __ + Ack \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ + _____ _____ _____ _____ + L/H XX\____/ \_____/ \_____/ \_____/ \_____X + ____ _____ _____ _____ _____ _____ _____ _____ _____ + D XXX____X_____X_____X_____X_____X_____X_____X_____X_____X + + The falling edge on Req causes data output to start. The host + can't control the speed, it just polls the L/H and Ack lines to + know when the data is ready to read. + + Step D0 D1 D2 D3 + 1 Select Start B' A' + 2 D C B A + 2 X4 X5 X6 X7 + 3 Y4 Y5 Y6 Y7 + 5 RZ4 RZ5 RZ6 RZ7 + 6 Z4 Z5 Z6 Z7 + 7 X0 X1 X2 X3 + 8 Y0 Y1 Y2 Y3 + 9 RZ0 RZ1 RZ2 RZ3 + 10 Z0 Z1 Z2 Z3 + 11 E2 E1 - - + + In digital mode, Req is a simple multiplexer input: + + Req 0 1 + D0 Up Throttle Up + D1 Down Throttle Down + D2 Left C + D3 Right D + L/H A E1 + Ack B E2 + + Start appears as simultaneous Left/Right + Select appears as simultaneous Up/Down + + This mode is almost compatible with a 6-button Towns Pad (on a + real 6-button Towns Pad, buttons A and B can be read in either + state, they bypass the multiplexer). + + TODO: + * Measure timings. + * What do the A/B Normal/Auto switches do? + * Confirm direction of RZ (throttle twist). + * Confirm A', B', E1 and E2 bit mappings in analog mode. + * Confirm buttons mapping in digital mode. + +**********************************************************************/ + +#include "emu.h" +#include "xe1ap.h" + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +namespace { + +class sms_xe1ap_device : public device_t, public device_sms_control_interface +{ +public: + sms_xe1ap_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual u8 in_r() override; + virtual void out_w(u8 data, u8 mem_mask) override; + + DECLARE_INPUT_CHANGED_MEMBER(mode_changed); + +protected: + virtual ioport_constructor device_input_ports() const override; + virtual void device_start() override; + +private: + TIMER_CALLBACK_MEMBER(step_output); + + required_ioport m_buttons; + required_ioport_array<4> m_axes; + required_ioport m_mode; + + emu_timer *m_output_timer; + + u8 m_req; + u8 m_out; +}; + + + +INPUT_PORTS_START( sms_xe1ap ) + PORT_START("BUTTONS") + PORT_BIT(0x0001, IP_ACTIVE_LOW, IPT_SELECT) // left face bottom + PORT_BIT(0x0002, IP_ACTIVE_LOW, IPT_START) // right face bottom + PORT_BIT(0x0004, IP_ACTIVE_LOW, IPT_BUTTON6) PORT_NAME("%p B'") // right face top inner + PORT_BIT(0x0008, IP_ACTIVE_LOW, IPT_BUTTON5) PORT_NAME("%p A'") // right face top outer + PORT_BIT(0x0010, IP_ACTIVE_LOW, IPT_BUTTON4) PORT_NAME("%p D") // left shoulder lower + PORT_BIT(0x0020, IP_ACTIVE_LOW, IPT_BUTTON3) PORT_NAME("%p C") // left shoulder upper + PORT_BIT(0x0040, IP_ACTIVE_LOW, IPT_BUTTON2) PORT_NAME("%p B") // right shoulder lower + PORT_BIT(0x0080, IP_ACTIVE_LOW, IPT_BUTTON1) PORT_NAME("%p A") // right shoulder upper + PORT_BIT(0x0100, IP_ACTIVE_LOW, IPT_BUTTON8) PORT_NAME("%p E2") // left face top inner + PORT_BIT(0x0200, IP_ACTIVE_LOW, IPT_BUTTON7) PORT_NAME("%p E1") // left face top outer + PORT_BIT(0x0c00, IP_ACTIVE_LOW, IPT_UNUSED) + + PORT_START("CH0") + PORT_BIT(0xff, 0x80, IPT_AD_STICK_X) PORT_SENSITIVITY(50) PORT_KEYDELTA(50) + + PORT_START("CH1") + PORT_BIT(0xff, 0x80, IPT_AD_STICK_Y) PORT_SENSITIVITY(50) PORT_KEYDELTA(50) + + PORT_START("CH2") + PORT_BIT(0xff, 0x80, IPT_PADDLE) PORT_SENSITIVITY(50) PORT_KEYDELTA(50) + + PORT_START("CH3") + PORT_BIT(0xff, 0x80, IPT_PADDLE_V) PORT_REVERSE PORT_SENSITIVITY(50) PORT_KEYDELTA(50) + + PORT_START("MODE") + PORT_CONFNAME(0x01, 0x01, "Mode") PORT_CHANGED_MEMBER(DEVICE_SELF, sms_xe1ap_device, mode_changed, 0) + PORT_CONFSETTING( 0x00, "Digital") + PORT_CONFSETTING( 0x01, "Analog") +INPUT_PORTS_END + + + +sms_xe1ap_device::sms_xe1ap_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, SMS_XE1AP, tag, owner, clock), + device_sms_control_interface(mconfig, *this), + m_buttons(*this, "BUTTONS"), + m_axes(*this, "CH%u", 0U), + m_mode(*this, "MODE"), + m_output_timer(nullptr), + m_req(1), + m_out(0x3f) +{ +} + + +u8 sms_xe1ap_device::in_r() +{ + if (BIT(m_mode->read(), 0)) + { + LOG( + "%s: analog mode read data = %02X\n", + machine().describe_context(), + m_out); + return m_out; + } + else + { + u16 const buttons = m_buttons->read(); + if (m_req) + { + u8 const x = m_axes[0]->read(); + u8 const y = m_axes[1]->read(); + u8 const result = + ((BIT(buttons, 0) && (0x40 <= y)) ? 0x01 : 0x00) | // Select/Up + ((BIT(buttons, 0) && (0xc0 > y)) ? 0x02 : 0x00) | // Select/Down + ((BIT(buttons, 1) && (0x40 <= x)) ? 0x04 : 0x00) | // Start/Left + ((BIT(buttons, 1) && (0xc0 > x)) ? 0x08 : 0x00) | // Start/Right + (BIT(buttons, 7) << 4) | // A + (BIT(buttons, 6) << 5); // B + LOG( + "%s: digital mode basic read = 0x%02X\n", + machine().describe_context(), + result); + return result; + } + else + { + u8 const z = m_axes[3]->read(); + u8 const result = + ((0xc0 > z) ? 0x01 : 0x00) | // Throttle Up + ((0x40 <= z) ? 0x02 : 0x00) | // Throttle Down + (BIT(buttons, 5) << 2) | // C + (BIT(buttons, 4) << 3) | // D + (BIT(buttons, 9) << 4) | // E1 + (BIT(buttons, 8) << 5); // E2 + LOG( + "%s: digital mode extended read = 0x%02X\n", + machine().describe_context(), + result); + return result; + } + } +} + + +void sms_xe1ap_device::out_w(u8 data, u8 mem_mask) +{ + u8 const req = BIT(data, 6); + if (req != m_req) + { + if (BIT(m_mode->read(), 0)) + { + LOG("%s: /Req = %u\n", machine().describe_context(), req); + if (!req) + m_output_timer->adjust(attotime::from_usec(4), 0); + } + else + { + LOG("%s: /Req = %u ignored in digital mode\n", machine().describe_context(), req); + } + m_req = req; + } +} + + +INPUT_CHANGED_MEMBER(sms_xe1ap_device::mode_changed) +{ + if (newval) + { + LOG("Analog mode selected\n"); + } + else + { + LOG("Digital mode selected\n"); + m_output_timer->enable(false); + m_out = 0x3f; + } +} + + +ioport_constructor sms_xe1ap_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(sms_xe1ap); +} + + +void sms_xe1ap_device::device_start() +{ + m_output_timer = timer_alloc(FUNC(sms_xe1ap_device::step_output), this); + + save_item(NAME(m_req)); + save_item(NAME(m_out)); +} + + +TIMER_CALLBACK_MEMBER(sms_xe1ap_device::step_output) +{ + if (!BIT(param, 0)) + { + auto const step = param >> 1; + if (12 > step) + { + switch (step) + { + case 0: + case 1: + m_out = BIT(m_buttons->read(), step ? 4 : 0, 4); + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + m_out = BIT(m_axes[(step - 2) & 0x03]->read(), (step < 6) ? 4 : 0, 4); + break; + case 10: + m_out = BIT(m_buttons->read(), 8, 4); + break; + default: + m_out = 0x0f; + } + m_out |= BIT(param, 1) ? 0x30 : 0x20; + LOG( + "Set nybble %u data = 0x%X, L/H = %d, /Ack = 1\n", + step, + BIT(m_out, 0, 4), + BIT(m_out, 4)); + m_output_timer->adjust(attotime::from_usec(4), param + 1); + } + else + { + m_out = 0x3f; + } + } + else + { + m_out &= 0x1f; + LOG("Set nybble %u /Ack = 0\n", param >> 1); + m_output_timer->adjust(attotime::from_usec(20), param + 1); + } +} + +} // anonymous namespace + + + +DEFINE_DEVICE_TYPE_PRIVATE(SMS_XE1AP, device_sms_control_interface, sms_xe1ap_device, "sms_xe1ap", "Dempa Micom Soft Analog Controller (XE-1AP)") diff --git a/src/devices/bus/sms_ctrl/xe1ap.h b/src/devices/bus/sms_ctrl/xe1ap.h new file mode 100644 index 00000000000..537f0c99562 --- /dev/null +++ b/src/devices/bus/sms_ctrl/xe1ap.h @@ -0,0 +1,19 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/********************************************************************** + + Dempa Micom Soft Analog/Digital Controller XE-1AP emulation + +**********************************************************************/ +#ifndef MAME_BUS_SMS_CTRL_XE1AP_H +#define MAME_BUS_SMS_CTRL_XE1AP_H + +#pragma once + +#include "smsctrl.h" + + +DECLARE_DEVICE_TYPE(SMS_XE1AP, device_sms_control_interface) +DECLARE_DEVICE_TYPE(SMS_XE1AP, device_sms_control_interface) + +#endif // MAME_BUS_SMS_CTRL_XE1AP_H -- cgit v1.2.3