// license:BSD-3-Clause // copyright-holders:Fabio Priuli /********************************************************************** Sega Master System "Sports Pad" (US model) emulation Release data from the Sega Retro project: Year: 1987 Country/region: US Model code: 3040 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. - 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). 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 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. See http://mametesters.org/view.php?id=5872 for discussion. **********************************************************************/ #include "emu.h" #include "sports.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) 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()); } } READ_LINE_MEMBER( sms_sports_pad_device::th_pin_r ) { return m_th_pin_state; } 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; } CUSTOM_INPUT_MEMBER( sms_sports_pad_device::rldu_pins_r ) { 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 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 //------------------------------------------------- // input_ports - device-specific input ports //------------------------------------------------- ioport_constructor sms_sports_pad_device::device_input_ports() const { return INPUT_PORTS_NAME( sms_sports_pad ); } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // sms_sports_pad_device - constructor //------------------------------------------------- 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_start - device-specific startup //------------------------------------------------- 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)); } //------------------------------------------------- // sms_peripheral_r - sports pad read //------------------------------------------------- uint8_t sms_sports_pad_device::peripheral_r() { return m_sports_in->read(); } //------------------------------------------------- // sms_peripheral_w - sports pad write //------------------------------------------------- void sms_sports_pad_device::peripheral_w(uint8_t data) { m_sports_out->write(data); }