microvsn: cleanup, Finish support for i8021 based cartridges (nw)

This commit is contained in:
hap 2020-04-13 00:12:23 +02:00
parent 2add7c158c
commit 7f3cbeb198
4 changed files with 253 additions and 282 deletions

View File

@ -106,7 +106,7 @@ The "overlay" feature indicates screen/keypad overlay
<info name="pcb" value="4952-79 REV-B" /> <!-- correct? -->
<info name="serial" value="4971" />
<part name="cart" interface="microvision_cart">
<feature name="clock" value="500000" /> <!-- correct? -->
<feature name="clock" value="500000" /> <!-- RC = 100pf/23.2K? (matches video recording) -->
<feature name="pla" value="1" />
<feature name="paddle" value="1" />
<feature name="overlay" value="2" />
@ -123,10 +123,10 @@ The "overlay" feature indicates screen/keypad overlay
<info name="pcb" value="4971-REV-C" />
<info name="serial" value="4971" />
<part name="cart" interface="microvision_cart">
<feature name="clock" value="2000000" /> <!-- LC circuit -->
<feature name="clock" value="3500000" /> <!-- LC circuit, correct? (matches video recording) -->
<feature name="paddle" value="1" />
<feature name="overlay" value="2" />
<dataarea name="rom" size="0x400"> <!-- I8021 MCU -->
<dataarea name="rom" size="0x400"> <!-- Intel 8021 MCU -->
<rom name="4971" size="0x400" crc="bdd8660b" sha1="971f9117dc809f7b9796134208b47ea34990ae71"/>
</dataarea>
</part>

View File

@ -14,6 +14,7 @@
TODO:
- split driver into several files?
- megaiv/smondial leds are tri-color
- why are megaiv/smondial2 beeps noisy?
- add Monte Carlo IV (non-LE)
- add MM 1000 module

View File

@ -1,7 +1,7 @@
// license:BSD-3-Clause
// copyright-holders:Wilbert Pol, hap
// thanks-to:Dan Boris, Kevin Horton, Sean Riddle
/***************************************************************************
/******************************************************************************
Milton Bradley MicroVision, handheld game console
@ -11,21 +11,16 @@ Hardware notes:
- no CPU on console, it is on the cartridge
12 games were released, all of them have a TMS1100 MCU. The first couple of
games had an I8021 MCU at first, but Milton Bradley switched to TMS1100.
Since the microcontrollers were on the cartridges it was possible to have
different clocks on different games.
The Connect Four I8021 game is clocked at around 2MHz. The TMS1100 versions
of the games were clocked at around 500KHz, 550KHz, or 350KHz.
games had an Intel 8021 MCU at first, but Milton Bradley switched to TMS1100.
Each game had a screen- and keypad overlay attached to it, MAME external
artwork is recommended. It's also advised to disable screen filtering,
eg. with -prescale, or on Windows simply -video gdi.
TODO:
- Finish support for i8021 based cartridges
- dump/add remaining 8021 cartridges
****************************************************************************/
******************************************************************************/
#include "emu.h"
@ -33,6 +28,7 @@ TODO:
#include "bus/generic/slot.h"
#include "cpu/mcs48/mcs48.h"
#include "cpu/tms1000/tms1100.h"
#include "machine/timer.h"
#include "sound/dac.h"
#include "sound/volt_reg.h"
#include "video/hlcd0488.h"
@ -43,21 +39,21 @@ TODO:
#include "screen.h"
#include "speaker.h"
//#define VERBOSE 1
#include "logmacro.h"
namespace {
class microvision_state : public driver_device
{
public:
microvision_state(const machine_config &mconfig, device_type type, const char *tag) :
driver_device(mconfig, type, tag),
m_dac( *this, "dac" ),
m_i8021( *this, "i8021_cpu" ),
m_tms1100( *this, "tms1100_cpu" ),
m_i8021( *this, "i8021_cpu" ),
m_lcd(*this, "lcd"),
m_lcd_pwm(*this, "lcd_pwm"),
m_dac( *this, "dac" ),
m_cart(*this, "cartslot"),
m_paddle_timer(*this, "paddle_timer"),
m_inputs(*this, "COL%u", 0),
m_paddle(*this, "PADDLE"),
m_conf(*this, "CONF"),
@ -69,56 +65,25 @@ public:
DECLARE_INPUT_CHANGED_MEMBER(conf_changed) { apply_settings(); }
protected:
static constexpr device_timer_id TIMER_PADDLE = 0;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
virtual void machine_start() override;
virtual void machine_reset() override;
virtual void device_post_load() override { apply_settings(); }
private:
uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
DECLARE_DEVICE_IMAGE_LOAD_MEMBER(cart_load);
// i8021 interface
DECLARE_WRITE8_MEMBER(i8021_p0_write);
DECLARE_WRITE8_MEMBER(i8021_p1_write);
DECLARE_WRITE8_MEMBER(i8021_p2_write);
DECLARE_READ_LINE_MEMBER(i8021_t1_read);
DECLARE_READ8_MEMBER(i8021_bus_read);
// TMS1100 interface
DECLARE_READ8_MEMBER(tms1100_read_k);
DECLARE_WRITE16_MEMBER(tms1100_write_o);
DECLARE_WRITE16_MEMBER(tms1100_write_r);
u32 tms1100_decode_micro(offs_t offset);
required_device<dac_byte_interface> m_dac;
optional_device<i8021_device> m_i8021;
optional_device<tms1100_cpu_device> m_tms1100;
optional_device<i8021_device> m_i8021;
required_device<hlcd0488_device> m_lcd;
required_device<pwm_display_device> m_lcd_pwm;
required_device<dac_byte_interface> m_dac;
required_device<generic_slot_device> m_cart;
required_device<timer_device> m_paddle_timer;
required_ioport_array<3> m_inputs;
required_ioport m_paddle;
required_ioport m_conf;
output_finder<> m_overlay_out;
// Timers
emu_timer *m_paddle_timer;
// i8021 variables
uint8_t m_p0;
uint8_t m_p2;
uint8_t m_t1;
// tms1100 variables
uint16_t m_r;
uint16_t m_o;
// generic variables
DECLARE_WRITE16_MEMBER(lcd_output_w);
u32 tms1100_decode_micro(offs_t offset);
DECLARE_DEVICE_IMAGE_LOAD_MEMBER(cart_load);
void apply_settings(void);
u8 m_pla_auto;
@ -126,238 +91,51 @@ private:
u16 m_button_mask;
bool m_paddle_auto;
bool m_paddle_on;
};
uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
DECLARE_WRITE16_MEMBER(lcd_output_w) { m_lcd_pwm->matrix(offset, data); }
// TMS1100 interface
DECLARE_READ8_MEMBER(tms1100_k_r);
DECLARE_WRITE16_MEMBER(tms1100_o_w);
DECLARE_WRITE16_MEMBER(tms1100_r_w);
u16 m_r = 0;
// Intel 8021 interface
DECLARE_READ8_MEMBER(i8021_p0_r);
DECLARE_WRITE8_MEMBER(i8021_p0_w);
DECLARE_WRITE8_MEMBER(i8021_p1_w);
DECLARE_WRITE8_MEMBER(i8021_p2_w);
DECLARE_READ_LINE_MEMBER(i8021_t1_r);
u8 m_p0 = 0xff;
u8 m_p2 = 0xff;
};
void microvision_state::machine_start()
{
m_paddle_timer = timer_alloc(TIMER_PADDLE);
m_overlay_out.resolve();
// register for savestates
save_item(NAME(m_r));
save_item(NAME(m_p0));
save_item(NAME(m_p2));
save_item(NAME(m_t1));
save_item(NAME(m_r));
save_item(NAME(m_o));
}
// don't save: m_pla_auto, m_overlay_auto, m_paddle_auto,
// m_button_mask, m_paddle_on
}
void microvision_state::machine_reset()
{
apply_settings();
m_o = 0;
m_r = 0;
m_p0 = 0;
m_p2 = 0;
m_t1 = 0;
m_paddle_timer->adjust(attotime::never);
}
uint32_t microvision_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
for (int y = 0; y < 16; y++)
{
for (int x = 0; x < 16; x++)
{
// simulate LCD persistence
int p = m_lcd_pwm->read_element_bri(y ^ 15, x ^ 15) * 25000;
p = (p > 255) ? 0 : p ^ 255;
bitmap.pix32(y, x) = p << 16 | p << 8 | p;
}
}
return 0;
}
WRITE16_MEMBER( microvision_state::lcd_output_w )
{
m_lcd_pwm->matrix(offset, data);
}
void microvision_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch ( id )
{
case TIMER_PADDLE:
m_t1 = 0;
break;
}
}
/*
x--- ---- KEY3
-x-- ---- KEY4
--x- ---- KEY5
---x ---- KEY6
---- x---
---- -x-- KEY0
---- --x- KEY1
---- ---x KEY2
*/
WRITE8_MEMBER( microvision_state::i8021_p0_write )
{
LOG( "p0_write: %02x\n", data );
m_p0 = data;
}
/*
x--- ---- LCD3
-x-- ---- LCD2
--x- ---- LCD1
---x ---- LCD0
---- --x- LCD5
---- ---x LCD4
*/
WRITE8_MEMBER( microvision_state::i8021_p1_write )
{
LOG( "p1_write: %02x\n", data );
m_lcd->data_w(data >> 4 & 0xf);
m_lcd->latch_pulse_w(BIT(data, 0));
m_lcd->data_clk_w(BIT(data, 1));
}
/*
---- xx-- CAP2 (paddle)
---- --x- SPKR1
---- ---x SPKR0
*/
WRITE8_MEMBER( microvision_state::i8021_p2_write )
{
LOG( "p2_write: %02x\n", data );
m_p2 = data;
m_dac->write(m_p2 & 0x03);
if ( m_p2 & 0x0c )
{
m_t1 = 1;
// Stop paddle timer
m_paddle_timer->adjust( attotime::never );
}
else
{
// Start paddle timer (min is 160uS, max is 678uS)
uint8_t paddle = 255 - ioport("PADDLE")->read();
m_paddle_timer->adjust( attotime::from_usec(160 + ( 518 * paddle ) / 255 ) );
}
}
READ_LINE_MEMBER( microvision_state::i8021_t1_read )
{
return m_t1;
}
READ8_MEMBER( microvision_state::i8021_bus_read )
{
uint8_t data = m_p0;
uint8_t col0 = ioport("COL0")->read();
uint8_t col1 = ioport("COL1")->read();
uint8_t col2 = ioport("COL2")->read();
// Row scanning
if ( ! ( m_p0 & 0x80 ) )
{
uint8_t t = ( ( col0 & 0x01 ) << 2 ) | ( ( col1 & 0x01 ) << 1 ) | ( col2 & 0x01 );
data &= ( t ^ 0xFF );
}
if ( ! ( m_p0 & 0x40 ) )
{
uint8_t t = ( ( col0 & 0x02 ) << 1 ) | ( col1 & 0x02 ) | ( ( col2 & 0x02 ) >> 1 );
data &= ( t ^ 0xFF );
}
if ( ! ( m_p0 & 0x20 ) )
{
uint8_t t = ( col0 & 0x04 ) | ( ( col1 & 0x04 ) >> 1 ) | ( ( col2 & 0x04 ) >> 2 );
data &= ( t ^ 0xFF );
}
if ( ! ( m_p0 & 0x10 ) )
{
uint8_t t = ( ( col0 & 0x08 ) >> 1 ) | ( ( col1 & 0x08 ) >> 2 ) | ( ( col2 & 0x08 ) >> 3 );
data &= ( t ^ 0xFF );
}
return data;
}
READ8_MEMBER( microvision_state::tms1100_read_k )
{
uint8_t data = 0;
LOG("read_k\n");
// multiplexed inputs
for (int i = 0; i < 3; i++)
if (BIT(m_r, i + 8))
data |= m_inputs[i]->read() & (m_button_mask >> (i * 4) & 0xf);
// K8: paddle capacitor
if (m_paddle_on)
{
u8 paddle = m_paddle_timer->enabled() ? 0 : BIT(m_r, 2);
return paddle << 3 | data;
}
else
return data;
}
WRITE16_MEMBER( microvision_state::tms1100_write_o )
{
LOG("write_o: %04x\n", data);
// O0-O3: LCD data
m_lcd->data_w(data & 0xf);
}
WRITE16_MEMBER( microvision_state::tms1100_write_r )
{
LOG("write_r: %04x\n", data);
// R2: charge paddle capacitor
if (~m_r & data & 4 && m_paddle_on)
{
// range is ~360us to ~2663us (measured on 4952-79 REV B PCB)
// note that the games don't use the whole range, so there's a deadzone around the edges
float step = (2000 - 500) / 255.0; // approximate it
m_paddle_timer->adjust(attotime::from_usec(500 + m_paddle->read() * step));
}
// R0: speaker lead 2
// R1: speaker lead 1 (GND on some carts)
m_dac->write((BIT(data, 0) << 1) | BIT(data, 1));
// R6: LCD latch pulse
// R7: LCD data clock
m_lcd->latch_pulse_w(BIT(data, 6));
m_lcd->data_clk_w(BIT(data, 7));
// R8-R10: input mux
m_r = data;
}
/******************************************************************************
Cartridge Init
******************************************************************************/
static const u16 microvision_output_pla[2][0x20] =
{
@ -424,7 +202,7 @@ DEVICE_IMAGE_LOAD_MEMBER(microvision_state::cart_load)
m_cart->common_load_rom(m_cart->get_rom_base(), size, "rom");
// set default settings
u32 clock = (size == 0x400) ? 2000000 : 500000;
u32 clock = (size == 0x400) ? 3500000 : 500000;
m_pla_auto = 0;
m_overlay_auto = 0;
m_paddle_auto = false;
@ -467,8 +245,31 @@ void microvision_state::apply_settings()
// overlay physically restricts button panel
switch (overlay)
{
case 1: case 5:
m_button_mask = 0x454;
break;
case 2: case 8: case 10:
m_button_mask = 0x555;
break;
case 3:
m_button_mask = 0x858;
break;
case 4:
m_button_mask = 0xaea;
break;
case 6:
m_button_mask = 0xaaa;
break;
case 11:
m_button_mask = 0xafa;
break;
case 12:
m_button_mask = 0x404;
break;
default:
m_button_mask = 0xfff;
break;
}
u8 pla = ((conf & 0x18) == 0x10) ? m_pla_auto : (conf >> 3 & 1);
@ -478,6 +279,155 @@ void microvision_state::apply_settings()
}
/******************************************************************************
Video
******************************************************************************/
uint32_t microvision_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
for (int y = 0; y < 16; y++)
{
for (int x = 0; x < 16; x++)
{
// simulate LCD persistence
int p = m_lcd_pwm->read_element_bri(y ^ 15, x ^ 15) * 25000;
p = (p > 255) ? 0 : p ^ 255;
bitmap.pix32(y, x) = p << 16 | p << 8 | p;
}
}
return 0;
}
/******************************************************************************
I/O
******************************************************************************/
// TMS1100 interface
READ8_MEMBER(microvision_state::tms1100_k_r)
{
u8 data = 0;
// multiplexed inputs
for (int i = 0; i < 3; i++)
if (BIT(m_r, i + 8))
data |= m_inputs[i]->read() & (m_button_mask >> (i * 4) & 0xf);
// K8: paddle capacitor
if (m_paddle_on)
{
u8 paddle = m_paddle_timer->enabled() ? 0 : BIT(m_r, 2);
return paddle << 3 | data;
}
else
return data;
}
WRITE16_MEMBER(microvision_state::tms1100_o_w)
{
// O0-O3: LCD data
m_lcd->data_w(data & 0xf);
}
WRITE16_MEMBER(microvision_state::tms1100_r_w)
{
// R2: charge paddle capacitor when high
if (~m_r & data & 4 && m_paddle_on)
{
// note that the games don't use the whole range, so there's a deadzone around the edges
const float step = (2000 - 500) / 255.0; // approximation
m_paddle_timer->adjust(attotime::from_usec(500 + m_paddle->read() * step));
}
// R0: speaker lead 2
// R1: speaker lead 1 (GND on some carts)
m_dac->write((BIT(data, 0) << 1) | BIT(data, 1));
// R6: LCD latch pulse
// R7: LCD data clock
m_lcd->latch_pulse_w(BIT(data, 6));
m_lcd->data_clk_w(BIT(data, 7));
// R8-R10: input mux
m_r = data;
}
// Intel 8021 interface
READ8_MEMBER( microvision_state::i8021_p0_r )
{
u8 in[3];
for (int i = 0; i < 3; i++)
in[i] = m_inputs[i]->read() & (m_button_mask >> (i * 4) & 0xf);
u8 data = 0;
// P00-P02: multiplexed inputs from P04-P07
for (int i = 0; i < 3; i++)
if (~(m_p0 >> 4) & in[i])
data |= 1 << i;
// P04-P07: multiplexed inputs from P00-P02
for (int i = 0; i < 3; i++)
if (BIT(~m_p0, i))
data |= in[i] << 4;
return ~data & m_p0;
}
WRITE8_MEMBER(microvision_state::i8021_p0_w)
{
// P0-P2, P4-P7: input mux
m_p0 = data;
}
WRITE8_MEMBER(microvision_state::i8021_p1_w)
{
// P14-P17: lcd data
m_lcd->data_w(data >> 4 & 0xf);
// P10: lcd latch pulse
// P11: lcd data clk
m_lcd->latch_pulse_w(BIT(data, 0));
m_lcd->data_clk_w(BIT(data, 1));
}
WRITE8_MEMBER(microvision_state::i8021_p2_w)
{
// P20: speaker lead 1
// P21: speaker lead 2
m_dac->write(data & 3);
// P22,P23: charge paddle capacitor when low
if (m_p2 & 0xc && (data & 0xc) == 0 && m_paddle_on)
{
const float step = (1000 - 10) / 255.0; // approximation
m_paddle_timer->adjust(attotime::from_usec(10 + (m_paddle->read() ^ 0xff) * step));
}
m_p2 = data;
}
READ_LINE_MEMBER(microvision_state::i8021_t1_r)
{
// T1: paddle capacitor (active low)
int active = (m_p2 & 0xc) ? 1 : 0;
active |= m_paddle_timer->enabled() ? 1 : 0;
return (m_paddle_on) ? active : 1;
}
/******************************************************************************
Input Ports
******************************************************************************/
static INPUT_PORTS_START( microvision )
PORT_START("COL0")
PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON12 ) PORT_CODE(KEYCODE_C)
@ -515,22 +465,29 @@ static INPUT_PORTS_START( microvision )
INPUT_PORTS_END
/******************************************************************************
Machine Configs
******************************************************************************/
void microvision_state::microvision(machine_config &config)
{
/* basic machine hardware */
I8021(config, m_i8021, 0);
m_i8021->bus_out_cb().set(FUNC(microvision_state::i8021_p0_write));
m_i8021->p1_out_cb().set(FUNC(microvision_state::i8021_p1_write));
m_i8021->p2_out_cb().set(FUNC(microvision_state::i8021_p2_write));
m_i8021->t1_in_cb().set(FUNC(microvision_state::i8021_t1_read));
m_i8021->bus_in_cb().set(FUNC(microvision_state::i8021_bus_read));
TMS1100(config, m_tms1100, 0);
m_tms1100->set_output_pla(microvision_output_pla[0]);
m_tms1100->set_decode_micro().set(FUNC(microvision_state::tms1100_decode_micro));
m_tms1100->k().set(FUNC(microvision_state::tms1100_read_k));
m_tms1100->o().set(FUNC(microvision_state::tms1100_write_o));
m_tms1100->r().set(FUNC(microvision_state::tms1100_write_r));
m_tms1100->k().set(FUNC(microvision_state::tms1100_k_r));
m_tms1100->o().set(FUNC(microvision_state::tms1100_o_w));
m_tms1100->r().set(FUNC(microvision_state::tms1100_r_w));
I8021(config, m_i8021, 0);
m_i8021->bus_in_cb().set(FUNC(microvision_state::i8021_p0_r));
m_i8021->bus_out_cb().set(FUNC(microvision_state::i8021_p0_w));
m_i8021->p1_out_cb().set(FUNC(microvision_state::i8021_p1_w));
m_i8021->p2_out_cb().set(FUNC(microvision_state::i8021_p2_w));
m_i8021->t1_in_cb().set(FUNC(microvision_state::i8021_t1_r));
TIMER(config, "paddle_timer").configure_generic(nullptr);
/* video hardware */
HLCD0488(config, m_lcd);
@ -561,6 +518,11 @@ void microvision_state::microvision(machine_config &config)
}
/******************************************************************************
ROM Definitions
******************************************************************************/
ROM_START( microvsn )
// nothing here yet, ROM is on the cartridge
ROM_REGION( 0x400, "i8021_cpu", ROMREGION_ERASE00 )
@ -569,5 +531,13 @@ ROM_START( microvsn )
ROM_REGION( 365, "tms1100_cpu:opla", ROMREGION_ERASE00 )
ROM_END
} // anonymous namespace
CONS( 1979, microvsn, 0, 0, microvision, microvision, microvision_state, empty_init, "Milton Bradley", "MicroVision", MACHINE_NOT_WORKING | MACHINE_REQUIRES_ARTWORK )
/******************************************************************************
Drivers
******************************************************************************/
// YEAR NAME PARENT CMP MACHINE INPUT CLASS INIT COMPANY, FULLNAME, FLAGS
CONS( 1979, microvsn, 0, 0, microvision, microvision, microvision_state, empty_init, "Milton Bradley", "MicroVision", MACHINE_NOT_WORKING | MACHINE_SUPPORTS_SAVE | MACHINE_REQUIRES_ARTWORK )

View File

@ -1,7 +1,7 @@
// license:BSD-3-Clause
// copyright-holders:hap
// thanks-to:Kevin Horton, Sean Riddle
/***************************************************************************
/******************************************************************************
Entex Select-A-Game Machine, handheld game console.
Technically, the main unit is the peripheral(buttons, display, speaker, power),
@ -32,7 +32,7 @@ are played, Space Invader 2 is an exception.
TODO:
- add the rest of the games
***************************************************************************/
******************************************************************************/
#include "emu.h"