mirror of
https://github.com/holub/mame
synced 2025-10-06 17:08:28 +03:00
IEEE-488 remotizer device (#3241)
* remote488: work started * remote488: fixed a crash when using socketed bitbangers on Linux machines * remote488: added ieee-488 remotizer device * remote488: added remotizer devices to ieee-488 buses of HP9845 & HP85 * remote488: added missing emu.h inclusion * Revert "remote488: fixed a crash when using socketed bitbangers on Linux machines" This reverts commit edfeb1768ec332ccdb77584e272d93b756819c41. * remote488: nudge.. * remote488: no longer use locale-dependent functions, added commas and semicolons as msg separators, improved use of util::string_format
This commit is contained in:
parent
077272c5b5
commit
2e4c96157d
@ -902,6 +902,8 @@ if (BUSES["IEEE488"]~=null) then
|
|||||||
MAME_DIR .. "src/devices/bus/ieee488/shark.h",
|
MAME_DIR .. "src/devices/bus/ieee488/shark.h",
|
||||||
MAME_DIR .. "src/devices/bus/ieee488/hp9895.cpp",
|
MAME_DIR .. "src/devices/bus/ieee488/hp9895.cpp",
|
||||||
MAME_DIR .. "src/devices/bus/ieee488/hp9895.h",
|
MAME_DIR .. "src/devices/bus/ieee488/hp9895.h",
|
||||||
|
MAME_DIR .. "src/devices/bus/ieee488/remote488.cpp",
|
||||||
|
MAME_DIR .. "src/devices/bus/ieee488/remote488.h",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -344,6 +344,7 @@ MACHINE_CONFIG_START(hp82937_io_card_device::device_add_mconfig)
|
|||||||
MCFG_1MB5_RESET_HANDLER(WRITELINE(hp82937_io_card_device , reset_w))
|
MCFG_1MB5_RESET_HANDLER(WRITELINE(hp82937_io_card_device , reset_w))
|
||||||
|
|
||||||
MCFG_IEEE488_SLOT_ADD("ieee_dev" , 0 , hp_ieee488_devices , nullptr)
|
MCFG_IEEE488_SLOT_ADD("ieee_dev" , 0 , hp_ieee488_devices , nullptr)
|
||||||
|
MCFG_IEEE488_SLOT_ADD("ieee_rem" , 0 , remote488_devices , nullptr)
|
||||||
MCFG_IEEE488_BUS_ADD()
|
MCFG_IEEE488_BUS_ADD()
|
||||||
MCFG_IEEE488_IFC_CALLBACK(WRITELINE(hp82937_io_card_device , ieee488_ctrl_w))
|
MCFG_IEEE488_IFC_CALLBACK(WRITELINE(hp82937_io_card_device , ieee488_ctrl_w))
|
||||||
MCFG_IEEE488_ATN_CALLBACK(WRITELINE(hp82937_io_card_device , ieee488_ctrl_w))
|
MCFG_IEEE488_ATN_CALLBACK(WRITELINE(hp82937_io_card_device , ieee488_ctrl_w))
|
||||||
|
@ -374,6 +374,7 @@ MACHINE_CONFIG_START(hp98034_io_card_device::device_add_mconfig)
|
|||||||
MCFG_CPU_IRQ_ACKNOWLEDGE_DRIVER(hp98034_io_card_device , irq_callback)
|
MCFG_CPU_IRQ_ACKNOWLEDGE_DRIVER(hp98034_io_card_device , irq_callback)
|
||||||
|
|
||||||
MCFG_IEEE488_SLOT_ADD("ieee_dev" , 0 , hp_ieee488_devices , nullptr)
|
MCFG_IEEE488_SLOT_ADD("ieee_dev" , 0 , hp_ieee488_devices , nullptr)
|
||||||
|
MCFG_IEEE488_SLOT_ADD("ieee_rem" , 0 , remote488_devices , nullptr)
|
||||||
MCFG_IEEE488_BUS_ADD()
|
MCFG_IEEE488_BUS_ADD()
|
||||||
MCFG_IEEE488_IFC_CALLBACK(WRITELINE(hp98034_io_card_device , ieee488_ctrl_w))
|
MCFG_IEEE488_IFC_CALLBACK(WRITELINE(hp98034_io_card_device , ieee488_ctrl_w))
|
||||||
MCFG_IEEE488_ATN_CALLBACK(WRITELINE(hp98034_io_card_device , ieee488_ctrl_w))
|
MCFG_IEEE488_ATN_CALLBACK(WRITELINE(hp98034_io_card_device , ieee488_ctrl_w))
|
||||||
|
@ -427,3 +427,15 @@ SLOT_INTERFACE_END
|
|||||||
SLOT_INTERFACE_START(hp_ieee488_devices)
|
SLOT_INTERFACE_START(hp_ieee488_devices)
|
||||||
SLOT_INTERFACE("hp9895", HP9895)
|
SLOT_INTERFACE("hp9895", HP9895)
|
||||||
SLOT_INTERFACE_END
|
SLOT_INTERFACE_END
|
||||||
|
|
||||||
|
//-------------------------------------------------
|
||||||
|
// SLOT_INTERFACE( remote488_devices )
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
// slot devices
|
||||||
|
#include "remote488.h"
|
||||||
|
|
||||||
|
SLOT_INTERFACE_START(remote488_devices)
|
||||||
|
SLOT_INTERFACE("remote488", REMOTE488)
|
||||||
|
|
||||||
|
SLOT_INTERFACE_END
|
||||||
|
@ -260,6 +260,7 @@ DECLARE_DEVICE_TYPE(IEEE488_SLOT, ieee488_slot_device)
|
|||||||
|
|
||||||
SLOT_INTERFACE_EXTERN( cbm_ieee488_devices );
|
SLOT_INTERFACE_EXTERN( cbm_ieee488_devices );
|
||||||
SLOT_INTERFACE_EXTERN( hp_ieee488_devices );
|
SLOT_INTERFACE_EXTERN( hp_ieee488_devices );
|
||||||
|
SLOT_INTERFACE_EXTERN( remote488_devices );
|
||||||
|
|
||||||
|
|
||||||
#endif // MAME_BUS_IEEE488_IEEE488_H
|
#endif // MAME_BUS_IEEE488_IEEE488_H
|
||||||
|
751
src/devices/bus/ieee488/remote488.cpp
Normal file
751
src/devices/bus/ieee488/remote488.cpp
Normal file
@ -0,0 +1,751 @@
|
|||||||
|
// license:BSD-3-Clause
|
||||||
|
// copyright-holders: F. Ulivi
|
||||||
|
/*********************************************************************
|
||||||
|
|
||||||
|
remote488.cpp
|
||||||
|
|
||||||
|
This device allows the interfacing of a local IEEE-488 bus with
|
||||||
|
external devices (i.e. outside of MAME environment).
|
||||||
|
It's based on a simple text based protocol. The protocol relies
|
||||||
|
on a bidirectional stream-based connection (a bitbanger).
|
||||||
|
Main design features of the protocol:
|
||||||
|
- Transparent to data & commands exchanged
|
||||||
|
- Transparent to remote/local position of controller & controlled
|
||||||
|
devices
|
||||||
|
- Can be cross-connected: by routing the output direction of a
|
||||||
|
running MAME instance into the input direction of another instance
|
||||||
|
and viceversa, the two buses form a single logical bus.
|
||||||
|
|
||||||
|
Protocol exchanges messages that are composed of a single uppercase
|
||||||
|
letter, a colon, a byte value expressed as 2 hex digits and a terminator
|
||||||
|
character.
|
||||||
|
Valid terminator characters are ',' or ';' or whitespace. Extra
|
||||||
|
whitespace before and after the message is ignored.
|
||||||
|
The letter encodes the type of the message and the byte has different
|
||||||
|
meaning according to type of message. Not all message types carry
|
||||||
|
a meaningful byte value (but for uniformity the byte is always sent).
|
||||||
|
Example of a message: "D:55 " (byte with 0x55 value exchanged on the
|
||||||
|
bus without EOI being asserted).
|
||||||
|
Example of a sequence of messages: D:AA,D:55,E:00 or
|
||||||
|
D:AA\nD:55\nE:00.
|
||||||
|
|
||||||
|
The following table summarizes the various message types, the directions
|
||||||
|
wrt the remotizer in which they are meaningful, whether the byte
|
||||||
|
carries a value and the purpose of the message.
|
||||||
|
|
||||||
|
| Type | Direction | Has byte? | Meaning |
|
||||||
|
|------+-----------+-----------+---------------------------------|
|
||||||
|
| D | I/O | Yes | Non-EOI DAB or command byte |
|
||||||
|
| E | I/O | Yes | EOI DAB |
|
||||||
|
| J | O | No | Echo request |
|
||||||
|
| K | I | No | Echo reply |
|
||||||
|
| P | I | Yes | Set byte for next parallel poll |
|
||||||
|
| Q | O | No | Request 'P' msg |
|
||||||
|
| R | I/O | Yes | Set bus control signals to 0 |
|
||||||
|
| S | I/O | Yes | Set bus control signals to 1 |
|
||||||
|
|
||||||
|
- D messages
|
||||||
|
|
||||||
|
This type of msg exchanges non-EOI data bytes and commands.
|
||||||
|
In the output direction the remotizer implements an acceptor so
|
||||||
|
that the proper 3-way handshake is implemented with the
|
||||||
|
source. The acceptor has no delays between state transitions so
|
||||||
|
that it can keep up with any speed of the source. The acceptor
|
||||||
|
doesn't wait for any acknowledgement from external receiver. The
|
||||||
|
buffering in the output stream must have enough space to hold
|
||||||
|
bursts of data bytes before they are processed by the external
|
||||||
|
receiver.
|
||||||
|
In the input direction a source is implemented to handshake with
|
||||||
|
local acceptor(s). The FSM in the source has no delays so that it can
|
||||||
|
operate at whatever speed the slowest acceptor on the bus can sustain.
|
||||||
|
Bytes are removed from the input buffer of the stream as the local
|
||||||
|
acceptors acknowledge them. No input message is processed by the
|
||||||
|
remotizer when it's waiting for 3-way handshake with acceptors to
|
||||||
|
complete. If a local controller asserts the ATN signal to regain
|
||||||
|
control of the bus when there are input bytes waiting, the bytes
|
||||||
|
are discarded.
|
||||||
|
Byte values are expressed in positive logic (which is the opposite
|
||||||
|
of what's sent on the bus). To discriminate between DAB and command
|
||||||
|
bytes the receiver of this message has to check its state of ATN
|
||||||
|
signal (see R&S messages).
|
||||||
|
|
||||||
|
- E messages
|
||||||
|
|
||||||
|
This type of message carries DAB that are sourced on the bus with
|
||||||
|
EOI asserted. These messages have the same logic of 'D' messages
|
||||||
|
(see above). These messages never carry command bytes (because
|
||||||
|
the condition where both ATN & EOI are asserted is used for
|
||||||
|
parallel polling).
|
||||||
|
|
||||||
|
- J & K messages
|
||||||
|
|
||||||
|
This message pair is used to probe the connection with the
|
||||||
|
external devices because bitbanger streams do not report in any
|
||||||
|
way if they are connected to outside or not. Any output byte is
|
||||||
|
silently discarded if the stream is not connected.
|
||||||
|
From the remotizer point of view the connection is up when a
|
||||||
|
message of any kind comes in from the outside. When there's no
|
||||||
|
traffic to exchange, the remotizer sends a J message every 0.5 s.
|
||||||
|
The remote device should reply with a K msg to every J msg it
|
||||||
|
sees. Connection status is set to "down" when three consecutive J
|
||||||
|
msgs are sent out without being replied to.
|
||||||
|
|
||||||
|
- P & Q messages
|
||||||
|
|
||||||
|
The 'P' msg is used by an external device to set the response to
|
||||||
|
be placed on the bus when a local controller does a parallel
|
||||||
|
poll. Message byte is encoded in positive logic (i.e. any bit set
|
||||||
|
to 1 is forced to 0 on data bus during parallel poll). The
|
||||||
|
response set by P msg is used during parallel polls until changed
|
||||||
|
by another P msg. A P msg is never sent out by the remotizer.
|
||||||
|
The Q msg is sent by the remotizer whenever a parallel poll is
|
||||||
|
performed locally. Its purpose is to solicit a P msg from the
|
||||||
|
external device. The external device may choose to send P msgs in
|
||||||
|
reply to Q msgs or to ignore them and send P msgs asynchronously.
|
||||||
|
Parallel poll is usually a very fast operation (a few microseconds)
|
||||||
|
so it's possible, due to latency, that the reply to a Q msg is
|
||||||
|
applied on the bus many polls later. The remotizer doesn't repeat
|
||||||
|
the transmission of a Q msg until a P msg is received to avoid
|
||||||
|
generating a lot of traffic if the local controller rapidly
|
||||||
|
repeats parallel polls.
|
||||||
|
|
||||||
|
- R & S messages
|
||||||
|
|
||||||
|
These messages are used to align the state of bus control lines
|
||||||
|
with the external devices. Each control line is mapped to a bit in
|
||||||
|
the accompanying byte, according to this table.
|
||||||
|
|
||||||
|
| Bit | Signal |
|
||||||
|
|-----+--------|
|
||||||
|
| 0 | ATN |
|
||||||
|
| 1 | IFC |
|
||||||
|
| 2 | REN |
|
||||||
|
| 3 | SRQ |
|
||||||
|
|
||||||
|
A "R" message clears (sets to 0, i.e. asserts) all signals that
|
||||||
|
are set to "1" in the byte, all others retain their value.
|
||||||
|
A "S" message sets signals to 1, i.e. de-asserts them.
|
||||||
|
The remotizer may respond with a "R" message if an incoming "S"
|
||||||
|
message is attempting to set to 1 a signal that is locally forced
|
||||||
|
to 0.
|
||||||
|
|
||||||
|
Limits & potential issues:
|
||||||
|
- There are no checks for violations of IEEE-488 protocol. It's
|
||||||
|
possible, for example, to put the bus in an illegal state by
|
||||||
|
sending a 'E' msg when the local controller is asserting ATN
|
||||||
|
(thus creating a phantom parallel poll).
|
||||||
|
- The unacknowledged nature of D/E msgs may cause problems if
|
||||||
|
the sender of the msgs must be synchronized byte-for-byte with
|
||||||
|
the receiver. There is no direct way, for example, for the
|
||||||
|
sender of a string of bytes to know how many have been accepted
|
||||||
|
by the receiver.
|
||||||
|
- It's difficult to achieve accurate synchronization between the
|
||||||
|
local emulation time and the external time.
|
||||||
|
|
||||||
|
TODOs/possible enhancements:
|
||||||
|
- Implement handling of incoming Q msgs (needed when parallel poll
|
||||||
|
is being performed by a remote controller)
|
||||||
|
- Enhancement: implement a msg for accurate time synchronization
|
||||||
|
- Enhancement: implement some form of sliding window acknowledgement
|
||||||
|
for cases when sender has to know how many bytes have been
|
||||||
|
processed by the receiver. The HP "Amigo" protocol I used for
|
||||||
|
my experiments has no such need.
|
||||||
|
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#include "emu.h"
|
||||||
|
#include "remote488.h"
|
||||||
|
|
||||||
|
// Debugging
|
||||||
|
#define VERBOSE 1
|
||||||
|
#include "logmacro.h"
|
||||||
|
|
||||||
|
// Bit manipulation
|
||||||
|
namespace {
|
||||||
|
template<typename T> constexpr T BIT_MASK(unsigned n)
|
||||||
|
{
|
||||||
|
return (T)1U << n;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> void BIT_CLR(T& w , unsigned n)
|
||||||
|
{
|
||||||
|
w &= ~BIT_MASK<T>(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> void BIT_SET(T& w , unsigned n)
|
||||||
|
{
|
||||||
|
w |= BIT_MASK<T>(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> void COPY_BIT(bool bit , T& w , unsigned n)
|
||||||
|
{
|
||||||
|
if (bit) {
|
||||||
|
BIT_SET(w , n);
|
||||||
|
} else {
|
||||||
|
BIT_CLR(w , n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message types
|
||||||
|
constexpr char MSG_SIGNAL_CLEAR = 'R'; // I/O: Clear signal(s)
|
||||||
|
constexpr char MSG_SIGNAL_SET = 'S'; // I/O: Set signal(s)
|
||||||
|
constexpr char MSG_DATA_BYTE = 'D'; // I/O: Cmd/data byte (no EOI)
|
||||||
|
constexpr char MSG_END_BYTE = 'E'; // I/O: Data byte (with EOI)
|
||||||
|
constexpr char MSG_PP_DATA = 'P'; // I: Parallel poll data
|
||||||
|
constexpr char MSG_PP_REQUEST = 'Q'; // O: Request PP data
|
||||||
|
constexpr char MSG_ECHO_REQ = 'J'; // O: Heartbeat msg: echo request
|
||||||
|
constexpr char MSG_ECHO_REPLY = 'K'; // I: Heartbeat msg: echo reply
|
||||||
|
|
||||||
|
// Timings
|
||||||
|
constexpr unsigned POLL_PERIOD_US = 20; // Poll period (µs)
|
||||||
|
constexpr unsigned HEARTBEAT_MS = 500; // Heartbeat ping period (ms)
|
||||||
|
constexpr unsigned MAX_MISSED_HB = 3; // Missed heartbeats to declare the connection dead
|
||||||
|
|
||||||
|
// device type definition
|
||||||
|
DEFINE_DEVICE_TYPE(REMOTE488, remote488_device, "remote488", "IEEE-488 Remotizer")
|
||||||
|
|
||||||
|
remote488_device::remote488_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
|
||||||
|
device_t(mconfig , REMOTE488 , tag , owner , clock),
|
||||||
|
device_ieee488_interface(mconfig , *this),
|
||||||
|
m_stream(*this , "stream")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MACHINE_CONFIG_START(remote488_device::device_add_mconfig)
|
||||||
|
MCFG_DEVICE_ADD("stream", BITBANGER, 0)
|
||||||
|
MACHINE_CONFIG_END
|
||||||
|
|
||||||
|
void remote488_device::ieee488_eoi(int state)
|
||||||
|
{
|
||||||
|
update_pp();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::ieee488_dav(int state)
|
||||||
|
{
|
||||||
|
update_ah_fsm();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::ieee488_nrfd(int state)
|
||||||
|
{
|
||||||
|
update_sh_fsm();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::ieee488_ndac(int state)
|
||||||
|
{
|
||||||
|
update_sh_fsm();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::ieee488_ifc(int state)
|
||||||
|
{
|
||||||
|
update_signal(SIGNAL_IFC_BIT , state);
|
||||||
|
if (!state) {
|
||||||
|
LOG("IFC\n");
|
||||||
|
bus_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::ieee488_srq(int state)
|
||||||
|
{
|
||||||
|
update_signal(SIGNAL_SRQ_BIT , state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::ieee488_atn(int state)
|
||||||
|
{
|
||||||
|
update_signal(SIGNAL_ATN_BIT , state);
|
||||||
|
update_sh_fsm();
|
||||||
|
update_pp();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::ieee488_ren(int state)
|
||||||
|
{
|
||||||
|
update_signal(SIGNAL_REN_BIT , state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::device_start()
|
||||||
|
{
|
||||||
|
m_poll_timer = timer_alloc(TMR_ID_POLL);
|
||||||
|
m_hb_timer = timer_alloc(TMR_ID_HEARTBEAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::device_reset()
|
||||||
|
{
|
||||||
|
m_no_propagation = true;
|
||||||
|
m_in_signals = 0xff;
|
||||||
|
m_bus->atn_w(this , 1);
|
||||||
|
m_bus->eoi_w(this , 1);
|
||||||
|
m_bus->ifc_w(this , 1);
|
||||||
|
m_bus->ren_w(this , 1);
|
||||||
|
m_bus->srq_w(this , 1);
|
||||||
|
m_out_signals = 0xff;
|
||||||
|
m_no_propagation = false;
|
||||||
|
|
||||||
|
// Fake disconnection
|
||||||
|
m_connected = true;
|
||||||
|
set_connection(false);
|
||||||
|
|
||||||
|
bus_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::bus_reset()
|
||||||
|
{
|
||||||
|
m_sh_state = REM_SH_SIDS;
|
||||||
|
m_ah_state = REM_AH_ACRS;
|
||||||
|
m_rx_state = REM_RX_WAIT_CH;
|
||||||
|
m_ibf = false;
|
||||||
|
m_flush_bytes = false;
|
||||||
|
m_poll_timer->adjust(attotime::from_usec(POLL_PERIOD_US) , 0 , attotime::from_usec(POLL_PERIOD_US));
|
||||||
|
m_pp_data = 0;
|
||||||
|
m_pp_requested = false;
|
||||||
|
update_ah_fsm();
|
||||||
|
update_sh_fsm();
|
||||||
|
update_pp();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::process_input_msgs()
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
char msg_ch;
|
||||||
|
while ((msg_ch = recv_update(data)) != 0) {
|
||||||
|
set_connection(true);
|
||||||
|
LOG("%.6f Rx %c %02x\n" , machine().time().as_double() , msg_ch , data);
|
||||||
|
|
||||||
|
switch (msg_ch) {
|
||||||
|
case MSG_SIGNAL_CLEAR:
|
||||||
|
m_flush_bytes = false;
|
||||||
|
update_signals_from_rem(0 , data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MSG_SIGNAL_SET:
|
||||||
|
m_flush_bytes = false;
|
||||||
|
update_signals_from_rem(data , 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MSG_DATA_BYTE:
|
||||||
|
if (m_flush_bytes) {
|
||||||
|
LOG("Flushed\n");
|
||||||
|
} else {
|
||||||
|
recvd_data_byte(data , false);
|
||||||
|
m_poll_timer->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MSG_END_BYTE:
|
||||||
|
if (m_flush_bytes) {
|
||||||
|
LOG("Flushed\n");
|
||||||
|
m_flush_bytes = false;
|
||||||
|
} else {
|
||||||
|
recvd_data_byte(data , true);
|
||||||
|
m_poll_timer->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MSG_PP_DATA:
|
||||||
|
m_flush_bytes = false;
|
||||||
|
m_pp_data = data;
|
||||||
|
m_pp_requested = false;
|
||||||
|
update_pp_dio();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MSG_ECHO_REQ:
|
||||||
|
send_update(MSG_ECHO_REPLY , 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_poll_timer->enabled()) {
|
||||||
|
m_poll_timer->adjust(attotime::from_usec(POLL_PERIOD_US) , 0 , attotime::from_usec(POLL_PERIOD_US));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
||||||
|
{
|
||||||
|
switch (id) {
|
||||||
|
case TMR_ID_POLL:
|
||||||
|
process_input_msgs();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TMR_ID_HEARTBEAT:
|
||||||
|
if (m_connected && m_connect_cnt && --m_connect_cnt == 0) {
|
||||||
|
set_connection(false);
|
||||||
|
}
|
||||||
|
send_update(MSG_ECHO_REQ , 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::set_connection(bool state)
|
||||||
|
{
|
||||||
|
if (state) {
|
||||||
|
if (!m_connected) {
|
||||||
|
// Just connected
|
||||||
|
m_connected = true;
|
||||||
|
LOG("Connected!\n");
|
||||||
|
// Align signal state on both sides
|
||||||
|
uint8_t tmp = m_out_signals & ((1 << SIGNAL_COUNT) - 1);
|
||||||
|
if (tmp) {
|
||||||
|
send_update(MSG_SIGNAL_SET , tmp);
|
||||||
|
}
|
||||||
|
tmp = ~m_out_signals & ((1 << SIGNAL_COUNT) - 1);
|
||||||
|
if (tmp) {
|
||||||
|
send_update(MSG_SIGNAL_CLEAR , tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_hb_timer->adjust(attotime::from_msec(HEARTBEAT_MS) , 0 , attotime::from_msec(HEARTBEAT_MS));
|
||||||
|
m_connect_cnt = MAX_MISSED_HB;
|
||||||
|
} else {
|
||||||
|
if (m_connected) {
|
||||||
|
// Disconnected
|
||||||
|
m_connected = false;
|
||||||
|
LOG("Connection lost!\n");
|
||||||
|
update_ah_fsm();
|
||||||
|
m_hb_timer->adjust(attotime::from_msec(HEARTBEAT_MS) , 0 , attotime::from_msec(HEARTBEAT_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::recvd_data_byte(uint8_t data , bool eoi)
|
||||||
|
{
|
||||||
|
m_ib = data;
|
||||||
|
m_ib_eoi = eoi;
|
||||||
|
m_ibf = true;
|
||||||
|
update_sh_fsm();
|
||||||
|
update_ah_fsm();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_signals_from_rem(uint8_t to_set , uint8_t to_clear)
|
||||||
|
{
|
||||||
|
uint8_t diff = m_in_signals;
|
||||||
|
m_in_signals |= to_set;
|
||||||
|
m_in_signals &= ~to_clear;
|
||||||
|
diff ^= m_in_signals;
|
||||||
|
m_out_signals = m_in_signals;
|
||||||
|
|
||||||
|
//LOG("REM SIG %02x %02x\n" , m_in_signals , diff);
|
||||||
|
m_no_propagation = true;
|
||||||
|
uint8_t tmp = m_out_signals;
|
||||||
|
|
||||||
|
if (BIT(diff , SIGNAL_ATN_BIT)) {
|
||||||
|
m_bus->atn_w(this , BIT(m_in_signals , SIGNAL_ATN_BIT));
|
||||||
|
COPY_BIT(m_bus->atn_r() , tmp , SIGNAL_ATN_BIT);
|
||||||
|
}
|
||||||
|
if (BIT(diff , SIGNAL_IFC_BIT)) {
|
||||||
|
m_bus->ifc_w(this , BIT(m_in_signals , SIGNAL_IFC_BIT));
|
||||||
|
COPY_BIT(m_bus->ifc_r() , tmp , SIGNAL_IFC_BIT);
|
||||||
|
}
|
||||||
|
if (BIT(diff , SIGNAL_REN_BIT)) {
|
||||||
|
m_bus->ren_w(this , BIT(m_in_signals , SIGNAL_REN_BIT));
|
||||||
|
COPY_BIT(m_bus->ren_r() , tmp , SIGNAL_REN_BIT);
|
||||||
|
}
|
||||||
|
if (BIT(diff , SIGNAL_SRQ_BIT)) {
|
||||||
|
m_bus->srq_w(this , BIT(m_in_signals , SIGNAL_SRQ_BIT));
|
||||||
|
COPY_BIT(m_bus->srq_r() , tmp , SIGNAL_SRQ_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_no_propagation = false;
|
||||||
|
|
||||||
|
update_state(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_signal(signal_bit bit , int state)
|
||||||
|
{
|
||||||
|
if (!m_no_propagation) {
|
||||||
|
uint8_t tmp = m_out_signals;
|
||||||
|
COPY_BIT(state , tmp , bit);
|
||||||
|
update_state(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_state(uint8_t new_signals)
|
||||||
|
{
|
||||||
|
uint8_t to_set = new_signals & ~m_out_signals;
|
||||||
|
uint8_t to_clear = ~new_signals & m_out_signals;
|
||||||
|
|
||||||
|
m_out_signals = new_signals;
|
||||||
|
|
||||||
|
if (is_local_atn_active() && m_ibf) {
|
||||||
|
LOG("Flushing enabled\n");
|
||||||
|
m_flush_bytes = true;
|
||||||
|
m_ibf = false;
|
||||||
|
m_poll_timer->adjust(attotime::zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_set) {
|
||||||
|
send_update(MSG_SIGNAL_SET , to_set);
|
||||||
|
}
|
||||||
|
if (to_clear) {
|
||||||
|
send_update(MSG_SIGNAL_CLEAR , to_clear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::send_update(char type , uint8_t data)
|
||||||
|
{
|
||||||
|
std::string buff = util::string_format("%c:%02x\n" , type , data);
|
||||||
|
LOG("%.6f %s" , machine().time().as_double() , buff);
|
||||||
|
for (char c : buff) {
|
||||||
|
m_stream->output(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remote488_device::a2hex(char c , uint8_t& out)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
out = c - '0';
|
||||||
|
return true;
|
||||||
|
} else if (c >= 'a' && c <= 'f') {
|
||||||
|
out = c - 'a' + 10;
|
||||||
|
return true;
|
||||||
|
} else if (c >= 'A' && c <= 'F') {
|
||||||
|
out = c - 'A' + 10;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remote488_device::is_msg_type(char c)
|
||||||
|
{
|
||||||
|
// Recognize type of input messages
|
||||||
|
return c == MSG_SIGNAL_CLEAR ||
|
||||||
|
c == MSG_SIGNAL_SET ||
|
||||||
|
c == MSG_DATA_BYTE ||
|
||||||
|
c == MSG_END_BYTE ||
|
||||||
|
c == MSG_PP_DATA ||
|
||||||
|
c == MSG_ECHO_REPLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remote488_device::is_terminator(char c)
|
||||||
|
{
|
||||||
|
// Match message terminator characters
|
||||||
|
return c == ',' ||
|
||||||
|
c == ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remote488_device::is_space(char c)
|
||||||
|
{
|
||||||
|
// Match whitespace characters
|
||||||
|
return c == ' ' ||
|
||||||
|
c == '\t' ||
|
||||||
|
c == '\r' ||
|
||||||
|
c == '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
char remote488_device::recv_update(uint8_t& data)
|
||||||
|
{
|
||||||
|
char c;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
// Do not iterate too much..
|
||||||
|
for (i = 0; i < 8 && m_stream->input(&c , 1); i++) {
|
||||||
|
int prev_state = m_rx_state;
|
||||||
|
switch (m_rx_state) {
|
||||||
|
case REM_RX_WAIT_CH:
|
||||||
|
if (is_msg_type(c)) {
|
||||||
|
m_rx_ch = c;
|
||||||
|
m_rx_state = REM_RX_WAIT_COLON;
|
||||||
|
} else if (!is_space(c)) {
|
||||||
|
m_rx_state = REM_RX_WAIT_WS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_RX_WAIT_COLON:
|
||||||
|
if (c == ':') {
|
||||||
|
m_rx_state = REM_RX_WAIT_1ST_HEX;
|
||||||
|
} else {
|
||||||
|
m_rx_state = REM_RX_WAIT_WS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_RX_WAIT_1ST_HEX:
|
||||||
|
if (a2hex(c , m_rx_data)) {
|
||||||
|
m_rx_state = REM_RX_WAIT_2ND_HEX;
|
||||||
|
} else {
|
||||||
|
m_rx_state = REM_RX_WAIT_WS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_RX_WAIT_2ND_HEX:
|
||||||
|
{
|
||||||
|
uint8_t tmp;
|
||||||
|
if (a2hex(c , tmp)) {
|
||||||
|
m_rx_data = (m_rx_data << 4) | tmp;
|
||||||
|
m_rx_state = REM_RX_WAIT_SEP;
|
||||||
|
} else {
|
||||||
|
m_rx_state = REM_RX_WAIT_WS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_RX_WAIT_SEP:
|
||||||
|
if (is_terminator(c) || is_space(c)) {
|
||||||
|
m_rx_state = REM_RX_WAIT_CH;
|
||||||
|
LOG("PARSE %02x %d->%d\n" , c , prev_state , m_rx_state);
|
||||||
|
data = m_rx_data;
|
||||||
|
return m_rx_ch;
|
||||||
|
} else {
|
||||||
|
m_rx_state = REM_RX_WAIT_WS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_RX_WAIT_WS:
|
||||||
|
if (is_terminator(c) || is_space(c)) {
|
||||||
|
m_rx_state = REM_RX_WAIT_CH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
m_rx_state = REM_RX_WAIT_CH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LOG("PARSE %02x %d->%d\n" , c , prev_state , m_rx_state);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remote488_device::is_local_atn_active() const
|
||||||
|
{
|
||||||
|
return !BIT(m_out_signals , SIGNAL_ATN_BIT) && BIT(m_in_signals , SIGNAL_ATN_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_ah_fsm()
|
||||||
|
{
|
||||||
|
bool changed = true;
|
||||||
|
|
||||||
|
while (changed) {
|
||||||
|
//LOG("AH %d DAV %d\n" , m_ah_state , m_bus->dav_r());
|
||||||
|
int prev_state = m_ah_state;
|
||||||
|
|
||||||
|
if (m_sh_state != REM_SH_SIDS || !m_connected) {
|
||||||
|
m_ah_state = REM_AH_AIDS;
|
||||||
|
} else {
|
||||||
|
switch (m_ah_state) {
|
||||||
|
case REM_AH_AIDS:
|
||||||
|
m_ah_state = REM_AH_ACRS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_AH_ACRS:
|
||||||
|
if (!m_bus->dav_r()) {
|
||||||
|
m_ah_state = REM_AH_ACDS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_AH_ACDS:
|
||||||
|
{
|
||||||
|
uint8_t dio = ~m_bus->dio_r();
|
||||||
|
|
||||||
|
if (!m_bus->eoi_r()) {
|
||||||
|
send_update(MSG_END_BYTE , dio);
|
||||||
|
} else {
|
||||||
|
send_update(MSG_DATA_BYTE , dio);
|
||||||
|
}
|
||||||
|
m_ah_state = REM_AH_AWNS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_AH_AWNS:
|
||||||
|
if (m_bus->dav_r()) {
|
||||||
|
m_ah_state = REM_AH_ACRS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
m_ah_state = REM_AH_ACRS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = prev_state != m_ah_state;
|
||||||
|
|
||||||
|
m_bus->ndac_w(this , m_ah_state == REM_AH_AIDS || m_ah_state == REM_AH_AWNS);
|
||||||
|
m_bus->nrfd_w(this , m_ah_state == REM_AH_AIDS || m_ah_state == REM_AH_ACRS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_sh_fsm()
|
||||||
|
{
|
||||||
|
bool changed = true;
|
||||||
|
|
||||||
|
while (changed) {
|
||||||
|
int prev_state = m_sh_state;
|
||||||
|
//LOG("SH %d LATN %d NRFD %d NDAC %d\n" , m_sh_state , is_local_atn_active() , m_bus->nrfd_r() , m_bus->ndac_r());
|
||||||
|
|
||||||
|
if (is_local_atn_active() || !m_ibf) {
|
||||||
|
// Reset condition
|
||||||
|
m_sh_state = REM_SH_SIDS;
|
||||||
|
} else {
|
||||||
|
switch (m_sh_state) {
|
||||||
|
case REM_SH_SIDS:
|
||||||
|
m_sh_state = REM_SH_SDYS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_SH_SDYS:
|
||||||
|
if (m_bus->nrfd_r()) {
|
||||||
|
m_sh_state = REM_SH_STRS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REM_SH_STRS:
|
||||||
|
if (m_bus->ndac_r()) {
|
||||||
|
m_sh_state = REM_SH_SIDS;
|
||||||
|
LOG("Sourced %02x\n" , m_ib);
|
||||||
|
m_ibf = false;
|
||||||
|
// Schedule an immediate poll for incoming messages
|
||||||
|
// This allows sourcing string of bytes on the bus at the highest possible speed
|
||||||
|
m_poll_timer->adjust(attotime::zero);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
m_sh_state = REM_SH_SIDS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = prev_state != m_sh_state;
|
||||||
|
|
||||||
|
if (m_sh_state == REM_SH_SDYS || m_sh_state == REM_SH_STRS) {
|
||||||
|
m_bus->eoi_w(this , !m_ib_eoi);
|
||||||
|
m_sh_dio = ~m_ib;
|
||||||
|
} else {
|
||||||
|
m_bus->eoi_w(this , 1);
|
||||||
|
m_sh_dio = 0xff;
|
||||||
|
}
|
||||||
|
update_dio();
|
||||||
|
m_bus->dav_w(this , m_sh_state == REM_SH_SIDS || m_sh_state == REM_SH_SDYS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remote488_device::is_local_pp_active() const
|
||||||
|
{
|
||||||
|
return is_local_atn_active() && !m_bus->eoi_r();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_pp()
|
||||||
|
{
|
||||||
|
if (is_local_pp_active() && m_connected && !m_pp_requested) {
|
||||||
|
send_update(MSG_PP_REQUEST , 0);
|
||||||
|
m_pp_requested = true;
|
||||||
|
}
|
||||||
|
update_pp_dio();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_pp_dio()
|
||||||
|
{
|
||||||
|
if (is_local_pp_active()) {
|
||||||
|
LOG("PP %02x\n" , m_pp_data);
|
||||||
|
m_pp_dio = ~m_pp_data;
|
||||||
|
} else {
|
||||||
|
m_pp_dio = 0xff;
|
||||||
|
}
|
||||||
|
update_dio();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote488_device::update_dio()
|
||||||
|
{
|
||||||
|
m_bus->dio_w(this , m_pp_dio & m_sh_dio);
|
||||||
|
}
|
128
src/devices/bus/ieee488/remote488.h
Normal file
128
src/devices/bus/ieee488/remote488.h
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// license:BSD-3-Clause
|
||||||
|
// copyright-holders: F. Ulivi
|
||||||
|
/*********************************************************************
|
||||||
|
|
||||||
|
remote488.h
|
||||||
|
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#ifndef MAME_BUS_IEEE488_REMOTE488_H
|
||||||
|
#define MAME_BUS_IEEE488_REMOTE488_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ieee488.h"
|
||||||
|
#include "imagedev/bitbngr.h"
|
||||||
|
|
||||||
|
class remote488_device : public device_t,
|
||||||
|
public device_ieee488_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// construction/destruction
|
||||||
|
remote488_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||||
|
|
||||||
|
// device_ieee488_interface overrides
|
||||||
|
virtual void ieee488_eoi(int state) override;
|
||||||
|
virtual void ieee488_dav(int state) override;
|
||||||
|
virtual void ieee488_nrfd(int state) override;
|
||||||
|
virtual void ieee488_ndac(int state) override;
|
||||||
|
virtual void ieee488_ifc(int state) override;
|
||||||
|
virtual void ieee488_srq(int state) override;
|
||||||
|
virtual void ieee488_atn(int state) override;
|
||||||
|
virtual void ieee488_ren(int state) override;
|
||||||
|
|
||||||
|
// Timers
|
||||||
|
enum {
|
||||||
|
TMR_ID_POLL,
|
||||||
|
TMR_ID_HEARTBEAT
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void device_start() override;
|
||||||
|
virtual void device_reset() override;
|
||||||
|
virtual void device_add_mconfig(machine_config &config) override;
|
||||||
|
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Position of signals in "S/R" msgs
|
||||||
|
enum signal_bit {
|
||||||
|
SIGNAL_ATN_BIT, // Bit 0
|
||||||
|
SIGNAL_IFC_BIT, // Bit 1
|
||||||
|
SIGNAL_REN_BIT, // Bit 2
|
||||||
|
SIGNAL_SRQ_BIT, // Bit 3
|
||||||
|
SIGNAL_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Source handshake states
|
||||||
|
enum {
|
||||||
|
REM_SH_SIDS,
|
||||||
|
REM_SH_SDYS,
|
||||||
|
REM_SH_STRS
|
||||||
|
};
|
||||||
|
|
||||||
|
// Acceptor handshake states
|
||||||
|
enum {
|
||||||
|
REM_AH_AIDS,
|
||||||
|
REM_AH_ACRS,
|
||||||
|
REM_AH_ACDS,
|
||||||
|
REM_AH_AWNS
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stream rx states
|
||||||
|
enum {
|
||||||
|
REM_RX_WAIT_CH,
|
||||||
|
REM_RX_WAIT_COLON,
|
||||||
|
REM_RX_WAIT_1ST_HEX,
|
||||||
|
REM_RX_WAIT_2ND_HEX,
|
||||||
|
REM_RX_WAIT_SEP,
|
||||||
|
REM_RX_WAIT_WS
|
||||||
|
};
|
||||||
|
|
||||||
|
required_device<bitbanger_device> m_stream;
|
||||||
|
uint8_t m_in_signals;
|
||||||
|
uint8_t m_out_signals;
|
||||||
|
bool m_no_propagation;
|
||||||
|
int m_sh_state;
|
||||||
|
int m_ah_state;
|
||||||
|
int m_rx_state;
|
||||||
|
char m_rx_ch;
|
||||||
|
uint8_t m_rx_data;
|
||||||
|
bool m_flush_bytes;
|
||||||
|
bool m_ibf;
|
||||||
|
uint8_t m_ib;
|
||||||
|
bool m_ib_eoi;
|
||||||
|
emu_timer *m_poll_timer;
|
||||||
|
emu_timer *m_hb_timer;
|
||||||
|
unsigned m_connect_cnt;
|
||||||
|
bool m_connected;
|
||||||
|
uint8_t m_pp_data;
|
||||||
|
bool m_pp_requested;
|
||||||
|
uint8_t m_pp_dio;
|
||||||
|
uint8_t m_sh_dio;
|
||||||
|
|
||||||
|
void bus_reset();
|
||||||
|
void process_input_msgs();
|
||||||
|
void set_connection(bool state);
|
||||||
|
void recvd_data_byte(uint8_t data , bool eoi);
|
||||||
|
void update_signals_from_rem(uint8_t to_set , uint8_t to_clear);
|
||||||
|
void update_signal(signal_bit bit , int state);
|
||||||
|
void update_state(uint8_t new_signals);
|
||||||
|
void send_update(char type , uint8_t data);
|
||||||
|
static bool a2hex(char c , uint8_t& out);
|
||||||
|
static bool is_msg_type(char c);
|
||||||
|
static bool is_terminator(char c);
|
||||||
|
static bool is_space(char c);
|
||||||
|
char recv_update(uint8_t& data);
|
||||||
|
bool is_local_atn_active() const;
|
||||||
|
void update_ah_fsm();
|
||||||
|
void update_sh_fsm();
|
||||||
|
bool is_local_pp_active() const;
|
||||||
|
void update_pp();
|
||||||
|
void update_pp_dio();
|
||||||
|
void update_dio();
|
||||||
|
};
|
||||||
|
|
||||||
|
// device type definition
|
||||||
|
DECLARE_DEVICE_TYPE(REMOTE488, remote488_device)
|
||||||
|
|
||||||
|
#endif // MAME_BUS_IEEE488_REMOTE488_H
|
Loading…
Reference in New Issue
Block a user