summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/a1bus/a1cassette.cpp
blob: 13140382f48e845579f6ac94e30213f33d396ecb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*********************************************************************

    a1cassette.c

    Apple I Cassette Interface

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

#include "emu.h"
#include "a1cassette.h"

/***************************************************************************
    PARAMETERS
***************************************************************************/

//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

#define CASSETTE_ROM_REGION "casrom"

DEFINE_DEVICE_TYPE(A1BUS_CASSETTE, a1bus_cassette_device, "a1cass", "Apple I cassette board")

ROM_START( cassette )
	/* 256-byte cassette interface ROM, in two 82s129 or mmi6301 256x4 proms at locations 3 and 4 on the cassette interface daughtercard (they are labeled "MMI 6301-IJ // 7623L // APPLE-A3" and "MMI 6301-IJ // 7623L // APPLE-A4") */
	ROM_REGION(0x100, CASSETTE_ROM_REGION, 0)
	ROM_LOAD_NIB_HIGH( "apple-a3.3",    0x0000, 0x0100, CRC(6eae8f52) SHA1(71906932727ef70952ef6afe6b08708df15cd67d) )
	ROM_LOAD_NIB_LOW( "apple-a4.4",    0x0000, 0x0100, CRC(94efa977) SHA1(851f3bd6863859a1a6909179a5e5bf744b3d807e) )
ROM_END

//-------------------------------------------------
//  device_add_mconfig - add device configuration
//-------------------------------------------------

/* sound output */

void a1bus_cassette_device::device_add_mconfig(machine_config &config)
{
	CASSETTE(config, m_cassette);
	m_cassette->set_default_state(CASSETTE_STOPPED);
	m_cassette->set_interface("apple1_cass");
}

const tiny_rom_entry *a1bus_cassette_device::device_rom_region() const
{
	return ROM_NAME( cassette );
}

//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

a1bus_cassette_device::a1bus_cassette_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: a1bus_cassette_device(mconfig, A1BUS_CASSETTE, tag, owner, clock)
{
}

a1bus_cassette_device::a1bus_cassette_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, type, tag, owner, clock)
	, device_a1bus_card_interface(mconfig, *this)
	, m_cassette(*this, "cassette")
	, m_rom(*this, CASSETTE_ROM_REGION)
	, m_cassette_output_flipflop(0)
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void a1bus_cassette_device::device_start()
{
	install_device(0xc000, 0xc0ff, read8_delegate(FUNC(a1bus_cassette_device::cassette_r), this), write8_delegate(FUNC(a1bus_cassette_device::cassette_w), this));
	install_bank(0xc100, 0xc1ff, "bank_a1cas", &m_rom[0]);

	save_item(NAME(m_cassette_output_flipflop));
}

void a1bus_cassette_device::device_reset()
{
	m_cassette_output_flipflop = 0;
}

/*****************************************************************************
**  Cassette interface I/O
**
**  The Apple I's cassette interface was a small card that plugged
**  into the expansion connector on the motherboard.  (This was a
**  slot-type connector, separate from the motherboard's edge
**  connector, but with the same signals.)  The cassette interface
**  provided separate cassette input and output jacks, some very
**  simple interface hardware, and 256 bytes of ROM containing the
**  cassette I/O code.
**
**  The interface was mostly software-controlled.  The only hardware
**  was an output flip-flop for generating the cassette output signal,
**  a National Semiconductor LM311 voltage comparator for generating a
**  digital signal from the analog cassette input, an input
**  signal-level LED, and some gates to control the interface logic
**  and address decoding.  The cassette ROM code did most of the work
**  of generating and interpreting tape signals.  It also contained
**  its own mini-monitor for issuing tape read and write commands.
**
**  The cassette interface was assigned to the $C000-$CFFF block of
**  addresses, although it did not use most of the space in that
**  block.  Addresses were mapped as follows:
**
**      $C000-$C0FF:  Cassette I/O space.
**                    Any access here toggles the output signal.
**          $C000-$C07F:  Cassette output only; input disabled.
**                        Mirrors $C100-$C17F on reads.
**          $C080-$C0FF:  Cassette input and output.
**                        When input low, mirrors $C180-$C1FF on reads.
**                        When input high, both odd and even addresses
**                        mirror even ROM addresses $C180-$C1FE.
**      $C100-$C1FF:  Cassette ROM code.
**
**  Note the peculiar addressing scheme.  Data was written simply
**  through repeated accesses, rather than by writing to an address.
**  Data was read by reading an odd input address and comparing the
**  ROM byte returned to detect signal changes.
**
**  The standard tape signal was a simple square wave, although this
**  was often greatly distorted by the cassette recorder.  A single
**  tape record consisted of a 10-second 800-Hz leader, followed by a
**  single short square-wave cycle used as a sync bit, followed by the
**  tape data.  The data was encoded using a single square-wave cycle
**  for each bit; "1" bits were at 1000 Hz, "0" bits at 2000 Hz.  (All
**  of these frequencies are approximate and could vary due to
**  differences in recorder speed.)  Each byte was written starting
**  from the most significant bit; bytes were written from low to high
**  addresses.  No error detection was provided.  Multiple records
**  could be placed on a single tape.
*****************************************************************************/

/* The cassette output signal for writing tapes is generated by a
   flip-flop which is toggled to produce the output waveform.  Any
   access to the cassette I/O range, whether a read or a write,
   toggles this flip-flop. */
void a1bus_cassette_device::cassette_toggle_output()
{
	m_cassette_output_flipflop = !m_cassette_output_flipflop;
	m_cassette->output(m_cassette_output_flipflop ? 1.0 : -1.0);
}

READ8_MEMBER(a1bus_cassette_device::cassette_r)
{
	cassette_toggle_output();

	if (offset <= 0x7f)
	{
		/* If the access is to address range $C000-$C07F, the cassette
		   input signal is ignored .  In this case the value read
		   always comes from the corresponding cassette ROM location
		   in $C100-$C17F. */

		return m_rom[offset];
	}
	else
	{
		/* For accesses to address range $C080-$C0FF, the cassette
		   input signal is enabled.  If the signal is low, the value
		   read comes from the corresponding cassette ROM location in
		   $C180-$C1FF.  If the signal is high, the low bit of the
		   address is masked before the corresponding cassette ROM
		   location is accessed; e.g., a read from $C081 would return
		   the ROM byte at $C180.  The cassette ROM routines detect
		   changes in the cassette input signal by repeatedly reading
		   from $C081 and comparing the values read. */

		/* (Don't try putting a non-zero "noise threshhold" here,
		   because it can cause tape header bits on real cassette
		   images to be misread as data bits.) */
		if (m_cassette->input() > 0.0)
			return m_rom[(offset & ~1)];
		else
			return m_rom[offset];
	}
}

WRITE8_MEMBER(a1bus_cassette_device::cassette_w)
{
	/* Writes toggle the output flip-flop in the same way that reads
	   do; other than that they have no effect.  Any repeated accesses
	   to the cassette I/O address range can be used to write data to
	   cassette, and the cassette ROM always uses reads to do this.
	   However, we still have to handle writes, since they may be done
	   by user code. */

	cassette_toggle_output();
}