// license:GPL-2.0+
// copyright-holders:Peter Trauner, Dan Boris, Dirk Best, Robbbert
/******************************************************************************
AIM65
******************************************************************************/
#include "emu.h"
#include "includes/aim65.h"
//#define VERBOSE 1
#include "logmacro.h"
/******************************************************************************
Interrupt handling
******************************************************************************/
/* STEP/RUN
*
* Switch S2 (STEP/RUN) causes AIM 65 to operate either in the RUN mode or the
* single STEP mode. In the STEP mode, the NMI interrupt line is driven low
* when SYNC and O2 go high during instruction execution if the address lines
* are outside the A000-FFFF range. The NMI interrupt occurs on the high to
* low transition of the NMI line. The Monitor software will trace instructions
* and register, outside the Monitor instruction address range if the trace
* modes are selected and the NMI Interrupt Routine is not bypassed.
*/
/******************************************************************************
6821 PIA
******************************************************************************/
/* PA0: A0 (Address)
* PA1: A1 (Address)
* PA2: CE1 (Chip enable)
* PA3: CE2 (Chip enable)
* PA4: CE3 (Chip enable)
* PA5: CE4 (Chip enable)
* PA6: CE5 (Chip enable)
* PA7: W (Write enable)
* PB0-6: D0-D6 (Data)
* PB7: CU (Cursor)
*/
void aim65_state::u1_pa_w( u8 data )
{
LOG("pia a: a=%u /ce=%u,%u,%u,%u,%u /wr=%u\n",
data & 0x03, BIT(data, 2), BIT(data, 3), BIT(data, 4), BIT(data, 5), BIT(data, 6), BIT(data, 7));
for (std::size_t index = 0; m_ds.size() > index; ++index)
{
m_ds[index]->wr_w(BIT(data, 7));
m_ds[index]->ce_w(BIT(data, 2 + index));
m_ds[index]->addr_w(data & 0x03);
}
}
void aim65_state::u1_pb_w( u8 data )
{
LOG("pia b: d=%02x /cu=%u\n", data & 0x7f, BIT(data, 7));
for (required_device<dl1416_device> &ds : m_ds)
{
ds->cu_w(BIT(data, 7));
ds->data_w(data & 0x7f);
}
}
template <unsigned D> void aim65_state::update_ds(offs_t offset, u16 data)
{
m_digits[((D - 1) << 2) | (offset ^ 3)] = data;
}
template void aim65_state::update_ds<1>(offs_t offset, u16 data);
template void aim65_state::update_ds<2>(offs_t offset, u16 data);
template void aim65_state::update_ds<3>(offs_t offset, u16 data);
template void aim65_state::update_ds<4>(offs_t offset, u16 data);
template void aim65_state::update_ds<5>(offs_t offset, u16 data);
/******************************************************************************
6532 RIOT
******************************************************************************/
u8 aim65_state::z33_pb_r()
{
u8 data = 0xff;
/* scan keyboard rows */
for (u8 i = 0; i < 8; i++)
if (!BIT(m_riot_port_a, i))
data &= m_io_keyboard[i]->read();
return data;
}
/***************************************************************************
DRIVER INIT
***************************************************************************/
void aim65_state::machine_start()
{
address_space &space = m_maincpu->space(AS_PROGRAM);
m_digits.resolve();
// Init ROM sockets
if (m_z24->exists())
space.install_read_handler(0xd000, 0xdfff, read8sm_delegate(*m_z24, FUNC(generic_slot_device::read_rom)));
if (m_z25->exists())
space.install_read_handler(0xc000, 0xcfff, read8sm_delegate(*m_z25, FUNC(generic_slot_device::read_rom)));
if (m_z26->exists())
space.install_read_handler(0xb000, 0xbfff, read8sm_delegate(*m_z26, FUNC(generic_slot_device::read_rom)));
// Init PROM/ROM module sockets
if (m_z12->exists())
space.install_read_handler(0x4000, 0x4fff, read8sm_delegate(*m_z12, FUNC(generic_slot_device::read_rom)));
if (m_z13->exists())
space.install_read_handler(0x5000, 0x5fff, read8sm_delegate(*m_z13, FUNC(generic_slot_device::read_rom)));
if (m_z14->exists())
space.install_read_handler(0x6000, 0x6fff, read8sm_delegate(*m_z14, FUNC(generic_slot_device::read_rom)));
if (m_z15->exists())
space.install_read_handler(0x7000, 0x7fff, read8sm_delegate(*m_z15, FUNC(generic_slot_device::read_rom)));
// Init RAM
space.install_ram(0x0000, m_ram->size() - 1, m_ram->pointer());
// Register save state
save_item(NAME(m_riot_port_a));
save_item(NAME(m_pb_save));
save_item(NAME(m_kb_en));
save_item(NAME(m_ca2));
save_item(NAME(m_cb2));
save_item(NAME(m_printer_x));
save_item(NAME(m_printer_y));
save_item(NAME(m_printer_flag));
save_item(NAME(m_printer_level));
m_print_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(aim65_state::printer_timer),this));
m_printerRAM = make_unique_clear<uint16_t[]>(64*100);
save_pointer(NAME(m_printerRAM), 64*100);
}
/* RESET
*
* Pushbutton switch S1 initiates RESET of the AIM65 hardware and software.
* Timer Z4 holds the RES low for at least 15 ms from the time the pushbutton
* is released. RES is routed to the R6502 CPU, the Monitor R6522 (Z32), the
* Monitor R6532 RIOT (Z33), the user R6522 VIA (Z1), and the display R6520 PIA
* (U1). To initiate the device RESET function is also routed to the expansion
* connector for off-board RESET functions. The Monitor performs a software
* reset when the RES line goes high.
*/
/******************************************************************************
Cassette
******************************************************************************/
void aim65_state::z32_pb_w( u8 data )
{
/*
d7 = cass out (both decks)
d5 = cass2 motor
d4 = cass1 motor
d2 = tty out
d0/1 = printer data
There seems to be a mistake in the code. When the cassettes should both be stopped, it turns them on instead.
*/
if ((data & 0x30)==0x30)
data &= ~0x30; // fix bug with motors
uint8_t bits = data ^ m_pb_save;
m_pb_save = data;
if (BIT(bits, 7) && !m_ca2)
{
if (BIT(data, 4))
m_cassette1->output(BIT(data, 7) ? -1.0 : +1.0);
if (BIT(data, 5))
m_cassette2->output(BIT(data, 7) ? -1.0 : +1.0);
}
if (BIT(bits, 5))
m_cassette2->change_state(BIT(data, 5) ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED, CASSETTE_MASK_MOTOR);
if (BIT(bits, 4))
m_cassette1->change_state(BIT(data, 4) ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED, CASSETTE_MASK_MOTOR);
if (!m_kb_en)
m_rs232->write_txd(BIT(data, 2));
// 4 left-most characters for the thermal printer
data &= 0x03;
if ((m_printer_flag == 1) && (!m_cb2))
m_printerRAM[(m_printer_y * 100) + m_printer_x ] |= (data << 8);
}
u8 aim65_state::z32_pb_r ()
{
/*
d7 = cassette in
d6 = tty in
d3 = kb/tty switch
*/
uint8_t data = ioport("switches")->read() & 8;
m_kb_en = BIT(data, 3);
if (m_ca2)
{
if (BIT(m_pb_save, 4))
data |= (m_cassette1->input() > +0.03) ? 0x80 : 0;
else
if (BIT(m_pb_save, 5))
data |= (m_cassette2->input() > +0.03) ? 0x80 : 0;
}
if (m_kb_en)
data |= 0x40; // TTY must be H if not used.
else
data |= (m_rs232->rxd_r()) ? 0x40 : 0;
data |= m_pb_save & 0x37;
return data;
}
/******************************************************************************
Printer
******************************************************************************/
/*
The system sends out the pattern of dots to the 'dot-matrix' print-head.
aim65 thermal printer (20 characters)
10 heat elements (place on 1 line, space between 2 characters(about 14dots))
(pa0..pa7,pb0,pb1 1 heat element on)
cb2 0 motor, heat elements on. Enables printer. Also indicates that a new line is coming.
cb1 0 input - printer is requesting data, driven by "start" line
ca1 ^ input - changes polarity on each line, driven by s1 and s2
normally printer 5x7 characters
(horizontal movement limits not known, normally 2 dots between characters)
3 dots space between lines
*/
// to get printer to pass the test at start.
TIMER_CALLBACK_MEMBER(aim65_state::printer_timer)
{
m_via0->write_cb1(m_printer_level);
m_via0->write_ca1(m_printer_level);
m_printer_level ^= 1;
}
// CB2 - enable, & new-line
void aim65_state::z32_cb2_w( bool state )
{
m_cb2 = state;
if (!state)
{
m_printer_x = 0;
m_printer_y++;
m_printer_y &= 63;
for (u8 i = 0; i < 100; i++)
m_printerRAM[m_printer_y*100+i] = 0;
m_printer_flag = 0;
m_via0->write_cb1(1);
m_print_timer->adjust(attotime::zero, 0, attotime::from_msec(1));
m_printer_level = 1;
}
else
m_print_timer->reset();
}
// new pixels spread among multiple thermal heads
void aim65_state::z32_pa_w( u8 data )
{
if (m_printer_flag == 1)
m_printerRAM[m_printer_y * 100 + m_printer_x] |= data;
m_printer_flag++;
if (m_printer_flag > 2)
{
m_printer_flag = 0;
m_printer_x++;
}
// toggle ca1 each 10 bytes
if ((m_printer_x % 10) == 0)
{
m_via0->write_ca1(m_printer_level);
m_printer_level ^= 1;
}
// take cb1 low after a complete line of characters
if (m_printer_x == 100)
{
m_printer_x = 0;
m_via0->write_cb1(0);
}
}
// Display printer output
uint32_t aim65_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
for (u8 y = 0; y<cliprect.max_y; y++)
{
u16 sy = m_printer_y*10 - cliprect.max_y + 10 + y;
if (sy > 0xfe80) // wrap-around correctly
sy += 0x280;
for(u8 x = 0; x < 10; x++)
{
uint16_t data;
if (!BIT(sy, 0))
data = m_printerRAM[sy * 10 + x];
else
data = m_printerRAM[sy * 10 + (9 - x)];
for (u8 b = 0; b < 10; b++)
bitmap.pix(y, 120 - (b * 12) - ((x > 4) ? x+1 : x)) = BIT(data, b);
}
}
return 0;
}