About the N76E006 Test Board

Shown below are the actual photo, the PCB layout and the schematic of the unofficial cheap N76E003 test/development board. This is the board I will be using in this tutorial series.

This board is 30mm x 43.5mm board. There are two sidewise header that bring out the GPIO pins and positive supply rails. There is another header opposite to the USB port. This header is for connecting a Nulink programmer-debugger interface and it also has a serial port interface brought out straight from the N76E003 chip. This serial port is useful for quickly debugging/testing stuffs with a serial port monitor. There is an LED connected with P15 pin via a computer jumper.

Coding Nuvoton N76E003

Both Keil and IAR are excellent tools for coding Nuvoton MCUs. I have used both and everything is same in both cases. Literally there is no difference at all. I won’t recommend which one to use and I leave the choice to the readers. However, there are some areas where you may find difficulty porting codes of one compiler to the other. The table below summarizes some of these differences.

General Purpose Input-Output (GPIO)

GPIOs are the most common hardware that we use in a microcontroller. Since N76E003 is based on 8051 architecture, we should be getting some similarities with the old school 8051s. Shown below is the hardware schematic of N76E003’s GPIO block:

On close inspection, we can realize that this structure has striking resemblance with the GPIO structure of a typical 8051 microcontroller as shown below:

Thus, we can expect similar behaviour.

There are four GPIO modes and these are as follows:

PxM1.n and PxM2.n bits decide these modes. For most cases, we can stick to push-pull and input modes as they are the most commonly used ones.

Code

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
void setup(void);
void main(void)
{ 
  setup();
  while(1)
  {
    if(P05 != 0x00)
    {
      Timer0_Delay1ms(900);
    }
    set_P15;
    Timer0_Delay1ms(100);
    clr_P15;
    Timer0_Delay1ms(100);
  };
}
void setup(void)
{ 
  P15_PushPull_Mode;
  P05_Input_Mode;
}

Schematic

Explanation

The Function_define BSP header file states GPIO modes as follows:

Similarly, SFR_Macro BSP header file defines the bit-level setting of all N76E003 registers. To set the logic level of GPIO pins we can use the following definitions:

However, these don’t restrict us from using classical register-level coding. N76E003 header file states all the registers present in it.

For port/pin reading I didn’t see any function definition as like one I already discussed. Thus, there are two ways to do it on your own. The following as two examples of such:

The demo here is a simple one. The onboard LED connected to P15 pin is toggled at a fixed interval. When a button connected to P05 is pressed the off time of the LED is increased, affecting toggle rate.

Driving 2×16 LCD

Driving alphanumeric/text LCDs requires no special hardware as simple manipulation of GPIO pins and understanding of their working principle are all that are needed.

Code

lcd.h

#define LCD_GPIO_init()                    do{P00_PushPull_Mode; P01_PushPull_Mode; P10_PushPull_Mode; P11_PushPull_Mode; P12_PushPull_Mode; P13_PushPull_Mode;}while(0)
#define LCD_RS_HIGH                        set_P00
#define LCD_RS_LOW                         clr_P00
#define LCD_EN_HIGH                        set_P01
#define LCD_EN_LOW                         clr_P01
#define LCD_DB4_HIGH                       set_P10
#define LCD_DB4_LOW                        clr_P10
#define LCD_DB5_HIGH                       set_P11
#define LCD_DB5_LOW                        clr_P11
#define LCD_DB6_HIGH                       set_P12
#define LCD_DB6_LOW                        clr_P12
#define LCD_DB7_HIGH                       set_P13
#define LCD_DB7_LOW                        clr_P13
#define clear_display                      0x01
#define goto_home                          0x02
#define cursor_direction_inc               (0x04 | 0x02)
#define cursor_direction_dec               (0x04 | 0x00)
#define display_shift                      (0x04 | 0x01)
#define display_no_shift                   (0x04 | 0x00)
#define display_on                         (0x08 | 0x04)
#define display_off                        (0x08 | 0x02)
#define cursor_on                          (0x08 | 0x02)
#define cursor_off                         (0x08 | 0x00)
#define blink_on                           (0x08 | 0x01)
#define blink_off                          (0x08 | 0x00)
#define _8_pin_interface                   (0x20 | 0x10)
#define _4_pin_interface                   (0x20 | 0x00)
#define _2_row_display                     (0x20 | 0x08)
#define _1_row_display                     (0x20 | 0x00)
#define _5x10_dots                         (0x20 | 0x40)
#define _5x7_dots                          (0x20 | 0x00)
#define DAT                                1
#define CMD                                0
void LCD_init(void);
void LCD_send(unsigned char value, unsigned char mode);
void LCD_4bit_send(unsigned char lcd_data);
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);
void toggle_EN_pin(void);

lcd.c

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "lcd.h"
void LCD_init(void)
{
    Timer0_Delay1ms(10);
    LCD_GPIO_init();
    Timer0_Delay1ms(100);
    toggle_EN_pin();
    LCD_RS_LOW;
    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;
    toggle_EN_pin();
    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;
    toggle_EN_pin();
    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_HIGH;
    toggle_EN_pin();
    LCD_DB7_LOW;
    LCD_DB6_LOW;
    LCD_DB5_HIGH;
    LCD_DB4_LOW;
    toggle_EN_pin();
    LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);
    LCD_send((display_on | cursor_off | blink_off), CMD);
    LCD_send(clear_display, CMD);
    LCD_send((cursor_direction_inc | display_no_shift), CMD);
}
void LCD_send(unsigned char value, unsigned char mode)
{
    switch(mode)
    {
        case DAT:
        {
            LCD_RS_HIGH;
            break;
        }
        case CMD:
        {
            LCD_RS_LOW;
            break;
        }
    }
    LCD_4bit_send(value);
}
void LCD_4bit_send(unsigned char lcd_data)
{
    unsigned char temp = 0;
    temp = ((lcd_data & 0x80) >> 7);
    switch(temp)
    {
        case 1:
        {
            LCD_DB7_HIGH;
            break;
        }
        default:
        {
            LCD_DB7_LOW;
            break;
        }
    }
    temp = ((lcd_data & 0x40) >> 6);
    switch(temp)
    {
        case 1:
        {
            LCD_DB6_HIGH;
            break;
        }
        default:
        {
            LCD_DB6_LOW;
            break;
        }
    }
    temp = ((lcd_data & 0x20) >> 5);
    switch(temp)
    {
        case 1:
        {
            LCD_DB5_HIGH;
            break;
        }
        default:
        {
            LCD_DB5_LOW;
            break;
        }
    }
    temp = ((lcd_data & 0x10) >> 4);
    switch(temp)
    {
        case 1:
        {
            LCD_DB4_HIGH;
            break;
        }
        default:
        {
            LCD_DB4_LOW;
            break;
        }
    }
    toggle_EN_pin();
    temp = ((lcd_data & 0x08) >> 3);
    switch(temp)
    {
        case 1:
        {
            LCD_DB7_HIGH;
            break;
        }
        default:
        {
            LCD_DB7_LOW;
            break;
        }
    }
    temp = ((lcd_data & 0x04) >> 2);
    switch(temp)
    {
        case 1:
        {
            LCD_DB6_HIGH;
            break;
        }
        default:
        {
            LCD_DB6_LOW;
            break;
        }
    }
    temp = ((lcd_data & 0x02) >> 1);
    switch(temp)
    {
        case 1:
        {
            LCD_DB5_HIGH;
            break;
        }
        default:
        {
            LCD_DB5_LOW;
            break;
        }
    }
    temp = ((lcd_data & 0x01));
    switch(temp)
    {
        case 1:
        {
            LCD_DB4_HIGH;
            break;
        }
        default:
        {
            LCD_DB4_LOW;
            break;
        }
    }
    toggle_EN_pin();
}
void LCD_putstr(char *lcd_string)
{
    do
    {
        LCD_send(*lcd_string++, DAT);
    }while(*lcd_string != '\0');
}
void LCD_putchar(char char_data)
{
    LCD_send(char_data, DAT);
}
void LCD_clear_home(void)
{
    LCD_send(clear_display, CMD);
    LCD_send(goto_home, CMD);
}
void LCD_goto(unsigned char x_pos, unsigned char y_pos)
{
    if(y_pos == 0)
    {
        LCD_send((0x80 | x_pos), CMD);
    }
    else
    {
        LCD_send((0x80 | 0x40 | x_pos), CMD);
    }
}
void toggle_EN_pin(void)
{
    LCD_EN_HIGH;
    Timer0_Delay1ms(4);
    LCD_EN_LOW;
    Timer0_Delay1ms(4);
}

main.c

#include "N76E003.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "lcd.h"
void show_value(unsigned char value);
void main(void)
{   
    unsigned char s = 0;
    const char txt1[] = {"MICROARENA"};
    const char txt2[] = {"SShahryiar"};
    const char txt3[] = {"Nuvoton 8-bit uC"};
    const char txt4[] = {"N76E003"};
    LCD_init();
    LCD_clear_home();
    LCD_goto(3, 0);
    LCD_putstr(txt1);
    LCD_goto(3, 1);
    LCD_putstr(txt2);
    Timer3_Delay100ms(30);
    LCD_clear_home();
    for(s = 0; s < 16; s++)
    {
        LCD_goto(s, 0);
        LCD_putchar(txt3[s]);
        Timer0_Delay1ms(90);
    }
    Timer3_Delay100ms(20);
    for(s = 0; s < 7; s++)
    {
        LCD_goto((4 + s), 1);
        LCD_putchar(txt4[s]);
        Timer0_Delay1ms(90);
    }
    Timer3_Delay100ms(30);
    s = 0;
    LCD_clear_home();
    LCD_goto(3, 0);
    LCD_putstr(txt1);
    while(1)
    {
        show_value(s);
        s++;
        Timer3_Delay100ms(4);
    };
}
void show_value(unsigned char value)
{
   unsigned char ch = 0x00;
   ch = ((value / 100) + 0x30);
   LCD_goto(6, 1);
   LCD_putchar(ch);
   ch = (((value / 10) % 10) + 0x30);
   LCD_goto(7, 1);
   LCD_putchar(ch);
   ch = ((value % 10) + 0x30);
   LCD_goto(8, 1);
   LCD_putchar(ch);
}

Schematic

Explanation

There is nothing to explain here. The LCD driver is based on simple manipulation of GPIO pins. The codes for the LCD are coded using all available info on LCD datasheet – just initialization and working principle. If you need to change GPIO pins just edit the following lines in the LCD header file:

Driving 2×16 LCD with Software SPI

One problem with alphanumeric LCDs and GLCDs is the number of GPIOs needed to connect so with host micros. For a small micro like N76E003, each GPIO pin is like a gem and we can’t afford to use too many GPIO pins for an LCD. The solution to this problem is to use SPI/I2C-based LCD drivers that significantly reduce GPIO pin requirement. Implementing software-based SPI/I2C for such LCD drivers is also both easy and universal since these solutions don’t need hardware SPI/I2C ports. Since the SPI/I2C functionality is software emulated, any set of GPIO pins can be used – another advantage.

In this segment, we will be driving a 2×16 LCD with CD4094B Serial-In-Parallel-Out (SIPO) shift register using software SPI. The same idea can be used for other similar shift registers like 74HC595. There are other ways of using SPI-based LCDs but the aforementioned are the cheapest ways.

Code

LCD_3_Wire.h

#define LCD_GPIO_init()                        do{P02_PushPull_Mode; P03_PushPull_Mode; P04_PushPull_Mode;}while(0)
#define LCD_SDO_HIGH()                          set_P00
#define LCD_SDO_LOW()                           clr_P00
#define LCD_SCK_HIGH()                          set_P01
#define LCD_SCK_LOW()                           clr_P01
#define LCD_STB_HIGH()                          set_P02
#define LCD_STB_LOW()                           clr_P02
#define DAT                                     1
#define CMD                                     0
#define clear_display                           0x01
#define goto_home                               0x02
#define cursor_direction_inc                    (0x04 | 0x02)
#define cursor_direction_dec                    (0x04 | 0x00)
#define display_shift                           (0x04 | 0x01)
#define display_no_shift                        (0x04 | 0x00)
#define display_on                              (0x08 | 0x04)
#define display_off                             (0x08 | 0x02)
#define cursor_on                               (0x08 | 0x02)
#define cursor_off                              (0x08 | 0x00)
#define blink_on                                (0x08 | 0x01)
#define blink_off                               (0x08 | 0x00)
#define _8_pin_interface                        (0x20 | 0x10)
#define _4_pin_interface                        (0x20 | 0x00)
#define _2_row_display                          (0x20 | 0x08)
#define _1_row_display                          (0x20 | 0x00)
#define _5x10_dots                              (0x20 | 0x40)
#define _5x7_dots                               (0x20 | 0x00)
extern unsigned char data_value;
void SIPO(void);
void LCD_init(void);
void LCD_toggle_EN(void);
void LCD_send(unsigned char value, unsigned char mode);
void LCD_4bit_send(unsigned char lcd_data);           
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);

LCD_3_Wire.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"
unsigned char data_value;
void SIPO(void)
{
    unsigned char bit_value = 0x00;
    unsigned char clk = 0x08;
    unsigned char temp = 0x00;
    temp = data_value;
    LCD_STB_LOW();
    while(clk > 0)
    {
        bit_value = ((temp & 0x80) >> 0x07);
        bit_value &= 0x01;
        switch(bit_value)
        {
            case 0:
            {
                LCD_SDO_LOW();
                break;
            }
            default:
            {
                LCD_SDO_HIGH();
                break;
            }
        }
        LCD_SCK_HIGH();
        temp <<= 0x01;
        clk--;
        LCD_SCK_LOW();
    };
    LCD_STB_HIGH();
}
void LCD_init(void)
{                                     
    Timer0_Delay1ms(10);
    LCD_GPIO_init();
    Timer0_Delay1ms(10);
    data_value = 0x08;
    SIPO();
    Timer0_Delay1ms(10);
    LCD_send(0x33, CMD);
    LCD_send(0x32, CMD);
    LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);        
    LCD_send((display_on | cursor_off | blink_off), CMD);    
    LCD_send((clear_display), CMD);        
    LCD_send((cursor_direction_inc | display_no_shift), CMD);       
}  
void LCD_toggle_EN(void)
{
    data_value |= 0x08;
    SIPO();
    Timer0_Delay1ms(2);
    data_value &= 0xF7;
    SIPO();
    Timer0_Delay1ms(2);
}
void LCD_send(unsigned char value, unsigned char mode)
{                              
    switch(mode)
    {
        case DAT:
        {
            data_value |= 0x04;
            break;
        }
        default:
        {
            data_value &= 0xFB;
            break;
        }
    }
    SIPO();
    LCD_4bit_send(value);
} 
void LCD_4bit_send(unsigned char lcd_data)       
{
    unsigned char temp = 0x00;
    temp = (lcd_data & 0xF0);
    data_value &= 0x0F;
    data_value |= temp;
    SIPO();
    LCD_toggle_EN();
    temp = (lcd_data & 0x0F);
    temp <<= 0x04;
    data_value &= 0x0F;
    data_value |= temp;
    SIPO();
    LCD_toggle_EN();
} 
void LCD_putstr(char *lcd_string)
{
    while(*lcd_string != '\0') 
    {
        LCD_putchar(*lcd_string++);
    }
}
void LCD_putchar(char char_data)
{
    if((char_data >= 0x20) && (char_data <= 0x7F))
    {
        LCD_send(char_data, DAT);
    }
}
void LCD_clear_home(void)
{
    LCD_send(clear_display, CMD);
    LCD_send(goto_home, CMD);
}
void LCD_goto(unsigned char x_pos,unsigned char y_pos)
{                                                  
    if(y_pos == 0)   
    {                             
        LCD_send((0x80 | x_pos), CMD);
    }
    else
    {                                             
        LCD_send((0x80 | 0x40 | x_pos), CMD);
    }
}

main.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_3_Wire.h"
void show_value(unsigned char value);
void main(void)
{ 
  unsigned char s = 0;
  static char txt1[] = {"MICROARENA"};
  static char txt2[] = {"SShahryiar"};
  static char txt3[] = {"Nuvoton 8-bit uC"};
  static char txt4[] = {"N76E003"};
  LCD_init();
  LCD_clear_home();
  LCD_goto(3, 0);
  LCD_putstr(txt1);
  LCD_goto(3, 1);
  LCD_putstr(txt2);
  Timer3_Delay100ms(30);
  LCD_clear_home();
  for(s = 0; s < 16; s++)
  {
    LCD_goto(s, 0);
    LCD_putchar(txt3[s]);
    Timer0_Delay1ms(90);
  }
  Timer3_Delay100ms(20);
  for(s = 0; s < 7; s++)
  {
    LCD_goto((4 + s), 1);
    LCD_putchar(txt4[s]);
    Timer0_Delay1ms(90);
  }
  Timer3_Delay100ms(30);
  s = 0;
  LCD_clear_home();
  LCD_goto(3, 0);
  LCD_putstr(txt1);
  while(1)
  {
    show_value(s);
    s++;
    Timer3_Delay100ms(4);
  };
}
void show_value(unsigned char value)
{
   unsigned char ch = 0x00;
   ch = ((value / 100) + 0x30);
   LCD_goto(6, 1);
   LCD_putchar(ch);
   ch = (((value / 10) % 10) + 0x30);
   LCD_goto(7, 1);
   LCD_putchar(ch);
   ch = ((value % 10) + 0x30);
   LCD_goto(8, 1);
   LCD_putchar(ch);
}

Schematic

Explanation

The code demoed here is same as the last LCD code and so there is not much to explain. The GPIO operations of the LCD are handled using a CD4094B Serial-In-Parallel-Out (SIPO) shift register. This shift register here acts like an output expander. With just three GPIOs we are able to interface a 4-bit LCD that needs at least six GPIOs to work.

The SIPO function shown below simulates software-based SPI:

void SIPO(void)
{
    unsigned char bit_value = 0x00;
    unsigned char clk = 0x08;
    unsigned char temp = 0x00;
    temp = data_value;
    LCD_STB_LOW();
    while(clk > 0)
    {
        bit_value = ((temp & 0x80) >> 0x07);
        bit_value &= 0x01;
        switch(bit_value)
        {
            case 0:
            {
                LCD_SDO_LOW();
                break;
            }
            default:
            {
                LCD_SDO_HIGH();
                break;
            }
        }
        LCD_SCK_HIGH();
        temp <<= 0x01;
        clk--;
        LCD_SCK_LOW();
    };
    LCD_STB_HIGH();
}

To change pins, change the following the lines in the LCD_3_Wire header file:

#define LCD_GPIO_init()                        do{P02_PushPull_Mode; P03_PushPull_Mode; P04_PushPull_Mode;}while(0)
#define LCD_SDO_HIGH()                          set_P00
#define LCD_SDO_LOW()                           clr_P00
#define LCD_SCK_HIGH()                          set_P01
#define LCD_SCK_LOW()                           clr_P01
#define LCD_STB_HIGH()                          set_P02
#define LCD_STB_LOW()                           clr_P02

Lastly, I have code two versions of this LCD library – one with BSP-based delays and the other with software delays. Technically there’s no big change. The software-based one frees up a hardware timer or two.

Driving 2×16 LCD with Software I2C

We have already seen in the last segment how to use software SPI with a shift register to drive a 2×16 LCD. In this segment, we will explore the same concept with software I2C and PCF8574 I2C port expander IC. There is a popular readymade module for such task and I used it here. The advantage of I2C-based LCD over SPI-based LCD driver is the lesser number of GPIOs required compared to SPI-based LCD. However, it is slower than SPI-based drivers.

Code

SW_I2C.h

#define SDA_DIR_OUT()   P03_PushPull_Mode
#define SDA_DIR_IN()    P03_Input_Mode
#define SCL_DIR_OUT()   P04_PushPull_Mode
#define SCL_DIR_IN()    P04_Input_Mode
#define SDA_HIGH()      set_P03
#define SDA_LOW()       clr_P03
#define SCL_HIGH()      set_P04
#define SCL_LOW()       clr_P04
#define SDA_IN()        P03
#define I2C_ACK         0xFF
#define I2C_NACK        0x00
#define I2C_timeout     1000
void SW_I2C_init(void);
void SW_I2C_start(void);
void SW_I2C_stop(void);
unsigned char SW_I2C_read(unsigned char ack);
void SW_I2C_write(unsigned char value);
void SW_I2C_ACK_NACK(unsigned char mode);
unsigned char SW_I2C_wait_ACK(void);

SW_I2C.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "SW_I2C.h"
void SW_I2C_init(void)
{
    SDA_DIR_OUT();
    SCL_DIR_OUT();
    Timer0_Delay100us(1);
    SDA_HIGH();
    SCL_HIGH();
}
void SW_I2C_start(void)
{
    SDA_DIR_OUT();
    SDA_HIGH();
    SCL_HIGH();
    Timer3_Delay10us(4);
    SDA_LOW();
    Timer3_Delay10us(4);
    SCL_LOW();
}
void SW_I2C_stop(void)
{
    SDA_DIR_OUT();
    SDA_LOW();
    SCL_LOW();
    Timer3_Delay10us(4);
    SDA_HIGH();
    SCL_HIGH();
    Timer3_Delay10us(4);
}
unsigned char SW_I2C_read(unsigned char ack)
{
    unsigned char i = 8;
    unsigned char j = 0;
    SDA_DIR_IN();
    while(i > 0)
    {
        SCL_LOW();
        Timer3_Delay10us(2);
        SCL_HIGH();
        Timer3_Delay10us(2);
        j <<= 1;
        if(SDA_IN() != 0x00)
        {
            j++;
        }
        Timer3_Delay10us(1);
        i--;
    };
    switch(ack)
    {
        case I2C_ACK:
        {
            SW_I2C_ACK_NACK(I2C_ACK);;
            break;
        }
        default:
        {
            SW_I2C_ACK_NACK(I2C_NACK);;
            break;
        }
    }
    return j;
}
void SW_I2C_write(unsigned char value)
{
    unsigned char i = 8;
    SDA_DIR_OUT();
    SCL_LOW();
    while(i > 0)
    {
        if(((value & 0x80) >> 7) != 0x00)
        {
            SDA_HIGH();
        }
        else
        {
            SDA_LOW();
        }
        value <<= 1;
        Timer3_Delay10us(2);
        SCL_HIGH();
        Timer3_Delay10us(2);
        SCL_LOW();
        Timer3_Delay10us(2);
        i--;
    };
}
void SW_I2C_ACK_NACK(unsigned char mode)
{
    SCL_LOW();
    SDA_DIR_OUT();
    switch(mode)
    {
        case I2C_ACK:
        {
            SDA_LOW();
            break;
        }
        default:
        {
            SDA_HIGH();
            break;
        }
    }
    Timer3_Delay10us(2);
    SCL_HIGH();
    Timer3_Delay10us(2);
    SCL_LOW();
}
unsigned char SW_I2C_wait_ACK(void)
{
    signed int timeout = 0;
    SDA_DIR_IN();
    SDA_HIGH();
    Timer3_Delay10us(1);
    SCL_HIGH();
    Timer3_Delay10us(1);
    while(SDA_IN() != 0x00)
    {
        timeout++;
        if(timeout > I2C_timeout)
        {
            SW_I2C_stop();
            return 1;
        }
    };
    SCL_LOW();
    return 0;
}

PCF8574.h

#include "SW_I2C.h"
#define PCF8574_address                 0x4E
#define PCF8574_write_cmd               PCF8574_address
#define PCF8574_read_cmd                (PCF8574_address | 1)
void PCF8574_init(void);
unsigned char PCF8574_read(void);
void PCF8574_write(unsigned char data_byte);

PCF8574.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "PCF8574.h"
void PCF8574_init(void)
{
    SW_I2C_init();
    Timer0_Delay1ms(20);
}
unsigned char PCF8574_read(void)
{
    unsigned char port_byte = 0;
    SW_I2C_start();
    SW_I2C_write(PCF8574_read_cmd);
    port_byte = SW_I2C_read(I2C_NACK);
    SW_I2C_stop();
    return port_byte;
}
void PCF8574_write(unsigned char data_byte)
{
    SW_I2C_start();
    SW_I2C_write(PCF8574_write_cmd);
    SW_I2C_ACK_NACK(I2C_ACK);
    SW_I2C_write(data_byte);
    SW_I2C_ACK_NACK(I2C_ACK);
    SW_I2C_stop();
}

LCD_2_Wire.h

#include "PCF8574.h"
#define clear_display                                0x01
#define goto_home                                    0x02
#define cursor_direction_inc                         (0x04 | 0x02)
#define cursor_direction_dec                         (0x04 | 0x00)
#define display_shift                                (0x04 | 0x01)
#define display_no_shift                             (0x04 | 0x00)
#define display_on                                   (0x08 | 0x04)
#define display_off                                  (0x08 | 0x02)
#define cursor_on                                    (0x08 | 0x02)
#define cursor_off                                   (0x08 | 0x00)
#define blink_on                                     (0x08 | 0x01)
#define blink_off                                    (0x08 | 0x00)
#define _8_pin_interface                             (0x20 | 0x10)
#define _4_pin_interface                             (0x20 | 0x00)
#define _2_row_display                               (0x20 | 0x08)
#define _1_row_display                               (0x20 | 0x00)
#define _5x10_dots                                   (0x20 | 0x40)
#define _5x7_dots                                    (0x20 | 0x00)
#define BL_ON                                        1
#define BL_OFF                                       0
#define dly                                          2
#define DAT                                          1
#define CMD                                          0
void LCD_init(void);
void LCD_toggle_EN(void);
void LCD_send(unsigned char value, unsigned char mode);
void LCD_4bit_send(unsigned char lcd_data);           
void LCD_putstr(char *lcd_string);
void LCD_putchar(char char_data);
void LCD_clear_home(void);
void LCD_goto(unsigned char x_pos, unsigned char y_pos);

LCD_2_Wire.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_2_Wire.h"
static unsigned char bl_state;
static unsigned char data_value;
void LCD_init(void)
{                       
  PCF8574_init();
  Timer0_Delay1ms(10);
  bl_state = BL_ON;
  data_value = 0x04;
  PCF8574_write(data_value);
  Timer0_Delay1ms(10);
  LCD_send(0x33, CMD);
  LCD_send(0x32, CMD);
  LCD_send((_4_pin_interface | _2_row_display | _5x7_dots), CMD);        
  LCD_send((display_on | cursor_off | blink_off), CMD);    
  LCD_send((clear_display), CMD);        
  LCD_send((cursor_direction_inc | display_no_shift), CMD);       
}  
void LCD_toggle_EN(void)
{
  data_value |= 0x04;
  PCF8574_write(data_value);
  Timer0_Delay1ms(1);
  data_value &= 0xF9;
  PCF8574_write(data_value);
  Timer0_Delay1ms(1);
}
void LCD_send(unsigned char value, unsigned char mode)
{
  switch(mode)
  {
     case CMD:
     {
        data_value &= 0xF4;
        break;
     }
     case DAT:
     {
        data_value |= 0x01;
        break;
     }
  }
  switch(bl_state)
  {
     case BL_ON:
     {
        data_value |= 0x08;
        break;
     }
     case BL_OFF:
     {
        data_value &= 0xF7;
        break;
     }
  }
  PCF8574_write(data_value);
  LCD_4bit_send(value);
  Timer0_Delay1ms(1);
}
void LCD_4bit_send(unsigned char lcd_data)      
{
  unsigned char temp = 0x00;
  temp = (lcd_data & 0xF0);
  data_value &= 0x0F;
  data_value |= temp;
  PCF8574_write(data_value);
  LCD_toggle_EN();
  temp = (lcd_data & 0x0F);
  temp <<= 0x04;
  data_value &= 0x0F;
  data_value |= temp;
  PCF8574_write(data_value);
  LCD_toggle_EN();
} 
void LCD_putstr(char *lcd_string)
{
  do
  {
    LCD_putchar(*lcd_string++);
  }while(*lcd_string != '\0') ;
}
void LCD_putchar(char char_data)
{
  if((char_data >= 0x20) && (char_data <= 0x7F))
  {
    LCD_send(char_data, DAT);
  }
}
void LCD_clear_home(void)
{
  LCD_send(clear_display, CMD);
  LCD_send(goto_home, CMD);
}
void LCD_goto(unsigned char x_pos,unsigned char y_pos)
{                                                  
  if(y_pos == 0)   
  {                             
    LCD_send((0x80 | x_pos), CMD);
  }
  else
  {                                              
    LCD_send((0x80 | 0x40 | x_pos), CMD);
  }
}

main.c

#include "N76E003_iar.h"
#include "SFR_Macro.h"
#include "Function_define.h"
#include "Common.h"
#include "Delay.h"
#include "LCD_2_Wire.h"
void show_value(unsigned char value);
void main(void)
{ 
  unsigned char s = 0;
  static char txt1[] = {"MICROARENA"};
  static char txt2[] = {"SShahryiar"};
  static char txt3[] = {"Nuvoton 8-bit uC"};
  static char txt4[] = {"N76E003"};
  LCD_init();
  LCD_clear_home();
  LCD_goto(3, 0);
  LCD_putstr(txt1);
  LCD_goto(3, 1);
  LCD_putstr(txt2);
  Timer3_Delay100ms(30);
  LCD_clear_home();
  for(s = 0; s < 16; s++)
  {
    LCD_goto(s, 0);
    LCD_putchar(txt3[s]);
    Timer0_Delay1ms(90);
  }
  Timer3_Delay100ms(20);
  for(s = 0; s < 7; s++)
  {
    LCD_goto((4 + s), 1);
    LCD_putchar(txt4[s]);
    Timer0_Delay1ms(90);
  }
  Timer3_Delay100ms(30);
  s = 0;
  LCD_clear_home();
  LCD_goto(3, 0);
  LCD_putstr(txt1);
  while(1)
  {
    show_value(s);
    s++;
    Timer3_Delay100ms(4);
  };
}
void show_value(unsigned char value)
{
  unsigned char ch = 0x00;
  ch = ((value / 100) + 0x30);
  LCD_goto(6, 1);
  LCD_putchar(ch);
  ch = (((value / 10) % 10) + 0x30);
  LCD_goto(7, 1);
  LCD_putchar(ch);
  ch = ((value % 10) + 0x30);
  LCD_goto(8, 1);
  LCD_putchar(ch);
}

Schematic

Explanation

Just like the last example, software method is used to emulate I2C protocol using ordinary GPIOs. There are three parts of the code – first the software I2C driver, second the driver library for PCF8574 I2C 8-bit port expander and lastly the LCD driver itself. The LCD driver is same as the other LCD drivers in this document. I kept the code modular so that it is easy to understand the role of each piece of code. The I2C driver (SW_I2C) implements software I2C which is used by the PCF8574 driver. Thus, the port expander driver is dependent on the SW_I2C driver and the LCD driver is dependent on the port expander driver, and in cases like such we must find add libraries according to the order of dependency.

The advantage of keeping things modular is to easily modify things in a fast and trouble-free manner while keeping things ready for other deployments. In my codes I try to avoid repetitive and meaningless stuffs with meaningful definitions. For instance, just change the following lines to change pin configurations without going through the whole code:

#define SDA_DIR_OUT()   P03_PushPull_Mode
#define SDA_DIR_IN()    P03_Input_Mode
#define SCL_DIR_OUT()   P04_PushPull_Mode
#define SCL_DIR_IN()    P04_Input_Mode
#define SDA_HIGH()      set_P03
#define SDA_LOW()       clr_P03
#define SCL_HIGH()      set_P04
#define SCL_LOW()       clr_P04
#define SDA_IN()        P03

Likewise, the SW_I2C functions are not implemented inside the LCD or port expander driver files so that they can be used for other I2C devices.