Header Ads

Header ADS

[M8CU]-[Nuvoton] Interface with I2C LCD

If you’ve ever tried connecting an LCD display, you’ve probably noticed it requires quite a few pins. Even when using 4-bit mode, you still need seven/six connections – which takes up about a lot of available digital I/O pins.

A better solution is to use an I2C LCD display. It only needs two pins that aren’t even part of the regular digital I/O pin set. Even better, these pins can be shared with other I2C devices. This makes your wiring much simpler and saves those valuable pins for connecting other sensors or components in your projects.

Hardware Overview

A typical I2C LCD display consists of two main parts: an HD44780-based character LCD display and an I2C LCD adapter. Let’s explore both of these components in more detail.

Character LCD Display

Character LCDs are specially designed to display letters, numbers, and symbols. A 16×2 character LCD, for example, can show 16 characters across each line, with two lines total.



If you look very closely at the screen, you’ll notice small rectangular boxes for each character position. Inside each of these rectangles is a grid of 5×8 tiny dots or pixels. These pixels light up in different patterns to form different letters, numbers, or symbols.

I2C LCD Adapter

The key component of this adapter is an 8-bit I/O expander chip called PCF8574. This clever chip converts the I2C data from your MCU into the parallel data that the LCD display needs to function.

PCF8574 Chip On I2C LCD

The adapter board also includes a small trimpot that lets you make fine adjustments to the display’s contrast.

I2C LCD Adapter Hardware Overview

You’ll notice there’s a jumper on the board that supplies power to the backlight. If you want to control how bright the backlight is, you can simply remove this jumper and connect an external voltage source to the header pin marked ‘LED’.

I2C Address of LCD

If you have multiple devices on the same I2C bus, you may need to set a different I2C address for the LCD adapter to avoid conflicts with other I2C devices.

For this purpose, the adapter comes with three solder jumpers/pads (labeled A0, A1, and A2). For a single LCD, you can leave these untouched; however, if you connect multiple I2C devices or LCDs, you’ll need to make sure each has a unique address by modifying these jumpers. You can change the address by shorting a jumper with a small blob of solder.

I2C Address selection jumpers on I2C LCD

An important thing to know is that different companies, like Texas Instruments and NXP Semiconductors, make the same PCF8574 chip. The I2C address of your LCD depends on which company manufactured the chip.

If your LCD has Texas Instruments’ PCF8574 chip:

According to Texas Instruments’ datasheet, the three address selection bits (A0, A1, and A2) are located at the end of the 7-bit I2C address register.

Texas Instruments PCF8574 I2C Address Register

Since there are three address inputs that can be either HIGH or LOW, there are eight possible combinations (2^3 = 8) of addresses.

All three address inputs are pulled HIGH by default using onboard pullup resistors. This gives the PCF8574 a default I2C address of 0x27.

When you short a solder jumper, you pull that address input LOW. If you were to short all three jumpers, the address would change to 0x20. So the range of possible addresses goes from 0x20 to 0x27.

You can set different I2C addresses according to this table:

I2C LCD Address Selection Jumper Table for TI

If your LCD has NXP’s PCF8574 chip:

According to NXP Semiconductors’ datasheet, the three address selection bits (A0, A1, and A2) are also located at the end of the 7-bit I2C address register. However, the remaining bits in the address register are different from the Texas Instruments chip.

NXP Semiconductors PCF8574 I2C Address Register

Just like with the Texas Instruments chip, there are eight possible address combinations since the three inputs can be either HIGH or LOW.

All three address inputs are pulled HIGH by default using onboard pullup resistors. This gives the PCF8574 a default I2C address of 0x3F.

When you short a solder jumper, you pull that address input LOW. If you were to short all three jumpers, the address would change to 0x38. So the range of possible addresses goes from 0x38 to 0x3F.

You can set different I2C addresses according to this table:

I2C LCD Address Selection Jumper Table for NXP

So the I2C address of your LCD is most likely 0x27 or 0x3F. If you’re not sure what your LCD’s I2C address is, don’t worry! There’s an easy way to figure it out, which you’ll learn about later in this tutorial.

I2C LCD Display Pinout

The I2C LCD Display has only four pins. The following is the pinout:


GND is a ground pin.

VCC is the power supply pin. Connect it to the external 5V power supply.

SDA is the I2C data pin.

SCL is the I2C clock pin.

Wiring an I2C LCD Display to an N76E/MS51

Connecting an I2C LCD is much simpler than connecting a standard LCD. You only need to connect four pins instead of many more.

  • First, connect the VCC pin to the 5V output and the GND pin to the ground.
  • Next, we need to connect the pins used for I2C communication.
  • The table below shows you all the pin connections you need to make:

I2C LCDN76E/MS51
VCC5V
GNDGND
SCLSCL (P1.3)
SDASDA (P1.4)

Adjusting The LCD Contrast

After you finish wiring the LCD to your MCU, you’ll need to adjust the contrast of the LCD display. Look for a small blue trimpot on the I2C adapter board – this controls the LCD’s contrast.

Now, power up your board. You should see the backlight glow up right away. Take a small screwdriver and gently turn the potentiometer knob. As you adjust it, you’ll start to see the first row of rectangles appear on the screen. These rectangles are where characters will show up. If you can see those rectangles clearly, congratulations—your LCD is working perfectly and is ready for you to display text!

Determining the I2C Address

Before we jump into the example code, you’ll need to know your LCD’s I2C address so you can properly communicate with it. As we mentioned earlier, your LCD’s I2C address is probably either 0x27 or 0x3F. You might find this address printed on a sticker that came with your LCD or listed in the product information. But don’t worry if you can’t find this information anywhere! There’s an easy solution – you can run a simple I2C scanner sketch that will help you discover the correct address automatically. Here’s how:

You can find this sketch in lib\N76_i2c\examples\I2C_scanDevice.c

Load the i2c_scanner sketch into your MCU.
/**
 * @file I2C_scanDevice.c
 * @author Van_BasTai (taivb.6dof@gmail.com)
 * @brief Nuvoton example code for I2C scan device
 * @version 0.1
 * @date 2021-02-03
 *
 * @copyright Copyright (c) 2021
 *
*/

#include <include.h>
#include <N76_uart0.h>
#include <N76_I2C.h>

void main(void)
{
    uint8_t i = 0;
    uint8_t error = 0;
   
    I2C_begin(); // start I2C
    UART0_begin(BAUD_115200); // setup uart0 baudrate
    while (1)
    {
        UART0_println("I2C scan device: ");
        for (i = 1; i < 127; i++)
        {
            error = I2C_beginTransmission(i);
            I2C_endTransmission();
            if (error == I2C_OK)
            {
                UART0_printNum(i, HEX);
                UART0_println(" ");
            }
        }
        UART0_println("");
        _delay_ms(2000);
    } // end while(1)
}

After uploading the sketch to your MCU, open the serial monitor and set it to 115200 baud. In a few moments, you should see the I2C address of your LCD display appear on the screen.


Be sure to write down this address somewhere safe. You’ll need it when we work with the LCD in later examples.

Basic Sketch – Hello World

With the hardware connected and the library, we can write a simple sketch to display text on the LCD. In this example, we’ll blink Hello world! in first line and N76E003 I2C LCD in second line.

Before you upload the sketch, you need to make two important changes to make it work with your specific LCD. You must enter the correct I2C address of your LCD (which we found earlier) and specify the display dimensions in the LCDI2C_begin() . If you’re using a 16×2 character LCD, enter 16 and 2; if you’re using a 20×4 character LCD, enter 20 and 4.

    // Initialize the LCD with I2C address 0x27, 16 columns, and 2 rows
    LCDI2C_begin(0x27, 16, 2);

Once you’ve made these changes, you’re ready to try the complete sketch:

/**
 * @file HelloWorld.c
 * @brief Example to display "Hello, World!" on the LCD
 * @version 1.0
 * @date 2025-04-25
 *
 * This example initializes the LCD and displays "Hello, World!" on the first row.
*/

#include <include.h>
#include <N76_uart0.h>
#include <N76_I2C.h>
#include <N76_i2c_lcd.h>

void main(void)
{
    // Initialize the LCD with I2C address 0x27, 16 columns, and 2 rows
    LCDI2C_begin(0x27, 16, 2);

    LCDI2C_setBacklight(true); // Turn on the backlight

    // Clear the LCD and set the cursor to the home position
    LCDI2C_clear();

    while (1)
    {
        // Display "Hello world!" on the first row
        LCDI2C_setCursor(0, 0);
        LCDI2C_print("Hello world!");
        _delay_ms(1000);

        // Clear the LCD
        LCDI2C_clear();

        // Display "N76E003 I2C LCD" on the second row
        LCDI2C_setCursor(1, 1);
        LCDI2C_print("N76E003 I2C LCD");
        _delay_ms(1000);

        // Clear the LCD
        LCDI2C_clear();
    }
}

After uploading this code to your MCU, this is what you should see on the screen:

Create and Display Custom Characters

Sometimes you may want to display special characters that aren’t part of the standard alphabet or numbers – for example, symbols like a smiley face, a degree symbol (°) for temperature readings, or fun icons like hearts, music notes, or arrows.

The good news is that HD44780 LCDs allow you to create up to 8 custom characters of your own design! As we learned earlier in this tutorial, each character on the LCD is displayed using a small grid of pixels arranged in a 5×8 pattern (5 pixels wide by 8 pixels tall). To create your own character, you’ll need to decide which of these tiny dots should be turned on and which should be off.

To create your custom character, you first need to make an 8-byte array in your code. Each byte in this array represents one horizontal row in your character, starting from the top row and going down to the bottom row. For each byte, you’ll use the bits (the 1s and 0s in binary) to indicate which pixels should be ON (1) and which should be OFF (0). Only the first 5 bits of each byte are used since the character is 5 pixels wide.

Once you’ve designed your character by setting up this array, you can use the createChar() function to store your custom character into the LCD’s CGRAM (Character Generator RAM), which is a special memory area designed just for holding custom characters.

Now, let’s go ahead and create some cool custom characters for your projects!

CGROM vs. CGRAM

All Hitachi HD44780 driver-based LCDs have two types of memory: CGROM (Character Generator Read-Only Memory) and CGRAM (Character Generator Random Access Memory).

CGROM is non-volatile memory, which means it keeps its data even when power is turned off. It stores predefined dot patterns for standard ASCII characters, such as letters, numbers, and common symbols. When you want to display an “A” on the screen, you send the code for “A” (which is 0x41 in hexadecimal), and the LCD controller looks up the dot pattern for “A” in its CGROM and displays it. This makes showing regular characters super quick and easy!

CGRAM, however, is volatile memory, so it loses its data when power is removed. This memory is flexible and lets you store custom dot patterns that aren’t part of the built-in set. For instance, you can design your own symbols, icons, or unique characters for your project. However, CGRAM has limited space – only 64 bytes total. On a standard 5×8 pixel LCD, this means you can only store 8 custom characters (since each character needs 8 bytes). If you’re using a 5×10 pixel LCD, you can only store 4 custom characters because each one needs more memory.

In summary, CGROM is read-only with fixed character patterns that can’t be changed, while CGRAM is writable, allowing you to create and save custom characters whenever you need them.

Custom Character Generator

Creating custom characters has never been easier! We’ve developed a helpful tool called the Custom Character Generator. See the blue grid below? You can click on any pixel to turn it on or off, and as you do this, the code for your character is automatically created right next to the grid. You can copy this code directly into your sketch.

byte Character[8] =
{
0b00000, 0b00000, 0b01010, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000
};

The possibilities for what you can create are almost endless! You could make arrows, simple animals, game characters, weather symbols, or any small icon you can fit in the 5×8 grid. The only limitation is that the library only lets you use eight custom characters at one time. But don’t worry – eight different custom characters are still plenty to make your project unique and interesting!

Example Code

The sketch below shows you exactly how to display your custom characters on the LCD:

/**
 * @file CustomChars.c
 * @brief Example to demonstrate creating and displaying custom characters on the LCD
 * @version 1.0
 * @date 2025-04-25
 *
 * This example defines a custom character (a smiley face) and displays it on the LCDI2C_
*/

#include <include.h>
#include <N76_I2C.h>
#include <N76_i2c_lcd.h>


// make some custom characters:
byte Heart[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

byte Bell[8] = {
0b00100,
0b01110,
0b01110,
0b01110,
0b11111,
0b00000,
0b00100,
0b00000
};

byte Alien[8] = {
0b11111,
0b10101,
0b11111,
0b11111,
0b01110,
0b01010,
0b11011,
0b00000
};

byte Check[8] = {
0b00000,
0b00001,
0b00011,
0b10110,
0b11100,
0b01000,
0b00000,
0b00000
};

byte Speaker[8] = {
0b00001,
0b00011,
0b01111,
0b01111,
0b01111,
0b00011,
0b00001,
0b00000
};

byte Sound[8] = {
0b00001,
0b00011,
0b00101,
0b01001,
0b01001,
0b01011,
0b11011,
0b11000
};

byte Skull[8] = {
0b00000,
0b01110,
0b10101,
0b11011,
0b01110,
0b01110,
0b00000,
0b00000
};

byte Lock[8] = {
0b01110,
0b10001,
0b10001,
0b11111,
0b11011,
0b11011,
0b11111,
0b00000
};

void main(void)
{
    // Initialize the LCD with I2C address 0x27, 16 columns, and 2 rows
    LCDI2C_begin(0x27, 16, 2);

    // Turn on the backlight
    LCDI2C_setBacklight(true);

    // Create the custom character in CGRAM
    LCDI2C_createChar(0, Heart);
    LCDI2C_createChar(1, Bell);
    LCDI2C_createChar(2, Alien);
    LCDI2C_createChar(3, Check);
    LCDI2C_createChar(4, Speaker);
    LCDI2C_createChar(5, Sound);
    LCDI2C_createChar(6, Skull);
    LCDI2C_createChar(7, Lock);
   
    // Display the custom character
    LCDI2C_setCursor(0, 0);
    LCDI2C_print("Custom Character");
    LCDI2C_write(1); // Display the custom character stored in CGRAM index 0

    while (1)
    {
        LCDI2C_setCursor(0, 1);
        LCDI2C_write(0);

        LCDI2C_setCursor(2, 1);
        LCDI2C_write(1);

        LCDI2C_setCursor(4, 1);
        LCDI2C_write(2);

        LCDI2C_setCursor(6, 1);
        LCDI2C_write(3);

        LCDI2C_setCursor(8, 1);
        LCDI2C_write(4);

        LCDI2C_setCursor(10, 1);
        LCDI2C_write(5);

        LCDI2C_setCursor(12, 1);
        LCDI2C_write(6);

        LCDI2C_setCursor(14, 1);
        LCDI2C_write(7);

        _delay_ms(1000);
    }
}

When you upload this code to your MCU, your LCD will display something like this:


Try it out and experiment with creating your own unique characters!

Display data from UART


In this example, we’ll explore how to interface an I2C LCD with a N76E/MS51 MCU to display data received from UART. This is a common requirement in embedded systems where a device receives serial data—such as sensor values or debug messages—and needs to present it on a display.

The MCU reads incoming data through its UART peripheral and updates the LCD display in real time.

Example Code


/**
 * @file SerialDisplay.c
 * @brief Example to display characters received via UART on the LCD
 * @version 1.0
 * @date 2025-04-25
 *
 * This example initializes the LCD and displays characters received via UART.
 * Send characters via UART to display them on the LCD in real-time.
*/

#include <include.h>
#include <N76_uart0.h>
#include <N76_I2C.h>
#include <N76_i2c_lcd.h>

#define LCD_ADDRESS 0x27 // I2C address of the LCD
#define LCD_COLUMNS 16U  // Number of columns on the LCD
#define LCD_ROWS    2U   // Number of rows on the LCD

void main(void)
{
    // Initialize UART for serial communication
    UART0_begin(BAUD_9600);
    // Enable UART0 interrupt
    UART0_attachInterrupt();
    // Initialize the LCD with I2C address 0x27, 16 columns, and 2 rows
    LCDI2C_begin(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS);

    // Turn on the backlight
    LCDI2C_setBacklight(true);

    // Display instructions on the LCD
    LCDI2C_setCursor(0, 0);
    LCDI2C_print("Send text via");
    LCDI2C_setCursor(0, 1);
    LCDI2C_print("UART to display");
    sei(); // Enable global interrupts

    while (1)
    {
        // Check if data is available on UART
        if (UART0_available())
        {
            // Display the received character on the LCD
            LCDI2C_clear();
            LCDI2C_setCursor(0, 0);
            LCDI2C_print("Received:");
            LCDI2C_setCursor(0, 1);
            while (UART0_available())
            {
                char receivedChar = UART0_read(); // Read the received character
                LCDI2C_write(receivedChar);
            }
        }
        UART0_print("Waiting for input...\r\n");
        _delay_ms(500); // Delay for a short period
    }
}

Test result


Picture below will show the result, data received from UART will be showed in LCD:


Hope you can find something helpful in my topic ^^
Don't hesitate to leave your comment below! 

No comments

Powered by Blogger.