summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices
diff options
context:
space:
mode:
author AJR <ajrhacker@users.noreply.github.com>2020-03-28 12:05:15 -0400
committer AJR <ajrhacker@users.noreply.github.com>2020-03-28 12:05:15 -0400
commit6c6a55d5bba7df20b31325f2a6a1d941a0e00dc1 (patch)
tree32d4e41e4a6cf1db154d9fcaa3b522cf5bb2e9b4 /src/devices
parent21bfb6a09ee3de9ec55dea716c98347b3188da86 (diff)
upd7001: New device
ssv.cpp: Split state class up a bit (nw)
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/machine/upd7001.cpp205
-rw-r--r--src/devices/machine/upd7001.h102
2 files changed, 307 insertions, 0 deletions
diff --git a/src/devices/machine/upd7001.cpp b/src/devices/machine/upd7001.cpp
new file mode 100644
index 00000000000..d4607e7a479
--- /dev/null
+++ b/src/devices/machine/upd7001.cpp
@@ -0,0 +1,205 @@
+// license:BSD-3-Clause
+// copyright-holders:AJR
+/*********************************************************************
+
+ NEC µPD7001 CMOS Serial I/O Analog-to-Digital Converter
+
+ This ADC takes 56 clock cycles to convert one of its four
+ multiplexed input channels by successive approximation. The
+ 8-bit result is shifted out MSB first on the SO line. (SO and
+ EOC are open-drain outputs, making them convenient to tie
+ together since CS disables EOC but enables SO.) If only one
+ input is used, it may be connected to A0, with DL tied to Vdd
+ and SI to Vss.
+
+ The internal clock (fCK) is normally generated from a resistor
+ bridging the CL0 and CL1 pins and a capacitor between CL1 and
+ ground. The typical values R = 27 kΩ and C = 47 pF produce an
+ operating frequency of about 400 kHz. (500 kHz is the maximum
+ according to the datasheet.)
+
+*********************************************************************/
+
+#include "emu.h"
+#include "upd7001.h"
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// device type definition
+DEFINE_DEVICE_TYPE(UPD7001, upd7001_device, "upd7001", "NEC uPD7001 A/D Converter")
+
+//**************************************************************************
+// DEVICE IMPLEMENTATION
+//**************************************************************************
+
+//-------------------------------------------------
+// upd7001_device - constructor
+//-------------------------------------------------
+
+upd7001_device::upd7001_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, UPD7001, tag, owner, clock)
+ , m_an_callback(*this)
+ , m_eoc_callback(*this)
+ , m_res(0.0)
+ , m_cap(0.0)
+ , m_fck_rc(attotime::attotime::zero)
+ , m_conv_timer(nullptr)
+ , m_scsk_timer(nullptr)
+ , m_cs_active(false)
+ , m_eoc_active(false)
+ , m_oe(false)
+ , m_sck(true)
+ , m_si(true)
+ , m_so(false)
+ , m_dl(false)
+ , m_sr(0)
+ , m_mpx(0)
+{
+}
+
+
+//-------------------------------------------------
+// device_resolve_objects -
+//-------------------------------------------------
+
+void upd7001_device::device_resolve_objects()
+{
+ // resolve callbacks
+ m_an_callback.resolve_all_safe(0);
+ m_eoc_callback.resolve_safe();
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void upd7001_device::device_start()
+{
+ // calculate RC timing (FIXME: math is not very accurate)
+ if (m_res == 0.0 || m_cap == 0.0)
+ m_fck_rc = attotime::zero;
+ else
+ m_fck_rc = attotime::from_double(m_res * m_cap * 1.97);
+
+ // initialize timers
+ m_conv_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(upd7001_device::conversion_done), this));
+ m_scsk_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(upd7001_device::output_enabled), this));
+
+ // save state
+ save_item(NAME(m_cs_active));
+ save_item(NAME(m_eoc_active));
+ save_item(NAME(m_oe));
+ save_item(NAME(m_sck));
+ save_item(NAME(m_si));
+ save_item(NAME(m_so));
+ save_item(NAME(m_dl));
+ save_item(NAME(m_sr));
+ save_item(NAME(m_mpx));
+}
+
+
+//-------------------------------------------------
+// cs_w - active-low chip select
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER(upd7001_device::cs_w)
+{
+ if (!state && !m_cs_active)
+ {
+ if (m_conv_timer->enabled())
+ {
+ logerror("%s: CS lowered while conversion still in progress\n", machine().describe_context());
+ m_conv_timer->enable(false);
+ }
+
+ // EOC deasserted on falling edge
+ m_eoc_active = false;
+ m_eoc_callback(1);
+
+ // Output is enabled after 5 internal clock pulses
+ m_scsk_timer->adjust(m_fck_rc != attotime::zero ? m_fck_rc * 5 : clocks_to_attotime(5));
+ }
+ else if (state && m_cs_active)
+ {
+ // Disable data output
+ m_oe = false;
+ m_scsk_timer->enable(false);
+
+ // Begin conversion on rising edge
+ m_conv_timer->adjust(m_fck_rc != attotime::zero ? m_fck_rc * 56 : clocks_to_attotime(56));
+ }
+
+ m_cs_active = !state;
+}
+
+
+//-------------------------------------------------
+// conversion_done - finish converting the
+// selected analog input
+//-------------------------------------------------
+
+TIMER_CALLBACK_MEMBER(upd7001_device::conversion_done)
+{
+ // Load conversion result into shift register
+ m_sr = m_an_callback[m_mpx]();
+
+ // Activate EOC output
+ m_eoc_active = true;
+ m_eoc_callback(0);
+}
+
+
+//-------------------------------------------------
+// output_enabled - enable data output on SO
+//-------------------------------------------------
+
+TIMER_CALLBACK_MEMBER(upd7001_device::output_enabled)
+{
+ m_oe = true;
+}
+
+
+//-------------------------------------------------
+// sck_w - shift data out of and into register
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER(upd7001_device::sck_w)
+{
+ if (m_cs_active)
+ {
+ if (!state && m_sck)
+ {
+ if (m_scsk_timer->enabled() && m_scsk_timer->remaining() > attotime::zero)
+ logerror("%s: SCK lowered %.2f microseconds too early\n", machine().describe_context(), m_scsk_timer->remaining().as_double() * 1.0E+6);
+
+ // SO updates on falling edge
+ m_so = BIT(m_sr, 7);
+ }
+ else if (state && !m_sck)
+ {
+ // SI shifted in on rising edge
+ m_sr = (m_sr << 1) | m_si;
+ if (m_dl)
+ m_mpx = m_sr & 3;
+ }
+ }
+
+ m_sck = state;
+}
+
+
+//-------------------------------------------------
+// dl_w - latch input data for multiplexer to
+// select input to convert
+//-------------------------------------------------
+
+WRITE_LINE_MEMBER(upd7001_device::dl_w)
+{
+ if (state && !m_dl && m_cs_active)
+ m_mpx = m_sr & 3;
+
+ m_dl = state;
+}
diff --git a/src/devices/machine/upd7001.h b/src/devices/machine/upd7001.h
new file mode 100644
index 00000000000..afc4b632293
--- /dev/null
+++ b/src/devices/machine/upd7001.h
@@ -0,0 +1,102 @@
+// license:BSD-3-Clause
+// copyright-holders:AJR
+/*********************************************************************
+
+ NEC µPD7001 CMOS Serial I/O Analog-to-Digital Converter
+
+**********************************************************************
+ ___ ___
+ _EOC 1 |* \__/ | 16 Vdd
+ DL 2 | | 15 Vref
+ SI 3 | | 14 AGND
+ _SCK 4 | | 13 A3
+ SO 5 | D7001C | 12 A2
+ _CS 6 | | 11 A1
+ CL0 7 | | 10 A0
+ CL1 8 |__________| 9 Vss
+
+*********************************************************************/
+
+#ifndef MAME_MACHINE_UPD7001_H
+#define MAME_MACHINE_UPD7001_H
+
+#pragma once
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// ======================> upd7001_device
+
+class upd7001_device : public device_t
+{
+public:
+ // device type constructors
+ upd7001_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+ upd7001_device(const machine_config &mconfig, const char *tag, device_t *owner, double r, double c)
+ : upd7001_device(mconfig, tag, owner, 0U)
+ {
+ set_rc(r, c);
+ }
+
+ // input callback configuration
+ auto a0_callback() { return m_an_callback[0].bind(); }
+ auto a1_callback() { return m_an_callback[1].bind(); }
+ auto a2_callback() { return m_an_callback[2].bind(); }
+ auto a3_callback() { return m_an_callback[3].bind(); }
+
+ // output callback configuration
+ auto eoc_callback() { return m_eoc_callback.bind(); }
+
+ // misc. configuration
+ void set_rc(double res, double cap) { assert(!configured()); m_res = res; m_cap = cap; }
+
+ // serial interface
+ DECLARE_WRITE_LINE_MEMBER(cs_w);
+ DECLARE_WRITE_LINE_MEMBER(sck_w);
+ DECLARE_WRITE_LINE_MEMBER(si_w) { m_si = state; }
+ DECLARE_WRITE_LINE_MEMBER(dl_w);
+ DECLARE_READ_LINE_MEMBER(so_r) { return m_oe ? m_so : 1; }
+ DECLARE_READ_LINE_MEMBER(eoc_r) { return m_eoc_active ? 0 : 1; }
+ DECLARE_READ_LINE_MEMBER(eoc_so_r) { return eoc_r() && so_r(); }
+
+protected:
+ // device-level overrides
+ virtual void device_resolve_objects() override;
+ virtual void device_start() override;
+
+private:
+ // timing helpers
+ TIMER_CALLBACK_MEMBER(conversion_done);
+ TIMER_CALLBACK_MEMBER(output_enabled);
+
+ // callback objects
+ devcb_read8::array<4> m_an_callback;
+ devcb_write_line m_eoc_callback;
+
+ // timing parameters
+ double m_res;
+ double m_cap;
+ attotime m_fck_rc;
+
+ // internal timers
+ emu_timer *m_conv_timer;
+ emu_timer *m_scsk_timer;
+
+ // internal state
+ bool m_cs_active;
+ bool m_eoc_active;
+ bool m_oe;
+ bool m_sck;
+ bool m_si;
+ bool m_so;
+ bool m_dl;
+ u8 m_sr;
+ u8 m_mpx;
+};
+
+// device type declaration
+DECLARE_DEVICE_TYPE(UPD7001, upd7001_device)
+
+#endif // MAME_MACHINE_UPD7001_H