From 365b977060dcb05a503f367e1b923a5778dd5d5f Mon Sep 17 00:00:00 2001 From: hap Date: Fri, 15 Dec 2023 21:29:34 +0100 Subject: add preliminary emulation of OKI MSM5001N CMOS LCD Watch IC --- scripts/src/machine.lua | 12 +++ src/devices/machine/msm5001n.cpp | 200 ++++++++++++++++++++++++++++++++++++++ src/devices/machine/msm5001n.h | 65 +++++++++++++ src/devices/machine/sensorboard.h | 2 +- src/mame/saitek/companion.cpp | 2 +- src/mame/saitek/companion2.cpp | 4 +- 6 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 src/devices/machine/msm5001n.cpp create mode 100644 src/devices/machine/msm5001n.h diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 35ee188a230..e57d938cc00 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -2652,6 +2652,18 @@ if (MACHINES["MOS6551"]~=null) then } end +--------------------------------------------------- +-- +--@src/devices/machine/msm5001n.h,MACHINES["MSM5001N"] = true +--------------------------------------------------- + +if (MACHINES["MSM5001N"]~=null) then + files { + MAME_DIR .. "src/devices/machine/msm5001n.cpp", + MAME_DIR .. "src/devices/machine/msm5001n.h", + } +end + --------------------------------------------------- -- --@src/devices/machine/msm5832.h,MACHINES["MSM5832"] = true diff --git a/src/devices/machine/msm5001n.cpp b/src/devices/machine/msm5001n.cpp new file mode 100644 index 00000000000..b1fdc1766ed --- /dev/null +++ b/src/devices/machine/msm5001n.cpp @@ -0,0 +1,200 @@ +// license:BSD-3-Clause +// copyright-holders:hap +/* + +OKI MSM5001N CMOS LCD Watch IC + +Decap shows it's not an MCU. No known documentation exists, but there are datasheets +available for equivalent Samsung chips: KS5198, KS5199A, KS5114. + +These kind of chips were used a lot for cheap 2-button digital wristwatches. + +TODO: +- add D/S inputs (other display modes, setup mode) +- datasheets mention a 4-year calendar, does it mean it supports leap years somehow? +- one of the Samsung datasheets mention 24hr mode, does the MSM5001N support that? + +*/ + +#include "emu.h" +#include "machine/msm5001n.h" + + +DEFINE_DEVICE_TYPE(MSM5001N, msm5001n_device, "msm5001n", "OKI MSM5001N LCD Watch") + +//------------------------------------------------- +// constructor +//------------------------------------------------- + +msm5001n_device::msm5001n_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + device_t(mconfig, MSM5001N, tag, owner, clock), + device_rtc_interface(mconfig, *this), + device_nvram_interface(mconfig, *this), + m_write_segs(*this) +{ } + + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +// allow save_item on a non-fundamental type +ALLOW_SAVE_TYPE(msm5001n_device::mode); + +void msm5001n_device::device_start() +{ + m_timer = timer_alloc(FUNC(msm5001n_device::clock_tick), this); + + m_power = true; + initialize(); + + // register for savestates + save_item(NAME(m_power)); + save_item(NAME(m_mode)); + save_item(NAME(m_counter)); +} + +void msm5001n_device::initialize() +{ + m_mode = MODE_NORMAL_HRMIN; + m_counter = 0; + + // 1 January, 1AM + set_time(true, 0, 1, 1, 1, 1, 0, 0); +} + + +//------------------------------------------------- +// input pins +//------------------------------------------------- + +void msm5001n_device::d_w(int state) +{ +} + +void msm5001n_device::s_w(int state) +{ +} + +void msm5001n_device::power_w(int state) +{ + if (m_power && !state) + { + // reset chip when power goes off + initialize(); + write_lcd(nullptr, false); + } + + m_power = bool(state); +} + +void msm5001n_device::device_clock_changed() +{ + // smallest interval (at default frequency of 32768Hz) is LCD refresh at 32Hz + attotime period = attotime::from_ticks(1024, clock()); + m_timer->adjust(period, 0, period); + + // clear LCD if clock stopped + if (clock() == 0) + write_lcd(nullptr, false); +} + + +//------------------------------------------------- +// process +//------------------------------------------------- + +void msm5001n_device::write_lcd(u8 *digits, bool colon) +{ + u32 segs = 0; + + if (digits) + { + // 0-9, A, P, none + static const u8 lut_segs[0x10] = + { 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x73,0x00,0x00,0x00,0x00 }; + + for (int i = 0; i < 4; i++) + segs |= lut_segs[digits[i]] << (8 * i); + } + segs |= colon ? 0x80 : 0; + + // COM1: BC1,F2,A2,B2,COLON,F3,AD3,B3,F4,A4,B4 + m_write_segs(0, bitswap<11>(segs,25,21,16,17,7,13,8,9,5,0,1)); + + // COM2: D2,E2,G2,C2,D4,E3,G3,C3,E4,G4,C4 + m_write_segs(1, bitswap<11>(segs,19,20,22,18,3,12,14,10,4,6,2)); +} + +TIMER_CALLBACK_MEMBER(msm5001n_device::clock_tick) +{ + if (!m_power) + return; + + m_counter++; + if ((m_counter & 0x1f) == 0) + advance_seconds(); + + u8 digits[4]; + for (int i = 0; i < 4; i++) + digits[i] = 0xf; + bool colon = false; + + // convert current time to BCD + u8 minute = convert_to_bcd(get_clock_register(RTC_MINUTE)); + u8 hour = get_clock_register(RTC_HOUR) % 12; + hour = convert_to_bcd((hour == 0) ? 12 : hour); + + switch (m_mode) + { + case MODE_NORMAL_HRMIN: + digits[0] = minute & 0xf; + digits[1] = minute >> 4; + digits[2] = hour & 0xf; + if (hour & 0xf0) + digits[3] = hour >> 4; + + colon = !BIT(m_counter, 4); + break; + + default: + break; + } + + write_lcd(digits, colon); +} + + +//------------------------------------------------- +// nvram +//------------------------------------------------- + +bool msm5001n_device::nvram_write(util::write_stream &file) +{ + size_t actual; + u8 buf[5]; + + // current time + for (int i = 0; i < 5; i++) + buf[i] = get_clock_register(i); + + if (file.write(&buf, sizeof(buf), actual) || (sizeof(buf) != actual)) + return false; + + return true; +} + +bool msm5001n_device::nvram_read(util::read_stream &file) +{ + size_t actual; + + u8 buf[5]; + if (file.read(&buf, sizeof(buf), actual) || (sizeof(buf) != actual)) + return false; + + // current time + for (int i = 0; i < 5; i++) + set_clock_register(i, buf[i]); + + return true; +} diff --git a/src/devices/machine/msm5001n.h b/src/devices/machine/msm5001n.h new file mode 100644 index 00000000000..d7e16998e34 --- /dev/null +++ b/src/devices/machine/msm5001n.h @@ -0,0 +1,65 @@ +// license:BSD-3-Clause +// copyright-holders:hap +/* + + OKI MSM5001N CMOS LCD Watch IC + +*/ + +#ifndef MAME_MACHINE_MSM5001N_H +#define MAME_MACHINE_MSM5001N_H + +#pragma once + +#include "dirtc.h" + +class msm5001n_device : public device_t, public device_rtc_interface, public device_nvram_interface +{ +public: + msm5001n_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock); + + // configuration helpers + auto write_segs() { return m_write_segs.bind(); } // COM in offset, SEG pins in data + + void d_w(int state); // D button + void s_w(int state); // S button + void power_w(int state); + + // set_current_time is unused here (only using dirtc for the counters) + virtual void set_current_time(const system_time &systime) override { ; } + +protected: + // device_t implementation + virtual void device_start() override; + virtual void device_clock_changed() override; + + // device_nvram_interface implementation + virtual void nvram_default() override { ; } + virtual bool nvram_read(util::read_stream &file) override; + virtual bool nvram_write(util::write_stream &file) override; + + virtual void rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) override { ; } // unused + +private: + enum mode : u8 + { + MODE_NORMAL_HRMIN = 0 + }; + + emu_timer *m_timer; + + bool m_power; + mode m_mode; + u8 m_counter; + + devcb_write16 m_write_segs; + + TIMER_CALLBACK_MEMBER(clock_tick); + void write_lcd(u8 *digits, bool colon); + void initialize(); +}; + + +DECLARE_DEVICE_TYPE(MSM5001N, msm5001n_device) + +#endif // MAME_MACHINE_MSM5001N_H diff --git a/src/devices/machine/sensorboard.h b/src/devices/machine/sensorboard.h index f8fb056e57b..97c6340f7bd 100644 --- a/src/devices/machine/sensorboard.h +++ b/src/devices/machine/sensorboard.h @@ -16,7 +16,7 @@ class sensorboard_device : public device_t, public device_nvram_interface public: sensorboard_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0); - enum sb_type + enum sb_type : u8 { NOSENSORS = 0, BUTTONS, diff --git a/src/mame/saitek/companion.cpp b/src/mame/saitek/companion.cpp index f44957660ff..279119772e4 100644 --- a/src/mame/saitek/companion.cpp +++ b/src/mame/saitek/companion.cpp @@ -173,7 +173,7 @@ static INPUT_PORTS_START( compan ) PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_UNUSED) PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_N) PORT_NAME("New Game") PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_3) PORT_CODE(KEYCODE_3_PAD) PORT_NAME("Rook") - PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_O) PORT_NAME("Play") + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_Y) PORT_NAME("Play") PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_B) PORT_NAME("Clear Board") PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_T) PORT_NAME("Take Back") PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_M) PORT_NAME("Multi Move") diff --git a/src/mame/saitek/companion2.cpp b/src/mame/saitek/companion2.cpp index 95fc32f8eaf..ecd6e2c8789 100644 --- a/src/mame/saitek/companion2.cpp +++ b/src/mame/saitek/companion2.cpp @@ -302,7 +302,7 @@ static INPUT_PORTS_START( expchess ) PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_4) PORT_CODE(KEYCODE_4_PAD) PORT_NAME("Bishop") PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_T) PORT_NAME("Take Back") PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_L) PORT_NAME("Level / Sound") - PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_O) PORT_NAME("Play / PVP") + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_Y) PORT_NAME("Play / PVP") PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_5) PORT_CODE(KEYCODE_5_PAD) PORT_NAME("Knight") PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_6) PORT_CODE(KEYCODE_6_PAD) PORT_NAME("Pawn") PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_2) PORT_CODE(KEYCODE_2_PAD) PORT_NAME("Queen") @@ -320,7 +320,7 @@ static INPUT_PORTS_START( compan2 ) PORT_MODIFY("IN.1") PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_L) PORT_NAME("Level") - PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_O) PORT_NAME("Play") + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_Y) PORT_NAME("Play") PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_CODE(KEYCODE_E) PORT_NAME("Enter Position") PORT_MODIFY("IN.2") -- cgit v1.2.3