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