From 4866b24120481a749b790923498d694eef9ae85b Mon Sep 17 00:00:00 2001 From: Sandro Ronco Date: Tue, 14 Mar 2023 17:30:13 +0100 Subject: amstrad/pda600.cpp: Added simulation of coprocessor, input and storage. (#10862) * Added HLE character recognition. * Added pen display input. * Added PCMCIA memory card support and initial software list. * Added internal layout. * cpu/z180: Fixed SLP instruction. New working software list items ------------------------------- pda600: Games (Crazy Money, Mosaic and Pagged) pda600: Games Demo (Game 44, Squares and FliView) --- hash/pda600.xml | 35 +++ src/devices/cpu/z180/z180ops.h | 10 +- src/mame/amstrad/pda600.cpp | 475 +++++++++++++++++++++++++++++----- src/mame/amstrad/pda600_copro.cpp | 518 ++++++++++++++++++++++++++++++++++++++ src/mame/amstrad/pda600_copro.h | 68 +++++ src/mame/layout/pda600.lay | 81 ++++++ src/mame/misc/luckybal.cpp | 67 +++-- 7 files changed, 1165 insertions(+), 89 deletions(-) create mode 100644 hash/pda600.xml create mode 100644 src/mame/amstrad/pda600_copro.cpp create mode 100644 src/mame/amstrad/pda600_copro.h create mode 100644 src/mame/layout/pda600.lay diff --git a/hash/pda600.xml b/hash/pda600.xml new file mode 100644 index 00000000000..41b85b6314b --- /dev/null +++ b/hash/pda600.xml @@ -0,0 +1,35 @@ + + + + + + Games (Crazy Money, Mosaic and Pagged) + 199? + Maddox Games + + + + + + + + + Games Demo (Game 44, Squares and FliView) + 199? + Maddox Games + + + + + + + diff --git a/src/devices/cpu/z180/z180ops.h b/src/devices/cpu/z180/z180ops.h index 0e57ff64532..7cd97f93e57 100644 --- a/src/devices/cpu/z180/z180ops.h +++ b/src/devices/cpu/z180/z180ops.h @@ -14,8 +14,15 @@ #define LEAVE_HALT() { \ if( m_HALT ) \ { \ + if( m_HALT == 2 ) \ + { \ + _PC += 2; \ + } \ + else \ + { \ + _PC++; \ + } \ m_HALT = 0; \ - _PC++; \ } \ } @@ -952,6 +959,7 @@ uint8_t z180_device::SET(uint8_t bit, uint8_t value) * OTDMR ***************************************************************/ #define SLP { \ + _PC -= 2; \ m_icount = 0; \ m_HALT = 2; \ } diff --git a/src/mame/amstrad/pda600.cpp b/src/mame/amstrad/pda600.cpp index 3b66fda4cab..024b5316f51 100644 --- a/src/mame/amstrad/pda600.cpp +++ b/src/mame/amstrad/pda600.cpp @@ -4,63 +4,106 @@ Amstrad PenPad PDA 600 - 05/11/2009 Skeleton driver. - - Hardware info: - U1: CPU - Z8S180 - Enhanced Z80 CPU - 1 MB MMU - 2 DMAs - 2 UARTs (up to 512 Kbps) - Two 16-Bit Timers - Clock Serial I/O - - U2: ROM - contains OS (binary and disassembled source) - apparently compatible to 27C1001 (128K*8) - - U3: RAM - 128K RAM, static - U4: RTC - U5: Amstrad ASIC (unknown functionality ) - U6, U7, U8, U9: Multiplexer - quad channel dual multiplexer - U11: RAM - 32K RAM, static - U12: LCD Controller - U21: 5V StepUp converter voltage converter - U27: OA quad operational amplifier - U30: RS232 Driver - U31: Amstrad ASIC - U32: FlipFlop Octal D-Flip-Flop - U33: RAM - 32K RAM, static - U35: Tone Dialer - generates DTMF frequencies - U36: unknown - U37: Nand - Dual Quad-Input Nand - U43: Bus Buffer - Octal Bus Buffer + PCB Layout (front): + /-----------------------------------------------------------------------\ + / U4 +---+ \ + / X1 HD64610FP U9 U6 | C | \ + / U3 HC157A HC157A U12 | N | \ + | KM681000ALT-8 HD64646FS | 3 | | + | +---+ | + | U36 U2 | + | TL061AC 42069 U35 U27 / + | U5 TCM5089 LP324M / + | +---+ 41857 | + | | C | U1 U31 U33 | + | | N | Z8S18016FSC U43 41863 KM62256BLG-10 | + | | 2 | HC541 | + | +---+ U32 \ + | U30 U37 HC574A \ + | MAX222CWN U24 HC20 +---+ | + | 41864 | C | | + | U21 U25 | N | | + | MAX731CWE MAX641 | 4 | | + | +---+ | + +-------------------------------------------------------------------------------+ + + PCB Layout (back): + +-------------------------------------------------------------------------------+ + | BAT- +-----+ BAT+ | + | / \ | + | | 3V | U23 | + | | Bat. | HC00A U20 | + | \ / +------+ U26 14052B | + | +-----+ | | 3226NUT / + | U10 U41 | | / + | HC245A HC541 | | U28 | + | X2 | | MC145053D | + | U42 | CN1 | | + | U22 HC541 | | | + | 7673CBA | | \ + | +----+ | | \ + | / Beep \ | | | + | \ / +------+ | + | +----+ U11 | + \ U18 U8 U7 KM62256BLG / + \ HC00A HC157A HC157A / + \ / + \-----------------------------------------------------------------------/ + + + Parts: + U2 - Amstrad 42069 ROM, probably 27C1001 + U5 - Amstrad 41857 100 Pin, unknown + U31 - Amstrad 41863 44 Pin, unknown + U24 - Amstrad 41864 18 Pin, unknown + X1 - 32768 XTAL + X2 - 28.6MHz XTAL Connectors: - LCD - Digitizer - Serial - PCMCIA + CN1 - PCMCIA + CN2 - 8 Pin Serial + CN3 - LCD + CN4 - Digitizer - Additional info: - Two Z8 for power management and character recognition + TODO: + - Sound (DTMF tone generator). + - Refactor the HD64646FS LCD controller into a device. + - Dump the character recognition MCU (possible?). + - Serial port doesn't work. ****************************************************************************/ #include "emu.h" + +#include "pda600_copro.h" + +#include "bus/generic/slot.h" +#include "bus/generic/carts.h" +#include "bus/rs232/rs232.h" #include "cpu/z180/z180.h" #include "machine/nvram.h" #include "machine/hd64610.h" +#include "machine/timer.h" +#include "sound/beep.h" + #include "emupal.h" #include "screen.h" +#include "softlist_dev.h" +#include "speaker.h" + +#include "pda600.lh" + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +static constexpr u32 PDA600_SCREEN_X = 38; +static constexpr u32 PDA600_SCREEN_Y = 54; +static constexpr u32 PDA600_SCREEN_W = (PDA600_SCREEN_X * 2 + 240); +static constexpr u32 PDA600_SCREEN_H = (PDA600_SCREEN_Y * 2 + 320); +static constexpr u32 PDA600_CSIO_RATE = 48000; +static constexpr u32 PDA600_SERIAL_PORT_RATE = 9600; namespace { @@ -71,30 +114,138 @@ public: pda600_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag) , m_maincpu(*this, "maincpu") + , m_copro(*this, "copro") + , m_card(*this, "pcmcia") + , m_beep(*this, "beeper") + , m_pen(*this, {"PEN", "PENX", "PENY"}) + , m_battery(*this, "BATTERY") , m_video_ram(*this, "videoram") { } void pda600(machine_config &config); + DECLARE_WRITE_LINE_MEMBER(power_off_w) { m_maincpu->set_input_line(Z180_INPUT_LINE_IRQ1, state); } private: - required_device m_maincpu; - required_shared_ptr m_video_ram; - - virtual void video_start() override; + required_device m_maincpu; + required_device m_copro; + required_device m_card; + required_device m_beep; + required_ioport_array<3> m_pen; + required_ioport m_battery; + required_shared_ptr m_video_ram; + + virtual void machine_start() override; virtual void machine_reset() override; - uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); + u32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); + void io_w(offs_t offset, u8 data); + u8 io_r(offs_t offset); + void pcmcia_w(offs_t offset, u8 data); + u8 pcmcia_r(offs_t offset); + void tone_w(u8 data); + TIMER_CALLBACK_MEMBER(csio_clk_timer); + TIMER_CALLBACK_MEMBER(serl_clk_timer); + TIMER_DEVICE_CALLBACK_MEMBER(pen_update_timer); + DECLARE_DEVICE_IMAGE_LOAD_MEMBER(card_load); + DECLARE_DEVICE_IMAGE_UNLOAD_MEMBER(card_unload); void pda600_io(address_map &map); void pda600_mem(address_map &map); + + emu_timer * m_csio_clk_timer; + emu_timer * m_serl_clk_timer; + u8 m_pen_data[6] = {}; + u8 m_pen_shift = 0; + u8 m_pen_cnt = 0; + u32 m_card_size = 0; + u8 m_rtc_irq = 0; + u8 m_serl_clk = 0; + u8 m_io_regs[4] = {}; + u8 m_lcd_ar = 0; + u8 m_lcd_regs[32] = {}; }; +void pda600_state::tone_w(u8 data) +{ + // xxxx ---- TCM5089 Column 1-4 + // ---- xxxx TCM5089 Row 1-4 + + // TODO: DTMF tone encoder + + if (data & 0x7f) + popmessage("DTMF tone %02X", data); + + m_beep->set_state(BIT(data, 7)); +} + + +u8 pda600_state::io_r(offs_t offset) +{ + return m_io_regs[offset]; +} + + +void pda600_state::io_w(offs_t offset, u8 data) +{ + switch (offset) + { + case 0: + // xx-- ---- PCMCIA bank select + // ---x -x-- Serial + if (BIT(m_io_regs[offset] ^ data, 4)) + { + auto period = attotime::zero; + if (BIT(data, 4)) + period = attotime::from_hz(PDA600_SERIAL_PORT_RATE * 16 * 2); + + m_serl_clk_timer->adjust(period, 0, period); + } + break; + case 1: + break; + case 2: + // ---- xxxx LCD contrast level + // ---x ---- Reset the coprocessor + // --x- ---- Wake up the coprocessor + if ((m_io_regs[offset] ^ data) & 0x0f) + logerror("Set LCD contrast level: %d\n", data & 0x0f); + + m_copro->wakeup_w(BIT(data, 4)); + m_copro->reset_w(BIT(data, 5)); + break; + case 3: + // --X- ---- ?? + break; + } + + m_io_regs[offset] = data; +} + + +u8 pda600_state::pcmcia_r(offs_t offset) +{ + offset |= (u32)(m_io_regs[0] & 0xc0) << 13; + if (offset < m_card_size) + return m_card->read_ram(offset); + + return 0xff; +} + + +void pda600_state::pcmcia_w(offs_t offset, u8 data) +{ + offset |= (u32)(m_io_regs[0] & 0xc0) << 13; + if (offset < m_card_size) + m_card->write_ram(offset, data); +} + + void pda600_state::pda600_mem(address_map &map) { map.unmap_value_high(); map(0x00000, 0x1ffff).rom(); - //map(0x20000, 0x9ffff).ram(); // PCMCIA Card + map(0x20000, 0x9ffff).rw(FUNC(pda600_state::pcmcia_r), FUNC(pda600_state::pcmcia_w)); map(0xa0000, 0xa7fff).ram().share("videoram"); map(0xe0000, 0xfffff).ram().share("nvram"); } @@ -104,37 +255,91 @@ void pda600_state::pda600_io(address_map &map) map.unmap_value_high(); map.global_mask(0xff); map(0x00, 0x3f).noprw(); /* Z180 internal registers */ - //map(0x40, 0x7f).noprw(); /* Z180 internal registers */ + map(0x40, 0x43).rw(FUNC(pda600_state::io_r), FUNC(pda600_state::io_w)); map(0x80, 0x8f).rw("rtc", FUNC(hd64610_device::read), FUNC(hd64610_device::write)); - //map(0xc0, 0xc1).noprw(); /* LCD */ + map(0xc0, 0xc0).lw8(NAME([this](u8 data) { m_lcd_ar = data & 0x1f; })); + map(0xc1, 0xc1).lw8(NAME([this](u8 data) { m_lcd_regs[m_lcd_ar] = data; })); } /* Input ports */ static INPUT_PORTS_START( pda600 ) + PORT_START("POWER") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_POWER_OFF) PORT_WRITE_LINE_MEMBER(pda600_state, power_off_w) + + PORT_START("BATTERY") + PORT_CONFNAME(0x0f, 0x0f, "Main battery status") + PORT_CONFSETTING(0x0f, "Good") + PORT_CONFSETTING(0x0c, "Fair") + PORT_CONFSETTING(0x06, "Low") + PORT_CONFSETTING(0x00, "Replace") + PORT_CONFNAME(0x10, 0x00, "Backup battery status") + PORT_CONFSETTING(0x00, "Good") + PORT_CONFSETTING(0x10, "Replace") + + PORT_START("PENX") + PORT_BIT(0x3ff, 0x000, IPT_LIGHTGUN_X) PORT_SENSITIVITY(40) PORT_CROSSHAIR(X, 1, 0, 0) PORT_MINMAX(0, 0x3ff) PORT_KEYDELTA(1) + + PORT_START("PENY") + PORT_BIT(0x3ff, 0x000, IPT_LIGHTGUN_Y) PORT_SENSITIVITY(40) PORT_CROSSHAIR(Y, 1, 0, 0) PORT_MINMAX(0, 0x3ff) PORT_KEYDELTA(1) + + PORT_START("PEN") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_NAME("Pen") INPUT_PORTS_END -void pda600_state::machine_reset() +void pda600_state::machine_start() { - // the PDA600 soon after start waits for something from the Z180 CSIO, I do not know exactly for what - // the CSIO is used and for now I forced the CNTR End-Flag bit to 1 for allow the emulation to continue. - m_maincpu->set_state_int(Z180_CNTR, m_maincpu->state_int(Z180_CNTR) | 0x80); + // state saving + save_item(NAME(m_pen_data)); + save_item(NAME(m_pen_shift)); + save_item(NAME(m_pen_cnt)); + save_item(NAME(m_card_size)); + save_item(NAME(m_rtc_irq)); + save_item(NAME(m_serl_clk)); + save_item(NAME(m_io_regs)); + save_item(NAME(m_lcd_ar)); + save_item(NAME(m_lcd_regs)); + + m_csio_clk_timer = timer_alloc(FUNC(pda600_state::csio_clk_timer), this); + m_serl_clk_timer = timer_alloc(FUNC(pda600_state::serl_clk_timer), this); } -void pda600_state::video_start() + +void pda600_state::machine_reset() { + m_pen_shift = -1; + m_pen_cnt = 0; + m_lcd_ar = 0; + m_rtc_irq = 0; + m_serl_clk = 0; + std::fill(std::begin(m_pen_data), std::end(m_pen_data), 0U); + std::fill(std::begin(m_io_regs), std::end(m_io_regs), 0U); + std::fill(std::begin(m_lcd_regs), std::end(m_lcd_regs), 0U); } -uint32_t pda600_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) + +u32 pda600_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { - for (int y=0; y<320; y++) - for (int x=0; x<30; x++) + bitmap.fill(1); + + if (!BIT(m_lcd_regs[0x16], 4)) + return 0; + + const u16 width = (m_lcd_regs[0x11] << 8) | m_lcd_regs[0x12]; + const u16 height = (m_lcd_regs[0x13] << 8) | m_lcd_regs[0x14]; + + for (int y = 0; y <= height; y++) + for (int x = 0; x < width; x++) { - uint8_t data = m_video_ram[y*30 + x]; + const s32 dst_y = PDA600_SCREEN_Y + y; + const s32 dst_x = PDA600_SCREEN_X + x * 8; + u8 data = m_video_ram[y * m_lcd_regs[1] + x] ^ 0xff; - for (int px=0; px<8; px++) + for (int px = 0; px < 8; px++) { - bitmap.pix(y, (x * 8) + px) = BIT(data, 7); + if (cliprect.contains(dst_x + px, dst_y)) + bitmap.pix(dst_y, dst_x + px) = BIT(data, 7); + data <<= 1; } } @@ -206,19 +411,125 @@ static GFXDECODE_START( gfx_pda600 ) GFXDECODE_END +TIMER_DEVICE_CALLBACK_MEMBER(pda600_state::pen_update_timer) +{ + u8 pen = m_pen[0]->read(); + + // Reduce the update rate when the pen is not held down + if (!pen && ++m_pen_cnt < 20) + return; + else + m_pen_cnt = 0; + + m_pen_data[0] = 0x80; // Start of new data + m_pen_data[0] |= pen << 6; // Pen up/down + m_pen_data[0] |= m_rtc_irq << 5; // RTC IRQ status + m_pen_data[0] |= m_battery->read(); // Battery status + + // Pen position (updated only when the pen is down) + if (pen) + { + u16 penx = m_pen[1]->read(); + u16 peny = m_pen[2]->read(); + m_pen_data[1] = penx & 0x7f; + m_pen_data[2] = (penx >> 7) & 0x7f; + m_pen_data[3] = peny & 0x7f; + m_pen_data[4] = (peny >> 7) & 0x7f; + } + + // Data checksum + m_pen_data[5] = m_pen_data[0] + m_pen_data[1] + m_pen_data[2] + m_pen_data[3] + m_pen_data[4]; + + // Start CSIO clock + m_pen_shift = 0; + m_csio_clk_timer->adjust(attotime::zero); +} + + +TIMER_CALLBACK_MEMBER(pda600_state::csio_clk_timer) +{ + if (m_pen_shift < 48) + { + m_maincpu->cks_w(0); + m_maincpu->rxs_cts1_w(BIT(m_pen_data[m_pen_shift / 8], m_pen_shift & 7)); + m_maincpu->cks_w(1); + m_pen_shift++; + + // If there is still data to send, reschedule the timer + if (m_pen_shift < 48) + { + auto delay = attotime::from_hz(PDA600_CSIO_RATE); + + // A delay is added after each byte to allow the maincpu to read the transmitted data + if (!(m_pen_shift & 7)) + delay += attotime::from_usec(150); + + m_csio_clk_timer->adjust(delay); + } + } +} + + +TIMER_CALLBACK_MEMBER(pda600_state::serl_clk_timer) +{ + // External clock for the Z180 ASCI0 + m_serl_clk ^= 1; + m_maincpu->cka0_w(m_serl_clk); +} + + +DEVICE_IMAGE_LOAD_MEMBER(pda600_state::card_load) +{ + if (!image.loaded_through_softlist()) + { + const u64 size = image.length(); + m_card->ram_alloc(size); + + if (size != image.fread(m_card->get_ram_base(), size)) + return image_init_result::FAIL; + + m_card_size = size; + } + else + { + m_card_size = image.get_software_region_length("rom"); + + if (m_card_size == 0) + return image_init_result::FAIL; + + m_card->ram_alloc(m_card_size); + memcpy(m_card->get_ram_base(), image.get_software_region("rom"), m_card_size); + } + + m_card->battery_load(m_card->get_ram_base(), m_card_size, nullptr); + return image_init_result::PASS; +} + + +DEVICE_IMAGE_UNLOAD_MEMBER(pda600_state::card_unload) +{ + m_card->battery_save(m_card->get_ram_base(), m_card_size); + memset(m_card->get_ram_base(), 0xff, m_card_size); + m_card_size = 0; +} + + void pda600_state::pda600(machine_config &config) { /* basic machine hardware */ - Z8S180(config, m_maincpu, XTAL(14'318'181)); + Z8S180(config, m_maincpu, 28'636'363_Hz_XTAL / 2); m_maincpu->set_addrmap(AS_PROGRAM, &pda600_state::pda600_mem); m_maincpu->set_addrmap(AS_IO, &pda600_state::pda600_io); + m_maincpu->txa0_wr_callback().set("serial", FUNC(rs232_port_device::write_txd)); + m_maincpu->rts0_wr_callback().set("serial", FUNC(rs232_port_device::write_rts)); + m_maincpu->txa1_wr_callback().set(m_copro, FUNC(pda600_copro_device::write_txd)); /* video hardware */ screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_LCD)); screen.set_refresh_hz(50); screen.set_vblank_time(ATTOSECONDS_IN_USEC(2500)); /* not accurate */ - screen.set_size(240, 320); - screen.set_visarea(0, 240-1, 0, 320-1); + screen.set_size(PDA600_SCREEN_W, PDA600_SCREEN_H); + screen.set_visarea_full(); screen.set_screen_update(FUNC(pda600_state::screen_update)); screen.set_palette("palette"); @@ -226,9 +537,35 @@ void pda600_state::pda600(machine_config &config) PALETTE(config, "palette", palette_device::MONOCHROME); // NVRAM needs to be filled with random data to fail the checksum and be initialized correctly - NVRAM(config, "nvram", nvram_device::DEFAULT_RANDOM); + NVRAM(config, "nvram", nvram_device::DEFAULT_ALL_1); + + hd64610_device &rtc(HD64610(config, "rtc", 32.768_kHz_XTAL)); + rtc.irq().set([this](int state) { m_rtc_irq = state; }).invert(); + + TIMER(config, "pen_update_timer").configure_periodic(FUNC(pda600_state::pen_update_timer), attotime::from_hz(100)); + + GENERIC_CARTSLOT(config, m_card, generic_romram_plain_slot, "pda600", "bin"); + m_card->set_device_load(FUNC(pda600_state::card_load)); + m_card->set_device_unload(FUNC(pda600_state::card_unload)); + m_card->set_interface("pcmcia"); + + PDA600_COPRO_HLE(config, m_copro, 28'636'363_Hz_XTAL / 2); + m_copro->tx_callback().set(m_maincpu, FUNC(z180_device::rxa1_w)); + m_copro->tone_callback().set(FUNC(pda600_state::tone_w)); + + rs232_port_device &rs232(RS232_PORT(config, "serial", default_rs232_devices, "printer")); + rs232.rxd_handler().set(m_maincpu, FUNC(z180_device::rxa0_w)); + rs232.cts_handler().set(m_maincpu, FUNC(z180_device::cts0_w)); + rs232.cts_handler().append_inputline(m_maincpu, Z180_INPUT_LINE_DREQ0).invert(); + + // sound hardware + SPEAKER(config, "mono").front_center(); + BEEP(config, m_beep, 1633).add_route(ALL_OUTPUTS, "mono", 0.80); // TODO: replace with TCM5089 + + // software lists + SOFTWARE_LIST(config, "card_list").set_original("pda600"); - HD64610(config, "rtc", XTAL(32'768)); + config.set_default_layout(layout_pda600); } /* ROM definition */ diff --git a/src/mame/amstrad/pda600_copro.cpp b/src/mame/amstrad/pda600_copro.cpp new file mode 100644 index 00000000000..175172ee0f0 --- /dev/null +++ b/src/mame/amstrad/pda600_copro.cpp @@ -0,0 +1,518 @@ +// license:BSD-3-Clause +// copyright-holders:Sandro Ronco +/********************************************************************** + + Amstrad PenPad PDA 600 character recognition Coprocessor HLE + + + The protocol flow control: + - 0x01 SOH Start a new frame + - 0x06 ACK Ack a request + - 0x15 NAK Negative ack + + + Frame format: + +--------+--------+--------+---//---+----------+ + | 0x01 | Length | Type | Data | Checksum | + +--------+--------+--------+---//---+----------+ + + Length: + Length of data + 1. + + Type: + Frame type: + - 0x54 (T) Train a character + - 0x52 (R) Recognize a character + - 0x42 (B) Beep + - 0x49 (I) Init + - 0x53 (S) Sleep + - 0x46 (F) Used before training a character + - 0x59 (Y) Affirmative response + - 0x4e (N) Negative response + + Checksum: + The two's complement of the 8-bit sum of Length, Type and Data. + +**********************************************************************/ + +#include "emu.h" +#include "pda600_copro.h" + + +//************************************************************************** +// MACROS / CONSTANTS +//************************************************************************** + +static constexpr u8 PDA600_SOH = 0x01; +static constexpr u8 PDA600_ACK = 0x06; +static constexpr u8 PDA600_NAK = 0x15; + +enum : u8 +{ + STATE_READY, + STATE_WAIT_ACK, + STATE_SLEEP, + STATE_BUSY, + STATE_PLAY_TONE, +}; + + + +//************************************************************************** +// DEVICE DEFINITIONS +//************************************************************************** + +DEFINE_DEVICE_TYPE(PDA600_COPRO_HLE, pda600_copro_device, "pda600_copro", "PDA600 Coprocessor (HLE)") + + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +//------------------------------------------------- +// pda600_copro_device - constructor +//------------------------------------------------- + +pda600_copro_device::pda600_copro_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : device_t(mconfig, PDA600_COPRO_HLE, tag, owner, clock) + , device_buffered_serial_interface(mconfig, *this) + , m_tx_cb(*this) + , m_tone_cb(*this) + , m_fake_ioport(*this, "FAKE%u", 0U) + , m_state(0) + , m_resp_type(0) + , m_resp_data(0) + , m_buf_size(0) +{ +} + + +static INPUT_PORTS_START(pda600_copro) + PORT_START("FAKE0") // 0x20 - 0x3f + PORT_BIT(0x80000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(' ') PORT_CODE(KEYCODE_SPACE) + PORT_BIT(0x40000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('!') + PORT_BIT(0x20000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'£') + PORT_BIT(0x10000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('#') + PORT_BIT(0x08000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('$') + PORT_BIT(0x04000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('%') + PORT_BIT(0x02000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('&') + PORT_BIT(0x01000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('\'') PORT_CODE(KEYCODE_QUOTE) + PORT_BIT(0x00800000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('(') + PORT_BIT(0x00400000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(')') + PORT_BIT(0x00200000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('*') PORT_CODE(KEYCODE_ASTERISK) + PORT_BIT(0x00100000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('+') PORT_CODE(KEYCODE_PLUS_PAD) + PORT_BIT(0x00080000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(',') PORT_CODE(KEYCODE_COMMA) + PORT_BIT(0x00040000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('-') PORT_CODE(KEYCODE_MINUS) + PORT_BIT(0x00020000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('.') PORT_CODE(KEYCODE_STOP) + PORT_BIT(0x00010000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('/') PORT_CODE(KEYCODE_SLASH) + PORT_BIT(0x00008000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('0') PORT_CODE(KEYCODE_0) + PORT_BIT(0x00004000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('1') PORT_CODE(KEYCODE_1) + PORT_BIT(0x00002000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('2') PORT_CODE(KEYCODE_2) + PORT_BIT(0x00001000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('3') PORT_CODE(KEYCODE_3) + PORT_BIT(0x00000800, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('4') PORT_CODE(KEYCODE_4) + PORT_BIT(0x00000400, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('5') PORT_CODE(KEYCODE_5) + PORT_BIT(0x00000200, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('6') PORT_CODE(KEYCODE_6) + PORT_BIT(0x00000100, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('7') PORT_CODE(KEYCODE_7) + PORT_BIT(0x00000080, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('8') PORT_CODE(KEYCODE_8) + PORT_BIT(0x00000040, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('9') PORT_CODE(KEYCODE_9) + PORT_BIT(0x00000020, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(':') + PORT_BIT(0x00000010, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(';') PORT_CODE(KEYCODE_COLON) + PORT_BIT(0x00000008, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'²') + PORT_BIT(0x00000004, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('=') PORT_CODE(KEYCODE_EQUALS) + PORT_BIT(0x00000002, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'³') + PORT_BIT(0x00000001, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('?') + + PORT_START("FAKE1") // 0x40 - 0x5f + PORT_BIT(0x80000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'°') + PORT_BIT(0x40000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('A') PORT_CODE(KEYCODE_A) + PORT_BIT(0x20000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('B') PORT_CODE(KEYCODE_B) + PORT_BIT(0x10000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('C') PORT_CODE(KEYCODE_C) + PORT_BIT(0x08000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('D') PORT_CODE(KEYCODE_D) + PORT_BIT(0x04000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('E') PORT_CODE(KEYCODE_E) + PORT_BIT(0x02000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('F') PORT_CODE(KEYCODE_F) + PORT_BIT(0x01000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('G') PORT_CODE(KEYCODE_G) + PORT_BIT(0x00800000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('H') PORT_CODE(KEYCODE_H) + PORT_BIT(0x00400000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('I') PORT_CODE(KEYCODE_I) + PORT_BIT(0x00200000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('J') PORT_CODE(KEYCODE_J) + PORT_BIT(0x00100000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('K') PORT_CODE(KEYCODE_K) + PORT_BIT(0x00080000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('L') PORT_CODE(KEYCODE_L) + PORT_BIT(0x00040000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('M') PORT_CODE(KEYCODE_M) + PORT_BIT(0x00020000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('N') PORT_CODE(KEYCODE_N) + PORT_BIT(0x00010000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('O') PORT_CODE(KEYCODE_O) + PORT_BIT(0x00008000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('P') PORT_CODE(KEYCODE_P) + PORT_BIT(0x00004000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('Q') PORT_CODE(KEYCODE_Q) + PORT_BIT(0x00002000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('R') PORT_CODE(KEYCODE_R) + PORT_BIT(0x00001000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('S') PORT_CODE(KEYCODE_S) + PORT_BIT(0x00000800, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('T') PORT_CODE(KEYCODE_T) + PORT_BIT(0x00000400, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('U') PORT_CODE(KEYCODE_U) + PORT_BIT(0x00000200, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('V') PORT_CODE(KEYCODE_V) + PORT_BIT(0x00000100, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('W') PORT_CODE(KEYCODE_W) + PORT_BIT(0x00000080, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('X') PORT_CODE(KEYCODE_X) + PORT_BIT(0x00000040, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('Y') PORT_CODE(KEYCODE_Y) + PORT_BIT(0x00000020, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('Z') PORT_CODE(KEYCODE_Z) + PORT_BIT(0x00000010, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'¿') + PORT_BIT(0x00000008, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ä') + PORT_BIT(0x00000004, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Å') + PORT_BIT(0x00000002, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Æ') + PORT_BIT(0x00000001, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ç') + + PORT_START("FAKE2") // 0x60 - 0x7f + PORT_BIT(0x80000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ë') + PORT_BIT(0x40000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('a') + PORT_BIT(0x20000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('b') + PORT_BIT(0x10000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('c') + PORT_BIT(0x08000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('d') + PORT_BIT(0x04000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('e') + PORT_BIT(0x02000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('f') + PORT_BIT(0x01000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('g') + PORT_BIT(0x00800000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('h') + PORT_BIT(0x00400000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('i') + PORT_BIT(0x00200000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('j') + PORT_BIT(0x00100000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('k') + PORT_BIT(0x00080000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('l') + PORT_BIT(0x00040000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('m') + PORT_BIT(0x00020000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('n') + PORT_BIT(0x00010000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('o') + PORT_BIT(0x00008000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('p') + PORT_BIT(0x00004000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('q') + PORT_BIT(0x00002000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('r') + PORT_BIT(0x00001000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('s') + PORT_BIT(0x00000800, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('t') + PORT_BIT(0x00000400, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('u') + PORT_BIT(0x00000200, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('v') + PORT_BIT(0x00000100, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('w') + PORT_BIT(0x00000080, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('x') + PORT_BIT(0x00000040, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('y') + PORT_BIT(0x00000020, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR('z') + PORT_BIT(0x00000010, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ñ') + PORT_BIT(0x00000008, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ö') + PORT_BIT(0x00000004, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ø') + PORT_BIT(0x00000002, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ù') + PORT_BIT(0x00000001, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'Ü') + + PORT_START("FAKE3") // 0x80 - 0x9a + PORT_BIT(0x80000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'à') + PORT_BIT(0x40000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'á') + PORT_BIT(0x20000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'â') + PORT_BIT(0x10000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ä') + PORT_BIT(0x08000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'å') + PORT_BIT(0x04000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'æ') + PORT_BIT(0x02000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ç') + PORT_BIT(0x01000000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'è') + PORT_BIT(0x00800000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'é') + PORT_BIT(0x00400000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ê') + PORT_BIT(0x00200000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ë') + PORT_BIT(0x00100000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ì') + PORT_BIT(0x00080000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'í') + PORT_BIT(0x00040000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'î') + PORT_BIT(0x00020000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ï') + PORT_BIT(0x00010000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ñ') + PORT_BIT(0x00008000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ò') + PORT_BIT(0x00004000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ó') + PORT_BIT(0x00002000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ô') + PORT_BIT(0x00001000, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ö') + PORT_BIT(0x00000800, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ø') + PORT_BIT(0x00000400, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ù') + PORT_BIT(0x00000200, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ú') + PORT_BIT(0x00000100, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'û') + PORT_BIT(0x00000080, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'ü') + PORT_BIT(0x00000040, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'β') + PORT_BIT(0x00000020, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CHAR(U'§') +INPUT_PORTS_END + +ioport_constructor pda600_copro_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(pda600_copro); +} + + +void pda600_copro_device::device_resolve_objects() +{ + // resolve callbacks + m_tx_cb.resolve_safe(); + m_tone_cb.resolve_safe(); +} + + +void pda600_copro_device::device_start() +{ + // parameters used by the MAINCPU to configure the Z180 ASCI1 + set_data_frame(1, 8, PARITY_NONE, STOP_BITS_1); + set_rate(clock() / 1280); + + // state saving + save_item(NAME(m_state)); + save_item(NAME(m_resp_type)); + save_item(NAME(m_resp_data)); + save_item(NAME(m_buf)); + save_item(NAME(m_buf_size)); + + m_update_timer = timer_alloc(FUNC(pda600_copro_device::update_timer), this); +} + + +void pda600_copro_device::device_reset() +{ + m_tx_cb(1); + m_tone_cb(0); + + m_state = STATE_READY; + m_buf_size = 0; + m_resp_type = m_resp_data = 0; + + m_update_timer->adjust(attotime::never); +} + + +TIMER_CALLBACK_MEMBER(pda600_copro_device::update_timer) +{ + if (m_state == STATE_PLAY_TONE && m_buf_size >= 2) + { + m_tone_cb(m_buf[0]); + m_update_timer->adjust(attotime::from_msec(m_buf[1] * 10)); + m_buf_size -= 2; + std::memmove(m_buf.begin(), m_buf.begin() + 2, m_buf_size); + } + else + { + if (m_state == STATE_PLAY_TONE) + m_tone_cb(0); // Mute the DTMF tone generator + + // We are ready to send the response + send_byte(PDA600_SOH); + m_state = STATE_WAIT_ACK; + } +} + + +WRITE_LINE_MEMBER( pda600_copro_device::wakeup_w ) +{ + if (m_state != STATE_SLEEP && m_state != STATE_READY) + logerror("PDA600: wakeup_w in %d state\n", m_state); + + if (state && m_state == STATE_SLEEP) + send_byte(PDA600_ACK); + + m_state = state ? STATE_READY : STATE_SLEEP; +} + + +void pda600_copro_device::send_byte(u8 byte) +{ + transmit_byte(byte); +} + + +void pda600_copro_device::received_byte(u8 byte) +{ + if (m_state != STATE_READY && m_state != STATE_WAIT_ACK) + { + logerror("PDA600: write %02x in %d state\n", byte, m_state); + return; + } + + if (m_state == STATE_WAIT_ACK) + { + if (byte == PDA600_ACK) + { + send_resp(); + } + else + { + logerror("PDA600: Invalid ack %02x\n", byte); + send_byte(PDA600_NAK); + } + + if (m_buf_size > 2 && m_buf[2] == 'S') + m_state = STATE_SLEEP; + else + m_state = STATE_READY; + + m_buf_size = 0; + return; + } + + m_buf[m_buf_size++] = byte; + + if (m_buf[0] == PDA600_ACK) + { + m_buf_size = 0; + } + else if (m_buf[0] != PDA600_SOH) + { + logerror("PDA600: unknown start %02x\n", byte); + send_byte(PDA600_NAK); + m_buf_size = 0; + } + else if (m_buf_size == 1) + { + send_byte(PDA600_ACK); + } + else if (m_buf_size == m_buf[1] + 3) + { + if (0) + { + std::string tmp = ""; + for (auto b : m_buf) + tmp += util::string_format("%02X ", b); + + logerror("%s\n", tmp); + } + + u8 checksum = 0; + for (int i = 1; i < m_buf_size; i++) + checksum += m_buf[i]; + + m_resp_type = m_resp_data = 0; + + if (checksum != 0) + { + logerror("PDA600: invalid frame checksum\n"); + send_byte(PDA600_NAK); + return; + } + + switch (m_buf[2]) + { + case 'T': // Train a character + exec_train(); + break; + case 'R': // Recognize a character + exec_recognize(); + break; + case 'B': // Beep + exec_beep(); + break; + case 'F': // Used before training a character + m_resp_type = 'Y'; + [[fallthrough]]; + case 'S': // Sleep + case 'I': // Init + m_update_timer->adjust(attotime::from_msec(1)); // Time required to complete the command + break; + default: + logerror("PDA600: unknown frame type %02x\n", m_buf[2]); + send_byte(PDA600_NAK); + } + } +} + + +void pda600_copro_device::send_resp() +{ +// Frame format: +// +--------+--------+--------+--------+--------+--------+----------+ +// | 0x01 | Length | Type | Param0 | Param1 | Param2 | Checksum | +// +--------+--------+--------+--------+--------+--------+----------+ +// +// Type: +// 'Y' for affirmative response +// 'N' for negative response +// +// Param0: +// Recognized character. +// +// Param1 / Param2: +// Appears to be unused. + + u8 checksum = 0; + if (m_resp_type != 0) + { + send_byte(0x04); // Data length + send_byte(m_resp_type); // Frame type + send_byte(m_resp_data); // Param0 + send_byte(0x00); // Param1 + send_byte(0x00); // Param2 + checksum += 0x04 + m_resp_type + m_resp_data; + } + else + send_byte(0x00); // Data length for an empty response + + send_byte(0x100 - checksum); // Checksum + + m_resp_type = m_resp_data = 0; +} + + +void pda600_copro_device::exec_beep() +{ +// Frame format: +// +--------+--------+--------+--------+----------+---//--+----------+ +// | 0x01 | Length | Type | Tone | Duration | ... | Checksum | +// +--------+--------+--------+--------+----------+---//--+----------+ +// +// Tone: +// xxxx ---- TMC5089 Column 1-4 +// ---- xxxx TMC5089 Row 1-4 +// +// Duration: +// Duration in centiseconds. +// +// More than one Tone/Duration pair can be used. + + m_resp_type = m_resp_data = 0; // Empty response + m_state = STATE_PLAY_TONE; + m_buf_size -= 4; + std::memmove(m_buf.begin(), m_buf.begin() + 3, m_buf_size); + m_update_timer->adjust(attotime::zero); +} + + +void pda600_copro_device::exec_recognize() +{ +// Frame format: +// +--------+--------+--------+-----//----+---------+-------+----------+ +// | 0x01 | Length | Type | Image[32] | Strokes | Flags | Checksum | +// +--------+--------+--------+-----//----+---------+-------+----------+ +// +// Image: +// 16x16 monochrome image of the character to be recognized. +// +// Strokes: +// Number of pen strokes used to draw the character. +// +// Flags: +// ---- ---x Set if the upper third of the vertical space is used. +// ---- --x- Set if the middle third of the vertical space is used. +// ---- -x-- Set if the lower third of the vertical space is used. + + m_resp_data = recognize_char(); + m_resp_type = m_resp_data != 0 ? 'Y' : 'N'; + m_state = STATE_BUSY; + m_update_timer->adjust(attotime::from_msec(50)); // Time required to complete the recognition +} + + +void pda600_copro_device::exec_train() +{ +// Frame format: +// +--------+--------+--------+-----//----+---------+-------+--------+----------+ +// | 0x01 | Length | Type | Image[32] | Strokes | Flags | Char | Checksum | +// +--------+--------+--------+-----//----+---------+-------+--------+----------+ +// +// Image, Strokes, Flags: +// Same as recognition frame. +// +// Char: +// Character to be trained with the given image. + + m_resp_type = 'Y'; + m_state = STATE_BUSY; + m_update_timer->adjust(attotime::from_msec(50)); // Time required to complete the training +} + + +u8 pda600_copro_device::recognize_char() +{ + // We don't implement any character recognition functionality, but simply return + // the character corresponding to the key pressed. + + for (int i = 0; i < 4; i++) + { + u32 value = m_fake_ioport[i]->read(); + if (value != 0) + return 0x20 + i * 32 + count_leading_zeros_32(value); + } + + return 0; +} diff --git a/src/mame/amstrad/pda600_copro.h b/src/mame/amstrad/pda600_copro.h new file mode 100644 index 00000000000..16187e06b59 --- /dev/null +++ b/src/mame/amstrad/pda600_copro.h @@ -0,0 +1,68 @@ +// license:BSD-3-Clause +// copyright-holders:Sandro Ronco +/********************************************************************** + + Amstrad PDA600 Coprocessor HLE + +**********************************************************************/ + +#ifndef MAME_MACHINE_PDA600_COPRO_H +#define MAME_MACHINE_PDA600_COPRO_H + +#pragma once + +#include "diserial.h" + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// ======================> pda600_copro_device + +class pda600_copro_device : public device_t, + public device_buffered_serial_interface<1 + 1 + 255 + 1> +{ +public: + pda600_copro_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock); + + auto tx_callback() { return m_tx_cb.bind(); } + auto tone_callback() { return m_tone_cb.bind(); } + + DECLARE_WRITE_LINE_MEMBER( wakeup_w ); + DECLARE_WRITE_LINE_MEMBER( reset_w ) { if (state) device_reset(); } + DECLARE_WRITE_LINE_MEMBER( write_txd ) { rx_w(state); } + +private: + // device-level overrides + virtual ioport_constructor device_input_ports() const override; + virtual void device_resolve_objects() override; + virtual void device_start() override; + virtual void device_reset() override; + + // device_serial_interface overrides + void tra_callback() override { m_tx_cb(transmit_register_get_data_bit()); } + void received_byte(u8 byte) override; + + TIMER_CALLBACK_MEMBER(update_timer); + void send_byte(u8 byte); + void send_resp(); + void exec_beep(); + void exec_train(); + void exec_recognize(); + u8 recognize_char(); + + devcb_write_line m_tx_cb; + devcb_write8 m_tone_cb; + required_ioport_array<4> m_fake_ioport; + emu_timer * m_update_timer; + u8 m_state; + u8 m_resp_type; + u8 m_resp_data; + u8 m_buf_size; + std::array m_buf; +}; + + +DECLARE_DEVICE_TYPE(PDA600_COPRO_HLE, pda600_copro_device) + +#endif // MAME_MACHINE_PDA600_COPRO_H diff --git a/src/mame/layout/pda600.lay b/src/mame/layout/pda600.lay new file mode 100644 index 00000000000..a24ae821621 --- /dev/null +++ b/src/mame/layout/pda600.lay @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mame/misc/luckybal.cpp b/src/mame/misc/luckybal.cpp index 995be5f36b8..5f6af35d9d6 100644 --- a/src/mame/misc/luckybal.cpp +++ b/src/mame/misc/luckybal.cpp @@ -307,9 +307,10 @@ public: protected: virtual void machine_start() override; + virtual void machine_reset() override; private: - void z180_trdr_w(uint8_t data); + void csio_cks_w(int state); void port90_bitswap_w(uint8_t data); uint8_t ppi_bitswap_r(offs_t offset); void ppi_bitswap_w(offs_t offset, uint8_t data); @@ -318,11 +319,14 @@ private: uint8_t input_port_c_r(); void output_port_c_w(uint8_t data); - uint8_t m_trdr = 0; + uint8_t m_csio_in = 0; + uint16_t m_csio_out = 0; + uint8_t m_csio_txs = 0; + uint8_t m_csio_cnt = 0; uint8_t m_led_on = 0; required_device m_v9938; - required_device m_maincpu; + required_device m_maincpu; required_device m_ppi; required_device m_dac; required_ioport m_aux; @@ -341,10 +345,21 @@ void luckybal_state::machine_start() { m_lamps.resolve(); - save_item(NAME(m_trdr)); + save_item(NAME(m_csio_in)); + save_item(NAME(m_csio_out)); + save_item(NAME(m_csio_txs)); + save_item(NAME(m_csio_cnt)); save_item(NAME(m_led_on)); } +void luckybal_state::machine_reset() +{ + m_csio_in = 0; + m_csio_out = 0; + m_csio_txs = 0; + m_csio_cnt = 0; +} + /************************************** * Memory Map * @@ -361,9 +376,7 @@ void luckybal_state::main_io(address_map &map) { map.global_mask(0xff); - map(0x00, 0x0a).nopr().nopw(); // Z180 Internal registers. - map(0x0b, 0x0b).nopr().w(FUNC(luckybal_state::z180_trdr_w)); - map(0x0c, 0x3f).nopr().nopw(); // Z180 Internal registers. + map(0x00, 0x3f).nopr().nopw(); // Z180 Internal registers. map(0x90, 0x90).w(FUNC(luckybal_state::port90_bitswap_w)); map(0xc0, 0xc3).rw(FUNC(luckybal_state::ppi_bitswap_r), FUNC(luckybal_state::ppi_bitswap_w)); @@ -394,9 +407,33 @@ M_MAP EQU 90H ; [A]= Bank to select (BIT6=MEM, BIT7=EN_NMI) * R/W handlers * **************************************/ -void luckybal_state::z180_trdr_w(uint8_t data) +void luckybal_state::csio_cks_w(int state) { - m_trdr = data; + if (!state) + { + m_maincpu->rxs_cts1_w(BIT(m_csio_out, 0)); + m_csio_out >>= 1; + } + else + { + m_csio_in >>= 1; + m_csio_in |= m_csio_txs << 7; + + if (++m_csio_cnt == 8) + { + m_csio_cnt = 0; + const uint8_t csio_data = m_csio_in & 0x7f; + if (csio_data <= 36) + { + m_lamps[m_led_on] = 0; + m_lamps[csio_data] = 1; + m_led_on = csio_data; + } + + // read back the previously written value + m_csio_out |= m_csio_in << 8; + } + } } void luckybal_state::port90_bitswap_w(uint8_t data) @@ -417,16 +454,6 @@ void luckybal_state::ppi_bitswap_w(offs_t offset, uint8_t data) void luckybal_state::output_port_a_w(uint8_t data) { - if (m_trdr & 0x80) - m_trdr = m_trdr & 0x7f; - - if (m_trdr <= 36) - { - m_lamps[m_led_on] = 0; - m_lamps[m_trdr] = 1; - m_led_on = m_trdr; - } - m_dac->write(data); } @@ -583,6 +610,8 @@ void luckybal_state::luckybal(machine_config &config) Z80180(config, m_maincpu, CPU_CLOCK); m_maincpu->set_addrmap(AS_PROGRAM, &luckybal_state::main_map); m_maincpu->set_addrmap(AS_IO, &luckybal_state::main_io); + m_maincpu->cks_wr_callback().set(FUNC(luckybal_state::csio_cks_w)); + m_maincpu->txs_wr_callback().set([this] (int state) { m_csio_txs = state; }); I8255A(config, m_ppi); m_ppi->out_pa_callback().set(FUNC(luckybal_state::output_port_a_w)); -- cgit v1.2.3