/* LiquidCrystal.cpp - Modified version revised by Donald Weiman, 13 May 2009. I have just identified a problem with my modifications to LiquidCrystal.cpp. The problem arises from the fact that the constructor either clears or sets the _four_bit_mode flag as soon as the library is invoked. On the other hand the LCD controller always starts out in the 8-bit mode and it takes four commands, sent in the 8-bit mode, to switch it to the 4-bit mode. My solution was to temporarily clear that flag for those first four instructions and then set it back. Unfortunately this causes an error in the utilization of the _data_pins array because it was set up by the four bit constructor to deal with 4 elements. During my first four commands, while the send() function is working in the 8-bit mode, that function assumes that the array has 8 elements. I understand that this misuse of arrays will not cause compiler errors but will cause run time errors. What we need to do for those first four instructions in the 4-bit initialization is to use only the first half of the 4-bit part of LiquidCrystal::send(). That is, put the 4 high bits on the data lines, pulse the Enable pin, then quit. The proper way to do this would involve either a new function or a new flag. I have chosen to use a less elegant soultion, in-line code, in keeping with my limited experience with C++ in general and libraries in particular. ====================================================================================================== LiquidCrystal.cpp - Modified by Donald Weiman, 12 April 2009. Some comments regarding delay() and delayMicroseconds() added on 14 April 2009. These modifications are intended to address two problems with the LiquidCrystal library. (1) The original library relies on the LCD controller being initialized by its internal reset circuitry. (2) The original library does not implement some of the recommended time delays. NOTE: I have used delayMicroseconds() for some of the longer time delays where delay() would be more appropriate. This is because because delay() doesn't seem to work for me when used in this library although it does work when used in the sketch. I believe that at least one other person has noticed a similar problem. I have tried to implement these changes so that previously written sketches (and LiquidCrystal.h) do not have to be modified. You should be able to simply replace your original LiquidCrystal.cpp file with this one, delete your current LiquidCrystal.o, and then recompile your sketch. I don't have too many different LCD display devices and I am really a beginner in C++ programming so I would really appreciate any feedback. Do so via the forum (I'm 'floresta') or Email me (weimandn at alfredstate dot edu) and don't worry about hurting my feelings. */ #include "LiquidCrystal.h" #include #include #include #include "WProgram.h" /* (DW) The following comments are applicable only if the 'Initialization by Internal Reset Circuit' was successful. Success is based upon the characteristics of the power supply. Most datasheets recommend using 'Initializing by Instruction', although some do not even mention it. */ // 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 // /* (DW) The following comment is absolutely correct. It emphasizes yet another reason to use 'Initializing by Instruction', which will result in this problem going away. */ // 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). /* (DW) This constructor is used for the 8-bit mode. It has been revised to use the 'Initializing by Instruction' technique recommended in most datasheets. The recommended time delays have also been implemented even though they might be satisfied by the time it takes the host processor to execute this program code. */ LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : _four_bit_mode(0), _rs_pin(rs), _rw_pin(rw), _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(_rs_pin, OUTPUT); pinMode(_rw_pin, OUTPUT); pinMode(_enable_pin, OUTPUT); for (int i = 0; i < 8; i++) pinMode(_data_pins[i], OUTPUT); /* (DW) This is the start of the 'Initializing by Instruction' procedure that is recommended in most datasheets. */ // delay(15); // "Wait for more than 15 ms" delayMicroseconds(15000); // "Wait for more than 15 ms" // reset the LCD controller by sending a special case of 'Function Set' three times, with appropriate delays command(0x30); // (00110000) special case of 'Function Set', lower four bits are irrelevant // delay(5); // "Wait for more than 4.1 ms" delayMicroseconds(5000); // "Wait for more than 4.1 ms" command(0x30); // (00110000) special case of 'Function Set', lower four bits are irrelevant delayMicroseconds(100); // "Wait for more than 100 us" command(0x30); // (00110000) special case of 'Function Set', lower four bits are irrelevant delayMicroseconds(100); // this is a guess - the data sheets are ambiguous here // finish the initialization procedure command(0x38); // (00111000) 'Function Set', D=1, N=1, F=0 (8 bits, 2 line, 5x8 dots) delayMicroseconds(80); command(0x08); // (00001000) 'Display ON/OFF Control', D=0, C=0, B=0 (display off, cursor off, blink off) delayMicroseconds(80); command(0x01); // (00000001) 'Clear Display' (this instruction has no configurable bits) // delay(5); delayMicroseconds(5000); command(0x06); // (00000110) 'Entry Mode Set', I/D=1, S=0 (increment addresses, do not shift display) delayMicroseconds(80); /* (DW) This is the end of the 'Initializing by Instruction' procedure that is recommended in most datasheets. Now turn the display ON because the recommended initialization procedure left it OFF. */ command(0x0C); // (00001100) Display ON/OFF Control, D=1, C=0, B=0 (display on, cursor off, blink off) delayMicroseconds(80); /* (DW) This is the old code that is being replaced - NOTE: the first comment is wrong (1 line) although the 0x38 instruction is correct command(0x38); // function set: 8 bits, 1 line, 5x8 dots command(0x0C); // display control: turn display on, cursor off, no blinking command(0x06); // entry mode set: increment automatically, display shift, right shift clear(); */ } /* (DW) This constructor is used for the 4-bit mode. It has been revised to use the 'Initializing by Instruction' technique recommended in most datasheets. The recommended time delays have also been implemented even though they might be satisfied by the time it takes the host processor to execute this program code. */ LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) : _four_bit_mode(1), _rs_pin(rs), _rw_pin(rw), _enable_pin(enable) { _data_pins[0] = d0; _data_pins[1] = d1; _data_pins[2] = d2; _data_pins[3] = d3; pinMode(_rs_pin, OUTPUT); pinMode(_rw_pin, OUTPUT); pinMode(_enable_pin, OUTPUT); for (int i = 0; i < 4; i++) pinMode(_data_pins[i], OUTPUT); // delay(15); // "Wait for more than 15 ms" delayMicroseconds(15000); // "Wait for more than 15 ms" /* ======================================================================================================== (DW) This is the incorrect code used in my original modification _four_bit_mode = 0; // specify 8-bit mode command(0x30); // (00110000) special case of 'Function Set', lower four bits are irrelevant // delay(5); // "Wait for more than 4.1 ms" delayMicroseconds(5000); // "Wait for more than 4.1 ms" command(0x30); // (00110000) special case of 'Function Set', lower four bits are irrelevant delayMicroseconds(100); // "Wait for more than 100 us" command(0x30); // (00110000) special case of 'Function Set', lower four bits are irrelevant delayMicroseconds(100); // this is a guess - the data sheets are ambiguous here command(0x20); // (00100000) 'Function Set', switch to 4-bit mode (lower four bits are still irrelevant) delayMicroseconds(100); // this is a guess - the data sheets are ambiguous here _four_bit_mode = 1; // specify 4-bit mode =========================================================================================================== */ // (DW) The code between here and the dotted line should be redone using a new function or flag. digitalWrite(_rs_pin, LOW); // select the instruction register digitalWrite(_rw_pin, LOW); // data transfer direction is from uP to LCD controller // (00110000) special case of 'Function Set', lower four bits are irrelevant for (int i = 0; i < 4; i++) { digitalWrite(_data_pins[i], (0x30 >> (i + 4)) & 0x01); // put 4 high bits on the data lines } digitalWrite(_enable_pin, HIGH); // pulse the enable pin to transfer the high bits delayMicroseconds(1); digitalWrite(_enable_pin, LOW); // (00110000) special case of 'Function Set', lower four bits are irrelevant for (int i = 0; i < 4; i++) { digitalWrite(_data_pins[i], (0x30 >> (i + 4)) & 0x01); // put 4 high bits on the data lines } digitalWrite(_enable_pin, HIGH); // pulse the enable pin to transfer the high bits delayMicroseconds(1); digitalWrite(_enable_pin, LOW); // (00110000) special case of 'Function Set', lower four bits are irrelevant for (int i = 0; i < 4; i++) { digitalWrite(_data_pins[i], (0x30 >> (i + 4)) & 0x01); // put 4 high bits on the data lines } digitalWrite(_enable_pin, HIGH); // pulse the enable pin to transfer the high bits delayMicroseconds(1); digitalWrite(_enable_pin, LOW); // (00100000) 'Function Set', switch to 4-bit mode (lower four bits are still irrelevant) for (int i = 0; i < 4; i++) { digitalWrite(_data_pins[i], (0x20 >> (i + 4)) & 0x01); // put 4 high bits on the data lines } digitalWrite(_enable_pin, HIGH); // pulse the enable pin to transfer the high bits delayMicroseconds(1); digitalWrite(_enable_pin, LOW); // ........................................................................................................ command(0x28); // (00101000) 'Function Set', D=0, N=1, F=0 (4 bits, 2 line, 5x8 dots) delayMicroseconds(80); command(0x08); // (00001000) 'Display ON/OFF Control', D=0, C=0, B=0 (display off, cursor off, blink off) delayMicroseconds(80); command(0x01); // (00000001) 'Clear Display' (this instruction has no configurable bits) // delay(5); delayMicroseconds(5000); command(0x06); // (00000110) 'Entry Mode Set', I/D=1, S=0 (increment addresses, do not shift display) delayMicroseconds(80); /* (DW) This is the end of the 'Initializing by Instruction' procedure that is recommended in most datasheets. Now turn the display ON because the recommended initialization procedure left it OFF. */ command(0x0C); // (00001100) Display ON/OFF Control, D=1, C=0, B=0 (display on, cursor off, blink off) delayMicroseconds(80); /* (DW) This is the old code that is being replaced - NOTE: the first comment is wrong (1 line) although the 0x28 instruction is correct command(0x28); // function set: 4 bits, 1 line, 5x8 dots command(0x0C); // display control: turn display on, cursor off, no blinking command(0x06); // entry mode set: increment automatically, display shift, right shift clear(); */ } /* (DW) No major code changes have been made below here although some missing delays have been added and others have been modified. Some comments have been added or modified. */ // Clear the display and position the cursor at the beginning of the first line void LiquidCrystal::clear() { command(0x01); // (00000001) 'Clear Display', set cursor position to zero // delay(5); delayMicroseconds(5000); } // Position the cursor at the beginning of the first line without clearing the display void LiquidCrystal::home() { command(0x02); // (00000010) 'Return Home', set cursor position to zero without clearing the display // delay(5); delayMicroseconds(5000); } // Position the cursor at any position (col) on any line (row) void LiquidCrystal::setCursor(int col, int row) { int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 }; command(0x80 | (col + row_offsets[row])); // (1nnnnnnn) 'Set DD RAM Address' delayMicroseconds(80); } // Sends an instruction to the LCD module in either 8-bit or 4-bit mode void LiquidCrystal::command(uint8_t value) { send(value, LOW); } // Sends character data to the LCD module in either 8-bit or 4-bit mode void LiquidCrystal::write(uint8_t value) { send(value, HIGH); } // Sends a byte of data to the LCD module in either 8-bit or 4-bit mode as determined by the '_four_bit_mode' flag void LiquidCrystal::send(uint8_t value, uint8_t mode) { digitalWrite(_rs_pin, mode); // select the appropriate register digitalWrite(_rw_pin, LOW); // data transfer direction is from uP to LCD controller // set up the data pins if (_four_bit_mode) { for (int i = 0; i < 4; i++) { digitalWrite(_data_pins[i], (value >> (i + 4)) & 0x01); // put 4 high bits on the data lines } digitalWrite(_enable_pin, HIGH); // pulse the enable pin to transfer the high bits delayMicroseconds(1); digitalWrite(_enable_pin, LOW); for (int i = 0; i < 4; i++) { digitalWrite(_data_pins[i], (value >> i) & 0x01); // put 4 low bits on the data lines } digitalWrite(_enable_pin, HIGH); // pulse the enable pin to transfer the low bits delayMicroseconds(1); digitalWrite(_enable_pin, LOW); } else { for (int i = 0; i < 8; i++) { digitalWrite(_data_pins[i], (value >> i) & 0x01); // put 8 bits on the data lines } digitalWrite(_enable_pin, HIGH); // pulse the enable pin to transfer the 8 bits delayMicroseconds(1); digitalWrite(_enable_pin, LOW); } }