diff options
Diffstat (limited to 'src/devices/bus/sms_ctrl/sports.cpp')
-rw-r--r-- | src/devices/bus/sms_ctrl/sports.cpp | 271 |
1 files changed, 132 insertions, 139 deletions
diff --git a/src/devices/bus/sms_ctrl/sports.cpp b/src/devices/bus/sms_ctrl/sports.cpp index dee6af4eb63..62a11a7d882 100644 --- a/src/devices/bus/sms_ctrl/sports.cpp +++ b/src/devices/bus/sms_ctrl/sports.cpp @@ -11,29 +11,28 @@ Release data from the Sega Retro project: TODO: -- For low-level emulation, a device for the TMP42C66P, a Toshiba 4bit - microcontroller, needs to be created, but a dump of its internal ROM - seems to be required. +- For low-level emulation, a device for the TMP42C66P, a Toshiba 4-bit + microcontroller, is needed along with a dump of its internal ROM. - Auto-repeat and Control/Sports mode switches are not emulated. Notes: Games designed for the US model of the Sports Pad controller use the - TH line of the controller port to select which nibble, of the two axis - bytes, will be read at a time. The Japanese cartridge Sports Pad Soccer - uses a different mode when not detect a SMSJ, because the Sega Mark III - lacks the TH line. There is a different Sports Pad model released in - Japan and no information was found about it supporting both modes, so - that model is currently emulated as a different device (see sportsjp.c). + TH line of the controller port to select which nibble of the two axis + to read. The Japanese cartridge Sports Pad Soccer uses a different mode + when it does not detect use by a Japanese SMS console, because the Sega + Mark III lacks the TH line. There was a different Sports Pad model released + in Japan and no information was found about it supporting both modes, so + that model is currently emulated as a different device (see sportsjp.cpp). It was discovered that games designed for the Paddle Controller, released - in Japan, switch to a mode incompatible with the original Paddle when - detect the system region as Export. Similar to how the US model of the - Sports Pad works, that mode uses the TH line as output to select which + in Japan, will switch to a mode incompatible with the original Paddle when + the system region is detected as Export. Similar to how the US model of the + Sports Pad works, that mode uses the TH line as an output to select which nibble of the X axis will be read. So, on an Export console version, paddle games are somewhat playable with the US Sport Pad model, though it - needs to be used inverted and the trackball needs to be moved slowly, else - the software for the paddle think it's moving backward. + needs to be used inverted and the trackball needs to be moved slowly, + otherwise the software for the paddle think it's moving backward. See http://mametesters.org/view.php?id=5872 for discussion. **********************************************************************/ @@ -41,120 +40,61 @@ Notes: #include "emu.h" #include "sports.h" +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" -//************************************************************************** -// DEVICE DEFINITIONS -//************************************************************************** - -DEFINE_DEVICE_TYPE(SMS_SPORTS_PAD, sms_sports_pad_device, "sms_sports_pad", "Sega SMS Sports Pad (US)") - -// time interval not verified -#define SPORTS_PAD_INTERVAL attotime::from_hz(XTAL(10'738'635)/3/512) +namespace { +INPUT_PORTS_START( sms_sports_pad ) + PORT_START("SPORTS_BT") + PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_BUTTON1 ) // TL + PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_BUTTON2 ) // TR -void sms_sports_pad_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) -{ - switch (id) - { - case TIMER_SPORTSPAD: - // values for x and y axis need to be resetted for Sports Pad games, but - // are not resetted for paddle games, so it was assumed the reset occurs - // only when this timer fires after the read state reached maximum value. - if (m_read_state == 3) - { - m_x_axis_reset_value = m_sports_x->read(); - m_y_axis_reset_value = m_sports_y->read(); - } - else - { - // set to maximum value, so it wraps to 0 at next increment - m_read_state = 3; - } - - break; - default: - throw emu_fatalerror("sms_sports_pad_device(%s): Unknown timer ID", tag()); - } -} - + PORT_START("SPORTS_X") // X axis + PORT_BIT( 0xff, 0x00, IPT_TRACKBALL_X ) PORT_SENSITIVITY(50) PORT_KEYDELTA(40) PORT_REVERSE -READ_LINE_MEMBER( sms_sports_pad_device::th_pin_r ) -{ - return m_th_pin_state; -} + PORT_START("SPORTS_Y") // Y axis + PORT_BIT( 0xff, 0x00, IPT_TRACKBALL_Y ) PORT_SENSITIVITY(50) PORT_KEYDELTA(40) PORT_REVERSE +INPUT_PORTS_END -WRITE_LINE_MEMBER( sms_sports_pad_device::th_pin_w ) -{ - m_read_state = (m_read_state + 1) & 3; - m_sportspad_timer->adjust(m_interval); - m_th_pin_state = state; -} +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** -CUSTOM_INPUT_MEMBER( sms_sports_pad_device::rldu_pins_r ) +class sms_sports_pad_device : public device_t, public device_sms_control_interface { - uint8_t data = 0; - - switch (m_read_state) - { - case 0: - data = (m_sports_x->read() - m_x_axis_reset_value) >> 4; - break; - case 1: - data = (m_sports_x->read() - m_x_axis_reset_value); - break; - case 2: - data = (m_sports_y->read() - m_y_axis_reset_value) >> 4; - break; - case 3: - data = (m_sports_y->read() - m_y_axis_reset_value); - break; - } - - // The returned value is inverted due to IP_ACTIVE_LOW mapping. - return ~(data & 0x0f); -} - - -static INPUT_PORTS_START( sms_sports_pad ) - PORT_START("SPORTS_IN") - PORT_BIT( 0x0f, IP_ACTIVE_LOW, IPT_CUSTOM ) PORT_CUSTOM_MEMBER(sms_sports_pad_device, rldu_pins_r) // R,L,D,U - PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_UNUSED ) // Vcc - PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_BUTTON1 ) // TL (Button 1) - PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_CUSTOM ) PORT_READ_LINE_MEMBER(sms_sports_pad_device, th_pin_r) // TH - PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_BUTTON2 ) // TR (Button 2) - - PORT_START("SPORTS_OUT") - PORT_BIT( 0x0f, IP_ACTIVE_LOW, IPT_UNUSED ) // Directional pins - PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_UNUSED ) // Vcc - PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_UNUSED ) // TL (Button 1) - PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_OUTPUT ) PORT_WRITE_LINE_MEMBER(sms_sports_pad_device, th_pin_w) // TH - PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_UNUSED ) // TR (Button 2) - - PORT_START("SPORTS_X") /* Sports Pad X axis */ - PORT_BIT( 0xff, 0x00, IPT_TRACKBALL_X ) PORT_SENSITIVITY(50) PORT_KEYDELTA(40) PORT_REVERSE +public: + // construction/destruction + sms_sports_pad_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - PORT_START("SPORTS_Y") /* Sports Pad Y axis */ - PORT_BIT( 0xff, 0x00, IPT_TRACKBALL_Y ) PORT_SENSITIVITY(50) PORT_KEYDELTA(40) PORT_REVERSE -INPUT_PORTS_END + // device_sms_control_interface implementation + virtual uint8_t in_r() override; + virtual void out_w(uint8_t data, uint8_t mem_mask) override; +protected: + // device_t implementation + virtual ioport_constructor device_input_ports() const override { return INPUT_PORTS_NAME(sms_sports_pad); } + virtual void device_start() override; -//------------------------------------------------- -// input_ports - device-specific input ports -//------------------------------------------------- +private: + TIMER_CALLBACK_MEMBER(timeout); -ioport_constructor sms_sports_pad_device::device_input_ports() const -{ - return INPUT_PORTS_NAME( sms_sports_pad ); -} + required_ioport m_buttons; + required_ioport_array<2> m_axes; + emu_timer *m_timeout_timer; + uint8_t m_th_state; + uint8_t m_phase; + uint8_t m_output; + uint8_t m_base[2]; + uint8_t m_data[2]; +}; -//************************************************************************** -// LIVE DEVICE -//************************************************************************** //------------------------------------------------- // sms_sports_pad_device - constructor @@ -162,17 +102,13 @@ ioport_constructor sms_sports_pad_device::device_input_ports() const sms_sports_pad_device::sms_sports_pad_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, SMS_SPORTS_PAD, tag, owner, clock), - device_sms_control_port_interface(mconfig, *this), - m_sports_in(*this, "SPORTS_IN"), - m_sports_out(*this, "SPORTS_OUT"), - m_sports_x(*this, "SPORTS_X"), - m_sports_y(*this, "SPORTS_Y"), - m_read_state(0), - m_th_pin_state(0), - m_x_axis_reset_value(0x80), // value 0x80 helps when start playing paddle games. - m_y_axis_reset_value(0x80), - m_interval(SPORTS_PAD_INTERVAL), - m_sportspad_timer(nullptr) + device_sms_control_interface(mconfig, *this), + m_buttons(*this, "SPORTS_BT"), + m_axes(*this, "SPORTS_%c", 'X'), + m_timeout_timer(nullptr), + m_th_state(1), + m_phase(0), + m_output(0x0f) { } @@ -183,30 +119,87 @@ sms_sports_pad_device::sms_sports_pad_device(const machine_config &mconfig, cons void sms_sports_pad_device::device_start() { - m_sportspad_timer = timer_alloc(TIMER_SPORTSPAD); - - save_item(NAME(m_read_state)); - save_item(NAME(m_th_pin_state)); - save_item(NAME(m_x_axis_reset_value)); - save_item(NAME(m_y_axis_reset_value)); + m_timeout_timer = timer_alloc(FUNC(sms_sports_pad_device::timeout), this); + + m_phase = 0; + m_output = 0x0f; + m_base[0] = 0x80; // value 0x80 helps when starting paddle games. + m_base[1] = 0x80; + m_data[0] = 0x00; + m_data[1] = 0x00; + + save_item(NAME(m_th_state)); + save_item(NAME(m_phase)); + save_item(NAME(m_output)); + save_item(NAME(m_base)); + save_item(NAME(m_data)); } -//------------------------------------------------- -// sms_peripheral_r - sports pad read -//------------------------------------------------- - -uint8_t sms_sports_pad_device::peripheral_r() +uint8_t sms_sports_pad_device::in_r() { - return m_sports_in->read(); + return m_buttons->read() | m_output; } -//------------------------------------------------- -// sms_peripheral_w - sports pad write -//------------------------------------------------- +void sms_sports_pad_device::out_w(uint8_t data, uint8_t mem_mask) +{ + // Sequence used to read Sports Pad: + // * TH = 0, spin for 80 microseconds, read X high nybble + // * TH = 1, spin for 40 microseconds, read X low nybble + // * TH = 0, spin for 40 microseconds, read Y high nybble + // * TH = 1, spin for 40 microseconds, read Y low nybble + uint8_t const th_state = BIT(data, 6); + if (th_state != m_th_state) + { + if (!th_state) + { + LOG( + "%s: TH falling, output %c high nybble\n", + machine().describe_context(), + m_phase ? 'Y' : 'X'); + + m_timeout_timer->reset(); + m_data[m_phase] = m_axes[m_phase]->read(); + m_output = BIT(m_data[m_phase] - m_base[m_phase], 4, 4); + } + else + { + LOG( + "%s: TH rising, output %c low nybble%s\n", + machine().describe_context(), + m_phase ? 'Y' : 'X', + m_phase ? ", zero counts" : ""); + + m_timeout_timer->adjust(attotime::from_hz(XTAL(10'738'635) / 3 / 512)); // timeout not verified + m_output = BIT(m_data[m_phase] - m_base[m_phase], 0, 4); + if (m_phase) + { + // assume the rising edge when Y is output zeroes the axes + m_base[0] = m_data[0]; + m_base[1] = m_data[1]; + } + m_phase ^= 1; + } + } + + m_th_state = th_state; +} + -void sms_sports_pad_device::peripheral_w(uint8_t data) +TIMER_CALLBACK_MEMBER(sms_sports_pad_device::timeout) { - m_sports_out->write(data); + // assume it just flips back to X, allowing paddle games to "work" + LOG("timeout, select X axis\n"); + m_phase = 0; } + +} // anonymous namespace + + + +//************************************************************************** +// DEVICE DEFINITIONS +//************************************************************************** + +DEFINE_DEVICE_TYPE_PRIVATE(SMS_SPORTS_PAD, device_sms_control_interface, sms_sports_pad_device, "sms_sports_pad", "Sega SMS Sports Pad (US)") |