diff options
Diffstat (limited to 'src/devices/machine/micomxe1a.cpp')
-rw-r--r-- | src/devices/machine/micomxe1a.cpp | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/src/devices/machine/micomxe1a.cpp b/src/devices/machine/micomxe1a.cpp new file mode 100644 index 00000000000..d8e0dde9a8b --- /dev/null +++ b/src/devices/machine/micomxe1a.cpp @@ -0,0 +1,372 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/********************************************************************** + + Dempa Micom Soft Analog/Digital Controller 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 twelve nybbles: + + _ ________________________________________________________________ + REQ \_________/ + ____ __ __ __ __ __ __ __ __ __ __ __ __ + ACK \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ + _____ _____ _____ _____ _____ _____ + L/H _______/ \_____/ \_____/ \_____/ \_____/ \_____/ \__ + _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ + D XXXX_____X_____X_____X_____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. + + Nybble D3 D2 D1 D0 + 1 A/A' B/B' C D + 2 E1 E2 Start Select + 3 Y7 Y6 Y5 Y4 + 4 X7 X6 X5 X4 + 5 Z7 Z6 Z5 Z4 + 6 RZ7 RZ6 RZ5 RZ4 + 7 Y3 Y2 Y1 Y0 + 8 X3 X2 X1 X0 + 9 Z3 Z2 Z1 Z0 + 10 RZ3 RZ2 RZ1 RZ0 + 11 A B A' B' + 12 - - - - + + In MD mode, each pair of nybbles is transmitted in reverse + order. + + Sharp released assembly language source code for an X68000 + driver. It uses the following algorithm: + 1. Generate falling edge on REQ + 2. Wait until L/H is low + 3. Wait until ACK is low + 4. Read a nybble + 5. Wait until L/H is high + 6. Wait until ACK is low + 7. Read a nybble + 8. If eight nybbles have been read, raise REQ. + 9. Loop to step 2 until twelve nybbles have been read + + Mega Drive games use a similar approach, but raise REQ after + reading two nybbles. PC Engine games only generate a short low + pulse on REQ, but use the same algorithm to determine when to + read data. + + CSK Research Institute games for FM Towns (including After + Burner III and Galaxy Force II) use a different algorithm: + 1. Generate falling edge on REQ + 2. Wait until L/H is high + 3. Wait until ACK is high + 4. Read a nybble + 5. Wait until L/H is low + 6. Wait until ACK is high + 7. Read a nybble + 8. Wait until L/H is high + 9. Wait until ACK is high + 10. Read a nybble + 11. Loop to step 5 until eleven nybbles have been read + 11. Raise REQ + + From this it can be deduced that: + * A negative edge on REQ triggers a report. + * The exact time REQ is held low isn't important. + * Data is valid while ACK is low and for some time after ACK is + raised. + * L/H is low when idle and changes some time before data is + updated. + + 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/A' E1 + ACK B/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). + + Digital MD mode emulates a 3-button Mega Drive pad: + + REQ 0 1 + D0 Up Up + D1 Down Down + D2 0 Left + D3 0 Right + L/H A B + ACK Start C + + TODO: + * Dump MB88513 microcontroller from original controller. + * Measure timings. + - Timings currently fudged for CRI games in FM Towns. + * Latch data at beginning of packet. + * Confirm button mapping in digital mode. + * Estimate thresholds in digital modes. + * Implement trigger A/B rapid fire switches. + * Implement channel shift switch (Y->X, X->Z, Z->X). + * Does channel shift affect digital mode? + * Implement special modes (holding buttons on power-on): + - Double displacement modes: + + X/Y (hold SELECT + A') + + Z (hold SELECT + B') + + X/Y/Z (hold SELECT + A' + B') + - Up/down reverse mode (hold C) + * Implement desktop (XE-1AJ/CZ-8NJ2) version: + - Four analog channels + - E1/E2 on a rocker switch (can't press simultaneously) + - Hold mode for A and B triggers + - Variable rapid fire rate for A and B triggers + - Reset button + - Different special modes + - No Mega Drive mode + - Start and Select not reported in digital mode + +**********************************************************************/ + +#include "emu.h" +#include "micomxe1a.h" + +//#define VERBOSE 1 +//#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + + +DEFINE_DEVICE_TYPE(MICOM_XE_1A, micom_xe_1a_device, "micom_xe_1a", "Dempa Micom Soft Analog/Digital Intelligent Controller") + + + +micom_xe_1a_device::micom_xe_1a_device( + machine_config const &mconfig, + char const *tag, + device_t *owner, + u32 clock): + device_t(mconfig, MICOM_XE_1A, tag, owner, clock), + m_buttons_callback(*this, 0xffff), + m_analog_callback(*this, 0x00), + m_output_timer(nullptr), + m_req(1), + m_mode(1), + m_interface(0), + m_out(0x2f) +{ +} + +micom_xe_1a_device::~micom_xe_1a_device() +{ +} + + +u8 micom_xe_1a_device::out_r() +{ + if (m_mode) + { + LOG("%s: analog mode read data = %02X\n", machine().describe_context(), m_out); + return m_out; + } + else + { + u16 const buttons = m_buttons_callback(); + if (m_interface) + { + u8 const y = m_analog_callback(0); + if (m_req) + { + u8 const x = m_analog_callback(1); + u8 const result = + ((0x40 <= y) ? 0x01 : 0x00) | // Up + ((0xc0 > y) ? 0x02 : 0x00) | // Down + ((0x40 <= x) ? 0x04 : 0x00) | // Left + ((0xc0 > x) ? 0x08 : 0x00) | // Right + ((BIT(buttons, 2) & BIT(buttons, 8)) << 4) | // B/B' + (BIT(buttons, 1) << 5); // C + LOG( + "%s: MD digital mode basic read = 0x%02X\n", + machine().describe_context(), + result); + return result; + } + else + { + u8 const result = + ((0x40 <= y) ? 0x01 : 0x00) | // Up + ((0xc0 > y) ? 0x02 : 0x00) | // Down + ((BIT(buttons, 3) & BIT(buttons, 9)) << 4) | // A/A' + (BIT(buttons, 5) << 5); // Start + LOG( + "%s: MD digital mode extended read = 0x%02X\n", + machine().describe_context(), + result); + return result; + } + } + else + { + if (m_req) + { + u8 const z = m_analog_callback(2); + u8 const result = + ((0x40 <= z) ? 0x01 : 0x00) | // Throttle Up + ((0xc0 > z) ? 0x02 : 0x00) | // Throttle Down + (BIT(buttons, 1) << 2) | // C + (BIT(buttons, 0) << 3) | // D + (BIT(buttons, 7) << 4) | // E1 + (BIT(buttons, 6) << 5); // E2 + LOG( + "%s: digital mode extended read = 0x%02X\n", + machine().describe_context(), + result); + return result; + } + else + { + u8 const y = m_analog_callback(0); + u8 const x = m_analog_callback(1); + u8 const result = + ((BIT(buttons, 4) && (0x40 <= y)) ? 0x01 : 0x00) | // Select/Up + ((BIT(buttons, 4) && (0xc0 > y)) ? 0x02 : 0x00) | // Select/Down + ((BIT(buttons, 5) && (0x40 <= x)) ? 0x04 : 0x00) | // Start/Left + ((BIT(buttons, 5) && (0xc0 > x)) ? 0x08 : 0x00) | // Start/Right + ((BIT(buttons, 3) & BIT(buttons, 9)) << 4) | // A/A' + ((BIT(buttons, 2) & BIT(buttons, 8)) << 5); // B/B' + LOG( + "%s: digital mode basic read = 0x%02X\n", + machine().describe_context(), + result); + return result; + } + } + } +} + + +void micom_xe_1a_device::req_w(int state) +{ + u8 const req = state ? 1 : 0; + if (req != m_req) + { + if (m_mode) + { + LOG("%s: /REQ = %u\n", machine().describe_context(), req); + if (!req) + { + // acquire data + u16 const buttons = m_buttons_callback(); + u8 analog[4]; + for (unsigned i = 0; std::size(analog) > i; ++i) + analog[i] = m_analog_callback(i); + + // pack data + m_data[0] = BIT(buttons, 0, 8) & ((BIT(buttons, 8, 2) << 2) | 0xf3); + m_data[1] = BIT(analog[0], 4, 4) | (BIT(analog[1], 4, 4) << 4); + m_data[2] = BIT(analog[2], 4, 4) | (BIT(analog[3], 4, 4) << 4); + m_data[3] = BIT(analog[0], 0, 4) | (BIT(analog[1], 0, 4) << 4); + m_data[4] = BIT(analog[2], 0, 4) | (BIT(analog[3], 0, 4) << 4); + m_data[5] = BIT(buttons, 8, 8) & ((BIT(buttons, 2, 2) << 2) | 0xf3); + + // takes a while to respond + m_output_timer->adjust(attotime::from_nsec(50'000), 0); + } + } + else + { + LOG("%s: /REQ = %u ignored in digital mode\n", machine().describe_context(), req); + } + m_req = req; + } +} + + +void micom_xe_1a_device::mode_w(int state) +{ + u8 const mode = state ? 1 : 0; + if (mode != m_mode) + { + if (mode) + { + LOG("Analog mode selected\n"); + } + else + { + LOG("Digital mode selected\n"); + m_output_timer->enable(false); + m_out = 0x2f; + } + m_mode = mode; + } +} + + +void micom_xe_1a_device::interface_w(int state) +{ + m_interface = state ? 1 : 0; +} + + +void micom_xe_1a_device::device_start() +{ + m_output_timer = timer_alloc(FUNC(micom_xe_1a_device::step_output), this); + + std::fill(std::begin(m_data), std::end(m_data), 0x00); + m_out = 0x2f; + + save_item(NAME(m_req)); + save_item(NAME(m_mode)); + save_item(NAME(m_interface)); + save_item(NAME(m_data)); + save_item(NAME(m_out)); +} + + +TIMER_CALLBACK_MEMBER(micom_xe_1a_device::step_output) +{ + auto const step = param >> 1; + if (!BIT(param, 0)) + { + m_out = (m_out & 0x0f) | (BIT(step, 0) ? 0x30 : 0x20); + LOG( + "Set nybble %u data = 0x%X, L/H = %u, /ACK = %u\n", + step, + BIT(m_out, 0, 4), + BIT(m_out, 4), + BIT(m_out, 5)); + if ((std::size(m_data) * 2) > step) + { + m_output_timer->adjust(attotime::from_nsec(10'000), param + 1); + } + } + else + { + if ((std::size(m_data) * 2) > step) + { + auto const nybble = step ^ m_interface; + if ((std::size(m_data) * 2) > step) + m_out = BIT(m_data[nybble >> 1], BIT(nybble, 0) ? 4 : 0, 4) | (m_out & 0x10); + else + m_out = 0x0f | (m_out & 0x10); + LOG( + "Set nybble %u data = 0x%X, L/H = %u, /ACK = %u\n", + step, + BIT(m_out, 0, 4), + BIT(m_out, 4), + BIT(m_out, 5)); + m_output_timer->adjust(attotime::from_nsec(10'000), param + 1); + } + } +} |