LCD3Wire: a 3-wire driver for HD44780-based LCDs

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:

//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

The 2nd file you will need is lcd3wire.cpp:

#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();
}

Here is the user file - a copy from the LiquidCrystal demo, with minor modifications:

// 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.

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.

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.

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

LCDiSpeed.pde.zip (2.92 KB)

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.

  • Vcc
  • Gnd
  • D0 to D7 (8 pins)
  • RS
  • EN
  • RW
  • Contrast adjust
  • Backlight power
  • Backlight ground

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?

Some rumbers running bill's code:

[all deleted]

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.

Also, the code works for 4015 and 4094, thanks in part to liquidcrystal's slow speed.

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

Here is where the speed bottleneck was for the LC library:

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.

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

fm's writenbit():

// 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.

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

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

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:

lcd2wire.cpp (9.26 KB)

lcd2wire.h:

lcd2wire.h (3.58 KB)

the calling convention is almost identical except the declaration:

// 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:

#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.

Again, some performance data using bill's code:

[all deleted]

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]

@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.

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