summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/sms_ctrl/sports.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/sms_ctrl/sports.cpp')
-rw-r--r--src/devices/bus/sms_ctrl/sports.cpp271
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)")