Continuing the STM8 Expedition

STM8S105 Discovery

Driving LCD with Hardware SPI and by Bit-Banging Method

Bit-banging and SPI-based communications are very popular in the embedded system world. These methods are not limited to communication with sensors, drives, etc. They can be used for driving an alphanumeric LCD with a few pins. Usually with shift registers like 74HC595 and CD4094B, we can make such display interfaces very simply. It is also possible to do so with SPI port expanders like MCP23S17 but those will be expensive solutions. All that we will need is to create a way to translate LCD I/O operations to SPI signals.

SPI_LCD

The main advantages of using SPI-based/bit-banging-based LCDs include immunity from noise and EMI, simplicity, reduction of GPIO usage and addressability. However, the downsides are slower refresh rates and requirement of additional coding and memory spaces. Even with these pluses and minuses, driving a LCD with such arrangements is often a requirement, especially when it comes to low pin count micros and fast debugging.

Hardware Connection

3 Wire LCD

4094 Module

Code Example

 

lcd.h

#include "stm8s.h"


#define LCD_PORT                        GPIOD

#define LCD_STB_pin                     GPIO_PIN_2 
#define LCD_SCK_pin                     GPIO_PIN_3
#define LCD_SDI_pin                     GPIO_PIN_4 

#define LCD_STB_HIGH()                  GPIO_WriteHigh(LCD_PORT, LCD_STB_pin)
#define LCD_STB_LOW()                   GPIO_WriteLow(LCD_PORT, LCD_STB_pin)
#define LCD_SCK_HIGH()                  GPIO_WriteHigh(LCD_PORT, LCD_SCK_pin)
#define LCD_SCK_LOW()                   GPIO_WriteLow(LCD_PORT, LCD_SCK_pin)
#define LCD_SDI_HIGH()                  GPIO_WriteHigh(LCD_PORT, LCD_SDI_pin)
#define LCD_SDI_LOW()                   GPIO_WriteLow(LCD_PORT, LCD_SDI_pin)


#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 dly                            2

#define DAT                              1
#define CMD                              0


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

#include "lcd.h"


void SIPO(void)
{
    unsigned char bit = 0x00;
    unsigned char clk = 0x08;
    unsigned char temp = 0x00;

    temp = data_value;
    LCD_STB_LOW();

    while(clk > 0)
    {
        bit = ((temp & 0x80) >> 0x07);
        bit &= 0x01;

        switch(bit)
        {
            case 0:
            {
                LCD_SDI_LOW();
                break;
            }
            default:
            {
                LCD_SDI_HIGH();
                break;
            }
        }

        LCD_SCK_HIGH();

        temp <<= 0x01;
        clk--;

        LCD_SCK_LOW();
    };

    LCD_STB_HIGH();
}


void LCD_init(void)
{                                    
    unsigned char t = 0x00;

    GPIO_Init(LCD_PORT,
               ((GPIO_Pin_TypeDef)(LCD_STB_pin | LCD_SCK_pin | LCD_SDI_pin)),
               GPIO_MODE_OUT_PP_HIGH_FAST);

    data_value = 0x08;
    SIPO();
    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 |= 0x08;
    SIPO();
    delay_ms(dly);
    data_value &= 0xF7;
    SIPO();
    delay_ms(dly);
}


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


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_SPI, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, 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(LCD_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 code demoed here is same as the other LCD codes in this article so there is not much to explain. The I/O 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 STM8 GPIOs we are able to interface a 4-bit LCD that needs at least six GPIOs to work.

CD4094B can be operated by either bit-banging GPIOs, i.e. using software SPI or using hardware SPI. If we are to use software-based SPI then we need to code the entire SPI operation as shown below:

void SIPO(void)
{
    unsigned char bit = 0x00;
    unsigned char clk = 0x08;
    unsigned char temp = 0x00;

    temp = data_value;
    LCD_STB_LOW();

    while(clk > 0)
    {
        bit = ((temp & 0x80) >> 0x07);
        bit &= 0x01;

        switch(bit)
        {
            case 0:
            {
                LCD_SDI_LOW();
                break;
            }
            default:
            {
                LCD_SDI_HIGH();
                break;
            }
        }

        LCD_SCK_HIGH();

        temp <<= 0x01;
        clk--;

        LCD_SCK_LOW();
    };

    LCD_STB_HIGH();
}

This function does the work of SPI clock generation, chip selection and data bit shifting.

If hardware SPI is used, we have to initialize SPI hardware, use hardware SPI pins and use SPI write technique to send data to shift register. The process of clock generation and data shifting is done inside the SPI hardware and so we have nothing else to do.

void SPI_setup(void)
{
       SPI_DeInit();

       SPI_Init(SPI_FIRSTBIT_MSB,
                SPI_BAUDRATEPRESCALER_64,
                SPI_MODE_MASTER,
                SPI_CLOCKPOLARITY_LOW,
                SPI_CLOCKPHASE_1EDGE,
                SPI_DATADIRECTION_1LINE_TX,
                SPI_NSS_SOFT,
                0x00);

       SPI_Cmd(ENABLE);
}


void LCD_write(void)
{
    while(SPI_GetFlagStatus(SPI_FLAG_BSY));
    GPIO_WriteHigh(GPIOC, GPIO_PIN_4);
    SPI_SendData(data_value);
    GPIO_WriteLow(GPIOC, GPIO_PIN_4);
    while(!SPI_GetFlagStatus(SPI_FLAG_TXE));
}

Just as with any hardware peripheral, we have to enable SPI peripheral clock before we use it.

CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE);

The rest of the code is just about translating and transferring GPIO patterns to the shift register. Remember everything is same the only this is the fact that the shift register here works as a GPIO expander. The code here works as per hardware connections shown. In the market, there is no ready-made SPI LCD module like the one I used here. It is my own designed. If different shift register or different hardware layout is used then you must modify the code accordingly.

 

Demo

SPI LCD (2) SPI LCD (1)

Continue Reading ...

Related Posts

Leave a Reply

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