will be read-only starting December 31st, 2018. For more info please look at this Forum Post

This is Legba7's modification of the modified, unofficial LCD4BitLibrary from Neillzero for the LCD Module TM404 TM404 from Tianma

This display has a second HD44780 for the third and fourth line with its own Enable Input.

So I wired the second Enable Input to pin 3 on the arduino and edited Jonathan's modified library

Read more on 20x4 modifications in the Arduino Forum

LCD4BitLibrary for TM404A

LCD4Bit v0.1 16/Oct/2006 neillzero
edited by lega7 05/Oct/2007 to work with tm404a
tm404a has a second HD44780 controller for line 3 and line 4 with a second Enable input

What is this?
An arduino library for comms with HD44780-compatible LCD, in 4-bit mode (saves pins)

- The original "LiquidCrystal" 8-bit library and tutorial
- DEM 16216 datasheet
- Massimo's suggested 4-bit code (I took initialization from here)
See also:
- glasspusher's code (probably more correct):,15480.html

Tested only with a DEM 16216 (maplin "N27AZ" -
Tested and modifier for a 20x4 lcd controller (blue background and white text) 
If you use this successfully, consider feeding back to the arduino wiki with a note of which LCD it worked on.

see the examples folder of this library distribution.


#include "LCD4Bit.h"
extern "C" {
  #include <stdio.h>  //not needed yet
  #include <string.h> //needed for strlen()
  #include <inttypes.h>
  #include "WConstants.h"  //all things wiring / arduino

//command bytes for LCD
#define CMD_CLR 0x01
#define CMD_RIGHT 0x1C
#define CMD_LEFT 0x18
#define CMD_HOME 0x02

// --------- PINS -------------------------------------
//is the RW pin of the LCD under our control?  If we're only ever going to write to the LCD, we can use one less microcontroller pin, and just tie the LCD pin to the necessary signal, high or low.
//this stops us sending signals to the RW pin if it isn't being used.
int USING_RW = false;

//RS, RW and Enable can be set to whatever you like
int RS = 12;
int RW = 11;
//legba7: two HD44780; per HD44780 one Enable Input
int Enable1 = 2;
int Enable2 = 3;
int Enable = Enable1;
//DB should be an unseparated group of pins  - because of lazy coding in pushNibble()
int DB[] = {7, 8, 9, 10};  //wire these to DB4~7 on LCD.


//how many lines has the LCD? (don't change here - specify on calling constructor)
int g_num_lines = 2;

void LCD4Bit::WriteCGRAM(int address, char code[]) {
   commandWrite(CMD_HOME); // How to return to previous position ?

//pulse the Enable pin high (for a microsecond).
//This clocks whatever command or data is in DB4~7 into the LCD controller.
void LCD4Bit::pulseEnablePin(){
  // send a pulse to enable
  delay(1);  // pause 1 ms.  TODO: what delay, if any, is necessary here? (it seems that 1ms is a good choice)

//push a nibble of data through the the LCD's DB4~7 pins, clocking with the Enable pin.
//We don't care what RS and RW are, here.
void LCD4Bit::pushNibble(int value){
  int val_nibble= value & 0x0F;  //clean the value.  (unnecessary)

  for (int i=DB[0]; i <= DB[3]; i++) {
    digitalWrite(i,val_nibble & 01);
    val_nibble >>= 1;

//push a byte of data through the LCD's DB4~7 pins, in two steps, clocking each with the enable pin.
void LCD4Bit::pushByte(int value){

  int val_lower = value & 0x0F;
  int val_upper = value >> 4;

//stuff the library user might call---------------------------------
//constructor.  num_lines must be 1 or 2 or 4, currently.
LCD4Bit::LCD4Bit (int num_lines) {
  g_num_lines = num_lines;
  if (g_num_lines != 1 && g_num_lines != 2 && g_num_lines != 4)
    g_num_lines = 1;

void LCD4Bit::commandWriteNibble(int nibble) {
  digitalWrite(RS, LOW);
  if (USING_RW) { digitalWrite(RW, LOW); }

void LCD4Bit::commandWrite(int value) {
  digitalWrite(RS, LOW);
  if (USING_RW) { digitalWrite(RW, LOW); }
  //TODO: perhaps better to add a delay after EVERY command, here.  many need a delay, apparently.

//print the given character at the current cursor position. overwrites, doesn't insert.
void LCD4Bit::print(int value) {
  //set the RS and RW pins to show we're writing data
  digitalWrite(RS, HIGH);
  if (USING_RW) { digitalWrite(RW, LOW); }

  //let pushByte worry about the intricacies of Enable, nibble order.

//print the given string to the LCD at the current cursor position.  overwrites, doesn't insert.
//While I don't understand why this was named printIn (PRINT IN?) in the original LiquidCrystal library, I've preserved it here to maintain the interchangeability of the two libraries.
void LCD4Bit::printIn(char msg[]) {
  uint8_t i;  //fancy int.  avoids compiler warning when comparing i with strlen()'s uint8_t
  for (i=0;i < strlen(msg);i++){

//send the clear screen command to the LCD
void LCD4Bit::clear(){

// initiatize lcd after a short pause
//while there are hard-coded details here of lines, cursor and blink settings, you can override these original settings after calling .init()
void LCD4Bit::init () {
  if (USING_RW) { pinMode(RW,OUTPUT); }


  //The first 4 nibbles and timings are not in my DEM16217 SYH datasheet, but apparently are HD44780 standard...

  // needed by the LCDs controller
  //this being 2 sets up 4-bit mode.
  //todo: make configurable by the user of this library.
  //NFXX where
  //N = num lines (0=1 line or 1=2 lines).
  //F= format (number of dots (0=5x7 or 1=5x10)).
  //X=don't care
  int num_lines_ptn ;
  if (g_num_lines != 4) {
  	num_lines_ptn = g_num_lines - 1 << 3;
  } else {
	num_lines_ptn = 2 - 1 << 3; // we are controlling the 4 lines display as an 2 lines.
  int dot_format_ptn = 0x00;      //5x7 dots.  0x04 is 5x10

  commandWriteNibble(num_lines_ptn | dot_format_ptn);

  //The rest of the init is not specific to 4-bit mode.
  //NOTE: we're writing full bytes now, not nibbles.

  // display control:
  // turn display on, cursor off, no blinking

  //clear display

  // entry mode set: 06
  // increment automatically, display shift, entire shift off

  delay(1);//TODO: remove unnecessary delays

//non-core stuff --------------------------------------
//move the cursor to the given absolute position.  line numbers start at 1.
//if this is not a 2-line LCD4Bit instance, will always position on first line.
//legba7: second controller, with its own Enable input, for line 3 and 4
void LCD4Bit::cursorTo(int line_num, int x){

  //if we are on a 1-line display, set line_num to 1st line, regardless of given
  if (g_num_lines==1){
    line_num = 1;
    Enable = Enable1;
  //offset 40 chars in if second line requested
  switch (line_num){
  case 1:
    Enable = Enable1;
    x=0x00 ; break;;
  case 2: 
    Enable = Enable1;
    x= 0x40 ; break;;
  case 3: 
    //line_num = 1;
    Enable = Enable2;
    x=0x00; break;;
    //x+= 0x14 ; break;; // In fact, line 3 is an extension of the line 1 (beyond the 20 first characters)
  case 4: 
    Enable = Enable2;
    //x+= 0x54 ; break;; // Line 4 is an extension of line 2
    x= 0x40 ; break;; // Line 4 is an extension of line 3


//scroll whole display to left
//Doesn't seems to be usefull with 4 lines displays!
void LCD4Bit::leftScroll(int num_chars, int delay_time){
  for (int i=0; i<num_chars; i++) {

//Improvements ------------------------------------------------
//Remove the unnecessary delays (e.g. from the end of pulseEnablePin()).
//Allow the user to pass the pins to be used by the LCD in the constructor, and store them as member variables of the class instance.

Example Code

#include <LCD4Bit.h>

LCD4Bit lcd = LCD4Bit(4);

void setup(){
  pinMode(13, OUTPUT); //we'll use the debug LED to output heartbeat
  lcd.cursorTo(3,0);  //select second displaycontroller, line 3, 4
void loop(){
  digitalWrite(13, HIGH); //light the debug LED
  lcd.printIn("Line 1");
  lcd.printIn("Line 2");
  lcd.printIn("Line 3");
  lcd.printIn("Line 4");
  lcd.cursorTo(1,0); // select first two rows
  lcd.leftScroll(40, 80); //scroll first two rows
  lcd.cursorTo(3,0); //select second displaycontroller, line 3, 4  
  lcd.leftScroll(40, 80); //scroll third and fourth row
  digitalWrite(13, LOW);


Edit: by bingo_

The code from above fixed with one extra line. It works now and all in one Zip!