STM8 Microcontrollers – the Final Chapters

Hardware

Driving LCD with I2C Interface

Recently ST released an 8 pin STM8 micro called STM8S001J3. This is a tiny but powerful microcontroller with lot of potentials. There are several other STM8 micros that have low pin counts and there are also times when we have to complete projects with least possible hardware and at low costs. We, then, don’t have the luxury of doing things in obvious ways. We need to use some supporting external hardware to get things done in such situations. Typically to interface a LCD in 4-bit mode with a micro it requires at least six GPIO pins. However, we can avoid so by using I2C port expanders like PCF8574. Since it is I2C based, it will just occupy two pins instead of six.

I2C_LCD

Owing to its popularity and simplicity, in the market there is a cheap and widely-available 2-wire LCD module based on PCF8574T. There are a few advantages of this module. Firstly, it is based on a PCF8574T chip that is made by NXP (a.k.a Philips). NXP happens to be the founder of I2C communication protocol and so the chip is well documented in terms of I2C communication. Secondly, there are three external address selection bits which can be used to address multiple LCDs coexisting on the same I2C bus. Lastly the module is compact and readily plug-and-playable. Additionally, the possibility of LCD data corruption due to noise and EMI is significantly reduced.

However, I2C communication is itself a slow communication and so interfacing displays via I2C may yield in slower performances and slower refresh rates. Additionally, extra coding and therefore extra memory spaces are needed.

Hardware Connection

2 Wire LCD

PCF8574 Module

Code Example

 

PCF8574.h

#include "stm8s.h"


#define I2C_PORT                                     GPIOB

#define SDA_pin                                      GPIO_PIN_5
#define SCL_pin                                      GPIO_PIN_4

#define PCF8574_address                               0x4E


void I2C_GPIO_setup(void);
void I2C_setup(void);
void PCF8574_init(void);
unsigned char PCF8574_read(void);
void PCF8574_write(unsigned char data_byte);

 

PCF8574.c

#include "PCF8574.h"


void I2C_GPIO_setup(void)
{  
    GPIO_Init(I2C_PORT,
              ((GPIO_Pin_TypeDef)(SCL_pin | SDA_pin)),
              GPIO_MODE_OUT_PP_HIGH_FAST);
}


void I2C_setup(void)
{
    I2C_DeInit();
    I2C_Init(100000,
                  PCF8574_address,
                  I2C_DUTYCYCLE_2,
                  I2C_ACK_CURR,
                  I2C_ADDMODE_7BIT,
                  (CLK_GetClockFreq() / 1000000));
    I2C_Cmd(ENABLE);
}


void PCF8574_init(void)
{
    I2C_GPIO_setup();
    I2C_setup();
}


unsigned char PCF8574_read(void)
{
   unsigned char port_byte = 0x00;
   unsigned char num_of_bytes = 0x01;

   while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY));

   I2C_GenerateSTART(ENABLE);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

   I2C_Send7bitAddress(PCF8574_address, I2C_DIRECTION_RX);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

   while(num_of_bytes)
   {
        if(I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED))
        {  
            if(num_of_bytes == 0)
            {
                I2C_AcknowledgeConfig(I2C_ACK_NONE);
                I2C_GenerateSTOP(ENABLE);  
            }

            port_byte = I2C_ReceiveData();

            num_of_bytes--;
        }
   }; 

   return port_byte;
}


void PCF8574_write(unsigned char data_byte)
{
   I2C_GenerateSTART(ENABLE);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

   I2C_Send7bitAddress(PCF8574_address, I2C_DIRECTION_TX);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

   I2C_SendData(data_byte);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

   I2C_GenerateSTOP(ENABLE);  
}

 

lcd.h

#include "stm8s.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


extern unsigned char bl_state;
extern unsigned char data_value;


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.c

#include "lcd.h"


void LCD_init(void)
{                      
    PCF8574_init();
    delay_ms(100);

    bl_state = BL_ON;
    data_value = 0x04;
    PCF8574_write(data_value);

    delay_ms(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);
    delay_ms(dly);
    data_value &= 0xF9;
    PCF8574_write(data_value);
    delay_ms(dly);
}


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);
    delay_ms(dly);
}


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 "STM8S.h"
#include "lcd.h"


unsigned char bl_state;
unsigned char data_value;


void clock_setup(void);
void GPIO_setup(void);
void show_value(unsigned char value);


void main(void)
{
  const char txt1[] = {"MICROARENA"};
  const char txt2[] = {"SShahryiar"};
  const char txt3[] = {"STM8S003K3"};
  const char txt4[] = {"Discovery!"};

  unsigned char s = 0x00;

  clock_setup();
  GPIO_setup();
  LCD_init();

    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);
    LCD_goto(3, 1);
    LCD_putstr(txt2);
    delay_ms(2600);

    LCD_clear_home();

    for(s = 0; s < 10; s++)
    {
        LCD_goto((3 + s), 0);
        LCD_putchar(txt3[s]);
        delay_ms(60);
    }
    for(s = 0; s < 10; s++)
    {
        LCD_goto((3 + s), 1);
        LCD_putchar(txt4[s]);
        delay_ms(60);
    }
  delay_ms(2600);

    s = 0;
    LCD_clear_home();

    LCD_goto(3, 0);
    LCD_putstr(txt1);

    while(1)
    {
        show_value(s);
        s++;
        delay_ms(200);
    };
}


void clock_setup(void)
{
  CLK_DeInit();

  CLK_HSECmd(DISABLE);
  CLK_LSICmd(DISABLE);

  CLK_HSICmd(ENABLE);
  while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);

  CLK_ClockSwitchCmd(ENABLE);
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
  CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);

  CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI,
  DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);

  CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE);
  CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
  CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
  CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
  CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
  CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
  CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
  CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}


void GPIO_setup(void)
{
  GPIO_DeInit(I2C_PORT);
}


void show_value(unsigned char value)
{
   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);
}

 

 

Explanation

The LCD example is nothing different from other LCD examples except for the I2C implementation part. For that we need to code for the PCF8574 I2C port expander first. SDA and SCL pins are initialized in the beginning and this is followed by I2C hardware initialization.

void I2C_GPIO_setup(void)
{  
    GPIO_Init(I2C_PORT,
              ((GPIO_Pin_TypeDef)(SCL_pin | SDA_pin)),
              GPIO_MODE_OUT_PP_HIGH_FAST);
}


void I2C_setup(void)
{
    I2C_DeInit();
    I2C_Init(100000,
                  PCF8574_address,
                  I2C_DUTYCYCLE_2,
                  I2C_ACK_CURR,
                  I2C_ADDMODE_7BIT,
                  (CLK_GetClockFreq() / 1000000));
    I2C_Cmd(ENABLE);
}

Don’t forget to enable I2C peripheral clock:

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
....
CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE);

We don’t need to read the I2C port expander, just need to write it and so we just need the write operation part only. I have still included the reading part.

void PCF8574_write(unsigned char data_byte)
{
   I2C_GenerateSTART(ENABLE);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

   I2C_Send7bitAddress(PCF8574_address, I2C_DIRECTION_TX);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

   I2C_SendData(data_byte);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

   I2C_GenerateSTOP(ENABLE);  
}

The rest of the code is just about coding the LCD as we would do with ordinary connections and running it. Therefore, the only theme here is to use PCF8574 to handle the I/O operations for driving the LCD connected with it.

 

Demo

I2C LCD (1) I2C LCD (2)

Continue Reading ...

Related Posts

5 comments

Leave a Reply

Your email address will not be published. Required fields are marked *