Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion docs/source/commandline/commandline-all.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1096,10 +1096,17 @@ OSD Output Options
to connect external outputs such as the LED lights for the Player 1/2 start
buttons on certain arcade machines.

You can choose from: ``auto``, ``none``, ``console`` or ``network``
You can choose from: ``auto``, ``none``, ``console``, ``network``,
or ``serial``.

Note that network port is fixed at 8000.

The ``serial`` provider writes output notifications as ``name = value``
lines (newline-terminated) to a configured serial port - useful for
driving external arcade I/O hardware such as ticket dispensers, coin
hoppers, and lamp boards. Configure the port and baud rate with the
options below.

Example:
.. code-block:: bash

Expand All @@ -1111,6 +1118,39 @@ OSD Output Options
led0 = 0


.. _mame-commandline-output-serial-port:

**-output_serial_port** *<port>*

Selects the serial port the ``serial`` output provider writes to.
Accepts platform-native port names, for example ``COM4`` on Windows
or ``/dev/ttyUSB0`` on Linux.

The default is empty; if no port is set when ``-output serial`` is
selected, the provider fails to initialise and MAME falls back to
``-output none``.

Example:
.. code-block:: bash

mame ddboy -output serial -output_serial_port COM4

.. _mame-commandline-output-serial-baud:

**-output_serial_baud** *<rate>*

Sets the baud rate for the ``serial`` output provider.

The default is ``9600``. Heavier games with frequent output
activity (e.g. many lamps blinking) may benefit from a higher rate
such as ``115200``.

Example:
.. code-block:: bash

mame ddboy -output serial -output_serial_port COM4 -output_serial_baud 115200


.. _mame-commandline-configoptions:

Configuration Options
Expand Down
2 changes: 2 additions & 0 deletions docs/source/commandline/commandline-index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ OSD Output Options
~~~~~~~~~~~~~~~~~~

| :ref:`output <mame-commandline-output>`
| :ref:`output_serial_port <mame-commandline-output-serial-port>`
| :ref:`output_serial_baud <mame-commandline-output-serial-baud>`

Configuration Options
Expand Down
1 change: 1 addition & 0 deletions scripts/src/osd/modules.lua
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function osdmodulesbuild()
MAME_DIR .. "src/osd/modules/output/network.cpp",
MAME_DIR .. "src/osd/modules/output/none.cpp",
MAME_DIR .. "src/osd/modules/output/output_module.h",
MAME_DIR .. "src/osd/modules/output/serial.cpp",
MAME_DIR .. "src/osd/modules/output/win32_output.cpp",
MAME_DIR .. "src/osd/modules/output/win32_output.h",
MAME_DIR .. "src/osd/modules/render/blit13.ipp",
Expand Down
3 changes: 3 additions & 0 deletions src/osd/modules/lib/osdobj_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const options_entry osd_options::s_option_entries[] =

{ nullptr, nullptr, core_options::option_type::HEADER, "OSD OUTPUT OPTIONS" },
{ OSD_OUTPUT_PROVIDER, OSDOPTVAL_AUTO, core_options::option_type::STRING, "provider for output notifications: " },
{ OSDOPTION_OUTPUT_SERIAL_PORT, "", core_options::option_type::STRING, "serial port for 'serial' output provider (e.g. COM4, /dev/ttyUSB0)" },
{ OSDOPTION_OUTPUT_SERIAL_BAUD, "9600", core_options::option_type::INTEGER, "serial port baud rate for 'serial' output provider" },

{ nullptr, nullptr, core_options::option_type::HEADER, "OSD INPUT OPTIONS" },
{ OSD_KEYBOARDINPUT_PROVIDER, OSDOPTVAL_AUTO, core_options::option_type::STRING, "provider for keyboard input: " },
Expand Down Expand Up @@ -330,6 +332,7 @@ void osd_common_t::register_options()
REGISTER_MODULE(m_mod_man, OUTPUT_NONE);
REGISTER_MODULE(m_mod_man, OUTPUT_CONSOLE);
REGISTER_MODULE(m_mod_man, OUTPUT_NETWORK);
REGISTER_MODULE(m_mod_man, OUTPUT_SERIAL);
REGISTER_MODULE(m_mod_man, OUTPUT_WIN32);


Expand Down
3 changes: 3 additions & 0 deletions src/osd/modules/lib/osdobj_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@

#define OSDOPTION_NETWORK_PROVIDER "networkprovider"

#define OSDOPTION_OUTPUT_SERIAL_PORT "output_serial_port"
#define OSDOPTION_OUTPUT_SERIAL_BAUD "output_serial_baud"

#define OSDOPTION_BGFX_PATH "bgfx_path"
#define OSDOPTION_BGFX_BACKEND "bgfx_backend"
#define OSDOPTION_BGFX_DEBUG "bgfx_debug"
Expand Down
255 changes: 255 additions & 0 deletions src/osd/modules/output/serial.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// license:BSD-3-Clause
// copyright-holders:Nadav Weiss
/***************************************************************************

serial.cpp

Serial port output interface.

Forwards MAME output notifications as "name = value\n" lines over a
serial port. Intended for driving external arcade hardware (ticket
dispensers, coin hoppers, lamp boards) through a user-space bridge.

***************************************************************************/

#include "output_module.h"

#include "modules/lib/osdobj_common.h"
#include "modules/osdmodule.h"

#include "osdcore.h"
#include "strformat.h"

#include <cstddef>
#include <cstdint>
#include <string>

#if defined(_WIN32)
#include <windows.h>
#else
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#endif


namespace osd {

namespace {

#if defined(_WIN32)

class serial_port
{
public:
serial_port() : m_handle(INVALID_HANDLE_VALUE) { }
~serial_port() { close(); }

bool open(const std::string &port, unsigned baud)
{
// Windows requires "\\.\COMn" form for COM ports above COM9, and it
// also works for COM1-COM9, so always use it.
std::string path = "\\\\.\\" + port;
m_handle = CreateFileA(
path.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (m_handle == INVALID_HANDLE_VALUE)
return false;

DCB dcb = {};
dcb.DCBlength = sizeof(dcb);
if (!GetCommState(m_handle, &dcb))
{
close();
return false;
}
dcb.BaudRate = baud;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
if (!SetCommState(m_handle, &dcb))
{
close();
return false;
}

// No blocking: zero write timeouts so a stuck host can't stall MAME.
COMMTIMEOUTS timeouts = {};
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.WriteTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
SetCommTimeouts(m_handle, &timeouts);

return true;
}

void close()
{
if (m_handle != INVALID_HANDLE_VALUE)
{
CloseHandle(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
}

bool write(const char *data, std::size_t length)
{
if (m_handle == INVALID_HANDLE_VALUE)
return false;
DWORD written = 0;
return WriteFile(m_handle, data, DWORD(length), &written, nullptr) != 0;
}

private:
HANDLE m_handle;
};

#else

class serial_port
{
public:
serial_port() : m_fd(-1) { }
~serial_port() { close(); }

bool open(const std::string &port, unsigned baud)
{
m_fd = ::open(port.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (m_fd < 0)
return false;

termios tio = {};
if (tcgetattr(m_fd, &tio) != 0)
{
close();
return false;
}

cfmakeraw(&tio);
tio.c_cflag |= CLOCAL | CREAD;
tio.c_cflag &= ~CRTSCTS;
tio.c_cflag = (tio.c_cflag & ~CSIZE) | CS8;
tio.c_cflag &= ~PARENB;
tio.c_cflag &= ~CSTOPB;
tio.c_iflag &= ~(IXON | IXOFF | IXANY);

speed_t speed = baud_to_speed(baud);
cfsetispeed(&tio, speed);
cfsetospeed(&tio, speed);

if (tcsetattr(m_fd, TCSANOW, &tio) != 0)
{
close();
return false;
}

return true;
}

void close()
{
if (m_fd >= 0)
{
::close(m_fd);
m_fd = -1;
}
}

bool write(const char *data, std::size_t length)
{
if (m_fd < 0)
return false;
ssize_t written = ::write(m_fd, data, length);
return written == ssize_t(length);
}

private:
static speed_t baud_to_speed(unsigned baud)
{
switch (baud)
{
case 1200: return B1200;
case 2400: return B2400;
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
default: return B9600;
}
}

int m_fd;
};

#endif


class output_serial : public osd_module, public output_module
{
public:
output_serial() :
osd_module(OSD_OUTPUT_PROVIDER, "serial"),
output_module()
{
}

virtual int init(osd_interface &osd, const osd_options &options) override
{
const char *port = options.value(OSDOPTION_OUTPUT_SERIAL_PORT);
if (port == nullptr || port[0] == '\0')
{
osd_printf_error("serial output: no port configured (set -%s)\n", OSDOPTION_OUTPUT_SERIAL_PORT);
return -1;
}

unsigned baud = 9600;
if (options.exists(OSDOPTION_OUTPUT_SERIAL_BAUD))
baud = unsigned(options.int_value(OSDOPTION_OUTPUT_SERIAL_BAUD));

if (!m_port.open(port, baud))
{
osd_printf_error("serial output: failed to open %s at %u baud\n", port, baud);
return -1;
}

osd_printf_verbose("serial output: opened %s at %u baud\n", port, baud);
return 0;
}

virtual void exit() override
{
m_port.close();
}

// output_module

virtual void notify(const char *outname, int32_t value) override
{
auto msg = util::string_format("%s = %d\n", ((outname == nullptr) ? "none" : outname), value);
m_port.write(msg.data(), msg.size());
}

private:
serial_port m_port;
};

} // anonymous namespace

} // namespace osd

MODULE_DEFINITION(OUTPUT_SERIAL, osd::output_serial)