1 // license:BSD-3-Clause
2 // copyright-holders:Sandro Ronco
3 /***************************************************************************
4
5 Hitachi HD44780 LCD controller
6
7 TODO:
8 - dump internal CGROM
9 - emulate osc pin, determine video timings and busy flag duration from it,
10 and if possible, remove m_busy_factor
11
12 ***************************************************************************/
13
14 #include "emu.h"
15 #include "video/hd44780.h"
16
17 //#define VERBOSE 1
18 #include "logmacro.h"
19
20
21 //**************************************************************************
22 // DEVICE DEFINITIONS
23 //**************************************************************************
24
25 DEFINE_DEVICE_TYPE(HD44780, hd44780_device, "hd44780_a00", "Hitachi HD44780 A00 LCD Controller")
26 DEFINE_DEVICE_TYPE(SED1278_0B, sed1278_0b_device, "sed1278_0b", "Epson SED1278-0B LCD Controller") // packaged as either SED1278F0B or SED1278D0B
27 DEFINE_DEVICE_TYPE(KS0066_F05, ks0066_f05_device, "ks0066_f05", "Samsung KS0066 F05 LCD Controller")
28
29
30 //-------------------------------------------------
31 // ROM( hd44780 )
32 //-------------------------------------------------
33
ROM_START(hd44780_a00)34 ROM_START( hd44780_a00 )
35 ROM_REGION( 0x1000, "cgrom", 0 )
36 ROM_LOAD( "hd44780_a00.bin", 0x0000, 0x1000, BAD_DUMP CRC(01d108e2) SHA1(bc0cdf0c9ba895f22e183c7bd35a3f655f2ca96f)) // from page 17 of the HD44780 datasheet
37 ROM_END
38
39 ROM_START( sed1278_0b )
40 ROM_REGION( 0x1000, "cgrom", 0 )
41 ROM_LOAD( "sed1278_0b.bin", 0x0000, 0x1000, BAD_DUMP CRC(eef342fa) SHA1(d6ac58a48e428e7cff26fb9c8ea9b4eeaa853038)) // from page 9-33 of the SED1278 datasheet
42 ROM_END
43
44 ROM_START( ks0066_f05 )
45 ROM_REGION( 0x1000, "cgrom", 0 )
46 ROM_LOAD( "ks0066_f05.bin", 0x0000, 0x1000, BAD_DUMP CRC(af9e7bd6) SHA1(0196e871584ee5d370856e7307c0f9d1466e3e51)) // from page 51 of the KS0066 datasheet
47 ROM_END
48
49 //**************************************************************************
50 // live device
51 //**************************************************************************
52
53 //-------------------------------------------------
54 // hd44780_device - constructor
55 //-------------------------------------------------
56
57 hd44780_device::hd44780_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
58 : hd44780_device(mconfig, HD44780, tag, owner, clock)
59 {
60 set_charset_type(CHARSET_HD44780_A00);
61 }
62
hd44780_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)63 hd44780_device::hd44780_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
64 : device_t(mconfig, type, tag, owner, clock)
65 , m_pixel_update_cb(*this)
66 , m_busy_factor(1.0)
67 , m_cgrom(nullptr)
68 , m_cgrom_region(*this, DEVICE_SELF)
69 , m_rs_input(0)
70 , m_rw_input(0)
71 , m_db_input(0)
72 , m_enabled(false)
73 {
74 }
75
sed1278_0b_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)76 sed1278_0b_device::sed1278_0b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
77 hd44780_device(mconfig, SED1278_0B, tag, owner, clock)
78 {
79 set_charset_type(CHARSET_SED1278_0B);
80 }
81
ks0066_f05_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)82 ks0066_f05_device::ks0066_f05_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
83 hd44780_device(mconfig, KS0066_F05, tag, owner, clock)
84 {
85 set_charset_type(CHARSET_KS0066_F05);
86 }
87
88
89 //-------------------------------------------------
90 // rom_region - device-specific ROM region
91 //-------------------------------------------------
92
device_rom_region() const93 const tiny_rom_entry *hd44780_device::device_rom_region() const
94 {
95 switch (m_charset_type)
96 {
97 case CHARSET_HD44780_A00: return ROM_NAME( hd44780_a00 );
98 case CHARSET_SED1278_0B: return ROM_NAME( sed1278_0b );
99 case CHARSET_KS0066_F05: return ROM_NAME( ks0066_f05 );
100 }
101
102 return nullptr;
103 }
104
105 //-------------------------------------------------
106 // device_start - device-specific startup
107 //-------------------------------------------------
108
device_start()109 void hd44780_device::device_start()
110 {
111 m_cgrom = m_cgrom_region.found() ? m_cgrom_region : memregion("cgrom")->base();
112
113 m_pixel_update_cb.resolve();
114
115 m_busy_timer = timer_alloc(TIMER_BUSY);
116 m_blink_timer = timer_alloc(TIMER_BLINKING);
117 m_blink_timer->adjust(attotime::from_msec(409), 0, attotime::from_msec(409));
118
119 // state saving
120 save_item(NAME(m_busy_factor));
121 save_item(NAME(m_busy_flag));
122 save_item(NAME(m_ac));
123 save_item(NAME(m_dr));
124 save_item(NAME(m_ir));
125 save_item(NAME(m_active_ram));
126 save_item(NAME(m_display_on));
127 save_item(NAME(m_cursor_on));
128 save_item(NAME(m_shift_on));
129 save_item(NAME(m_blink_on));
130 save_item(NAME(m_direction));
131 save_item(NAME(m_data_len));
132 save_item(NAME(m_num_line));
133 save_item(NAME(m_char_size));
134 save_item(NAME(m_disp_shift));
135 save_item(NAME(m_blink));
136 save_item(NAME(m_ddram));
137 save_item(NAME(m_cgram));
138 save_item(NAME(m_nibble));
139 save_item(NAME(m_rs_input));
140 save_item(NAME(m_rw_input));
141 save_item(NAME(m_db_input));
142 save_item(NAME(m_enabled));
143 save_item(NAME(m_rs_state));
144 save_item(NAME(m_rw_state));
145 }
146
147 //-------------------------------------------------
148 // device_reset - device-specific reset
149 //-------------------------------------------------
150
device_reset()151 void hd44780_device::device_reset()
152 {
153 memset(m_ddram, 0x20, sizeof(m_ddram)); // filled with SPACE char
154 memset(m_cgram, 0, sizeof(m_cgram));
155
156 m_ac = 0;
157 m_dr = 0;
158 m_ir = 0;
159 m_active_ram = DDRAM;
160 m_display_on = false;
161 m_cursor_on = false;
162 m_blink_on = false;
163 m_shift_on = false;
164 m_direction = 1;
165 m_data_len = 8;
166 m_num_line = 1;
167 m_char_size = 8;
168 m_disp_shift = 0;
169 m_blink = false;
170 m_nibble = false;
171 m_first_cmd = true;
172 m_rs_state = 0;
173 m_rw_state = 0;
174
175 set_busy_flag(1520);
176 }
177
178
179 //-------------------------------------------------
180 // device_timer - handler timer events
181 //-------------------------------------------------
182
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)183 void hd44780_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
184 {
185 switch (id)
186 {
187 case TIMER_BUSY:
188 m_busy_flag = false;
189 break;
190
191 case TIMER_BLINKING:
192 m_blink = !m_blink;
193 break;
194 }
195 }
196
197
198 //**************************************************************************
199 // HELPERS
200 //**************************************************************************
201
set_charset_type(int type)202 void hd44780_device::set_charset_type(int type)
203 {
204 m_charset_type = type;
205 }
206
set_busy_flag(uint16_t usec)207 void hd44780_device::set_busy_flag(uint16_t usec)
208 {
209 m_busy_flag = true;
210
211 usec = float(usec) * m_busy_factor + 0.5;
212 m_busy_timer->adjust(attotime::from_usec(usec));
213 }
214
correct_ac()215 void hd44780_device::correct_ac()
216 {
217 if (m_active_ram == DDRAM)
218 {
219 int max_ac = (m_num_line == 1) ? 0x4f : 0x67;
220
221 if (m_ac > max_ac)
222 m_ac -= max_ac + 1;
223 else if (m_ac < 0)
224 m_ac = max_ac;
225 else if (m_num_line == 2 && m_ac > 0x27 && m_ac < 0x40)
226 m_ac = 0x40 + (m_ac - 0x28);
227 }
228 else
229 m_ac &= 0x3f;
230 }
231
update_ac(int direction)232 void hd44780_device::update_ac(int direction)
233 {
234 if (m_active_ram == DDRAM && m_num_line == 2 && direction == -1 && m_ac == 0x40)
235 m_ac = 0x27;
236 else
237 m_ac += direction;
238
239 correct_ac();
240 }
241
shift_display(int direction)242 void hd44780_device::shift_display(int direction)
243 {
244 m_disp_shift += direction;
245
246 if (m_disp_shift == 0x50)
247 m_disp_shift = 0;
248 else if (m_disp_shift == -1)
249 m_disp_shift = 0x4f;
250 }
251
update_nibble(int rs,int rw)252 void hd44780_device::update_nibble(int rs, int rw)
253 {
254 if (m_rs_state != rs || m_rw_state != rw)
255 {
256 m_rs_state = rs;
257 m_rw_state = rw;
258 m_nibble = false;
259 }
260
261 m_nibble = !m_nibble;
262 }
263
pixel_update(bitmap_ind16 & bitmap,u8 line,u8 pos,u8 y,u8 x,int state)264 inline void hd44780_device::pixel_update(bitmap_ind16 &bitmap, u8 line, u8 pos, u8 y, u8 x, int state)
265 {
266 if (!m_pixel_update_cb.isnull())
267 {
268 m_pixel_update_cb(bitmap, line, pos, y, x, state);
269 }
270 else
271 {
272 u8 line_height = (m_char_size == 8) ? m_char_size : m_char_size + 1;
273
274 if (m_lines <= 2)
275 {
276 if (pos < m_chars)
277 bitmap.pix(line * (line_height + 1) + y, pos * 6 + x) = state;
278 }
279 else if (m_lines <= 4)
280 {
281 if (pos < m_chars*2)
282 {
283 if (pos >= m_chars)
284 {
285 line += 2;
286 pos -= m_chars;
287 }
288
289 if (line < m_lines)
290 bitmap.pix(line * (line_height + 1) + y, pos * 6 + x) = state;
291 }
292 }
293 else
294 {
295 fatalerror("%s: use a custom callback for this LCD configuration (%d x %d)\n", tag(), m_lines, m_chars);
296 }
297 }
298 }
299
300
301 //**************************************************************************
302 // device interface
303 //**************************************************************************
304
render()305 const u8 *hd44780_device::render()
306 {
307 memset(m_render_buf, 0, sizeof(m_render_buf));
308
309 if (m_display_on)
310 {
311 u8 line_size = 80 / m_num_line;
312
313 for (int line = 0; line < m_num_line; line++)
314 {
315 for (int pos = 0; pos < line_size; pos++)
316 {
317 uint16_t char_pos = line * 0x40 + ((pos + m_disp_shift) % line_size);
318
319 int char_base;
320 if (m_ddram[char_pos] < 0x10)
321 {
322 // draw CGRAM characters
323 if (m_char_size == 8)
324 char_base = (m_ddram[char_pos] & 0x07) * 8;
325 else
326 char_base = ((m_ddram[char_pos] >> 1) & 0x03) * 16;
327 }
328 else
329 {
330 // draw CGROM characters
331 char_base = m_ddram[char_pos] * 0x10;
332 }
333
334 const u8 *charset = (m_ddram[char_pos] < 0x10) ? m_cgram : m_cgrom;
335 u8 *dest = m_render_buf + 16 * (line * line_size + pos);
336 memcpy (dest, charset + char_base, m_char_size);
337
338 if (char_pos == m_ac)
339 {
340 // draw the cursor
341 if (m_cursor_on)
342 dest[m_char_size - 1] = 0x1f;
343
344 if (!m_blink && m_blink_on)
345 memset(dest, 0x1f, m_char_size);
346 }
347 }
348 }
349 }
350
351 return m_render_buf;
352 }
353
screen_update(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)354 uint32_t hd44780_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
355 {
356 bitmap.fill(0, cliprect);
357 const u8 *img = render();
358
359 u8 line_size = 80 / m_num_line;
360
361 for (int line = 0; line < m_num_line; line++)
362 {
363 for (int pos = 0; pos < line_size; pos++)
364 {
365 const u8 *src = img + 16 * (line * line_size + pos);
366 for (int y = 0; y < m_char_size; y++)
367 for (int x = 0; x < 5; x++)
368 pixel_update(bitmap, line, pos, y, x, BIT(src[y], 4 - x));
369 }
370 }
371
372 return 0;
373 }
374
read(offs_t offset)375 u8 hd44780_device::read(offs_t offset)
376 {
377 if (m_data_len == 4 && !machine().side_effects_disabled())
378 update_nibble(offset & 0x01, 1);
379
380 switch (offset & 0x01)
381 {
382 case 0: return control_read();
383 case 1: return data_read();
384 }
385
386 return 0;
387 }
388
write(offs_t offset,u8 data)389 void hd44780_device::write(offs_t offset, u8 data)
390 {
391 if (m_data_len == 4 && !machine().side_effects_disabled())
392 update_nibble(offset & 0x01, 0);
393
394 switch (offset & 0x01)
395 {
396 case 0: control_write(data); break;
397 case 1: data_write(data); break;
398 }
399 }
400
db_r()401 u8 hd44780_device::db_r()
402 {
403 if (m_enabled && m_rw_input == 1)
404 {
405 switch (m_rs_input)
406 {
407 case 0: return control_read();
408 case 1: return data_read();
409 }
410 }
411
412 return 0xff;
413 }
414
db_w(u8 data)415 void hd44780_device::db_w(u8 data)
416 {
417 m_db_input = data;
418 }
419
WRITE_LINE_MEMBER(hd44780_device::rs_w)420 WRITE_LINE_MEMBER(hd44780_device::rs_w)
421 {
422 m_rs_input = state;
423 }
424
WRITE_LINE_MEMBER(hd44780_device::rw_w)425 WRITE_LINE_MEMBER(hd44780_device::rw_w)
426 {
427 m_rw_input = state;
428 }
429
WRITE_LINE_MEMBER(hd44780_device::e_w)430 WRITE_LINE_MEMBER(hd44780_device::e_w)
431 {
432 if (m_data_len == 4 && state && !m_enabled && !machine().side_effects_disabled())
433 update_nibble(m_rs_input, m_rw_input);
434
435 if (!state && m_enabled && m_rw_input == 0)
436 {
437 switch (m_rs_input)
438 {
439 case 0: control_write(m_db_input); break;
440 case 1: data_write(m_db_input); break;
441 }
442 }
443
444 m_enabled = state;
445 }
446
control_write(u8 data)447 void hd44780_device::control_write(u8 data)
448 {
449 if (m_data_len == 4)
450 {
451 if (m_nibble)
452 {
453 m_ir = data & 0xf0;
454 return;
455 }
456 else
457 {
458 m_ir |= ((data >> 4) & 0x0f);
459 }
460 }
461 else
462 {
463 m_ir = data;
464 }
465
466 if (BIT(m_ir, 7))
467 {
468 // set DDRAM address
469 m_active_ram = DDRAM;
470 m_ac = m_ir & 0x7f;
471 correct_ac();
472 set_busy_flag(37);
473
474 LOG("HD44780: set DDRAM address %x\n", m_ac);
475 return;
476 }
477 else if (BIT(m_ir, 6))
478 {
479 // set CGRAM address
480 m_active_ram = CGRAM;
481 m_ac = m_ir & 0x3f;
482 set_busy_flag(37);
483
484 LOG("HD44780: set CGRAM address %x\n", m_ac);
485 return;
486 }
487 else if (BIT(m_ir, 5))
488 {
489 // function set
490 if (!m_first_cmd && m_data_len == (BIT(m_ir, 4) ? 8 : 4) && (m_char_size != (BIT(m_ir, 2) ? 10 : 8) || m_num_line != (BIT(m_ir, 3) + 1)))
491 {
492 logerror("HD44780: function set cannot be executed after other instructions unless the interface data length is changed\n");
493 return;
494 }
495 m_first_cmd = true;
496
497 m_char_size = BIT(m_ir, 2) ? 10 : 8;
498 m_data_len = BIT(m_ir, 4) ? 8 : 4;
499 m_num_line = BIT(m_ir, 3) + 1;
500 correct_ac();
501 set_busy_flag(37);
502
503 LOG("HD44780: char size 5x%d, data len %d, lines %d\n", m_char_size, m_data_len, m_num_line);
504 return;
505 }
506 else if (BIT(m_ir, 4))
507 {
508 // cursor or display shift
509 int direction = (BIT(m_ir, 2)) ? +1 : -1;
510
511 LOG("HD44780: %s shift %d\n", BIT(m_ir, 3) ? "display" : "cursor", direction);
512
513 if (BIT(m_ir, 3))
514 shift_display(direction);
515 else
516 update_ac(direction);
517
518 set_busy_flag(37);
519 }
520 else if (BIT(m_ir, 3))
521 {
522 // display on/off control
523 m_display_on = BIT(m_ir, 2);
524 m_cursor_on = BIT(m_ir, 1);
525 m_blink_on = BIT(m_ir, 0);
526 set_busy_flag(37);
527
528 LOG("HD44780: display %d, cursor %d, blink %d\n", m_display_on, m_cursor_on, m_blink_on);
529 }
530 else if (BIT(m_ir, 2))
531 {
532 // entry mode set
533 m_direction = (BIT(m_ir, 1)) ? +1 : -1;
534 m_shift_on = BIT(m_ir, 0);
535 set_busy_flag(37);
536
537 LOG("HD44780: entry mode set: direction %d, shift %d\n", m_direction, m_shift_on);
538 }
539 else if (BIT(m_ir, 1))
540 {
541 // return home
542 LOG("HD44780: return home\n");
543
544 m_ac = 0;
545 m_active_ram = DDRAM;
546 m_direction = 1;
547 m_disp_shift = 0;
548 set_busy_flag(1520);
549 }
550 else if (BIT(m_ir, 0))
551 {
552 // clear display
553 LOG("HD44780: clear display\n");
554
555 m_ac = 0;
556 m_active_ram = DDRAM;
557 m_direction = 1;
558 m_disp_shift = 0;
559 memset(m_ddram, 0x20, sizeof(m_ddram));
560 set_busy_flag(1520);
561
562 // Some machines do a "clear display" first, even though the datasheet insists "function set" must come before all else
563 return;
564 }
565
566 m_first_cmd = false;
567 }
568
control_read()569 u8 hd44780_device::control_read()
570 {
571 if (m_data_len == 4)
572 {
573 if (m_nibble)
574 return (m_busy_flag ? 0x80 : 0) | (m_ac & 0x70);
575 else
576 return (m_ac << 4) & 0xf0;
577 }
578 else
579 {
580 return (m_busy_flag ? 0x80 : 0) | (m_ac & 0x7f);
581 }
582 }
583
data_write(u8 data)584 void hd44780_device::data_write(u8 data)
585 {
586 if (m_busy_flag)
587 {
588 logerror("HD44780: Ignoring data write %02x due of busy flag\n", data);
589 return;
590 }
591
592 if (m_data_len == 4)
593 {
594 if (m_nibble)
595 {
596 m_dr = data & 0xf0;
597 return;
598 }
599 else
600 {
601 m_dr |= ((data >> 4) & 0x0f);
602 }
603 }
604 else
605 {
606 m_dr = data;
607 }
608
609 LOG("HD44780: %sRAM write %x %x '%c'\n", m_active_ram == DDRAM ? "DD" : "CG", m_ac, m_dr, isprint(m_dr) ? m_dr : '.');
610
611 if (m_active_ram == DDRAM)
612 m_ddram[m_ac] = m_dr;
613 else
614 m_cgram[m_ac] = m_dr;
615
616 update_ac(m_direction);
617 if (m_shift_on)
618 shift_display(m_direction);
619 set_busy_flag(41);
620 }
621
data_read()622 u8 hd44780_device::data_read()
623 {
624 u8 data = (m_active_ram == DDRAM) ? m_ddram[m_ac] : m_cgram[m_ac];
625
626 LOG("HD44780: %sRAM read %x %c\n", m_active_ram == DDRAM ? "DD" : "CG", m_ac, data);
627
628 if (m_data_len == 4)
629 {
630 if (m_nibble)
631 return data & 0xf0;
632 else
633 data = (data << 4) & 0xf0;
634 }
635
636 if (!machine().side_effects_disabled())
637 {
638 update_ac(m_direction);
639 set_busy_flag(41);
640 }
641
642 return data;
643 }
644