The 16x2 LCD with I2C interface is connected to Arduino Nano (ATmega328P) and programmed without Arduino libraries.
Components List:
- Arduino Nano
- 16x2 LCD with I2C interface - NXP PCF8574T
- LM35 Temperature sensor
- Resistors
Temperature sensor is powered by Nano and its output is connected to an analog pin. Without additional setup, only positive temperature is read by the sensor (2 to 150 degC).
The sensor output is a voltage signal, where each 10mV represents a degree Celsius (degC).
Vout (mV) = 10 (mV) * degC
degC = Voltage(mV) / 10mV
Arduino has 10-bit ADC and is operating on 5V. Hence each digital value represents a voltage of:
Voltage = 5 / 1024 = 0.0048V, to mV: 5 / 1024 * 1000 = 4.8 mV
To get temperature: degC = (5 / 1024) * (1000 / 10) = 500 / 1024 degC
LCD interface that normally takes at least 11 I/O pins can be reduced to 2 pins using the NXP PCF8574T 8-bit I/O expander. With the expander the LCD now can communicate with Nano via I2C interface using only 2 lines - SDA (data line) and SCL (clock), along with 5V and Gnd connection. Use pull up resistors in the I2C interface to prevent busy state.
Tip: Make sure that the jumper on the I/O expander board is connected to enable the backlight. Then the display pixels can be turned ON and or their brightness can be controlled with the potentiometer on board.
Check out the PCF8574 datasheet for detailed explanation on I2C interface rules to follow for this setup.
Below is how the I/O expander and LCD pins are connected:
PCF8574 only has 8 data pins and LCD control needs at least 11 pins including 8 data pins, Register select (RS), Read/Write (RW), Enable pin (EN). So the LCD is operated in 4-bit mode - first sending upper nibble and then the lower nibble, both ORed with RS, RW and EN control bits.
Programming:
There is a dedicated I2C pin available in Arduino (Nano - A4, A5) which are used by respective internal I2C registers for performing I2C protocol (TWI). User has to configure and provide data like clock frequency, slave address, data to transfer, etc. to use the feature.
When using TWI, normally the SDA and SCL pins are a open drain circuit hence we connect resistors between the data lines and 5V. Instead internal built in resistors can also be used by writing '1' to the pins:
digitalWrite(SDA, HIGH);
digitalWrite(SCL, HIGH);
Bit rate calculation:
SCL frequency = CPU clock frequency of Nano / 16 + 2(TWBR) x Prescale value
Serial clock speed (SCL) required for I2C interface - NXP PCF8574T is 100KHz
16 + 2(TWBR) x Prescale value = 16MHz / 100KHz
2(TWBR) x Prescale value = 160 - 16
TWBR x Prescale value = 144 / 2
TWBR x (1) = 72
TWBR = 72
If you are generating the I2C logic without the use of the TWI registers, any 2 I/O pins in Arduino Nano configured as output can be used as SDA and SCL. Even analog pins in Arduino can be used as digital pins with digital Read/Write functions.
Find slave address using I2C scanner program from libraries or refer the I2C interface controller datasheet.
I2C has 7-bit or 10-bit address configuration. When using 7-bit configuration, up to 127 devices can be connected. Here, to use the slave address in I2C protocol, convert 8-bit address to 7-bit (shift << 1) and add (| OR) a Read/Write bit to the value.
Arduino IDE code:
1. Using 2-wire interface - TWI (ATMega328P datasheet)
/* 2-wire interface (TWI):
* TWEN = 1 to enable I2C / TWI interface
* TWSTA = 1 - Eable Start condition
* TWSTO = 1 - Enable Stop condition
* TWDR - contains data to transfer
* TWINT - Flag to be cleared by setting it to '1' after writing data into registers like TWDR,
to continue TWI
* TWSR - Status register - Indicates Start completed, ACK status,.,
*/
#define TSens A3
#define SDA A4 // Built-in SDA I2C pin
#define SCL A5 // Built-in SCL I2C pin
#define LCDaddr 0x27 // NXP PCF8574T slave address
#define I2c_write_LCDcmd(x) I2c_write(x, 0x0C, 0x08)
#define I2c_write_LCDdata(x) I2c_write(x, 0x0D, 0x09)
float deg, val;
void I2C_init()
{
TWBR = 72; // Bit rate
TWSR = TWSR & (0xFC); // Prescale value - bit 0,1 (0, 0 = value of 1)
TWCR |= (1 << TWEN);
}
inline void Clear_Start()
{
TWCR ^= (1 << TWSTA);
delay(1); // delay to give TWI time to finish current action
}
void I2c_datawrite(unsigned char d)
{
TWDR = d;
TWCR |= (1 << TWINT) ;
delay(1); // delay to give TWI and LCD-en to finish current action
/*if((TWSR & 0xFC) == 0x18)
Serial.println("SLA+W sent and ACK received");
if((TWSR & 0xFC) == 0x28)
Serial.println("Data byte sent and ACK received");*/
}
void I2c_write(unsigned char d, unsigned char a, unsigned char b)
{
TWCR |= (1 << TWSTA) | (1 << TWINT) ; // START condition
Clear_Start();
/*if((TWSR & 0xFC) == 0x08)
Serial.println("Start Transmitted");*/
I2c_datawrite(LCDaddr << 1); // 0th bit will always be zero in ATMega328P
/* data write */
I2c_datawrite(0x00);
I2c_datawrite((d & 0xF0) | a);
I2c_datawrite((d & 0xF0) | b);
I2c_datawrite(((d & 0x0F) << 4) | a);
I2c_datawrite(((d & 0x0F) << 4) | b);
/* data write */
TWCR |= (1 << TWSTO) | (1 << TWINT) ; // STOP condition
delay(10);
}
void LCD_init()
{
delay(20);
I2c_write_LCDcmd(0x01);
I2c_write_LCDcmd(0x02);
I2c_write_LCDcmd(0x28);
I2c_write_LCDcmd(0x0C);
I2c_write_LCDcmd(0x80);
}
void inttochar(int d, char * q)
{
q[4] = d % 10 + 48;
q[3] = (d / 10) % 10 + 48;
q[2] = '.';
q[1] = (d / 100) % 10 + 48;
q[0] = d / 1000 + 48;
}
void setup() {
//Serial.begin(9600);
pinMode(TSens, INPUT);
I2C_init();
LCD_init();
}
void loop() {
int i; char p[12] = {'T', 'E', 'M', 'P', ' ', '=', ' '};
val = analogRead(TSens);
deg = (val * 500) / 1024;
I2c_write_LCDcmd(0x80);
for(i = 0; i <= 6; i ++)
I2c_write_LCDdata(p[i]);
inttochar(deg * 100, &p[7]);
for(; i < 12; i ++)
I2c_write_LCDdata(p[i]);
}
2. Without TWI interface - Using digital output pins as SDA and SCL
/* I2C: start - slave address + R/W + ACK - data + ACK - stop */
#define TSens A3
#define SDA 6
#define SCL 7
#define LCDaddr 0x27 // NXP PCF8574T slave address
float deg, val;
void I2c_start()
{
digitalWrite(SCL, HIGH);
digitalWrite(SDA, HIGH);
digitalWrite(SDA, LOW);
}
void I2c_stop()
{
digitalWrite(SDA, LOW);
digitalWrite(SCL, HIGH);
digitalWrite(SDA, HIGH);
}
void I2c_ACK()
{
digitalWrite(SCL, LOW);
digitalWrite(SDA, HIGH);
digitalWrite(SCL, HIGH);
digitalWrite(SCL, LOW);
}
void I2c_bytewrite(unsigned char d) // Sending MSB first
{
unsigned char i;
for(i = 0; i < 8; i ++)
{
digitalWrite(SCL, LOW);
digitalWrite(SDA, ((d & (0x80 >> i)) ? HIGH : LOW));
digitalWrite(SCL, HIGH);
}
I2c_ACK();
}
void I2c_write_LCDcmd(unsigned char d)
{
I2c_start();
I2c_bytewrite((LCDaddr << 1) | 0); //8 to 7 bit | last bit R/W
/* data write */
I2c_bytewrite(0x00); //I2c_send configure port pins as outputs
I2c_bytewrite((d & 0xF0) | 0x0C); //4-bit upper nibble data | Light(1)-en(1)-rw(0)-rs(0)
delay(2);
I2c_bytewrite((d & 0xF0) | 0x08); //en(0)
delay(1);
I2c_bytewrite(((d & 0x0F) << 4) | 0x0C); //4-bit lower nibble data | Light(1)-en(1)-rw(0)-rs(0)
delay(2);
I2c_bytewrite(((d & 0x0F) << 4) | 0x08); //en(0)
delay(1);
/* data write */
I2c_stop();
delay(10);
}
void LCD_init()
{
delay(20); //NXP PCF8574T stat up time
I2c_write_LCDcmd(0x01); //Clear screen
I2c_write_LCDcmd(0x02); // Return home addr
I2c_write_LCDcmd(0x28); // Select both line of display - 4 bit
I2c_write_LCDcmd(0x0C); //display on, cursor off
I2c_write_LCDcmd(0x80); //Select DDRAM
}
void I2c_write_LCDdata(unsigned char d)
{
I2c_start();
I2c_bytewrite((LCDaddr << 1) | 0);
/* data write */
I2c_bytewrite(0x00);
I2c_bytewrite((d & 0xF0) | 0x0D);
delay(2);
I2c_bytewrite((d & 0xF0) | 0x09);
delay(1);
I2c_bytewrite(((d & 0x0F) << 4) | 0x0D);
delay(2);
I2c_bytewrite(((d & 0x0F) << 4) | 0x09);
delay(1);
/* data write */
I2c_stop();
delay(10);
}
void inttochar(int d, char * q) //4-digits with 2 decimal values
{
q[4] = d % 10 + 48;
q[3] = (d / 10) % 10 + 48;
q[2] = '.';
q[1] = (d / 100) % 10 + 48;
q[0] = d / 1000 + 48;
}
void setup() {
pinMode(TSens, INPUT);
pinMode(SDA, OUTPUT); // using A4 as SDA generator
pinMode(SCL, OUTPUT); //using A5 as SCL generator
LCD_init();
}
void loop() {
int i; char p[12] = {'T', 'E', 'M', 'P', ' ', '=', ' '};
val = analogRead(TSens); // LM35
deg = (val * 500) / 1024;
I2c_write_LCDcmd(0x80); //Select first position each time
for(i = 0; i <= 6; i ++)
I2c_write_LCDdata(p[i]);
inttochar(deg * 100, &p[7]); //LCD works with ASCII values
for(; i < 12; i ++)
I2c_write_LCDdata(p[i]);
}
For getting started with STM32, click here
Comments
Post a Comment