1 // license:BSD-3-Clause
2 // copyright-holders:R. Belmont
3 /*
4     Ensoniq Vacuum Fluorescent Displays (VFDs)
5     Emulation by R. Belmont
6 */
7 
8 #include "emu.h"
9 #include "esqvfd.h"
10 
11 #include "esq1by22.lh"
12 #include "esq2by40.lh"
13 
14 DEFINE_DEVICE_TYPE(ESQ1X22,     esq1x22_device,     "esq1x22",     "Ensoniq 1x22 VFD")
15 DEFINE_DEVICE_TYPE(ESQ2X40,     esq2x40_device,     "esq2x40",     "Ensoniq 2x40 VFD")
16 DEFINE_DEVICE_TYPE(ESQ2X40_SQ1, esq2x40_sq1_device, "esq2x40_sq1", "Ensoniq 2x40 VFD (SQ-1 variant)")
17 
18 // adapted from bfm_bd1, rearranged to work with ASCII data used by the Ensoniq h/w
19 static const uint16_t font[]=
20 {           // FEDC BA98 7654 3210
21 	0x0000, // 0000 0000 0000 0000 (space)
22 	0x0000, // 0000 0000 0000 0000 ! (not defined)
23 	0x0009, // 0000 0000 0000 1001 ".
24 	0xC62A, // 1100 0110 0010 1010 #.
25 	0xC62D, // 1100 0110 0010 1101 $.
26 	0x0000, // 0000 0000 0000 0000 % (not defined)
27 	0x0000, // 0000 0000 0000 0000 & (not defined)
28 	0x0040, // 0000 0000 1000 0000 '.
29 	0x0880, // 0000 1000 1000 0000 (.
30 	0x0050, // 0000 0000 0101 0000 ).
31 	0xCCD8, // 1100 1100 1101 1000 *.
32 	0xC408, // 1100 0100 0000 1000 +.
33 	0x0000, // 0000 0000 0000 0000 , (not defined)
34 	0xC000, // 1100 0000 0000 0000 -.
35 	0x1000, // 0001 0000 0000 0000 .
36 	0x0090, // 0000 0000 1001 0000 /
37 	0x22B7, // 0010 0010 1011 0111 0.
38 	0x0408, // 0000 0100 0000 1000 1.
39 	0xE206, // 1110 0010 0000 0110 2.
40 	0x4226, // 0100 0010 0010 0110 3.
41 	0xC023, // 1100 0000 0010 0011 4.
42 	0xC225, // 1100 0010 0010 0101 5.
43 	0xE225, // 1110 0010 0010 0101 6.
44 	0x0026, // 0000 0000 0010 0110 7.
45 	0xE227, // 1110 0010 0010 0111 8.
46 	0xC227, // 1100 0010 0010 0111 9.
47 	0x0000, // 0000 0000 0000 0000 : (not defined)
48 	0x0000, // 0000 0000 0000 0000 ; (not defined)
49 	0x0290, // 0000 0010 1001 0000 <.
50 	0xC200, // 1100 0010 0000 0000 =.
51 	0x0A40, // 0000 1010 0100 0000 >.
52 	0x0000, // 0000 0000 0000 0000 ? (not defined)
53 	0xA626, // 1010 0110 0010 0110 @.
54 	0xE027, // 1110 0000 0010 0111 A.
55 	0x462E, // 0100 0110 0010 1110 B.
56 	0x2205, // 0010 0010 0000 0101 C.
57 	0x062E, // 0000 0110 0010 1110 D.
58 	0xA205, // 1010 0010 0000 0101 E.
59 	0xA005, // 1010 0000 0000 0101 F.
60 	0x6225, // 0110 0010 0010 0101 G.
61 	0xE023, // 1110 0000 0010 0011 H.
62 	0x060C, // 0000 0110 0000 1100 I.
63 	0x2222, // 0010 0010 0010 0010 J.
64 	0xA881, // 1010 1000 1000 0001 K.
65 	0x2201, // 0010 0010 0000 0001 L.
66 	0x20E3, // 0010 0000 1110 0011 M.
67 	0x2863, // 0010 1000 0110 0011 N.
68 	0x2227, // 0010 0010 0010 0111 O.
69 	0xE007, // 1110 0000 0000 0111 P.
70 	0x2A27, // 0010 1010 0010 0111 Q.
71 	0xE807, // 1110 1000 0000 0111 R.
72 	0xC225, // 1100 0010 0010 0101 S.
73 	0x040C, // 0000 0100 0000 1100 T.
74 	0x2223, // 0010 0010 0010 0011 U.
75 	0x2091, // 0010 0000 1001 0001 V.
76 	0x2833, // 0010 1000 0011 0011 W.
77 	0x08D0, // 0000 1000 1101 0000 X.
78 	0x04C0, // 0000 0100 1100 0000 Y.
79 	0x0294, // 0000 0010 1001 0100 Z.
80 	0x2205, // 0010 0010 0000 0101 [.
81 	0x0840, // 0000 1000 0100 0000 \.
82 	0x0226, // 0000 0010 0010 0110 ].
83 	0x0810, // 0000 1000 0001 0000 ^.
84 	0x0200, // 0000 0010 0000 0000 _
85 	0x0040, // 0000 0000 0100 0000 `
86 	0xE027, // 1110 0000 0010 0111 A.
87 	0x462E, // 0100 0110 0010 1110 B.
88 	0x2205, // 0010 0010 0000 0101 C.
89 	0x062E, // 0000 0110 0010 1110 D.
90 	0xA205, // 1010 0010 0000 0101 E.
91 	0xA005, // 1010 0000 0000 0101 F.
92 	0x6225, // 0110 0010 0010 0101 G.
93 	0xE023, // 1110 0000 0010 0011 H.
94 	0x060C, // 0000 0110 0000 1100 I.
95 	0x2222, // 0010 0010 0010 0010 J.
96 	0xA881, // 1010 1000 1000 0001 K.
97 	0x2201, // 0010 0010 0000 0001 L.
98 	0x20E3, // 0010 0000 1110 0011 M.
99 	0x2863, // 0010 1000 0110 0011 N.
100 	0x2227, // 0010 0010 0010 0111 O.
101 	0xE007, // 1110 0000 0000 0111 P.
102 	0x2A27, // 0010 1010 0010 0111 Q.
103 	0xE807, // 1110 1000 0000 0111 R.
104 	0xC225, // 1100 0010 0010 0101 S.
105 	0x040C, // 0000 0100 0000 1100 T.
106 	0x2223, // 0010 0010 0010 0011 U.
107 	0x2091, // 0010 0000 1001 0001 V.
108 	0x2833, // 0010 1000 0011 0011 W.
109 	0x08D0, // 0000 1000 1101 0000 X.
110 	0x04C0, // 0000 0100 1100 0000 Y.
111 	0x0294, // 0000 0010 1001 0100 Z.
112 	0x2205, // 0010 0010 0000 0101 [.
113 	0x0408, // 0000 0100 0000 1000 |
114 	0x0226, // 0000 0010 0010 0110 ].
115 	0x0810, // 0000 1000 0001 0000 ~.
116 	0x0000, // 0000 0000 0000 0000 (DEL)
117 };
118 
esqvfd_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock,dimensions_param && dimensions)119 esqvfd_device::esqvfd_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, dimensions_param &&dimensions) :
120 	device_t(mconfig, type, tag, owner, clock),
121 	m_vfds(std::move(std::get<0>(dimensions))),
122 	m_rows(std::get<1>(dimensions)),
123 	m_cols(std::get<2>(dimensions))
124 {
125 }
126 
device_start()127 void esqvfd_device::device_start()
128 {
129 	m_vfds->resolve();
130 }
131 
device_reset()132 void esqvfd_device::device_reset()
133 {
134 	m_cursx = m_cursy = 0;
135 	m_savedx = m_savedy = 0;
136 	m_curattr = AT_NORMAL;
137 	m_lastchar = 0;
138 	memset(m_chars, 0, sizeof(m_chars));
139 	memset(m_attrs, 0, sizeof(m_attrs));
140 	memset(m_dirty, 1, sizeof(m_attrs));
141 }
142 
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)143 void esqvfd_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
144 {
145 }
146 
147 // generic display update; can override from child classes if not good enough
update_display()148 void esqvfd_device::update_display()
149 {
150 	for (int row = 0; row < m_rows; row++)
151 	{
152 		for (int col = 0; col < m_cols; col++)
153 		{
154 			if (m_dirty[row][col])
155 			{
156 				uint32_t segdata = conv_segments(font[m_chars[row][col]]);
157 
158 				// force bottom bar on all underlined chars
159 				if (m_attrs[row][col] & AT_UNDERLINE)
160 					segdata |= 0x0008;
161 
162 				m_vfds->set((row * m_cols) + col, segdata);
163 
164 				m_dirty[row][col] = 0;
165 			}
166 		}
167 	}
168 }
169 
170 /* 2x40 VFD display used in the ESQ-1, VFX-SD, SD-1, and others */
171 
device_add_mconfig(machine_config & config)172 void esq2x40_device::device_add_mconfig(machine_config &config)
173 {
174 	config.set_default_layout(layout_esq2by40);
175 }
176 
write_char(int data)177 void esq2x40_device::write_char(int data)
178 {
179 	// ESQ-1 sends (cursor move) 0xfa 0xYY to mark YY characters as underlined at the current cursor location
180 	if (m_lastchar == 0xfa)
181 	{
182 		if ((m_cursx + data) > m_rows)
183 			data = m_rows - m_cursx;
184 		for (int i = 0; i < data; i++)
185 		{
186 			m_attrs[m_cursy][m_cursx + i] |= AT_UNDERLINE;
187 			m_dirty[m_cursy][m_cursx + i] = 1;
188 		}
189 
190 		m_lastchar = 0;
191 		update_display();
192 		return;
193 	}
194 
195 	m_lastchar = data;
196 
197 	if ((data >= 0x80) && (data < 0xd0))
198 	{
199 		m_cursy = ((data & 0x7f) >= 40) ? 1 : 0;
200 		m_cursx = (data & 0x7f) % 40;
201 	}
202 	else if (data >= 0xd0)
203 	{
204 		switch (data)
205 		{
206 			case 0xd0:  // blink start
207 				m_curattr |= AT_BLINK;
208 				break;
209 
210 			case 0xd1:  // blink stop (cancel all attribs on VFX+)
211 				m_curattr = 0; //&= ~AT_BLINK;
212 				break;
213 
214 			case 0xd2:  // blinking underline on VFX
215 				m_curattr |= AT_BLINK | AT_UNDERLINE;
216 				break;
217 
218 			case 0xd3:  // start underline
219 				m_curattr |= AT_UNDERLINE;
220 				break;
221 
222 			case 0xd6:  // clear screen
223 				m_cursx = m_cursy = 0;
224 				memset(m_chars, 0, sizeof(m_chars));
225 				memset(m_attrs, 0, sizeof(m_attrs));
226 				memset(m_dirty, 1, sizeof(m_dirty));
227 				break;
228 
229 			case 0xf5:  // save cursor position
230 				m_savedx = m_cursx;
231 				m_savedy = m_cursy;
232 				break;
233 
234 			case 0xf6:  // restore cursor position
235 				m_cursx = m_savedx;
236 				m_cursy = m_savedy;
237 				m_curattr = m_attrs[m_cursy][m_cursx];
238 				break;
239 
240 			default:
241 //                printf("Unknown control code %02x\n", data);
242 				break;
243 		}
244 	}
245 	else
246 	{
247 		if ((data >= 0x20) && (data <= 0x5f))
248 		{
249 			m_chars[m_cursy][m_cursx] = data - ' ';
250 			m_attrs[m_cursy][m_cursx] = m_curattr;
251 			m_dirty[m_cursy][m_cursx] = 1;
252 			m_cursx++;
253 
254 			if (m_cursx >= 39)
255 			{
256 				m_cursx = 39;
257 			}
258 		}
259 	}
260 
261 	update_display();
262 }
263 
write_contents(std::ostream & o)264 bool esq2x40_device::write_contents(std::ostream &o)
265 {
266 	o.put((char) 0xd6); // clear screen
267 
268 	uint8_t attrs = 0;
269 	for (int row = 0; row < 2; row++)
270 	{
271 		o.put((char) (0x80 + (40 * row))); // move to first column this row
272 
273 		for (int col = 0; col < 40; col++)
274 		{
275 			if (m_attrs[row][col] != attrs)
276 			{
277 				attrs = m_attrs[row][col];
278 
279 				o.put((char) 0xd1); // all attributes off
280 
281 				if (attrs & AT_BLINK)
282 				{
283 					o.put((char) 0xd0); // blink on
284 				}
285 
286 				if (attrs & AT_UNDERLINE)
287 				{
288 					o.put((char) 0xd3); // underline
289 				}
290 			}
291 
292 			o.put((char) (m_chars[row][col] + ' '));
293 		}
294 	}
295 	return true;
296 }
297 
298 
esq2x40_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)299 esq2x40_device::esq2x40_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
300 	esqvfd_device(mconfig, ESQ2X40, tag, owner, clock, make_dimensions<2, 40>(*this))
301 {
302 }
303 
304 /* 1x22 display from the VFX (not right, but it'll do for now) */
305 
device_add_mconfig(machine_config & config)306 void esq1x22_device::device_add_mconfig(machine_config &config)
307 {
308 	config.set_default_layout(layout_esq1by22);
309 }
310 
311 
write_char(int data)312 void esq1x22_device::write_char(int data)
313 {
314 	if (data >= 0x60)
315 	{
316 		switch (data)
317 		{
318 			case 'f':   // clear screen
319 				m_cursx = m_cursy = 0;
320 				memset(m_chars, 0, sizeof(m_chars));
321 				memset(m_attrs, 0, sizeof(m_attrs));
322 				memset(m_dirty, 1, sizeof(m_dirty));
323 				break;
324 
325 			default:
326 				printf("Unhandled control code %02x\n", data);
327 				break;
328 		}
329 	}
330 	else
331 	{
332 		if ((data >= 0x20) && (data <= 0x5f))
333 		{
334 			m_chars[0][m_cursx] = data - ' ';
335 			m_dirty[0][m_cursx] = 1;
336 			m_cursx++;
337 
338 			if (m_cursx >= 23)
339 			{
340 				m_cursx = 23;
341 			}
342 		}
343 	}
344 
345 	update_display();
346 }
347 
esq1x22_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)348 esq1x22_device::esq1x22_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
349 	esqvfd_device(mconfig, ESQ1X22, tag, owner, clock, make_dimensions<1, 22>(*this))
350 {
351 }
352 
353 /* SQ-1 display, I think it's really an LCD but we'll deal with it for now */
device_add_mconfig(machine_config & config)354 void esq2x40_sq1_device::device_add_mconfig(machine_config &config)
355 {
356 	config.set_default_layout(layout_esq2by40);  // we use the normal 2x40 layout
357 }
358 
write_char(int data)359 void esq2x40_sq1_device::write_char(int data)
360 {
361 	if (data == 0x09)   // musical note
362 	{
363 		data = '^'; // approximate for now
364 	}
365 
366 	if (m_wait87shift)
367 	{
368 		m_cursy = (data >> 4) & 0xf;
369 		m_cursx = data & 0xf;
370 		m_wait87shift = false;
371 	}
372 	else if (m_wait88shift)
373 	{
374 		m_wait88shift = false;
375 	}
376 	else if ((data >= 0x20) && (data <= 0x7f))
377 	{
378 		m_chars[m_cursy][m_cursx] = data - ' ';
379 		m_attrs[m_cursy][m_cursx] = m_curattr;
380 		m_dirty[m_cursy][m_cursx] = 1;
381 		m_cursx++;
382 
383 		if (m_cursx >= 39)
384 		{
385 			m_cursx = 39;
386 		}
387 
388 		update_display();
389 	}
390 	else if (data == 0x83)
391 	{
392 		m_cursx = m_cursy = 0;
393 		memset(m_chars, 0, sizeof(m_chars));
394 		memset(m_attrs, 0, sizeof(m_attrs));
395 		memset(m_dirty, 1, sizeof(m_dirty));
396 	}
397 	else if (data == 0x87)
398 	{
399 		m_wait87shift = true;
400 	}
401 	else if (data == 0x88)
402 	{
403 		m_wait88shift = true;
404 	}
405 	else
406 	{
407 //        printf("SQ-1 unhandled display char %02x\n", data);
408 	}
409 }
410 
esq2x40_sq1_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)411 esq2x40_sq1_device::esq2x40_sq1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
412 	esqvfd_device(mconfig, ESQ2X40_SQ1, tag, owner, clock, make_dimensions<2, 40>(*this))
413 {
414 	m_wait87shift = false;
415 	m_wait88shift = false;
416 }
417