// license:BSD-3-Clause // copyright-holders:Sandro Ronco /*************************************************************************** Hitachi HD44780 LCD controller TODO: - dump internal CGROM via decap - finish emulating osc pin: determine video timings from it, and if possible, remove m_busy_factor The internal oscillator is known to be based on the value of the resistor Rf, presumably (based on the HD44780U datasheet diagrams) with an equation close to this: For 5V power, Frequency in Hz = 1 / (2 * PI * Rf * 6.5pF) For 3V power, Frequency in Hz = 1 / (2 * PI * Rf * 7.86pF) The vast majority of devices use the internal oscillator with an Rf resistor of 91kOhms, for ~270kHz clock speed. However, a few Hitachi-made LCD modules use an Rf resistor of 200kOhms instead, for a ~122.4kHz clock speed, and it isn't clear why they did this, as it is lower than the minimum intended clock speed on the datasheet. DONE - busy flag durations in clock cycles - blink timing in clock cycles BUSY TIMINGS: Manufacturer: Hitachi Epson Samsung Device: HD44780 SED1278 KS0066+S6A0069 Clear display: 410 cycles 410 cycles 410 cycles Return Home: 410 cycles 410 cycles 410 cycles Entry Mode: 10 cycles 10 cycles 10 cycles Display on/off: 10 cycles 10 cycles 10 cycles Cursor/Display Shift: 10 cycles 10 cycles 10 cycles Function Set: 10 cycles 10 cycles 10 cycles Set CGRAM: 10 cycles 10 cycles 10 cycles Set DDRAM: 10 cycles 10 cycles 10 cycles Data Write: 10 cycles[1] 10 cycles 11 cycles Data Read: 10 cycles[1] 10 cycles 11 cycles[2,3] Blink Invert Rate: 102400 cycles 102400 cycles ? cycles[4] [1] HD44780 bug: BUSY is only active for 10 cycles, but the ac inc/dec on data read or write happens on the 11th cycle. This bug is documented in the later HD44780U datasheet, as well as in the SED1278 datasheet to note the bug is fixed there. We do not emulate this bug, but emulate as SED1278 where it is fixed. [2] KS0066 bug: a Data read from CGRAM after a CGRAM write + increment/decrement has completed will read the previous byte, not the new one the address counter now points to! This bug is documented in the datasheet. [3] S6A0069 note: the first data read from CGRAM must have a set address command or a display shift command sent before the read command, or it will instead return the potentially garbage data that was most recently put into the read buffer register as the serially-accessed ram hasn't had a chance to fully complete a serial cycle and update said buffer. Also, if the AC direction has not been explicitly set beforehand by a display shift command, the byte address auto-increment vs auto-decrement might be doing either one randomly after power-up. In short, this documentation change plus some minor behavior change "fixes" the bug of the KS0066, with the above caveat. [4] In all likelyhood, this is almost certainly 102400 for compatibility with the original HD44780A00 Known HD44780-compatible clones: Epson SED1278 Samsung KS0066[U] Samsung S6A0069 (equivalent to KS0066U?) Sitronix ST7066U Sunplus SPLC780C (timings equivalent to SED1278?) Possibly compatible clones: Toshiba T1719A (may be custom for Brother) Similar, but not quite compatible variants: Hitachi HD66780 LCD-IIA (more or less the same as 44780 but has a different LCD driver waveform, and like 44780 has 16 Common/40 Segment drivers) Samsung KS0073 (uses different DPRAM memory mapping than HD44780, and has 34 Common/64 Segment drivers; it also has a low power mode and 6-pixel character width modes) Samsung S6A0073 (equivalent to KS0073) Novatek NT7603 (clocked at twice the speed, has different LCD driver waveforms, and has 16 Common/80 Segment drivers) Novatek NT7605 (clocked at twice the speed, has different LCD driver waveforms, and has 16 Common/100 Segment drivers) Solomon Systech SSD1803 (2.7-3.45V only, 34 Common/100 Segment drivers, has a 6-pixel character width mode, and when in 4-line mode, has a memory map like KS0073; has two unique CGROMs) Character set equivalency: Hitachi Samsung Epson Sitronix Sunplus [Supported] HD44780A00 KS0066F00 SED1278F0A ST7066-0A SPLC780C-01 Y HD44780UA00 Y HD44780UA01 SPLC780C-17 HD44780UA02 ST7066-0R Y SED1278F0H ST7066-0T SPLC780C-02 SED1278F0B ST7066-0B SPLC780C-03 Y KS0066F03 SPLC780C-08 KS0066F04 SPLC780C-13 KS0066F05 ST7066-0E SPLC780C-11 Y KS0066F06 SPLC780C-12 ST7066-1G SPLC780C-14 SPLC780C-15 SPLC780C-19 SED1278F0C KS0066F59 SED1278F0E SED1278F0G SPLC780C-18 DPRAM 80-byte wrap behavior is based on increment/decrement: In 1-line mode: 4E 4F 00 01 02 ... 4D 4E 4F 00 01 02 ... In 2-line mode: L1: 26 27 00 01 02 ... 24 25 26 27 00 01 02 ... L2: 66 67 40 41 42 ... 64 65 66 67 40 41 42 ... ***************************************************************************/ #include "emu.h" #include "hd44780.h" //#define VERBOSE 1 #include "logmacro.h" //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** DEFINE_DEVICE_TYPE(HD44780, hd44780_device, "hd44780", "Hitachi HD44780 LCD Controller") DEFINE_DEVICE_TYPE(HD44780U, hd44780u_device, "hd44780u", "Hitachi HD44780U LCD Controller") DEFINE_DEVICE_TYPE(SED1278, sed1278_device, "sed1278", "Epson SED1278 LCD Controller") // packaged as either SED1278F0B or SED1278D0B DEFINE_DEVICE_TYPE(KS0066, ks0066_device, "ks0066", "Samsung KS0066 LCD Controller") //------------------------------------------------- // ROM( hd44780 ) //------------------------------------------------- ROM_START( hd44780 ) ROM_DEFAULT_BIOS("a00") ROM_SYSTEM_BIOS(0, "a00", "A00") ROM_REGION( 0x1000, "cgrom", 0 ) ROMX_LOAD( "hd44780_a00.bin", 0x0000, 0x1000, BAD_DUMP CRC(e459877c) SHA1(65cf075a988cdcbb316b9afdd0529b374a1a65ec), ROM_BIOS(0)) // from page 97 of the 1985 HD44780 datasheet from crystalfontz ROM_END ROM_START( hd44780u ) ROM_DEFAULT_BIOS("a00") ROM_SYSTEM_BIOS(0, "a00", "A00") ROM_SYSTEM_BIOS(1, "a02", "A02") // Note the HD44780UA01 font does exist in the 1994 M24T026 Hitachi LCD Controller Driver LSI Data Book on bitsavers as well ROM_REGION( 0x1000, "cgrom", 0 ) ROMX_LOAD( "hd44780u_a00.bin", 0x0000, 0x1000, BAD_DUMP CRC(8494cb6b) SHA1(2d4f9cf5ff81f20d2f4e4640f5b8a697a3781eef), ROM_BIOS(0)) // from page 17 of the 1999 HD44780U datasheet // this has 6 characters different from the HD44780A00 // this differs slightly (see the '7') from the HD44780UA00 font in the 1994 M24T026 Hitachi LCD Controller Driver LSI Data Book on bitsavers // and needs confirmation from a real device as to which is correct; confirmation on the horizontal offset of the '[' would also be helpful ROMX_LOAD( "hd44780u_a02.bin", 0x0000, 0x1000, BAD_DUMP CRC(6d522b42) SHA1(db8f59573c81933cfc9d3232d419406f0896e60b), ROM_BIOS(1)) // from page 18 of the 1999 HD44780U datasheet // this differs slightly (see the '7') from the HD44780UA02 font in the 1994 M24T026 Hitachi LCD Controller Driver LSI Data Book on bitsavers // and needs confirmation from a real device as to which is correct ROM_END ROM_START( sed1278 ) ROM_DEFAULT_BIOS("0a") ROM_SYSTEM_BIOS(0, "0a", "0A") ROM_SYSTEM_BIOS(1, "0b", "0B") ROM_REGION( 0x1000, "cgrom", 0 ) ROMX_LOAD( "sed1278_0a.bin", 0x0000, 0x1000, BAD_DUMP CRC(e459877c) SHA1(65cf075a988cdcbb316b9afdd0529b374a1a65ec), ROM_BIOS(0)) // from page 9-32 of the SED1278 datasheet (100% identical to hd44780a00) ROMX_LOAD( "sed1278_0b.bin", 0x0000, 0x1000, BAD_DUMP CRC(962498b7) SHA1(41866836ab4ed7bd4c3539bc8df492ba7d7ff42a), ROM_BIOS(1)) // from page 9-33 of the SED1278 datasheet ROM_END // TODO: many other SED1278 variants, documented in the datasheet // ks0066_f00 is 100% identical to hd44780a00, see page 61 of the KS0066 non-U datasheet // later ks0066 masks may be different, see page 48 of the other KS0066 datasheet ROM_START( ks0066 ) ROM_DEFAULT_BIOS("f00") ROM_SYSTEM_BIOS(0, "f00", "F00") //ROM_SYSTEM_BIOS(?, "f03", "F03") //ROM_SYSTEM_BIOS(?, "f04", "F04") ROM_SYSTEM_BIOS(1, "f05", "F05") //ROM_SYSTEM_BIOS(?, "f06", "F06") //ROM_SYSTEM_BIOS(?, "f59", "F59") ROM_REGION( 0x1000, "cgrom", 0 ) ROMX_LOAD( "ks0066_f00.bin", 0x0000, 0x1000, CRC(e459877c) SHA1(65cf075a988cdcbb316b9afdd0529b374a1a65ec), ROM_BIOS(0)) // from page 61 of the KS0066 non-U datasheet, validated on a psr540 ROMX_LOAD( "ks0066_f05.bin", 0x0000, 0x1000, BAD_DUMP CRC(af9e7bd6) SHA1(0196e871584ee5d370856e7307c0f9d1466e3e51), ROM_BIOS(1)) // from page 51 of the KS0066 datasheet ROM_END // TODO: Sitronix and Sunplus devices/variants //************************************************************************** // live device //************************************************************************** //------------------------------------------------- // hd44780_device - constructor //------------------------------------------------- hd44780_device::hd44780_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : hd44780_device(mconfig, HD44780, tag, owner, clock) { } hd44780_device::hd44780_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock) , m_pixel_update_cb(*this) , m_busy_factor(1.0) , m_cgrom(nullptr) , m_cgrom_region(*this, DEVICE_SELF) , m_rs_input(0) , m_rw_input(0) , m_db_input(0) , m_enabled(false) , m_function_set_at_any_time(false) { } hd44780u_device::hd44780u_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : hd44780_device(mconfig, HD44780U, tag, owner, clock) { } sed1278_device::sed1278_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : hd44780_device(mconfig, SED1278, tag, owner, clock) { } ks0066_device::ks0066_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : hd44780_device(mconfig, KS0066, tag, owner, clock) { } //------------------------------------------------- // rom_region - device-specific ROM region //------------------------------------------------- const tiny_rom_entry *hd44780_device::device_rom_region() const { return ROM_NAME(hd44780); } const tiny_rom_entry *hd44780u_device::device_rom_region() const { return ROM_NAME(hd44780u); } const tiny_rom_entry *sed1278_device::device_rom_region() const { return ROM_NAME(sed1278); } const tiny_rom_entry *ks0066_device::device_rom_region() const { return ROM_NAME(ks0066); } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void hd44780_device::device_start() { m_cgrom = m_cgrom_region.found() ? m_cgrom_region : memregion("cgrom")->base(); m_pixel_update_cb.resolve(); m_busy_timer = timer_alloc(FUNC(hd44780_device::clear_busy_flag), this); m_blink_timer = timer_alloc(FUNC(hd44780_device::blink_tick), this); // state saving save_item(NAME(m_busy_factor)); save_item(NAME(m_busy_flag)); save_item(NAME(m_ac)); save_item(NAME(m_dr)); save_item(NAME(m_ir)); save_item(NAME(m_active_ram)); save_item(NAME(m_display_on)); save_item(NAME(m_cursor_on)); save_item(NAME(m_shift_on)); save_item(NAME(m_blink_on)); save_item(NAME(m_direction)); save_item(NAME(m_data_len)); save_item(NAME(m_num_line)); save_item(NAME(m_char_size)); save_item(NAME(m_disp_shift)); save_item(NAME(m_blink)); save_item(NAME(m_ddram)); save_item(NAME(m_cgram)); save_item(NAME(m_nibble)); save_item(NAME(m_rs_input)); save_item(NAME(m_rw_input)); save_item(NAME(m_db_input)); save_item(NAME(m_enabled)); save_item(NAME(m_rs_state)); save_item(NAME(m_rw_state)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void hd44780_device::device_reset() { memset(m_ddram, 0x20, sizeof(m_ddram)); // filled with SPACE char memset(m_cgram, 0, sizeof(m_cgram)); m_ac = 0; m_dr = 0; m_ir = 0; m_active_ram = DDRAM; m_display_on = false; m_cursor_on = false; m_blink_on = false; m_shift_on = false; m_direction = 1; m_data_len = 8; m_num_line = 1; m_char_size = 8; m_disp_shift = 0; m_blink = false; m_nibble = false; m_first_cmd = true; m_rs_state = 0; m_rw_state = 0; set_busy_flag(410); } //------------------------------------------------- // device_clock_changed //------------------------------------------------- void hd44780_device::device_clock_changed() { // (re)adjust blink timer attotime period = attotime::from_ticks(102400, clock()); // blink happens every 102400 cycles attotime remain = m_blink_timer->remaining(); m_blink_timer->adjust((remain > period) ? period : remain, 0, period); } //------------------------------------------------- // device validity check //------------------------------------------------- void hd44780_device::device_validity_check(validity_checker &valid) const { if (clock() == 0) osd_printf_error("LCDC clock cannot be zero!\n"); } //------------------------------------------------- // timer events //------------------------------------------------- TIMER_CALLBACK_MEMBER(hd44780_device::clear_busy_flag) { m_busy_flag = false; } TIMER_CALLBACK_MEMBER(hd44780_device::blink_tick) { m_blink = !m_blink; } //************************************************************************** // HELPERS //************************************************************************** void hd44780_device::set_busy_flag(uint16_t cycles) { m_busy_flag = true; m_busy_timer->adjust(attotime::from_ticks(cycles, clock() / m_busy_factor)); } void hd44780_device::correct_ac() { if (m_active_ram == DDRAM) { int max_ac = (m_num_line == 1) ? 0x4f : 0x67; if (m_ac > max_ac) m_ac -= max_ac + 1; else if (m_ac < 0) m_ac = max_ac; else if (m_num_line == 2 && m_ac > 0x27 && m_ac < 0x40) m_ac = 0x40 + (m_ac - 0x28); } else m_ac &= 0x3f; } void hd44780_device::update_ac(int direction) { if (m_active_ram == DDRAM && m_num_line == 2 && direction == -1 && m_ac == 0x40) m_ac = 0x27; else m_ac += direction; correct_ac(); } void hd44780_device::shift_display(int direction) { m_disp_shift += direction; if (m_disp_shift == 0x50) m_disp_shift = 0; else if (m_disp_shift == -1) m_disp_shift = 0x4f; } void hd44780_device::update_nibble(int rs, int rw) { if (m_rs_state != rs || m_rw_state != rw) { m_rs_state = rs; m_rw_state = rw; m_nibble = false; } m_nibble = !m_nibble; } inline void hd44780_device::pixel_update(bitmap_ind16 &bitmap, u8 line, u8 pos, u8 y, u8 x, int state) { if (!m_pixel_update_cb.isnull()) { m_pixel_update_cb(bitmap, line, pos, y, x, state); } else { u8 line_height = (m_char_size == 8) ? m_char_size : m_char_size + 1; if (m_lines <= 2) { if (pos < m_chars) bitmap.pix(line * (line_height + 1) + y, pos * 6 + x) = state; } else if (m_lines <= 4) { if (pos < m_chars*2) { if (pos >= m_chars) { line += 2; pos -= m_chars; } if (line < m_lines) bitmap.pix(line * (line_height + 1) + y, pos * 6 + x) = state; } } else { fatalerror("%s: use a custom callback for this LCD configuration (%d x %d)\n", tag(), m_lines, m_chars); } } } //************************************************************************** // device interface //************************************************************************** const u8 *hd44780_device::render() { memset(m_render_buf, 0, sizeof(m_render_buf)); if (m_display_on) { u8 line_size = 80 / m_num_line; for (int line = 0; line < m_num_line; line++) { for (int pos = 0; pos < line_size; pos++) { uint16_t char_pos = line * 0x40 + ((pos + m_disp_shift) % line_size); const u8 *src; if (m_ddram[char_pos] < 0x10) { // draw CGRAM characters if (m_char_size == 8) src = m_cgram + (m_ddram[char_pos] & 0x07) * 8; else src = m_cgram + ((m_ddram[char_pos] >> 1) & 0x03) * 16; } else { // draw CGROM characters src = m_cgrom + m_ddram[char_pos] * 0x10; } u8 *dest = m_render_buf + 16 * (line * line_size + pos); memcpy (dest, src, m_char_size); if (char_pos == m_ac) { // draw the cursor if (m_cursor_on) dest[m_char_size - 1] = 0x1f; if (!m_blink && m_blink_on) memset(dest, 0x1f, m_char_size); } } } } return m_render_buf; } uint32_t hd44780_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { bitmap.fill(0, cliprect); const u8 *img = render(); u8 line_size = 80 / m_num_line; for (int line = 0; line < m_num_line; line++) { for (int pos = 0; pos < line_size; pos++) { const u8 *src = img + 16 * (line * line_size + pos); for (int y = 0; y < m_char_size; y++) for (int x = 0; x < 5; x++) pixel_update(bitmap, line, pos, y, x, BIT(src[y], 4 - x)); } } return 0; } u8 hd44780_device::read(offs_t offset) { if (m_data_len == 4 && !machine().side_effects_disabled()) update_nibble(offset & 0x01, 1); switch (offset & 0x01) { case 0: return control_read(); case 1: return data_read(); } return 0; } void hd44780_device::write(offs_t offset, u8 data) { if (m_data_len == 4 && !machine().side_effects_disabled()) update_nibble(offset & 0x01, 0); switch (offset & 0x01) { case 0: control_write(data); break; case 1: data_write(data); break; } } u8 hd44780_device::db_r() { if (m_enabled && m_rw_input == 1) { switch (m_rs_input) { case 0: return control_read(); case 1: return data_read(); } } return 0xff; } void hd44780_device::db_w(u8 data) { m_db_input = data; } void hd44780_device::rs_w(int state) { m_rs_input = state; } void hd44780_device::rw_w(int state) { m_rw_input = state; } void hd44780_device::e_w(int state) { if (m_data_len == 4 && state && !m_enabled && !machine().side_effects_disabled()) update_nibble(m_rs_input, m_rw_input); if (!state && m_enabled && m_rw_input == 0) { switch (m_rs_input) { case 0: control_write(m_db_input); break; case 1: data_write(m_db_input); break; } } m_enabled = state; } void hd44780_device::control_write(u8 data) { if (m_data_len == 4) { if (m_nibble) { m_ir = data & 0xf0; return; } else { m_ir |= ((data >> 4) & 0x0f); } } else { m_ir = data; } if (BIT(m_ir, 7)) { // set DDRAM address m_active_ram = DDRAM; m_ac = m_ir & 0x7f; correct_ac(); set_busy_flag(10); LOG("HD44780: set DDRAM address %x\n", m_ac); return; } else if (BIT(m_ir, 6)) { // set CGRAM address m_active_ram = CGRAM; m_ac = m_ir & 0x3f; set_busy_flag(10); LOG("HD44780: set CGRAM address %x\n", m_ac); return; } else if (BIT(m_ir, 5)) { // function set if (!m_function_set_at_any_time && !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))) { logerror("HD44780: function set cannot be executed after other instructions unless the interface data length is changed\n"); return; } m_first_cmd = true; m_char_size = BIT(m_ir, 2) ? 10 : 8; m_data_len = BIT(m_ir, 4) ? 8 : 4; m_num_line = BIT(m_ir, 3) + 1; correct_ac(); set_busy_flag(10); LOG("HD44780: char size 5x%d, data len %d, lines %d\n", m_char_size, m_data_len, m_num_line); return; } else if (BIT(m_ir, 4)) { // cursor or display shift int direction = (BIT(m_ir, 2)) ? +1 : -1; LOG("HD44780: %s shift %d\n", BIT(m_ir, 3) ? "display" : "cursor", direction); if (BIT(m_ir, 3)) shift_display(direction); else update_ac(direction); set_busy_flag(10); } else if (BIT(m_ir, 3)) { // display on/off control m_display_on = BIT(m_ir, 2); m_cursor_on = BIT(m_ir, 1); m_blink_on = BIT(m_ir, 0); set_busy_flag(10); LOG("HD44780: display %d, cursor %d, blink %d\n", m_display_on, m_cursor_on, m_blink_on); } else if (BIT(m_ir, 2)) { // entry mode set m_direction = (BIT(m_ir, 1)) ? +1 : -1; m_shift_on = BIT(m_ir, 0); set_busy_flag(10); LOG("HD44780: entry mode set: direction %d, shift %d\n", m_direction, m_shift_on); } else if (BIT(m_ir, 1)) { // return home LOG("HD44780: return home\n"); m_ac = 0; m_active_ram = DDRAM; m_direction = 1; m_disp_shift = 0; set_busy_flag(410); } else if (BIT(m_ir, 0)) { // clear display LOG("HD44780: clear display\n"); m_ac = 0; m_active_ram = DDRAM; m_direction = 1; m_disp_shift = 0; memset(m_ddram, 0x20, sizeof(m_ddram)); set_busy_flag(410); // Some machines do a "clear display" first, even though the datasheet insists "function set" must come before all else return; } m_first_cmd = false; } u8 hd44780_device::control_read() { if (m_data_len == 4) { if (m_nibble) return (m_busy_flag ? 0x80 : 0) | (m_ac & 0x70); else return (m_ac << 4) & 0xf0; } else { return (m_busy_flag ? 0x80 : 0) | (m_ac & 0x7f); } } void hd44780_device::data_write(u8 data) { if (false && m_busy_flag) { logerror("HD44780: Ignoring data write %02x due of busy flag\n", data); return; } if (m_data_len == 4) { if (m_nibble) { m_dr = data & 0xf0; return; } else { m_dr |= ((data >> 4) & 0x0f); } } else { m_dr = data; } LOG("HD44780: %sRAM write %x %x '%c'\n", m_active_ram == DDRAM ? "DD" : "CG", m_ac, m_dr, isprint(m_dr) ? m_dr : '.'); if (m_active_ram == DDRAM) m_ddram[m_ac] = m_dr; else m_cgram[m_ac] = m_dr; set_busy_flag(10); update_ac(m_direction); if (m_shift_on) shift_display(m_direction); } u8 hd44780_device::data_read() { u8 data = (m_active_ram == DDRAM) ? m_ddram[m_ac] : m_cgram[m_ac]; LOG("HD44780: %sRAM read %x %c\n", m_active_ram == DDRAM ? "DD" : "CG", m_ac, data); if (m_data_len == 4) { if (m_nibble) return data & 0xf0; else data = (data << 4) & 0xf0; } if (!machine().side_effects_disabled()) { set_busy_flag(10); update_ac(m_direction); } return data; }