diff --git a/docs/source/commandline/commandline-all.rst b/docs/source/commandline/commandline-all.rst index 57cf9e5fc1b6a..96a581efe88b6 100644 --- a/docs/source/commandline/commandline-all.rst +++ b/docs/source/commandline/commandline-all.rst @@ -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 @@ -1111,6 +1118,39 @@ OSD Output Options led0 = 0 +.. _mame-commandline-output-serial-port: + +**-output_serial_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** ** + + 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 diff --git a/docs/source/commandline/commandline-index.rst b/docs/source/commandline/commandline-index.rst index 76f4e5cb5cc92..4ddc89cbb7892 100644 --- a/docs/source/commandline/commandline-index.rst +++ b/docs/source/commandline/commandline-index.rst @@ -79,6 +79,8 @@ OSD Output Options ~~~~~~~~~~~~~~~~~~ | :ref:`output ` +| :ref:`output_serial_port ` +| :ref:`output_serial_baud ` Configuration Options diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua index b7fe831439b1d..2ea2b9a2e22a4 100644 --- a/scripts/src/osd/modules.lua +++ b/scripts/src/osd/modules.lua @@ -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", diff --git a/src/osd/modules/lib/osdobj_common.cpp b/src/osd/modules/lib/osdobj_common.cpp index 2e2b198cd0e70..0ddfde98322e9 100644 --- a/src/osd/modules/lib/osdobj_common.cpp +++ b/src/osd/modules/lib/osdobj_common.cpp @@ -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: " }, @@ -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); diff --git a/src/osd/modules/lib/osdobj_common.h b/src/osd/modules/lib/osdobj_common.h index 02b3d858173e3..31fe81104feac 100644 --- a/src/osd/modules/lib/osdobj_common.h +++ b/src/osd/modules/lib/osdobj_common.h @@ -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" diff --git a/src/osd/modules/output/serial.cpp b/src/osd/modules/output/serial.cpp new file mode 100644 index 0000000000000..b07f0e24449e5 --- /dev/null +++ b/src/osd/modules/output/serial.cpp @@ -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 +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#include +#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)