Arduino Forum

Using Arduino => Microcontrollers => Topic started by: dhenry on Nov 23, 2012, 01:42 pm

Title: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 23, 2012, 01:42 pm
I was talking in a separate spread with someone else and as a result of that conversation decided to convert LiquidCrystal to a 3-wire solution.

LiquidCrystal requires anywhere between 6 lines (enable, rs, and d4..7). Sometimes, that can be difficult for a board with limited pins. Thus, the 3-wire solution.

The 3-wire solution implemented here is wholly based on LiquidCrystal, including the user interface and calling convention / syntax. It utilizes a shift register (HC164 or HC595) to shift the data pins (D0..7) to the shift register (Q0..7).

Connection:
MOSI pin: from the mcu to shift register's data pin (pin1+2 on HC164 and pin 14 on HC595). LCD's RS pin tied to this line as well.
SCK pin: from the mcu to shift register's serial clock pin (pin 8 on HC164 and pin 11 on HC595)
Enable pin: from the mcu to the lcd.

Advantages over other 3-wire solutions:
1) It allows 8-bit as well as 4-bit mode.
2) It uses no parts other than the shift register.
3) It allows HC164 as well as HC595.

You will need two files:

lcd3wire.h:
Code: [Select]

//header file to drive HD44780 using a shift register in a 3-wire connection
//based on LiquidCrystal
/*The 3-wire solution implemented here is wholly based on LiquidCrystal, including the user interface and calling convention / syntax. It utilizes a shift register (HC164 or HC595) to shift the data pins (D0..7) to the shift register (Q0..7).

Connection:
MOSI pin: from the mcu to shift register's data pin (pin1+2 on HC164 and pin 14 on HC595). LCD's RS pin tied to this line as well.
SCK pin: from the mcu to shift register's serial clock pin (pin 8 on HC164 and pin 11 on HC595)
Enable pin: from the mcu to the lcd.

Advantages over other 3-wire solutions:
1) It allows 8-bit as well as 4-bit mode.
2) It uses no parts other than the shift register.
3) It allows HC164 as well as HC595.

You will need two files: lcd3wire.h and lcd3wire.cpp
*/

#ifndef LiquidCrystal_h
#define LiquidCrystal_h

#include <inttypes.h>
#include "Print.h"

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

class LiquidCrystal : public Print {
public:
  LiquidCrystal(uint8_t mosi, uint8_t sck, uint8_t enable);
  void init(uint8_t fourbitmode, uint8_t mosi, uint8_t sck, uint8_t enable);

  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);

  void clear();
  void home();

  void noDisplay();
  void display();
  void noBlink();
  void blink();
  void noCursor();
  void cursor();
  void scrollDisplayLeft();
  void scrollDisplayRight();
  void leftToRight();
  void rightToLeft();
  void autoscroll();
  void noAutoscroll();

  void createChar(uint8_t, uint8_t[]);
  void setCursor(uint8_t, uint8_t);
  virtual size_t write(uint8_t);
  void command(uint8_t);
 
  using Print::write;
private:
  void send(uint8_t, uint8_t);
  void write4bits(uint8_t);
  void write8bits(uint8_t);
  void pulseEnable();

  uint8_t _rs_pin; // LOW: command.  HIGH: character.
  //uint8_t _rw_pin; // LOW: write to LCD.  HIGH: read from LCD.
  uint8_t _enable_pin; // activated by a HIGH pulse.
  //uint8_t _data_pins[8];
  uint8_t _sck_pin; //SR's sck pin. data on rising edge
#define _mosi_pin _rs_pin //mosi pin share the same connection with rs pin

  uint8_t _displayfunction;
  uint8_t _displaycontrol;
  uint8_t _displaymode;

  uint8_t _initialized;

  uint8_t _numlines,_currline;
};

#endif

Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 23, 2012, 01:43 pm
The 2nd file you will need is lcd3wire.cpp:

Code: [Select]

#include "lcd3wire.h"

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "Arduino.h"

// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set:
//    DL = 1; 8-bit interface data
//    N = 0; 1-line display
//    F = 0; 5x8 dot character font
// 3. Display on/off control:
//    D = 0; Display off
//    C = 0; Cursor off
//    B = 0; Blinking off
// 4. Entry mode set:
//    I/D = 1; Increment by 1
//    S = 0; No shift
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).

LiquidCrystal::LiquidCrystal(uint8_t mosi,  uint8_t sck, uint8_t enable)
{
  init(0, mosi, sck, enable);
}

void LiquidCrystal::init(uint8_t fourbitmode, uint8_t mosi, uint8_t sck, uint8_t enable)
{
  _mosi_pin = mosi;
  _sck_pin = sck;
  _enable_pin = enable;
 
  //_data_pins[0] = d0;
  //_data_pins[1] = d1;
  //_data_pins[2] = d2;
  //_data_pins[3] = d3;
  //_data_pins[4] = d4;
  //_data_pins[5] = d5;
  //_data_pins[6] = d6;
  //_data_pins[7] = d7;

  pinMode(_mosi_pin, OUTPUT);

  pinMode(_sck_pin, OUTPUT);
  // we can save 1 pin by not using RW. Indicate by passing 255 instead of pin#
  //if (_rw_pin != 255) {
  //  pinMode(_rw_pin, OUTPUT);
  //}
  pinMode(_enable_pin, OUTPUT);
 
  if (fourbitmode)
    _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
  else
    _displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS;
 
  begin(16, 1); 
}

void LiquidCrystal::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
  if (lines > 1) {
    _displayfunction |= LCD_2LINE;
  }
  _numlines = lines;
  _currline = 0;

  // for some 1 line displays you can select a 10 pixel high font
  if ((dotsize != 0) && (lines == 1)) {
    _displayfunction |= LCD_5x10DOTS;
  }

  // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
  // according to datasheet, we need at least 40ms after power rises above 2.7V
  // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
  delayMicroseconds(50000);
  // Now we pull both RS and R/W low to begin commands
  digitalWrite(_mosi_pin, LOW);
  digitalWrite(_sck_pin, LOW);
  digitalWrite(_enable_pin, LOW);
  //if (_rw_pin != 255) {
  //  digitalWrite(_rw_pin, LOW);
  //}
 
  //put the LCD into 4 bit or 8 bit mode
  if (! (_displayfunction & LCD_8BITMODE)) {
    // this is according to the hitachi HD44780 datasheet
    // figure 24, pg 46

    // we start in 8bit mode, try to set 4 bit mode
    write4bits(0x03);digitalWrite(_rs_pin, LOW); pulseEnable();
    delayMicroseconds(4500); // wait min 4.1ms

    // second try
    write4bits(0x03);    digitalWrite(_rs_pin, LOW); pulseEnable();
    delayMicroseconds(4500); // wait min 4.1ms
   
    // third go!
    write4bits(0x03);    digitalWrite(_rs_pin, LOW); pulseEnable();
    delayMicroseconds(150);

    // finally, set to 4-bit interface
    write4bits(0x02);    digitalWrite(_rs_pin, LOW); pulseEnable();
  } else {
    // this is according to the hitachi HD44780 datasheet
    // page 45 figure 23

    // Send function set command sequence
    command(LCD_FUNCTIONSET | _displayfunction);
    delayMicroseconds(4500);  // wait more than 4.1ms

    // second try
    command(LCD_FUNCTIONSET | _displayfunction);
    delayMicroseconds(150);

    // third go
    command(LCD_FUNCTIONSET | _displayfunction);
  }

  // finally, set # lines, font size, etc.
  command(LCD_FUNCTIONSET | _displayfunction); 

  // turn the display on with no cursor or blinking default
  _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; 
  display();

  // clear it off
  clear();

  // Initialize to default text direction (for romance languages)
  _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
  // set the entry mode
  command(LCD_ENTRYMODESET | _displaymode);

}

/********** high level commands, for the user! */
void LiquidCrystal::clear()
{
  command(LCD_CLEARDISPLAY);  // clear display, set cursor position to zero
  delayMicroseconds(2000);  // this command takes a long time!
}

void LiquidCrystal::home()
{
  command(LCD_RETURNHOME);  // set cursor position to zero
  delayMicroseconds(2000);  // this command takes a long time!
}

void LiquidCrystal::setCursor(uint8_t col, uint8_t row)
{
  int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
  if ( row >= _numlines ) {
    row = _numlines-1;    // we count rows starting w/0
  }
 
  command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

// Turn the display on/off (quickly)
void LiquidCrystal::noDisplay() {
  _displaycontrol &= ~LCD_DISPLAYON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::display() {
  _displaycontrol |= LCD_DISPLAYON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turns the underline cursor on/off
void LiquidCrystal::noCursor() {
  _displaycontrol &= ~LCD_CURSORON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::cursor() {
  _displaycontrol |= LCD_CURSORON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turn on and off the blinking cursor
void LiquidCrystal::noBlink() {
  _displaycontrol &= ~LCD_BLINKON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::blink() {
  _displaycontrol |= LCD_BLINKON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// These commands scroll the display without changing the RAM
void LiquidCrystal::scrollDisplayLeft(void) {
  command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void LiquidCrystal::scrollDisplayRight(void) {
  command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

// This is for text that flows Left to Right
void LiquidCrystal::leftToRight(void) {
  _displaymode |= LCD_ENTRYLEFT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This is for text that flows Right to Left
void LiquidCrystal::rightToLeft(void) {
  _displaymode &= ~LCD_ENTRYLEFT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'right justify' text from the cursor
void LiquidCrystal::autoscroll(void) {
  _displaymode |= LCD_ENTRYSHIFTINCREMENT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'left justify' text from the cursor
void LiquidCrystal::noAutoscroll(void) {
  _displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) {
  location &= 0x7; // we only have 8 locations 0-7
  command(LCD_SETCGRAMADDR | (location << 3));
  for (int i=0; i<8; i++) {
    write(charmap[i]);
  }
}

/*********** mid level commands, for sending data/cmds */

inline void LiquidCrystal::command(uint8_t value) {
  send(value, LOW);
}

inline size_t LiquidCrystal::write(uint8_t value) {
  send(value, HIGH);
  return 1; // assume sucess
}

/************ low level data pushing commands **********/

// write either command or data, with automatic 4/8-bit selection
void LiquidCrystal::send(uint8_t value, uint8_t mode) {
  //digitalWrite(_rs_pin, mode);

  // if there is a RW pin indicated, set it low to Write
  //if (_rw_pin != 255) {
  //  digitalWrite(_rw_pin, LOW);
  //}
 
  if (_displayfunction & LCD_8BITMODE) {
    write8bits(value);
  } else {
    write4bits(value>>4);
    digitalWrite(_rs_pin, mode); pulseEnable();
    write4bits(value);
  }
  digitalWrite(_rs_pin, mode); pulseEnable();
}

void LiquidCrystal::pulseEnable(void) {
  digitalWrite(_enable_pin, LOW);
  delayMicroseconds(1);   
  digitalWrite(_enable_pin, HIGH);
  delayMicroseconds(1);    // enable pulse must be >450ns
  digitalWrite(_enable_pin, LOW);
  delayMicroseconds(100);   // commands need > 37us to settle
}

void LiquidCrystal::write4bits(uint8_t value) {
  //for (int i = 0; i < 4; i++) {
  //  pinMode(_data_pins[i], OUTPUT);
  //  digitalWrite(_data_pins[i], (value >> i) & 0x01);
  //}
  shiftOut(_mosi_pin, _sck_pin, MSBFIRST, value<<4);

  //pulseEnable();
}

void LiquidCrystal::write8bits(uint8_t value) {
  //for (int i = 0; i < 8; i++) {
  //  pinMode(_data_pins[i], OUTPUT);
  //  digitalWrite(_data_pins[i], (value >> i) & 0x01);
  //}
  shiftOut(_mosi_pin, _sck_pin, MSBFIRST, value);
 
  //pulseEnable();
}

Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 23, 2012, 01:45 pm
Here is the user file - a copy from the LiquidCrystal demo, with minor modifications:

Code: [Select]

// include the library code:
#include "lcd3wire.h"

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(9, 8, 10);  //mosi = pin9, sck = pin8, enable = pin10

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("hello, world!");
}

void loop() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis()/1000);
}


As you can see, the change is limited to the declaration - since we need only 3 pins.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 23, 2012, 01:50 pm
Some performance figures / notes:
1) each frame (16-char) line is transmitted in 10ms / 4bit and 5ms / 8bit, on a 16MIPS avr.
2) with a minor modification (commenting out a line), you can half that time.
3) LiquidCrystal utilizes the wrong initialization timing, and other oddities. So you have the ability to speed it up substantially. I made none of those corrections, just so you can drive any LCDs LiquidCrystal can drive.
4) the clk's period is 15us, with a high pulse of 4us. So it should work on pretty much any shift register (most shift registers are good for a 1us pulse width at least).

Disclaimer: Not extensively tested. So use at your own risk.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Nov 23, 2012, 09:29 pm
Try running the attached sketch with your library.
It will measure the byte transfer time as well as produce a FPS and an independent 16x2 FPS time
so that numbers can be compared regardless of LCD geometry.
Below are are some numbers that compare the original Arduino LiquidCrystal
library with the speeds of of using fm's new LiquidCrystal library replacement library
with different interfaces.

Code: [Select]
16Mhz AVR
Interface    ByteXfer    16x2FPS      Ftime
----------------------------------------------
4BIT          338uS        86.92     11.51ms (orignal Liquid Crystal)
4BIT           98uS       298.58      3.35ms
SR1W          312uS        94.14     10.62ms
SR2W           76uS       388.62      2.57ms
SR_2W          72uS       406.90      2.46ms
SR_3W          61uS       480.03      2.08ms
SR3W          102uS       287.92      3.47ms
i2c           957uS        30.74      32.25ms (pcf8574 @ 100kbit/sec)


80Mhz Pic32 (ChipKit Uno32)
Interface    ByteXfer    16x2FPS      Ftime
----------------------------------------------
4BIT          232uS       126.73      7.89ms (orignal mpide Liquid Crystal)
4BIT           57uS       517.41      1.93ms
SR2W           53uS       557.35      1.79ms
SR_2W          53uS       554.66      1.80ms
SR_3W          50uS       591.40      1.69ms
SR3W           56uS       524.91      1.91ms



--- bill

Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: nickgammon on Nov 23, 2012, 09:38 pm
You know you can make attachments to a post? Saves posting the code over multiple posts.

Can you explain in more detail about the connections? A standard HD44780 LCD module will have 16 pins.



Your instructions are a bit vague as to which of the 8 shift-register pins go to which pins on the LCD.

And how are you recommending wiring the SS pin of the 595?
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 12:18 am
Some rumbers running bill's code:

[all deleted]
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 12:20 am
Each bit transfer takes about 15us -> 230 instructions for the stock liquidcrystal library.

The stock code uses shifts extensively. They could have saved time with masks.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 12:26 am
Also, the code works for 4015 and 4094, thanks in part to liquidcrystal's slow speed.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Nov 24, 2012, 01:27 am
The 3 wire/pin interfaces included in fm's library also do not use any additional diodes
or resistors to run the shift register and the code runs on pretty much any shift register
and has been tested on 74164, 74hc595, and 4094.

The SR_3W code which is using 4 bit mode is quite a bit faster
than the LCD3Wire code running in 8 bit mode.

One thing that is nice with fm's library is that you also get backlight control
which is something that can't be done when using a device that only has 8 bits of output latches
and running the LCD in 8 bit mode.
That is the primary reason that the fm library is limited to 4 bit mode.

But like I said before, while the actual size of the code is not very large,
to get things like shift registers working with a LiquidCrystal type library,
it takes a while to get this stuff working fast and reliably
and to ensure that it works across multiple processors at different speeds.

It is definitely not a 15 minute job.

And to get a 1 wire interface working with a shift register
fast and reliably takes considerably longer as it requires
not only software but matching the s/w to the hardware
and optimizing them both together.
It requires using a scope and possibly a logic analyzer to really ensure that it
is fully working and works at all voltage and temperature conditions.

The one advantage that LCD3Wire might have over fm's library is that is probably
a bit smaller since it is a little bit less complex (no layering) and has a bit less functionality (no backlight support).

--- bill
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 02:40 am
Here is where the speed bottleneck was for the LC library:

Code: [Select]

void LiquidCrystal::write4bits(uint8_t value) {
  for (int i = 0; i < 4; i++) {
    pinMode(_data_pins[i], OUTPUT);
    digitalWrite(_data_pins[i], (value >> i) & 0x01);
  }

  pulseEnable();
}

void LiquidCrystal::write8bits(uint8_t value) {
  for (int i = 0; i < 8; i++) {
    pinMode(_data_pins[i], OUTPUT);
    digitalWrite(_data_pins[i], (value >> i) & 0x01);
  }
 
  pulseEnable();
}


Two issues:
1) The original LiquidCrystal appears to be written with the intent to read the busy flag. Thus, it is switching pin mode in write4bit() and write8bit() - which slows it down significantly.

fm's library essentially eliminated the pin mode switches.

So one way to address it is to set the pin mode in init() and be done with it. You however lose the ability to read busy flag, unless you switch pin mode in the reading routine.

2) The shifting: lots of shifting in both the original LC and fm's library as well:

fm's writenbit():
Code: [Select]
// write4bits
void LiquidCrystal::writeNbits(uint8_t value, uint8_t numBits)
{
   for (uint8_t i = 0; i < numBits; i++)
   {
      digitalWrite(_data_pins[i], (value >> i) & 0x01);
   }
   pulseEnable();
}



It really don't make much sense with all the shifting.

One way would be to unroll the loop and use a mask.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 04:11 am
On a 16MIPS avr, time to send a byte:

stock write8bits() from liquidcrystal: 88.4us
fm's writeNbits(): 55.2us (
fm's writeNbits, unrolled: 35.5us
direct operation on the pins: 3.1us
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Nov 24, 2012, 04:35 am
I don't think the intent was to read the busy flag but rather to allow the data pins to
potentially be used for other purposes when not used to talk to the lcd.

The biggest slowdown won't be due to initializing the pins over and over again.
It is the sloppiness of the code in the LiquidCrystal library that does needless delays and the core code itself
that consumes more time than any other potential code optimizations in the library.
i.e. the original LiquidCrystal code is doing lots of extra delays at the millisecond level that
don't need to be done.

Once the needless delays are eliminated, then a sharp focus can be done on
library code optimization and eliminating using as much Arduino core code as possible.
The 4bit low level code has not  been optimized.
We were more concerned with the shift register and i2c interfaces.


Things like shiftout() are horribly slow compared to what is possible with the AVR.
The code in shiftout() is terrible. It can be sped up quite a bit by simply re-writing it.
But the biggest speed up for shiftout() is to avoid using digitalWrite() because
the way the core code implemented the digital i/o functions is awful. There is much
that can be done to speed up the routines even if the same API is used and the values
are not compile time constants.
A further bump in speed can be done if the API were changed a bit.

--- bill
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 01:50 pm
Here is the two-wire version, using a r/c filter on the mosi line (mosi tied to sck via a rc filter. rs tied to sck directly).

Again, it comes with two files, lcd2wire.cpp is the source file and lcd2wire.h is the header file to be included in your application.

lcd2wire.cpp:
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 01:50 pm
lcd2wire.h:
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 01:58 pm
the calling convention is almost identical except the declaration:

Code: [Select]

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 10);  //mosi = r/c filter, sck = pin8, enable = pin10


mosi is no longer declared as it is tied to the sck line.

One perameter that you need to watch is this line in lcd2wire.h:

Code: [Select]

#define LCD2WIRE_DLY    50      //lcd2wire's delay. Much be 5x or more of the rc's time constant to ensure reliable operations


It determines how long, in us, the code will wait to bring up / down the sck line to send a '1'/'0'. The code, as written, takes about 4us to send a '1' - and the sck line dips to about 3.5v on a 100k/110p filter (time constant of 11us), below my comfort for a logic '1'.

I didn't adjust the code as it worked on my first try. But you really want the rc filter's constant to be at least 5x that required to send a logic '1' (aka you want a r/c filter in this case similar to 100k/220p). And you want LCD2WIRE_DLY to be another 5x of that -> 100us.

Again, it works with all shift registers.

Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 02:01 pm
Again, some performance data using bill's code:

[all deleted]
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 02:24 pm
You can use the same technique to reduce it to 1-wire. But you will find quickly that the transmission slows down exponentially.

Here is the demo code that I used to run bill's benchmark.

[all deleted]
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: nickgammon on Nov 24, 2012, 09:09 pm
@dhenry:

You can have two attachments in a single post.

Alternatively, the attachment can be a .zip file, so related files can be logically grouped. Then you could include a readme.txt file as well to help explain how to use the files.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Nov 24, 2012, 09:50 pm
dhenry,
The demo code you published is clearly from the LCDispeed sketch I posted.
You did give a slight attribution in the post; however,
LCDispeed is an open source copyrighted and licensed work under creative commons license 3.0
( CC BY-NC-SA 3.0 )

It was not intended to be freeware.
As such, please remove the posting, or at a minimum
restore the original copyright message from the original work.
(Restoring the entire leadin comment would be better)

I will also update the code to more clearly indicate the licensing and attribution requirements
which requires preserving the original copyright and attribution messages in derivative versions of the code.

--- bill
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 24, 2012, 10:16 pm
Not a problem, Bill. I have the post deleted, as well as the performance data generated by the code: I do not like including any copyright statement in my code, and don't like to publish performance data if I cannot publish the code.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Nov 24, 2012, 10:58 pm

Not a problem, Bill. I have the post deleted, as well as the performance data generated by the code: I do not like including any copyright statement in my code, and don't like to publish performance data if I cannot publish the code.


In this case it is not *your* code.
And while the license does have some restrictions, it does not restrict you from modifying or publishing the code.
It merely prevents you from removing or modifying the original licensing agreement and attribution
that comes with the code.

When the lead in header comment was removed,
the license agreement was removed, and essentially changed the code to "free ware"
with no attribution to the original author which is not permitted under the licensing terms.

While many people often violate these types of licensing terms,
these type of restrictions exist in pretty much all open source code.

--- bill
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 25, 2012, 02:59 am
That's why I cannot afford to use those "free" stuff, :)
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: nickgammon on Nov 25, 2012, 03:15 am
Which library did you base it on? The one on this page:

http://arduino.cc/en/Reference/LiquidCrystal

... has this license:

Quote
The text of the Arduino reference is licensed under a Creative Commons Attribution-ShareAlike 3.0 License. Code samples in the reference are released into the public domain.


Here is that license: http://creativecommons.org/licenses/by-sa/3.0/

What is onerous about it? You can copy, adapt and make commercial use of it. You just have to attribute the original author, and if you redistribute, keep the terms of the license on the copies.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Nov 25, 2012, 03:48 am
Nick,
The licensing comments were actually with respect to a sketch not the library code.

With respect to the LiquidCrystal library, it is one of the worst examples in the Arduino libraries
with respect to copyright/licensing/attribution.
Yes there is a small notice on the Arduino LiquidCrystal site referring to Creative Commons licensing,
But there is nothing in the actual source files, not even a reference in the source back to the
Arduino LiquidCrystal web page.

So as far as the actual LiquidCrystal library source goes, the LiquidCrystal code and examples
appear to be distributed as free-ware since there are no copyright or licensing notices.
i.e. someone receiving the library source code in its original form, would have no idea
of any sort of associated copyrights or licensing or even who the author is
from looking at the source code which all they would have.

The authors have failed to take even basic steps to preserve their copyrights
by not including the necessary information in the source code itself
or some sort of readme or license file that is distributed with the source.

Nearly all the other libraries have properly properly delt with this.
wifi and stepper are close runners up to LiquidCrystal in that they don't have
proper copyright and licensing notices.

Most of the others are LGPL 2.1 but SD may be a bit of a surprise for many people in that
it is GPL v3 which requires that any source that use it be open source.
This means that the SD library cannot be used in closed source projects/products.
While I like this, many folks do not.

--- bill
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 25, 2012, 04:08 am
Quote
The authors have failed to ...


Or maybe they just don't care: if I were to release something into the public, I don't want to put any restrictions on anyone using it - or I wouldn't have released to the public.

If I cannot use a piece of code in a way that I see fit, I have no use for it: I am not in the business of furthering someone's claims on anything.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: nickgammon on Nov 25, 2012, 04:12 am
The stuff I release states that you can use it as you see fit. You just can't claim that you wrote it, nor apply extra restrictions on it that I didn't have.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Nov 25, 2012, 04:15 am

Quote
The authors have failed to ...


Or maybe they just don't care:

That is my guess as well. But that would be in conflict with the Creative Commons licensing mentioned
on the Arduino LiquidCrystal page.

Quote

If I cannot use a piece of code in a way that I see fit, I have no use for it: I am not in the business of furthering someone's claims on anything.

Hmmm. I guess that means you have no use for Arduino or any open source tools like gcc and its associated tools.  :D
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 25, 2012, 02:33 pm
Quote
I guess that means you have no use for Arduino or any open source tools like gcc and its associated tools.


True, if I cannot use a piece of code in a way that I see fit.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 25, 2012, 07:21 pm
Here is probably an extreme implementation of a 2-wire interface. I wrote this to really show the limitations of such a concept in a multi-pin environment.

Here is the code.

Code: [Select]
/*
Demo code to run lcd1602 with two io pins
Code runs on 1MIPS avr / gcc-avr

Parts required:
- 1 mcu (avr for now but portable to ther chips) running at 1MIPS. Need adjustments to other frequencies
- two pins (digital outs)
- 4 rc filters: 2 x 1k resistors, 1 x 10k resistor, 1 x 100k resistor and 4x 110n capacitors
- supports hd44780-compatible lcds, up to 4 lines. lcd in 4-bit mode, no reading of busy flag

Connection:
- RC filters: from a mcu pin to a resistor and then capacitor to ground. a lcd pin is connected to the joint of the capacitor + resistor.
- RS: connected to the ENABLE pin via a 1k/110n filter.
- RW: tied to ground
- ENABLE: connected to the mcu pin designated as LCD_EN. No rc filter
- D0..3: not connected
- D4: connected to the mcu pin designated as LCD_Ds
- D5: connected to D4 via a 1k/110n filter
- D6: connected to D4 via a 10k/110n filter
- D7: connected to D4 via a 100k/110n filter

Disclaimer: no warranty of any kind made. Use at your own risk.
*/

#include <avr/io.h> //gcc-avr

//pin configuration
#define LCD_EN D8 //defined to D8
#define LCD_Ds D9 //defined to D9
#define DLY_min 1 //delays to send D4/en. determined by your rc time constant
#define DLY_x 10ul //delay multipliers. "ul" for possible overflow. 10x to match the 10x ratio for rc constants

#define LCD_RS LCD_EN //en / rs the same pin
#define DLY_D4 (DLY_min) //delays to send D7. In cycles
#define DLY_D5 (DLY_D4 * DLY_x) //delays to send D7. In cycles
#define DLY_D6 (DLY_D5 * DLY_x) //delays to send D7. In cycles
#define DLY_D7 (DLY_D6 * DLY_x) //delays to send D7. In cycles

typedef struct {
unsigned char B0: 1;
unsigned char B1: 1;
unsigned char B2: 1;
unsigned char B3: 1;
unsigned char B4: 1;
unsigned char B5: 1;
unsigned char B6: 1;
unsigned char B7: 1;
} BIT8_T;

//extend bit fields
#if defined (PORTA)
#define PORTAbits (*(volatile BIT8_T *)&PORTA)
#define DDRAbits (*(volatile BIT8_T *)&DDRA)
#define PINAbits (*(volatile BIT8_T *)&PINA)
#endif

#if defined (PORTB)
#define PORTBbits (*(volatile BIT8_T *)&PORTB)
#define DDRBbits (*(volatile BIT8_T *)&DDRB)
#define PINBbits (*(volatile BIT8_T *)&PINB)
#endif

#if defined (PORTC)
#define PORTCbits (*(volatile BIT8_T *)&PORTC)
#define DDRCbits (*(volatile BIT8_T *)&DDRC)
#define PINCbits (*(volatile BIT8_T *)&PINC)
#endif

//helper functions. don't call directly
#define _DDR(id, pin) DDR ## id ## bits.B ## pin
#define _PORT(id, pin) PORT ## id ## bits.B ## pin

//port functions
#define DDR(pin) _DDR(pin)
#define PORT(pin) _PORT(pin)

//arduino compatibility
#define OUTPUT 1 //set a pin to output
#define INPUT 0 //set a pin to input
#define HIGH 1 //set a pin
#define LOW 0 //clear a pin
//pin macros
#define pinMode(pin, mode) _DDR(pin) = (mode)
#define digitalWrite(pin, val) _PORT(pin) = (val)
//pin definitions
#define D8 B, 0 //pin D8 on B.0
#define D9 B, 1 //pin D9 on B.1

#define NOP() asm("NOP")

//set cursor to row/col
#define lcd_goto(row, col) lcd_write(0, 0x80 | (row) | (col));
#define LCD_LINE0 0x00 //lcd display line0
#define LCD_LINE1 0x40 //lcd display line1
#define LCD_LINE3 0x14 //lcd display line2
#define LCD_LINE4 0x54 //lcd display line3

//delay routines
void lcd_delay(unsigned long cycles) {
while (cycles--) NOP();
}

//write 8-bits to the lcd in 4-bit mode
void lcd_write(unsigned char rs, unsigned char dat) {
//send the highest 4 bits. RS has the same rc filter as D6
if (dat & 0x80) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D7);
if (dat & 0x40) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D6);
if (rs)         digitalWrite(LCD_RS, HIGH); else digitalWrite(LCD_EN, LOW);
if (dat & 0x20) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D5);
//like D4, EN has no rc filter
if (dat & 0x10) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW);
digitalWrite(LCD_EN, HIGH);
//lcd_delay(DLY_D4); in case you need it
digitalWrite(LCD_EN, LOW); //strobe out data

//send the lowest 4 bits
//send the highest 4 bits. RS has the same rc filter as D6
if (dat & 0x08) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D7);
if (dat & 0x04) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D6);
if (rs)         digitalWrite(LCD_RS, HIGH); else digitalWrite(LCD_EN, LOW);
if (dat & 0x02) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D5);
//like D4, EN has no rc filter
if (dat & 0x01) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW);
digitalWrite(LCD_EN, HIGH);
//lcd_delay(DLY_D4); in case you need it
digitalWrite(LCD_EN, LOW); //strobe out data
}

//initialize the lcd
void lcd_init(void) {
//set the pin modes
pinMode(LCD_EN, OUTPUT);
pinMode(LCD_Ds, OUTPUT);

//initialize the pins
digitalWrite(LCD_EN, LOW);
digitalWrite(LCD_Ds, LOW);

//send the initialize sequence
lcd_write(0, 0x33); //send 1 of 3 0b0011
lcd_write(0, 0x32); //send 2+3 of 3 0b0011, 4-bit mode
lcd_write(0, 0x28); //4bit 2 lines
lcd_write(0, 0x01); //clear display
lcd_delay(1000); //wait for 2ms - need additional work
lcd_write(0, 0x0c); //display on, cursor off, no blinking
}

//send string
void lcd_str(unsigned char *str) {
while (*str) lcd_write(1, *str++); //send display data
}

//ends lcd2wire driver

//demo code starts here
void setup(void) {
lcd_init();
}

void loop(void) {
//display 1st line
lcd_goto(LCD_LINE0, 0); //start in col 0
lcd_str("abcdefg");

//display 2nd line
lcd_goto(LCD_LINE1, 2); //start in col 2
lcd_str("0123456");
}

//stylized main
int main(void)
{

    // Insert code

setup(); //reset the mcu
while(1) {
loop(); //looping around
}

return 0;
}
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 25, 2012, 07:25 pm
Unlike earlier 2-wire implementation show, this one requires no shift registers. Just two IO pins + 4 rc filters (4x110n capacitors, 2x1k resistors, 1x10k resistor and 1x100k resistor). The capacitors are chosen so that parasitics is minimized for real life reliability.

The code is really written around 1 function, lcd_write(). That's where all the timing stuff is implemented.

lcd_init() calls on lcd_write() to bring the lcd out of its power-on state.

As the timing gets expotentially slower for a 2-wire system, this implementation is very slow: about 2 seconds for each line of display. You can literally see the characters showing up one at a time, :).

Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Nov 25, 2012, 07:31 pm
As is, the code is actually written in gcc-avr (the compiler under-neath arduino).

However, it implemented a digital IO subsystem whose calling convention is identical to Arduino's, but much more efficient, much faster, more portable and more flexible - it can be easily ported to any mcu while retaining the arduino calling convention.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: Mike-K8LH on Jan 13, 2013, 03:17 pm

Connection:
MOSI pin: from the mcu to shift register's data pin (pin1+2 on HC164 and pin 14 on HC595). LCD's RS pin tied to this line as well.
SCK pin: from the mcu to shift register's serial clock pin (pin 8 on HC164 and pin 11 on HC595)
Enable pin: from the mcu to the lcd.


What about the RCK line (pin 12) on the HC595?  Anything connected to that?  A schematic for your 3wire HC595 interface would be welcome.

Quote
Advantages over other 3-wire solutions:
1) It allows 8-bit as well as 4-bit mode.
2) It uses no parts other than the shift register.
3) It allows HC164 as well as HC595.


Are there any example 3-wire interfaces that drive an LCD in 8-bit interface mode, please?  I designed one that uses 3 pins on a 74HC164 but I was wondering if there were any others?

TIA, Mike
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Jan 13, 2013, 03:55 pm
Quote
What about the RCK line (pin 12) on the HC595?


Connect it to the Enable line: those people who designed HD44780 are incredibly smart and with tons of foresight, :)
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: Mike-K8LH on Jan 13, 2013, 04:19 pm

Quote
What about the RCK line (pin 12) on the HC595?


Connect it to the Enable line: those people who designed HD44780 are incredibly smart and with tons of foresight, :)



Ahhh!  Oooo!  Nice!  Haven't seen that one anywhere before...  So something like the second drawing below.  That's much better than what I came up with a couple days ago which requires an extra clock pulse.

Thank you Sir.  Cheerful regards, Mike
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Jan 13, 2013, 05:07 pm
Yes.

In the code I wrote earlier, I forced a particular connection (Q0 -> D0, Q1 -> D1, etc.) With a little modification, you can remap those pin connections to your own liking, providing additional flexibility for your layout guy.

BTW, what's the capture tool you used there? Pretty refreshing.
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: Mike-K8LH on Jan 14, 2013, 10:42 am
The drawings were done using the drawing tools in MS Excel.

Hey, I added an RC integrator and tested a 2-pin 8-bit design but I'm a PIC guy (sorry).  Anyway, if you're interested, I've attached a shot of the board just moments after it came up (first try!) and another couple shots of the board before I added the RC parts.  The host is a PIC18F14K22 (16 MHz) with an assembly language program. The low level LCD driver uses 28 words of program memory and is isochronous.  With delays for the RC tau of 3.3 microseconds, it takes precisely 83.75 microseconds to send each byte to the LCD.

Cheerful regards, Mike
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: bperrybap on Jan 14, 2013, 06:58 pm
Mike,
If you are wanting a 2 pin design, you can get significantly better throughput using an and gate instead of an RC
network to multiplex the pins. The and gate can be created using a resistor and a diode.
When doing that, I was able to get 76us per byte transfer on a 16 mhz AVR.
And that 76 us is the full overhead from an Arduino sketch going through the Print class then down through the library
sending the byte to the display and returning back to the sketch.
The library is also using 4 bit mode which requires sending 2 nibbles and because of the and gate you have
clear the SR before each nibble transfer.
The code I used the SR2W code in this library:
https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home (https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home)
(I am the author of the SR2W interface code)
You can see a diagram of how the SR is wired up in LiquidCrystal_SR2W.h

Since you are a PIC guys here is a link to a page that is doing essentially the same thing
for PIC:
http://www.rentron.com/Myke1.htm (http://www.rentron.com/Myke1.htm)

From an overall design perspective I think the AND gate multiplexor is better
than the RC network multiplexor even though it requires 4 times the number of shifts to the SR
since it is faster and allows backlight control.
It also has not timing critical sections so interrupts won't ever have to be masked
while doing the transfers.
Cost wise, it should be nearly the same resistor & diode vs resistor and cap.
Then for a few additional pennies you can add backlight control.
The backlight control does require some dampening on the transistor input to remove
the flicker because of live output bits on the SR during shifting.

You can see a sample backlight circuit in the SR2W header file.

--- bill
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: Mike-K8LH on Jan 14, 2013, 07:40 pm
Thanks for all the great info', Bill...  I'm not really interested in 2-pin designs, per se, or simply driving an LCD, per se.  I think there may be more possible applications for an 8-bit shift register + latch than just simply duplicating Myke Predko's old 74HC174 2-wire LCD interface (grin).  Anyway, I'm having fun discovering different clever, creative, and elegant design solutions...  
Title: Re: LCD3Wire: a 3-wire driver for HD44780-based LCDs
Post by: dhenry on Jan 15, 2013, 12:19 am
Quote
The low level LCD driver uses 28 words of program memory and is isochronous. 


Excellent. The advantage of coding in assembly.

Quote
With delays for the RC tau of 3.3 microseconds, it takes precisely 83.75 microseconds to send each byte to the LCD.


As long as your refresh time for a line is within 5 - 6 ms, you are fine.

A word of caution: the r/c approach is of no use in a noisy environment. As a matter of fact, I would recommend that you perform periodic software reset of the display, in case it goes out of sync.