summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices
diff options
context:
space:
mode:
author Nigel Barnes <ngbarnes@gmail.com>2026-05-16 13:00:54 +0100
committer Nigel Barnes <ngbarnes@gmail.com>2026-05-16 13:02:19 +0100
commitf061a1ee1c1fb705f8665f8c8a34319b801b2ee6 (patch)
tree559f3f797c5bbfa7da635a080b37aa8ec6226be5 /src/devices
parent80168132d950d25992518dfbcac62cda9ef54713 (diff)
sound/pcd3311.cpp: Implemented DTMF tone generator, and I2C interface.
psion/psion3.cpp: DTMF tone generator is now working, removed MACHINE_IMPERFECT_SOUND flag. atari/pofo.cpp: DTMF tone generator is now working, removed MACHINE_IMPERFECT_SOUND flag.
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/sound/pcd3311.cpp266
-rw-r--r--src/devices/sound/pcd3311.h40
2 files changed, 293 insertions, 13 deletions
diff --git a/src/devices/sound/pcd3311.cpp b/src/devices/sound/pcd3311.cpp
index 03e5cd2aa81..6943b71277b 100644
--- a/src/devices/sound/pcd3311.cpp
+++ b/src/devices/sound/pcd3311.cpp
@@ -4,11 +4,19 @@
PCD3311 DTMF/modem/musical tone generator emulation
+ TODO:
+ - derive tone frequencies from clock (ROM dividers need extracting)
+
**********************************************************************/
#include "emu.h"
#include "pcd3311.h"
+#define LOG_DATA (1U << 1)
+#define LOG_LINE (1U << 2)
+
+#define VERBOSE (0)
+#include "logmacro.h"
//**************************************************************************
@@ -18,7 +26,6 @@
DEFINE_DEVICE_TYPE(PCD3311, pcd3311_device, "pcd3311", "PCD3311")
-
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
@@ -28,8 +35,16 @@ DEFINE_DEVICE_TYPE(PCD3311, pcd3311_device, "pcd3311", "PCD3311")
//-------------------------------------------------
pcd3311_device::pcd3311_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
- device_t(mconfig, PCD3311, tag, owner, clock),
- device_sound_interface(mconfig, *this)
+ device_t(mconfig, PCD3311, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+ , m_slave_address(PCD3311_SLAVE_ADDRESS)
+ , m_scl(0)
+ , m_sdaw(0)
+ , m_sdar(1)
+ , m_state(STATE_IDLE)
+ , m_bits(0)
+ , m_shift(0)
+ , m_devsel(0)
{
}
@@ -40,10 +55,31 @@ pcd3311_device::pcd3311_device(const machine_config &mconfig, const char *tag, d
void pcd3311_device::device_start()
{
- save_item(NAME(m_a0));
+ m_stream = stream_alloc(0, 1, SAMPLE_RATE_OUTPUT_ADAPTIVE);
+
+ m_mode = 0;
+ m_strobe = 0;
+ m_data = 0;
+
+ m_freq[0] = m_freq[1] = 0;
+ m_incr[0] = m_incr[1] = 0;
+ m_signal[0] = m_signal[1] = 1.0;
+
save_item(NAME(m_mode));
save_item(NAME(m_strobe));
save_item(NAME(m_data));
+
+ save_item(NAME(m_scl));
+ save_item(NAME(m_sdaw));
+ save_item(NAME(m_sdar));
+ save_item(NAME(m_state));
+ save_item(NAME(m_bits));
+ save_item(NAME(m_shift));
+ save_item(NAME(m_devsel));
+
+ save_item(NAME(m_freq));
+ save_item(NAME(m_incr));
+ save_item(NAME(m_signal));
}
@@ -54,4 +90,226 @@ void pcd3311_device::device_start()
void pcd3311_device::sound_stream_update(sound_stream &stream)
{
+ if (m_freq[0] == 0)
+ return;
+
+ // fill in the sample
+ for (int sampindex = 0; sampindex < stream.samples(); sampindex++)
+ {
+ for (int dac = 0; dac < 2; dac++)
+ {
+ m_incr[dac] -= m_freq[dac];
+ while (m_incr[dac] < 0)
+ {
+ m_incr[dac] += stream.sample_rate() / 2;
+ m_signal[dac] = -m_signal[dac];
+ }
+ stream.add(0, sampindex, m_signal[dac]);
+ }
+ }
+}
+
+
+void pcd3311_device::load_data(uint8_t data)
+{
+ m_stream->update();
+
+ static const float dtmf_tones[12] { 697.90, 770.46, 850.45, 943.23, 1206.45, 1341.66, 1482.21, 1638.24, 1875.1, 1970.0, 2358.1, 2470.4 };
+ static const float modem_tones[12] { 1296.94, 2103.14, 1197.17, 2192.01, 978.82, 1179.03, 1073.33, 1265.30, 1655.66, 1852.77, 2021.20, 2223.32 };
+ static const float music_tones[16] { 622.5, 659.5, 697.9, 741.1, 782.1, 832.3, 879.3, 931.9, 985.0, 1044.5, 1111.7, 1245.1, 1318.9, 1402.1, 1572.0, 1768.5 };
+
+ m_freq[0] = m_freq[1] = 0;
+
+ switch (data & 0x30)
+ {
+ case 0x00: // DTMF single tones; musical tones
+ switch (data & 0x0f)
+ {
+ case 0x04: m_freq[0] = dtmf_tones[8]; break;
+ case 0x05: m_freq[0] = dtmf_tones[9]; break;
+ case 0x06: m_freq[0] = dtmf_tones[10]; break;
+ case 0x07: m_freq[0] = dtmf_tones[11]; break;
+ case 0x08: m_freq[0] = dtmf_tones[0]; break;
+ case 0x09: m_freq[0] = dtmf_tones[1]; break;
+ case 0x0a: m_freq[0] = dtmf_tones[2]; break;
+ case 0x0b: m_freq[0] = dtmf_tones[3]; break;
+ case 0x0c: m_freq[0] = dtmf_tones[4]; break;
+ case 0x0d: m_freq[0] = dtmf_tones[5]; break;
+ case 0x0e: m_freq[0] = dtmf_tones[6]; break;
+ case 0x0f: m_freq[0] = dtmf_tones[7]; break;
+ }
+ break;
+ case 0x10: // DTMF dual tones
+ switch (data & 0x0f)
+ {
+ case 0x00: m_freq[0] = dtmf_tones[3]; m_freq[1] = dtmf_tones[5]; break;
+ case 0x01: m_freq[0] = dtmf_tones[0]; m_freq[1] = dtmf_tones[4]; break;
+ case 0x02: m_freq[0] = dtmf_tones[0]; m_freq[1] = dtmf_tones[5]; break;
+ case 0x03: m_freq[0] = dtmf_tones[0]; m_freq[1] = dtmf_tones[6]; break;
+ case 0x04: m_freq[0] = dtmf_tones[1]; m_freq[1] = dtmf_tones[4]; break;
+ case 0x05: m_freq[0] = dtmf_tones[1]; m_freq[1] = dtmf_tones[5]; break;
+ case 0x06: m_freq[0] = dtmf_tones[1]; m_freq[1] = dtmf_tones[6]; break;
+ case 0x07: m_freq[0] = dtmf_tones[2]; m_freq[1] = dtmf_tones[4]; break;
+ case 0x08: m_freq[0] = dtmf_tones[2]; m_freq[1] = dtmf_tones[5]; break;
+ case 0x09: m_freq[0] = dtmf_tones[2]; m_freq[1] = dtmf_tones[6]; break;
+ case 0x0a: m_freq[0] = dtmf_tones[0]; m_freq[1] = dtmf_tones[7]; break;
+ case 0x0b: m_freq[0] = dtmf_tones[1]; m_freq[1] = dtmf_tones[7]; break;
+ case 0x0c: m_freq[0] = dtmf_tones[2]; m_freq[1] = dtmf_tones[7]; break;
+ case 0x0d: m_freq[0] = dtmf_tones[3]; m_freq[1] = dtmf_tones[7]; break;
+ case 0x0e: m_freq[0] = dtmf_tones[3]; m_freq[1] = dtmf_tones[4]; break;
+ case 0x0f: m_freq[0] = dtmf_tones[3]; m_freq[1] = dtmf_tones[6]; break;
+ }
+ break;
+ case 0x20: // modem tones
+ switch (data & 0x0f)
+ {
+ case 0x04: m_freq[0] = modem_tones[0]; break;
+ case 0x05: m_freq[0] = modem_tones[1]; break;
+ case 0x06: m_freq[0] = modem_tones[2]; break;
+ case 0x07: m_freq[0] = modem_tones[3]; break;
+ case 0x08: m_freq[0] = modem_tones[4]; break;
+ case 0x09: m_freq[0] = modem_tones[5]; break;
+ case 0x0a: m_freq[0] = modem_tones[6]; break;
+ case 0x0b: m_freq[0] = modem_tones[7]; break;
+ case 0x0c: m_freq[0] = modem_tones[8]; break;
+ case 0x0d: m_freq[0] = modem_tones[9]; break;
+ case 0x0e: m_freq[0] = modem_tones[10]; break;
+ case 0x0f: m_freq[0] = modem_tones[11]; break;
+ }
+ break;
+ case 0x30: // musical tones
+ m_freq[0] = music_tones[data & 0x0f];
+ break;
+ }
+
+ // restart waves from beginning
+ m_incr[0] = m_incr[1] = 0;
+ m_signal[0] = m_signal[1] = 1.0;
+}
+
+
+void pcd3311_device::a0_w(int state)
+{
+ state &= 1;
+ if (BIT(m_slave_address, 1) != state)
+ {
+ LOGMASKED(LOG_LINE, "set a0 %d\n", state );
+ m_slave_address = (m_slave_address & 0xfd) | (state << 1);
+ }
+}
+
+
+void pcd3311_device::strobe_w(int state)
+{
+ if (m_strobe && !state)
+ load_data(m_data);
+
+ m_strobe = state;
+}
+
+
+void pcd3311_device::scl_w(int state)
+{
+ if (m_scl != state)
+ {
+ m_scl = state;
+ LOGMASKED(LOG_LINE, "set_scl_line %d\n", m_scl);
+
+ switch (m_state)
+ {
+ case STATE_DEVSEL:
+ case STATE_DATAIN:
+ if (m_bits < 8)
+ {
+ if (m_scl)
+ {
+ m_shift = ((m_shift << 1) | m_sdaw) & 0xff;
+ m_bits++;
+ }
+ }
+ else
+ {
+ if (m_scl)
+ {
+ m_bits++;
+ }
+ else
+ {
+ if (m_bits == 8)
+ {
+ switch (m_state)
+ {
+ case STATE_DEVSEL:
+ m_devsel = m_shift;
+
+ if ((m_devsel & 0xfe) != m_slave_address)
+ {
+ LOGMASKED(LOG_DATA, "devsel %02x: not this device\n", m_devsel);
+ m_state = STATE_IDLE;
+ }
+ else if ((m_devsel & 1) == 0)
+ {
+ LOGMASKED(LOG_DATA, "devsel %02x: write\n", m_devsel);
+ m_state = STATE_DATAIN;
+ }
+ break;
+
+ case STATE_DATAIN:
+ load_data(m_shift);
+
+ LOGMASKED(LOG_DATA, "data %02x\n", m_shift);
+ break;
+ }
+
+ if( m_state != STATE_IDLE )
+ {
+ m_sdar = 0 ;
+ }
+ }
+ else
+ {
+ m_bits = 0;
+ m_sdar = 1;
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+void pcd3311_device::sda_w(int state)
+{
+ state &= 1;
+ if (m_sdaw != state)
+ {
+ LOGMASKED(LOG_LINE, "set sda %d\n", state);
+ m_sdaw = state;
+
+ if (m_scl)
+ {
+ if (m_sdaw)
+ {
+ LOGMASKED(LOG_DATA, "stop\n");
+ m_state = STATE_IDLE;
+ }
+ else
+ {
+ LOGMASKED(LOG_DATA, "start\n");
+ m_state = STATE_DEVSEL;
+ m_bits = 0;
+ }
+
+ m_sdar = 1;
+ }
+ }
+}
+
+int pcd3311_device::sda_r()
+{
+ int res = m_sdar & 1;
+
+ LOGMASKED(LOG_LINE, "read sda %d\n", res);
+
+ return res;
}
diff --git a/src/devices/sound/pcd3311.h b/src/devices/sound/pcd3311.h
index 840b9da69ad..f3e904b5116 100644
--- a/src/devices/sound/pcd3311.h
+++ b/src/devices/sound/pcd3311.h
@@ -23,38 +23,60 @@
#pragma once
-
-
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// ======================> pcd3311_device
-class pcd3311_device : public device_t,
- public device_sound_interface
+class pcd3311_device : public device_t, public device_sound_interface
{
public:
// construction/destruction
pcd3311_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
void write(uint8_t data) { m_data = data; }
- void strobe_w(int state) { m_strobe = state; }
+ void strobe_w(int state);
void mode_w(int state) { m_mode = state; }
- void a0_w(int state) { m_a0 = state; }
+ void a0_w(int state);
+
+ void scl_w(int state);
+ void sda_w(int state);
+ int sda_r();
+
+ // use in memory maps to avoid strobe logic.
+ void write_direct(uint8_t data) { load_data(data); }
protected:
- // device-level overrides
+ // device_t overrides
virtual void device_start() override ATTR_COLD;
- // internal callbacks
virtual void sound_stream_update(sound_stream &stream) override;
private:
- int m_a0;
+ static constexpr uint8_t PCD3311_SLAVE_ADDRESS = 0x48;
+
+ enum { STATE_IDLE, STATE_DEVSEL, STATE_DATAIN };
+
+ void load_data(uint8_t data);
+
int m_mode;
int m_strobe;
uint8_t m_data;
+
+ int m_slave_address;
+ int m_scl;
+ int m_sdaw;
+ int m_sdar;
+ int m_state;
+ int m_bits;
+ int m_shift;
+ int m_devsel;
+
+ sound_stream *m_stream; // stream number
+ uint32_t m_freq[2]; // current frequencies
+ int32_t m_incr[2]; // initial wave state
+ sound_stream::sample_t m_signal[2]; // current signals
};