diff options
Diffstat (limited to 'src/mame/entex/advision.cpp')
-rw-r--r-- | src/mame/entex/advision.cpp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/src/mame/entex/advision.cpp b/src/mame/entex/advision.cpp new file mode 100644 index 00000000000..e26f5e89404 --- /dev/null +++ b/src/mame/entex/advision.cpp @@ -0,0 +1,471 @@ +// license:BSD-3-Clause +// copyright-holders:Dan Boris, hap +/******************************************************************************* + +Entex Adventure Vision, tabletop game console + +Hardware notes: +- INS8048-11 @ 11MHz (1KB internal ROM) +- COP411 for the sound, 1-bit speaker with volume control +- Molex socket for 4KB cartridges +- 1KB external RAM (2*MM2114N) +- 40 small rectangular red LEDs, a motor with a fast spinning mirror gives the + illusion of a 150*40 screen +- 4-way joystick, 8 buttons (other than having buttons 2/4 swapped, left and + right button panels are electronically the same) +- expansion port (unused) + +The cartridge slot is different from what is common on other consoles. A game +cartridge is basically an EPROM chip wearing a jacket. Only 4 games were released +in total. + +The mirror rotates at around 7Hz, the motor speed is not controlled by software, +and it differs a bit per console. This can be adjusted after enabling -cheat. +There's a mirror on both sides so the display refreshes at around 14Hz. A similar +technology was later used in the Nintendo Virtual Boy. + +The display is faked in MAME. On the real thing, the picture is not as stable. The +width of 150 is specified by the BIOS, but it's possible to update the leds at a +different rate, hence MAME configures a larger screen. In fact, the homebrew demo +Code Red doesn't use the BIOS for it, and runs at 50*40 to save some RAM. + +TODO: +- display refresh is actually ~14Hz, but doing that will make MAME very sluggish + +BTANB: +- 2 thin vertical seams (do a hyperspace in defender to see them more clearly) +- glitches at the right edge during gameplay in scobra, and also during some + explosion sounds in defender +- scobra screen is shifted to the right when the game scrolls + +*******************************************************************************/ + +#include "emu.h" + +#include "bus/generic/slot.h" +#include "bus/generic/carts.h" +#include "cpu/cop400/cop400.h" +#include "cpu/mcs48/mcs48.h" +#include "machine/timer.h" +#include "sound/dac.h" +#include "sound/flt_vol.h" + +#include "screen.h" +#include "softlist_dev.h" +#include "speaker.h" + +namespace { + +class advision_state : public driver_device +{ +public: + advision_state(const machine_config &mconfig, device_type type, const char *tag) : + driver_device(mconfig, type, tag), + m_maincpu(*this, "maincpu"), + m_soundcpu(*this, "soundcpu"), + m_dac(*this, "dac"), + m_volume(*this, "volume"), + m_screen(*this, "screen"), + m_mirror_sync(*this, "mirror_sync"), + m_led_update(*this, "led_update"), + m_led_off(*this, "led_off"), + m_joy(*this, "JOY"), + m_conf(*this, "CONF") + { } + + void advision(machine_config &config); + + DECLARE_INPUT_CHANGED_MEMBER(set_screensize); + +protected: + virtual void machine_start() override ATTR_COLD; + virtual void machine_reset() override ATTR_COLD; + +private: + required_device<i8048_device> m_maincpu; + required_device<cop411_cpu_device> m_soundcpu; + required_device<dac_1bit_device> m_dac; + required_device<filter_volume_device> m_volume; + required_device<screen_device> m_screen; + required_device<timer_device> m_mirror_sync; + required_device<timer_device> m_led_update; + required_device<timer_device> m_led_off; + required_ioport m_joy; + required_ioport m_conf; + + static constexpr u32 DISPLAY_WIDTH = 0x400; + + bool m_video_strobe = false; + bool m_video_enable = false; + u8 m_video_bank = 0; + u32 m_video_hpos = 0; + u8 m_led_output[5] = { }; + u8 m_led_latch[5] = { }; + std::unique_ptr<u8[]> m_display; + + std::vector<u8> m_ext_ram; + u16 m_rambank = 0; + u8 m_sound_cmd = 0; + + void io_map(address_map &map) ATTR_COLD; + void program_map(address_map &map) ATTR_COLD; + + u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); + void av_control_w(u8 data); + void vblank(int state); + TIMER_DEVICE_CALLBACK_MEMBER(led_update); + TIMER_DEVICE_CALLBACK_MEMBER(led_off); + int vsync_r(); + + TIMER_CALLBACK_MEMBER(sound_cmd_sync); + u8 sound_cmd_r(); + void sound_g_w(u8 data); + void sound_d_w(u8 data); + + void bankswitch_w(u8 data); + u8 ext_ram_r(offs_t offset); + void ext_ram_w(offs_t offset, u8 data); + u8 controller_r(); +}; + + + +/******************************************************************************* + Video +*******************************************************************************/ + +INPUT_CHANGED_MEMBER(advision_state::set_screensize) +{ + // reconfigure screen size when settings changed + const int width = 9600 / (m_conf->read() & 0x3f); + const int height = 40 * 4 + 3 - (m_conf->read() >> 6 & 3); + m_screen->set_visible_area(0, width - 1, 0, height - 1); +} + +u32 advision_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + u8 led_height = (m_conf->read() >> 6 & 3) + 1; + + bitmap.fill(0, cliprect); + + for (int y = 0; y < 40; y++) + { + u8 *src = &m_display[y * DISPLAY_WIDTH]; + + for (int x = cliprect.left(); x <= cliprect.right(); x++) + { + u8 red = src[x] ? 0xff : 0; + u8 green = red / 16; + u8 blue = red / 12; + + int dy = y * 4 + (4 - led_height); + + for (int i = 0; i < led_height; i++) + { + if (cliprect.contains(x, dy + i)) + bitmap.pix(dy + i, x) = red << 16 | green << 8 | blue; + } + } + } + + return 0; +} + +void advision_state::av_control_w(u8 data) +{ + // P25-P27: led latch select + m_video_bank = data >> 5 & 7; + + // disable led outputs (there is some delay before it takes effect) + // see for example codered twister and anime girl, gaps between the 'pixels' should be visible but minimal + if (m_video_bank == 0 && m_led_off->expire().is_never()) + m_led_off->adjust(attotime::from_usec(39)); + + // P24 rising edge: transfer led latches to outputs + if (!m_video_strobe && bool(data & 0x10)) + { + std::copy_n(m_led_latch, std::size(m_led_output), m_led_output); + m_led_off->adjust(attotime::never); + m_video_enable = true; + } + m_video_strobe = bool(data & 0x10); + + // sync soundlatch (using gen_latch device here would give lots of logerror) + machine().scheduler().synchronize(timer_expired_delegate(FUNC(advision_state::sound_cmd_sync), this), data >> 4); +} + +void advision_state::vblank(int state) +{ + if (state && (m_screen->frame_number() & 3) == 0) + { + // starting a new frame + std::fill_n(m_display.get(), DISPLAY_WIDTH * 40, 0); + + m_mirror_sync->adjust(attotime::from_usec(100)); + + m_video_hpos = 0; + m_led_update->adjust(attotime::zero); + } +} + +TIMER_DEVICE_CALLBACK_MEMBER(advision_state::led_update) +{ + // write current leds to display buffer + for (int y = 0; y < 8; y++) + { + for (int b = 0; b < 5; b++) + { + int pixel = m_video_enable ? BIT(m_led_output[b], y ^ 7) : 0; + m_display[(b * 8 + y) * DISPLAY_WIDTH + m_video_hpos] = pixel; + } + } + + if (m_video_hpos < DISPLAY_WIDTH - 1) + { + // for official games, 1 'pixel' is 60us, but there are two spots that have + // a longer duration: at x=50 and x=100 (see BTANB note about seams) + m_led_update->adjust(attotime::from_usec(m_conf->read() & 0x3f)); + m_video_hpos++; + } +} + +TIMER_DEVICE_CALLBACK_MEMBER(advision_state::led_off) +{ + m_video_enable = false; +} + +int advision_state::vsync_r() +{ + // T1: mirror sync pulse (half rotation) + return (m_mirror_sync->enabled()) ? 0 : 1; +} + + + +/******************************************************************************* + Sound +*******************************************************************************/ + +TIMER_CALLBACK_MEMBER(advision_state::sound_cmd_sync) +{ + m_sound_cmd = param; +} + +u8 advision_state::sound_cmd_r() +{ + // L0-L3: sound command + return m_sound_cmd; +} + +void advision_state::sound_g_w(u8 data) +{ + // G0: speaker out + m_dac->write(data & 1); +} + +void advision_state::sound_d_w(u8 data) +{ + // D0: speaker volume + m_volume->set_gain((data & 1) ? 0.5 : 1.0); +} + + + +/******************************************************************************* + Memory +*******************************************************************************/ + +void advision_state::bankswitch_w(u8 data) +{ + // P10,P11: RAM bank + m_rambank = data & 3; + + // P12: 8048 EA pin + m_maincpu->set_input_line(MCS48_INPUT_EA, BIT(data, 2) ? ASSERT_LINE : CLEAR_LINE); +} + +u8 advision_state::ext_ram_r(offs_t offset) +{ + // read from external RAM + u8 data = m_ext_ram[m_rambank << 8 | offset]; + if (machine().side_effects_disabled()) + return data; + + // transfer data to led latch + if (m_video_bank > 0 && m_video_bank < 6) + m_led_latch[5 - m_video_bank] = data ^ 0xff; + + // reset sound cpu + else if (m_video_bank == 6) + m_soundcpu->set_input_line(INPUT_LINE_RESET, (data & 1) ? CLEAR_LINE : ASSERT_LINE); + + return data; +} + +void advision_state::ext_ram_w(offs_t offset, u8 data) +{ + // write to external RAM + m_ext_ram[m_rambank << 8 | offset] = data; +} + +void advision_state::program_map(address_map &map) +{ + map(0x0000, 0x0fff).r("cartslot", FUNC(generic_slot_device::read_rom)); +} + +void advision_state::io_map(address_map &map) +{ + map(0x00, 0xff).rw(FUNC(advision_state::ext_ram_r), FUNC(advision_state::ext_ram_w)); +} + + + +/******************************************************************************* + Inputs +*******************************************************************************/ + +u8 advision_state::controller_r() +{ + u8 data = m_joy->read(); + + // some of the buttons share the same bitmask as joystick directions + if (~data & 0x01) data &= 0xcf; // button 1: down + up + if (~data & 0x02) data &= 0xaf; // button 2: down + right + if (~data & 0x04) data &= 0x6f; // button 4: down + left + + return data | 0x07; +} + +static INPUT_PORTS_START( advision ) + PORT_START("JOY") + PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_BUTTON1 ) // u + PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_BUTTON2 ) // r + PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_BUTTON4 ) // l + PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_BUTTON3 ) // d + PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN ) PORT_4WAY + PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_JOYSTICK_UP ) PORT_4WAY + PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_4WAY + PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT ) PORT_4WAY + + PORT_START("CONF") + PORT_CONFNAME( 0x3f, 20, "Screen Width" ) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(advision_state::set_screensize), 0) // factors of 60 + PORT_CONFSETTING( 60, "160" ) + PORT_CONFSETTING( 30, "320" ) + PORT_CONFSETTING( 20, "480" ) + PORT_CONFSETTING( 15, "640" ) + PORT_CONFSETTING( 12, "800" ) + PORT_CONFSETTING( 10, "960" ) + PORT_CONFNAME( 0xc0, 0x80, "LED Height" ) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(advision_state::set_screensize), 0) + PORT_CONFSETTING( 0x00, "25%" ) + PORT_CONFSETTING( 0x40, "50%" ) + PORT_CONFSETTING( 0x80, "75%" ) + PORT_CONFSETTING( 0xc0, "100%" ) +INPUT_PORTS_END + + + +/******************************************************************************* + Machine Initialization +*******************************************************************************/ + +void advision_state::machine_start() +{ + // allocate display buffer + m_display = std::make_unique<u8 []>(DISPLAY_WIDTH * 40); + std::fill_n(m_display.get(), DISPLAY_WIDTH * 40, 0); + save_pointer(NAME(m_display), DISPLAY_WIDTH * 40); + + // allocate external RAM + m_ext_ram.resize(0x400); + save_item(NAME(m_ext_ram)); + + save_item(NAME(m_led_output)); + save_item(NAME(m_led_latch)); + + save_item(NAME(m_rambank)); + save_item(NAME(m_video_strobe)); + save_item(NAME(m_video_enable)); + save_item(NAME(m_video_bank)); + save_item(NAME(m_sound_cmd)); + save_item(NAME(m_video_hpos)); +} + +void advision_state::machine_reset() +{ + m_video_hpos = 0; + m_led_update->adjust(attotime::never); + + // reset sound CPU + m_soundcpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE); +} + + + +/******************************************************************************* + Machine Config +*******************************************************************************/ + +void advision_state::advision(machine_config &config) +{ + // basic machine hardware + I8048(config, m_maincpu, 11_MHz_XTAL); + m_maincpu->set_addrmap(AS_PROGRAM, &advision_state::program_map); + m_maincpu->set_addrmap(AS_IO, &advision_state::io_map); + m_maincpu->p1_in_cb().set(FUNC(advision_state::controller_r)); + m_maincpu->p1_out_cb().set(FUNC(advision_state::bankswitch_w)); + m_maincpu->p2_out_cb().set(FUNC(advision_state::av_control_w)); + m_maincpu->t1_in_cb().set(FUNC(advision_state::vsync_r)); + + COP411(config, m_soundcpu, 200000); // COP411L-KCN/N, R11=82k, C8=56pF + m_soundcpu->set_config(COP400_CKI_DIVISOR_4, COP400_CKO_RAM_POWER_SUPPLY, false); + m_soundcpu->read_l().set(FUNC(advision_state::sound_cmd_r)); + m_soundcpu->write_g().set(FUNC(advision_state::sound_g_w)); + m_soundcpu->write_d().set(FUNC(advision_state::sound_d_w)); + + // video hardware + SCREEN(config, m_screen, SCREEN_TYPE_RASTER); + m_screen->set_refresh_hz(4 * 14); // see notes + m_screen->set_vblank_time(0); + m_screen->set_size(960, 40 * 4 + 4); + m_screen->set_visarea(0, 480 - 1, 0, 40 * 4 - 0); // default setting + m_screen->set_screen_update(FUNC(advision_state::screen_update)); + m_screen->screen_vblank().set(FUNC(advision_state::vblank)); + + TIMER(config, "mirror_sync").configure_generic(nullptr); + TIMER(config, "led_update").configure_generic(FUNC(advision_state::led_update)); + TIMER(config, "led_off").configure_generic(FUNC(advision_state::led_off)); + + // sound hardware + SPEAKER(config, "speaker").front_center(); + DAC_1BIT(config, m_dac).add_route(ALL_OUTPUTS, "volume", 0.25); + FILTER_VOLUME(config, m_volume).add_route(ALL_OUTPUTS, "speaker", 1.0); + + // cartridge + GENERIC_CARTSLOT(config, "cartslot", generic_plain_slot, "advision_cart"); + SOFTWARE_LIST(config, "cart_list").set_original("advision"); +} + + + +/******************************************************************************* + ROMs +*******************************************************************************/ + +ROM_START( advision ) + ROM_REGION( 0x1000, "maincpu", ROMREGION_ERASE00 ) + ROM_LOAD( "ins8048-11kdp_n.u5", 0x000, 0x400, CRC(279e33d1) SHA1(bf7b0663e9125c9bfb950232eab627d9dbda8460) ) // "<natsemi logo> /B8225 \\ INS8048-11KDP/N" + + ROM_REGION( 0x200, "soundcpu", 0 ) + ROM_LOAD( "cop411l-kcn_n.u8", 0x000, 0x200, CRC(81e95975) SHA1(8b6f8c30dd3e9d8e43f1ea20fba2361b383790eb) ) // "<natsemi logo> /B8223 \\ COP411L-KCN/N" +ROM_END + +} // anonymous namespace + + + +/******************************************************************************* + Driver +*******************************************************************************/ + +// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS +SYST( 1982, advision, 0, 0, advision, advision, advision_state, empty_init, "Entex", "Adventure Vision", MACHINE_IMPERFECT_GRAPHICS | MACHINE_SUPPORTS_SAVE ) |