Skip to content

Commit

Permalink
add preliminary emulation of OKI MSM5001N CMOS LCD Watch IC
Browse files Browse the repository at this point in the history
  • Loading branch information
happppp committed Dec 15, 2023
1 parent 74c4a0c commit 365b977
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 4 deletions.
12 changes: 12 additions & 0 deletions scripts/src/machine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
200 changes: 200 additions & 0 deletions src/devices/machine/msm5001n.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
65 changes: 65 additions & 0 deletions src/devices/machine/msm5001n.h
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/devices/machine/sensorboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/mame/saitek/companion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions src/mame/saitek/companion2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down

0 comments on commit 365b977

Please sign in to comment.