/* Hello Wokwi! by mr_piva
This example implements the I2C Protocol for communicating with
an LCD without using any library.
It considers the following pinout between the PCF8574T chip and the
HD44780 LCD Controller chip. The communication works in 4-bit mode.
See the HD44780 datasheet for additional information about LCD
commands and characters.
| PCF8574T Pin | HD44780 Pin |
| ------------ | ----------- |
| P7 | D7 |
| P6 | D6 |
| P5 | D5 |
| P4 | D4 |
| P3 | A | (backlight anode)
| P2 | E (enable) |
| P1 | R/W |
| P0 | RS |
If you are trying to learn how to implement the I2C Protocol, it's highly
recommended to add a Wokwi logic analyser to the SCL and SDA lines. Use
PulseView to see the results, adding an I2C protocol decoder.
*/
int CLK=3; // pin 3 is set for clock
int DTA=4; // pin 4 is set for data
int TIME=1; // time reference; increase it if you want to debug the code
/*
Let's set the address of the LCD: it's 0x27 (00100111), but we
should send it in 7 bits (0100111) plus an 0, that indicates
a write operation, resulting in 0x4E (01001110)
*/
byte ADR=0x4E;
void setup(){
// sets pins 3 (clock) and 4 (data) as outputs
pinMode(CLK, OUTPUT);
pinMode(DTA, OUTPUT);
// sets initial conditions of clock and data as 1 (high level)
digitalWrite(CLK, HIGH);
digitalWrite(DTA, HIGH);
// inicialization as Figure 24 - page 46 of HD44780U Hitachi datasheet
writeCommand(DTA,CLK,TIME,ADR,0x30); // wake up, LCD!
writeCommand(DTA,CLK,TIME,ADR,0x30);
writeCommand(DTA,CLK,TIME,ADR,0x30);
writeCommand(DTA,CLK,TIME,ADR,0x20); // sets 4 bits mode... so, all commands and data should be sent in 2 parts
writeCommand(DTA,CLK,TIME,ADR,0x20); // sets 2 lines LCD and 5x8 dots
writeCommand(DTA,CLK,TIME,ADR,0x80);
writeCommand(DTA,CLK,TIME,ADR,0x00); // turns the LCD on
writeCommand(DTA,CLK,TIME,ADR,0xC0);
writeCommand(DTA,CLK,TIME,ADR,0x00); // cleans the display
writeCommand(DTA,CLK,TIME,ADR,0x10);
writeCommand(DTA,CLK,TIME,ADR,0x00); // sets Entry Mode
writeCommand(DTA,CLK,TIME,ADR,0x60);
writeCommand(DTA,CLK,TIME,ADR,0x00); // sets DDRAM address 0 in address counter. Also
writeCommand(DTA,CLK,TIME,ADR,0x20); // returns display from being shifted to original position.
// write the message - remember the need to set the backlight anode (P3 of the PCF8574T)
writeCommand(DTA,CLK,TIME,ADR,0x49); // write "H"
writeCommand(DTA,CLK,TIME,ADR,0x89);
writeCommand(DTA,CLK,TIME,ADR,0x69); // write "e"
writeCommand(DTA,CLK,TIME,ADR,0x59);
writeCommand(DTA,CLK,TIME,ADR,0x69); // write "l"
writeCommand(DTA,CLK,TIME,ADR,0xC9);
writeCommand(DTA,CLK,TIME,ADR,0x69); // write "l"
writeCommand(DTA,CLK,TIME,ADR,0xC9);
writeCommand(DTA,CLK,TIME,ADR,0x69); // write "o"
writeCommand(DTA,CLK,TIME,ADR,0xF9);
writeCommand(DTA,CLK,TIME,ADR,0x29); // write " "
writeCommand(DTA,CLK,TIME,ADR,0x09);
writeCommand(DTA,CLK,TIME,ADR,0x59); // write "W"
writeCommand(DTA,CLK,TIME,ADR,0x79);
writeCommand(DTA,CLK,TIME,ADR,0x69); // write "o"
writeCommand(DTA,CLK,TIME,ADR,0xF9);
writeCommand(DTA,CLK,TIME,ADR,0x69); // write "k"
writeCommand(DTA,CLK,TIME,ADR,0xB9);
writeCommand(DTA,CLK,TIME,ADR,0x79); // write "w"
writeCommand(DTA,CLK,TIME,ADR,0x79);
writeCommand(DTA,CLK,TIME,ADR,0x69); // write "i"
writeCommand(DTA,CLK,TIME,ADR,0x99);
writeCommand(DTA,CLK,TIME,ADR,0x29); // write "!"
writeCommand(DTA,CLK,TIME,ADR,0x19);
}
void loop() {
// let's blink the backlight, just for fun...
writeCommand(DTA,CLK,TIME,ADR,0x00); // resets the backlight anode (P3 of the PCF8574T)
delay(500);
writeCommand(DTA,CLK,TIME,ADR,0x08); // sets the backlight anode (P3 of the PCF8574T)
delay(500);
}
// method for starting transmition - I2C protocol
void start(int d,int c,int t){
// c is the clock pin
// d is the data pin
// t is the time reference
digitalWrite(d, LOW);
delay(t/6);
digitalWrite(c, LOW);
delay(t/6);
}
// method for stoping transmition - I2C protocol
void stop(int d,int c,int t){
// c is the clock pin
// d is the data pin
// t is the time reference
digitalWrite(c, HIGH);
delay(t/6);
digitalWrite(d, HIGH);
}
// method for transmiting a bit 1 - I2C protocol
void writeOne(int d,int c,int t){
// c is the clock pin
// d is the data pin
// t is the time reference
digitalWrite(d, HIGH);
delay(t/6);
digitalWrite(c, HIGH);
delay(t/3);
digitalWrite(c, LOW);
delay(t/6);
digitalWrite(d, LOW);
}
// method for transmiting a bit 0 - I2C protocol
void writeZero(int d,int c,int t){
// c is the clock pin
// d is the data pin
// t is the time reference
digitalWrite(d, LOW);
digitalWrite(c, LOW);
delay(t/6);
digitalWrite(c, HIGH);
delay(t/3);
digitalWrite(c, LOW);
delay(t/6);
}
// method for transmiting an entire byte
void writeByte(int d,int c,int t,byte b){
// c is the clock pin
// d is the data pin
// t is the time reference
// b is the byte to be sent
// loop for transmiting the 8 bits
for(int i=8;i>=1;i--){
if((( b & (1 << (i-1) ) ) ? 1 : 0 )==0){
writeZero(d,c,t);
}
else{
writeOne(d,c,t);
}
}
// procedure for receiving the acknowledge bit from the LCD - I2C protocol
// NOTE: nothing is done with this bit, but you can verify it if you want
pinMode(d, INPUT);
delay(t/6);
digitalWrite(c, HIGH);
delay(t/6);
digitalWrite(c, LOW);
delay(t/6);
pinMode(d, OUTPUT);
delay(t/6);
}
// method for transmiting an entire command or data to the LCD
// NOTE: each command or data is sent in an entire start-stop I2C protocol cycle,
// but it's not mandatory
void writeCommand(int d,int c,int t,byte a,byte b){
// c is the clock pin
// d is the data pin
// t is the time reference
// a is the address of the LCD (0x4E)
// b is the byte to be sent
byte temp = b;
// sending the command or data WITHOUT to active the E (enable) line
start(d,c,t);
writeByte(d,c,t,a); // sends the address
writeByte(d,c,t,b); // sends the command or data
stop(d,c,t);
b = b | 0x04; // combining the command or data with the E (enable) line
delay(t);
// sending the command or data activating the E (enable) line
start(d,c,t);
writeByte(d,c,t,a); // sends the address
writeByte(d,c,t,b); // sends the command or data with the enable signal
stop(d,c,t);
delay(t);
// sending the command or data WITHOUT to active the E (enable) line again
start(d,c,t);
writeByte(d,c,t,a); // sends the address
writeByte(d,c,t,temp); // sends the command or data again
stop(d,c,t);
delay(t);
}