diff options
| author | 2026-05-15 18:01:35 +0200 | |
|---|---|---|
| committer | 2026-05-15 18:01:35 +0200 | |
| commit | 182ecdd6c9f05a4e32f1c32ddc368f104c36ec91 (patch) | |
| tree | 6893117fca62fc1cb29137b7fb3d92ea2ad71523 /src/devices | |
| parent | 6db34f14119b1b46c87e17af74b07544b6948e7a (diff) | |
ti99: Add sidecar Thermal Printer PHP1900
Diffstat (limited to 'src/devices')
| -rw-r--r-- | src/devices/bus/ti99/internal/ioport.cpp | 3 | ||||
| -rw-r--r-- | src/devices/bus/ti99/sidecar/thermal.cpp | 367 | ||||
| -rw-r--r-- | src/devices/bus/ti99/sidecar/thermal.h | 82 |
3 files changed, 452 insertions, 0 deletions
diff --git a/src/devices/bus/ti99/internal/ioport.cpp b/src/devices/bus/ti99/internal/ioport.cpp index 43d083c5797..63c587c20eb 100644 --- a/src/devices/bus/ti99/internal/ioport.cpp +++ b/src/devices/bus/ti99/internal/ioport.cpp @@ -82,6 +82,7 @@ #include "bus/ti99/peb/peribox.h" #include "bus/ti99/sidecar/arcturus.h" #include "bus/ti99/sidecar/speechsyn.h" +#include "bus/ti99/sidecar/thermal.h" DEFINE_DEVICE_TYPE(TI99_IOPORT, bus::ti99::internal::ioport_device, "ti99_ioport", "TI-99 I/O Port") @@ -186,6 +187,7 @@ void ti99_ioport_options_plain(device_slot_interface &device) device.option_add("splitter", TI99_IOSPLIT); device.option_add("arcturus", TI99_ARCTURUS); device.option_add("speechsyn", TI99_SPEECHSYN); + device.option_add("thermal", TI99_THERMAL); } void ti99_ioport_options_evpc(device_slot_interface &device) @@ -194,6 +196,7 @@ void ti99_ioport_options_evpc(device_slot_interface &device) device.option_add("splitter", TI99_IOSPLIT); device.option_add("arcturus", TI99_ARCTURUS); device.option_add("speechsyn", TI99_SPEECHSYN); + device.option_add("thermal", TI99_THERMAL); } // Used for the splitter (to avoid getting multiple EVPCs in the system) diff --git a/src/devices/bus/ti99/sidecar/thermal.cpp b/src/devices/bus/ti99/sidecar/thermal.cpp new file mode 100644 index 00000000000..70603570e9a --- /dev/null +++ b/src/devices/bus/ti99/sidecar/thermal.cpp @@ -0,0 +1,367 @@ +// license:LGPL-2.1+ +// copyright-holders:Michael Zapf +/**************************************************************************** + + Thermal Printer + Model PHP1900 + + Typical sidecar setup: + +-- Thermal Printer + v + +----------------+---+------+---------- + | TI-99/4(A) | | | (PEB connection cable) + +------------+---+ |+----+|---------- + | oooooooooo | |---|| || + | oooooooooo | | ^ |+----+| + +----------------+ | | o | + | +------+ + | + (Speech Synthesizer) + + The Thermal Printer is a sidecar device for the TI-99/4(A) console. It + enabled the user to create printouts of BASIC programs via the LIST "TP" + command, and to create other text-based output using OPEN / PRINT in + BASIC or the usual DSR calls in assembly language. + + Speed is limited from its technology, and special thermal paper is needed. + Beside the standard ASCII set, user-defined patterns may be printed, but + there will always be some empty pixel colums due to the construction of + the print heads. + + Some games like "A-Maze-Ing" have a built-in screen dump for the + Thermal Printer (press FCTN-P when the game is over). + + The button on the printer chassis is used to advance the paper. + + Technology: + + The Thermal Printer uses two fixed print heads with 16*5 dots each. + + |***** ***** ***** ... *****|***** ***** ***** ... *****| + 01234 01234 01234 01234 56789 56789 56789 56789 dot number + 15 14 13 0 15 14 13 0 char column number + + |------------------- width of paper --------------------| + + Columns are powered one after another; the selected dots cause the heating + element to get hot at that location. It is possible to activate multiple + columns at once, if the selected dot is supposed to be active in all these + columns. + + Each character column is surrounded by one blank pixel column before and + one blank pixel column after it, so we get 7 dots width per character + column. + + The burning is done while the retriggered monoflop LS122 is active. The + monoflop is a safety precaution to avoid burning the paper during a paper + jam or a crash of the printer driver. + + Pressing the paper feed button causes an interrupt which runs the paper + advance routine in the built-in driver. By default, this is bound to the + END key on the keyboard. + + Michael Zapf + May 2026 + +*****************************************************************************/ + +#include "emu.h" +#include "thermal.h" +#include "machine/rescap.h" + +#define LOG_WARN (1U << 1) // Warnings +#define LOG_INTERRUPT (1U << 2) +#define LOG_PRINT (1U << 3) +#define LOG_CRU (1U << 4) + +#define VERBOSE 0 +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(TI99_THERMAL, bus::ti99::sidecar::ti_thermal_printer_device, "ti99_thermal", "TI-99 Thermal Printer") + +namespace bus::ti99::sidecar { + +#define PORT "extport" +#define BITMAPPR "bitmap_print" + +#define TI99_DSRROM "dsr" + +#define LATCH_U4 "latch_u4" +#define LATCH_U5 "latch_u5" +#define LATCH_U6 "latch_u6" +#define LATCH_U7 "latch_u7" +#define HEATMF_U8 "heatmf_u8" + +static constexpr unsigned LEFTPAD = 12; +static constexpr unsigned PAPER_WIDTH = 242; +static constexpr unsigned PAPER_HEIGHT = (11*72); + +/* + Constructor called from subclasses. +*/ +ti_thermal_printer_device::ti_thermal_printer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : bus::ti99::internal::ioport_attached_device(mconfig, TI99_THERMAL, tag, owner, clock), + m_bitmap_printer(*this, BITMAPPR), + m_port(*this, PORT), + m_latch_u4(*this, LATCH_U4), + m_latch_u5(*this, LATCH_U5), + m_latch_u6(*this, LATCH_U6), + m_latch_u7(*this, LATCH_U7), + m_heatmf_u8(*this, HEATMF_U8) +{ +} + +void ti_thermal_printer_device::write_line() +{ + // Get the active columns and dots from the latches + int actcolumns = (m_latch_u6->output_state() << 8) | m_latch_u7->output_state(); + int actdots = (m_latch_u4->output_state() << 2) | ((m_latch_u5->output_state() & 0xc0) >> 6); + + LOGMASKED(LOG_PRINT, "Printing col=%04x dots=%02x\n", actcolumns, actdots); + for (int col = 0; col < 16; col++) // multiple columns can be energized + { + if (BIT(actcolumns, col)) + { + for (int dot = 0; dot < 10; dot++) + { + if (BIT(actdots, dot)) + { + int physcol = (dot >= 5)? (31 - col) : (15 - col); + m_bitmap_printer->draw_pixel(m_bitmap_printer->m_xpos + + 7*physcol + (dot%5) + LEFTPAD, + m_bitmap_printer->m_ypos, + 0x000000); //rrggbb + } + } + } + } +} + +/* + The heat monoflop is a safety feature in case of a paper jam. + It turns off the current of the write heads after 15 ms when not retriggered. +*/ +void ti_thermal_printer_device::enable_column_latches(int state) +{ + m_latch_u7->clear_w(state); + m_latch_u6->clear_w(state); +} + +/* + Memory read +*/ + +void ti_thermal_printer_device::readz(offs_t offset, uint8_t *value) +{ + if (((offset & 0xe000) == 0x4000) && m_latch_u5->q0_r()) + { + *value = m_dsrrom[offset & 0x0fff]; + } + + // Pass through to the external port + if (m_port != nullptr) + m_port->readz(offset, value); +} + +/* + Memory write +*/ +void ti_thermal_printer_device::write(offs_t offset, uint8_t data) +{ + // Pass through to the external port + if (m_port != nullptr) + m_port->write(offset, data); +} + +void ti_thermal_printer_device::setaddress_dbin(offs_t offset, int state) +{ + // Pass through to the external port + if (m_port != nullptr) + m_port->setaddress_dbin(offset, state); +} + +void ti_thermal_printer_device::crureadz(offs_t offset, uint8_t *value) +{ + if ((offset & 0xff00) == 0x1800) + { + // Only delivers a 0 when pressed, else Z + if (m_feed_pressed) *value = 0; + } + + // Pass through to the external port + if (m_port != nullptr) + m_port->crureadz(offset, value); +} + +void ti_thermal_printer_device::cruwrite(offs_t offset, uint8_t data) +{ + if ((offset & 0xff00) == 0x1800) + { + LOGMASKED(LOG_CRU, "CRU address = %04x, data = %x\n", offset, data); + // LOGMASKED(LOG_CRU, "addr %d, bit %d = %d\n", (offset & 0xf0)>>4, (offset & 0x0e)>>1, data); + + int bitaddr = (offset & 0x0e)>>1; + switch (offset & 0xf0) + { + case 0x00: + m_latch_u5->write_bit(bitaddr, data); + if (bitaddr == 5) line_feed(); + break; + case 0x10: + m_latch_u4->write_bit(bitaddr, data); + break; + case 0x20: + m_latch_u7->write_bit(bitaddr, data); + break; + case 0x30: + m_latch_u6->write_bit(bitaddr, data); + if (bitaddr == 7) write_line(); + break; + default: + break; + } + } + // Pass through to the external port + if (m_port != nullptr) + m_port->cruwrite(offset, data); +} + +void ti_thermal_printer_device::line_feed() +{ + // Run the stepper + int pattern = bitswap<4>(m_latch_u5->output_state()>>2, 0, 1, 2, 3); + m_bitmap_printer->update_pf_stepper(pattern); + m_bitmap_printer->update_cr_stepper(1); // have to move the cr stepper to get the page to change + m_bitmap_printer->update_cr_stepper(5); + m_bitmap_printer->update_cr_stepper(1); +} + +/* + Just forwarding to the external port +*/ +void ti_thermal_printer_device::memen_in(int state) +{ + // Pass through to the external port + if (m_port != nullptr) + m_port->memen_in(state); +} + +void ti_thermal_printer_device::msast_in(int state) +{ + // Pass through to the external port + if (m_port != nullptr) + m_port->msast_in(state); +} + +void ti_thermal_printer_device::clock_in(int state) +{ + // Pass through to the external port + if (m_port != nullptr) + m_port->clock_in(state); +} + +void ti_thermal_printer_device::reset_in(int state) +{ + clear_latches(state); + + // Pass through to the external port + if (m_port != nullptr) + m_port->reset_in(state); +} + +/* + Forward external interrupts to the console. +*/ +void ti_thermal_printer_device::extint(int state) +{ + LOGMASKED(LOG_INTERRUPT, "Incoming EXTINT=%d from external port\n", state); + m_ext_int = (line_state)state; + set_extint(((m_ext_int == ASSERT_LINE) || m_feed_pressed)? ASSERT_LINE : CLEAR_LINE); +} + +/* + Forward the incoming READY to the console +*/ +void ti_thermal_printer_device::extready(int state) +{ + set_ready(state); +} + +void ti_thermal_printer_device::clear_latches(int state) +{ + int value = (state==ASSERT_LINE)? 0 : 1; + m_latch_u5->clear_w(value); + m_latch_u4->clear_w(value); + m_heatmf_u8->clear_w(value); +} + +void ti_thermal_printer_device::device_start() +{ + m_bitmap_printer->set_pf_stepper_ratio(1,8); // pf stepper moves + m_bitmap_printer->set_cr_stepper_ratio(1,6); // this doesn't use the cr stepper + m_dsrrom = memregion(TI99_DSRROM)->base(); +} + +void ti_thermal_printer_device::device_reset() +{ + clear_latches(ASSERT_LINE); + clear_latches(CLEAR_LINE); +} + +INPUT_CHANGED_MEMBER( ti_thermal_printer_device::feed_button ) +{ + bool pressed = (newval==0); + if (m_feed_pressed != pressed) + { + LOGMASKED(LOG_INTERRUPT, "Feed button %s\n", pressed? "pressed" : "released"); + m_feed_pressed = pressed; + set_extint(((m_ext_int == ASSERT_LINE) || m_feed_pressed)? ASSERT_LINE : CLEAR_LINE); + } +} + +ROM_START( ti99_thermal ) + ROM_REGION(0x1000, TI99_DSRROM, 0) + ROM_LOAD("thermal.u12", 0x0000, 0x1000, CRC(83376edb) SHA1(2f923e59a3a52b3645e8d26e11a697f320c6d4cb)) +ROM_END + +void ti_thermal_printer_device::device_add_mconfig(machine_config& config) +{ + BITMAP_PRINTER(config, m_bitmap_printer, PAPER_WIDTH, PAPER_HEIGHT, 120, 72); // do 72 dpi + + TTL74123(config, m_heatmf_u8, 0); + m_heatmf_u8->set_connection_type(TTL74123_GROUNDED); + m_heatmf_u8->set_resistor_value(RES_K(10)); // actually, the 74122 is used (Rint = 10k) + m_heatmf_u8->set_capacitor_value(CAP_U(4.7f)); + m_heatmf_u8->set_clear_pin_value(1); + m_heatmf_u8->set_a_pin_value(0); + + LS259(config, m_latch_u4); // Dots 2-9 + LS259(config, m_latch_u5); // ROM / Heat trigger / stepper / dots 0 and 1 + LS259(config, m_latch_u6); // columns 8-15 and 24-31 + LS259(config, m_latch_u7); // columns 0-7 and 16-23 + + m_latch_u5->q_out_cb<1>().set(m_heatmf_u8, FUNC(ttl74123_device::b_w)); + + m_heatmf_u8->out_cb().set(FUNC(ti_thermal_printer_device::enable_column_latches)); + + TI99_IOPORT(config, m_port, 0, ti99_ioport_options_evpc1, nullptr); + m_port->extint_cb().set(FUNC(ti_thermal_printer_device::extint)); + m_port->ready_cb().set(FUNC(ti_thermal_printer_device::extready)); +} + +INPUT_PORTS_START(ti99_thermal) + PORT_START("PAPERFEED") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Paper feed") PORT_CODE(KEYCODE_END) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(ti_thermal_printer_device::feed_button), 1) +INPUT_PORTS_END + +ioport_constructor ti_thermal_printer_device::device_input_ports() const +{ + return INPUT_PORTS_NAME( ti99_thermal ); +} + +const tiny_rom_entry *ti_thermal_printer_device::device_rom_region() const +{ + return ROM_NAME( ti99_thermal ); +} + +} // end namespace bus::ti99::sidecar
\ No newline at end of file diff --git a/src/devices/bus/ti99/sidecar/thermal.h b/src/devices/bus/ti99/sidecar/thermal.h new file mode 100644 index 00000000000..505b1954292 --- /dev/null +++ b/src/devices/bus/ti99/sidecar/thermal.h @@ -0,0 +1,82 @@ +// license:LGPL-2.1+ +// copyright-holders:Michael Zapf +/**************************************************************************** + + Thermal Printer + Model PHP1900 + + Michael Zapf + May 2026 + +*****************************************************************************/ + +#ifndef MAME_BUS_TI99_SIDECAR_THERMAL_H +#define MAME_BUS_TI99_SIDECAR_THERMAL_H + +#pragma once + +#include "bus/ti99/internal/ioport.h" +#include "machine/bitmap_printer.h" +#include "machine/74123.h" +#include "machine/74259.h" + +namespace bus::ti99::sidecar { + +class ti_thermal_printer_device : public bus::ti99::internal::ioport_attached_device +{ +public: + ti_thermal_printer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + void readz(offs_t offset, uint8_t *value) override; + void write(offs_t offset, uint8_t data) override; + void setaddress_dbin(offs_t offset, int state) override; + + void crureadz(offs_t offset, uint8_t *value) override; + void cruwrite(offs_t offset, uint8_t data) override; + + void memen_in(int state) override; + void msast_in(int state) override; + + void clock_in(int state) override; + void reset_in(int state) override; + + INPUT_CHANGED_MEMBER( feed_button ); + +protected: + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + virtual const tiny_rom_entry *device_rom_region() const override ATTR_COLD; + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; + virtual ioport_constructor device_input_ports() const override ATTR_COLD; + + // Callbacks from the external port + void extint(int state); + void extready(int state); + +private: + required_device<bitmap_printer_device> m_bitmap_printer; + required_device<bus::ti99::internal::ioport_device> m_port; + + required_device<ls259_device> m_latch_u4; + required_device<ls259_device> m_latch_u5; + required_device<ls259_device> m_latch_u6; + required_device<ls259_device> m_latch_u7; + required_device<ttl74123_device> m_heatmf_u8; + + bool m_feed_pressed; + line_state m_ext_int; + + // DSR ROM + uint8_t* m_dsrrom; + + void write_line(); + void line_feed(); + void enable_column_latches(int state); + void clear_latches(int state); +}; + +} // end namespace bus::ti99::sidecar + +DECLARE_DEVICE_TYPE_NS(TI99_THERMAL, bus::ti99::sidecar, ti_thermal_printer_device) + +#endif // MAME_BUS_TI99_SIDECAR_THERMAL_H |
