Interfacing the 128×64 ST7920 GLCD with STM32F4 via SPI – Part 2: LCD Initialization and Drawing

In the second part of the ST7920 GLCD guide, we shall start the initialization sequence and display some strings and display pictures.

In this guide, we shall cover the following:

  • Creating source and header file.
  • Header file development.
  • Source file development.
  • Testing the library.
  • Source code download.

5. Creating Source and Header File:

After the project has been generated by STM32CubeMX within STM32CubeIDE, we shall add new source file and header file.

Right click on Src folder and add new source file as following:

Give a name for the source file, we shall name it as ST7920.c as following:

In similar manner, right click on Inc folder and add new header file with name of ST7920.h.

6. Developing the Header File:

Open the newest created header file.

We start with off by including the stdint.h header as following:

#include "stdint.h"

Next, we shall declare the following functions:

void ST7920_init();
void ST7920_Clear();
void ST7920_SendString(int row, int col, char* string);
void ST7920_GraphicMode (int enable) ;
void ST7920_DrawBitmap(const unsigned char* graphic);
void ST7920_Update(void);
void DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1);
void DrawRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void DrawFilledRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void DrawCircle(uint8_t x0, uint8_t y0, uint8_t radius);
void DrawFilledCircle(int16_t x0, int16_t y0, int16_t r);
void DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3);
void DrawFilledTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3);

Hence, the entire header file as following:

#ifndef INC_ST7920_H_
#define INC_ST7920_H_

#include "stdint.h"


void ST7920_init();
void ST7920_Clear();
void ST7920_SendString(int row, int col, char* string);
void ST7920_GraphicMode (int enable) ;
void ST7920_DrawBitmap(const unsigned char* graphic);
void ST7920_Update(void);
void DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1);
void DrawRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void DrawFilledRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void DrawCircle(uint8_t x0, uint8_t y0, uint8_t radius);
void DrawFilledCircle(int16_t x0, int16_t y0, int16_t r);
void DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3);
void DrawFilledTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3);






#endif /* INC_ST7920_H_ */

Thats all for the header file.

7. Developing the Source File:

Open ST7920 source file and we start by including the following header files:

#include "ST7920.h"
#include "main.h"
#include "spi.h"
  • ST7920 header file which is already created by us.
  • main.h to access HAL API.
  • spi.h to access SPI peripheral.

Since the GLCD has two extra pins to control, we can create macros to handle the state of them as following:

#define RS_LOW 		HAL_GPIO_WritePin(RS_GPIO_Port,RS_Pin,GPIO_PIN_RESET);
#define RS_HIGH		HAL_GPIO_WritePin(RS_GPIO_Port,RS_Pin,GPIO_PIN_SET);

#define RST_LOW 	HAL_GPIO_WritePin(RST_GPIO_Port,RST_Pin,GPIO_PIN_RESET);
#define RST_HIGH	HAL_GPIO_WritePin(RST_GPIO_Port,RST_Pin,GPIO_PIN_SET);

Next, declare an array that holds three variables to be send over SPI as following:

uint8_t lcd_data[3];

Needed variable for the graphics:

uint8_t startRow, startCol, endRow, endCol; // coordinates of the dirty rectangle
uint8_t numRows = 64;
uint8_t numCols = 128;
uint8_t Graphic_Check = 0;
uint8_t image[(128 * 64)/8];

Next, we need two functions to send either command or data.

In order to send data/command, the datasheet clearly states the following:

  • Set CS pin to high.
  • Send 5bits of 1 to sync the display.
  • Send RW and RS state.
  • Followed by zero.
  • Then send data/command as two byte as shown in figure below:

Hence, the functions as following:

static void ST7920_SendCmd (uint8_t cmd)
{
	RS_HIGH;  // PUll the CS high

	lcd_data[0]=0xF8;
	lcd_data[1]=(cmd&0xf0);
	lcd_data[2]=((cmd<<4)&0xf0);

	HAL_SPI_Transmit(&hspi1, lcd_data, 3, 100);

	RS_LOW;  // PUll the CS LOW

}

static void ST7920_SendData (uint8_t data)
{

	RS_HIGH;

	lcd_data[0]=0xFA;
	lcd_data[1]=(data&0xf0);
	lcd_data[2]=((data<<4)&0xf0);

	HAL_SPI_Transmit(&hspi1, lcd_data, 3, 100);


	RS_LOW;  // PUll the CS LOW
}

Notice they are also static since they only being used in the current source file.

To intialize the LCD, we need the following steps:

  • Wait for 100ms to for power stability,
  • Reset the LCD by pulling RST pin to low for 50ms then high and wait for 100.
  • Send the command 0x03 to enter 8-bit mode (repeat twice to ensure the GLCD in SPI mode).
  • Turn off the display, cursor and blinking cursor.
  • Clear the display.
  • Increment the cursor after each character.
  • Turn on the display.
  • Return to home (set cursor back to 0,0).

void ST7920_init()
{
	HAL_Delay(100);
	RST_LOW;
	HAL_Delay(50);
	RST_HIGH;
	HAL_Delay(100);
	ST7920_SendCmd(0x30);  		// 8bit mode
	HAL_Delay(1);  				//  >100us delay

	ST7920_SendCmd(0x30);  		// 8bit mode
	HAL_Delay(1);  				// >37us delay

	ST7920_SendCmd(0x08);  // D=0, C=0, B=0
	HAL_Delay(1);  // >100us delay

	ST7920_SendCmd(0x01);  // clear screen
	HAL_Delay(12);  // >10 ms delay


	ST7920_SendCmd(0x06);  // cursor increment right no shift
	HAL_Delay(1);  // 1ms delay

	ST7920_SendCmd(0x0C);  // D=1, C=0, B=0
	HAL_Delay(1);  // 1ms delay

	ST7920_SendCmd(0x02);  // return to home
	HAL_Delay(1);  // 1ms delay

}

Support functions:

void ST7920_SendString(int row, int col, char* string)
{
    switch (row)
    {
        case 0:
            col |= 0x80;
            break;
        case 1:
            col |= 0x90;
            break;
        case 2:
            col |= 0x88;
            break;
        case 3:
            col |= 0x98;
            break;
        default:
            col |= 0x80;
            break;
    }

    ST7920_SendCmd(col);

    while (*string)
    	{
    		ST7920_SendData(*string++);
    	}
}



// switch to graphic mode or normal mode::: enable = 1 -> graphic mode enable = 0 -> normal mode

void ST7920_GraphicMode (int enable)   // 1-enable, 0-disable
{
	if (enable == 1)
	{
		ST7920_SendCmd(0x30);  // 8 bit mode
		HAL_Delay (1);
		ST7920_SendCmd(0x34);  // switch to Extended instructions
		HAL_Delay (1);
		ST7920_SendCmd(0x36);  // enable graphics
		HAL_Delay (1);
		Graphic_Check = 1;  // update the variable
	}

	else if (enable == 0)
	{
		ST7920_SendCmd(0x30);  // 8 bit mode
		HAL_Delay (1);
		Graphic_Check = 0;  // update the variable
	}
}

void ST7920_DrawBitmap(const unsigned char* graphic)
{
	uint8_t x, y;
	for(y = 0; y < 64; y++)
	{
		if(y < 32)
		{
			for(x = 0; x < 8; x++)							// Draws top half of the screen.
			{												// In extended instruction mode, vertical and horizontal coordinates must be specified before sending data in.
				ST7920_SendCmd(0x80 | y);				// Vertical coordinate of the screen is specified first. (0-31)
				ST7920_SendCmd(0x80 | x);				// Then horizontal coordinate of the screen is specified. (0-8)
				ST7920_SendData(graphic[2*x + 16*y]);		// Data to the upper byte is sent to the coordinate.
				ST7920_SendData(graphic[2*x+1 + 16*y]);	// Data to the lower byte is sent to the coordinate.
			}
		}
		else
		{
			for(x = 0; x < 8; x++)							// Draws bottom half of the screen.
			{												// Actions performed as same as the upper half screen.
				ST7920_SendCmd(0x80 | (y-32));			// Vertical coordinate must be scaled back to 0-31 as it is dealing with another half of the screen.
				ST7920_SendCmd(0x88 | x);
				ST7920_SendData(graphic[2*x + 16*y]);
				ST7920_SendData(graphic[2*x+1 + 16*y]);
			}
		}

	}
}


// Update the display with the selected graphics
void ST7920_Update(void)
{
	ST7920_DrawBitmap(image);
}



void ST7920_Clear()
{
	if (Graphic_Check == 1)  // if the graphic mode is set
	{
		uint8_t x, y;
		for(y = 0; y < 64; y++)
		{
			if(y < 32)
			{
				ST7920_SendCmd(0x80 | y);
				ST7920_SendCmd(0x80);
			}
			else
			{
				ST7920_SendCmd(0x80 | (y-32));
				ST7920_SendCmd(0x88);
			}
			for(x = 0; x < 8; x++)
			{
				ST7920_SendData(0);
				ST7920_SendData(0);
			}
		}
	}

	else
	{
		ST7920_SendCmd(0x01);   // clear the display using command
		HAL_Delay(20); // delay >1.6 ms
	}
}




static void SetPixel(uint8_t x, uint8_t y)
{
  if (y < numRows && x < numCols)
  {
    uint8_t *p = image + ((y * (numCols/8)) + (x/8));
    *p |= 0x80u >> (x%8);

    *image = *p;

    // Change the dirty rectangle to account for a pixel being dirty (we assume it was changed)
    if (startRow > y) { startRow = y; }
    if (endRow <= y)  { endRow = y + 1; }
    if (startCol > x) { startCol = x; }
    if (endCol <= x)  { endCol = x + 1; }


  }

}

/* draw a line
 * start point (X0, Y0)
 * end point (X1, Y1)
 */
void DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
{
  int dx = (x1 >= x0) ? x1 - x0 : x0 - x1;
  int dy = (y1 >= y0) ? y1 - y0 : y0 - y1;
  int sx = (x0 < x1) ? 1 : -1;
  int sy = (y0 < y1) ? 1 : -1;
  int err = dx - dy;

  for (;;)
  {
    SetPixel(x0, y0);
    if (x0 == x1 && y0 == y1) break;
    int e2 = err + err;
    if (e2 > -dy)
    {
      err -= dy;
      x0 += sx;
    }
    if (e2 < dx)
    {
      err += dx;
      y0 += sy;
    }
  }
}




/* Draw rectangle
 * start point (x,y)
 * w -> width
 * h -> height
 */
void DrawRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
	/* Check input parameters */
	if (
		x >= numCols ||
		y >= numRows
	) {
		/* Return error */
		return;
	}

	/* Check width and height */
	if ((x + w) >= numCols) {
		w = numCols - x;
	}
	if ((y + h) >= numRows) {
		h = numRows - y;
	}

	/* Draw 4 lines */
	DrawLine(x, y, x + w, y);         /* Top line */
	DrawLine(x, y + h, x + w, y + h); /* Bottom line */
	DrawLine(x, y, x, y + h);         /* Left line */
	DrawLine(x + w, y, x + w, y + h); /* Right line */
}




/* Draw filled rectangle
 * Start point (x,y)
 * w -> width
 * h -> height
 */
void DrawFilledRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
	uint8_t i;

	/* Check input parameters */
	if (
		x >= numCols ||
		y >= numRows
	) {
		/* Return error */
		return;
	}

	/* Check width and height */
	if ((x + w) >= numCols) {
		w = numCols - x;
	}
	if ((y + h) >= numRows) {
		h = numRows - y;
	}

	/* Draw lines */
	for (i = 0; i <= h; i++) {
		/* Draw lines */
		DrawLine(x, y + i, x + w, y + i);
	}
}




/* draw circle
 * centre (x0,y0)
 * radius = radius
 */
void DrawCircle(uint8_t x0, uint8_t y0, uint8_t radius)
{
  int f = 1 - (int)radius;
  int ddF_x = 1;

  int ddF_y = -2 * (int)radius;
  int x = 0;

  SetPixel(x0, y0 + radius);
  SetPixel(x0, y0 - radius);
  SetPixel(x0 + radius, y0);
  SetPixel(x0 - radius, y0);

  int y = radius;
  while(x < y)
  {
    if(f >= 0)
    {
      y--;
      ddF_y += 2;
      f += ddF_y;
    }
    x++;
    ddF_x += 2;
    f += ddF_x;
    SetPixel(x0 + x, y0 + y);
    SetPixel(x0 - x, y0 + y);
    SetPixel(x0 + x, y0 - y);
    SetPixel(x0 - x, y0 - y);
    SetPixel(x0 + y, y0 + x);
    SetPixel(x0 - y, y0 + x);
    SetPixel(x0 + y, y0 - x);
    SetPixel(x0 - y, y0 - x);
  }
}


// Draw Filled Circle

void DrawFilledCircle(int16_t x0, int16_t y0, int16_t r)
{
	int16_t f = 1 - r;
	int16_t ddF_x = 1;
	int16_t ddF_y = -2 * r;
	int16_t x = 0;
	int16_t y = r;

    SetPixel(x0, y0 + r);
    SetPixel(x0, y0 - r);
    SetPixel(x0 + r, y0);
    SetPixel(x0 - r, y0);
    DrawLine(x0 - r, y0, x0 + r, y0);

    while (x < y) {
        if (f >= 0) {
            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;

        DrawLine(x0 - x, y0 + y, x0 + x, y0 + y);
        DrawLine(x0 + x, y0 - y, x0 - x, y0 - y);

        DrawLine(x0 + y, y0 + x, x0 - y, y0 + x);
        DrawLine(x0 + y, y0 - x, x0 - y, y0 - x);
    }
}



// Draw Traingle with coordimates (x1, y1), (x2, y2), (x3, y3)
void DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3)
{
	/* Draw lines */
	DrawLine(x1, y1, x2, y2);
	DrawLine(x2, y2, x3, y3);
	DrawLine(x3, y3, x1, y1);
}



// Draw Filled Traingle with coordimates (x1, y1), (x2, y2), (x3, y3)
void DrawFilledTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3)
{
	int16_t deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0,
	yinc1 = 0, yinc2 = 0, den = 0, num = 0, numadd = 0, numpixels = 0,
	curpixel = 0;

#define ABS(x)   ((x) > 0 ? (x) : -(x))

	deltax = ABS(x2 - x1);
	deltay = ABS(y2 - y1);
	x = x1;
	y = y1;

	if (x2 >= x1) {
		xinc1 = 1;
		xinc2 = 1;
	} else {
		xinc1 = -1;
		xinc2 = -1;
	}

	if (y2 >= y1) {
		yinc1 = 1;
		yinc2 = 1;
	} else {
		yinc1 = -1;
		yinc2 = -1;
	}

	if (deltax >= deltay){
		xinc1 = 0;
		yinc2 = 0;
		den = deltax;
		num = deltax / 2;
		numadd = deltay;
		numpixels = deltax;
	} else {
		xinc2 = 0;
		yinc1 = 0;
		den = deltay;
		num = deltay / 2;
		numadd = deltax;
		numpixels = deltay;
	}

	for (curpixel = 0; curpixel <= numpixels; curpixel++)
	{
		DrawLine(x, y, x3, y3);

		num += numadd;
		if (num >= den) {
			num -= den;
			x += xinc1;
			y += yinc1;
		}
		x += xinc2;
		y += yinc2;
	}
}


8. Testing the library:

In main.c, in user begin, include the ST7920 header file as following:

#include "ST7920.h"

Extra header files to test:

#include "horse.h"
#include "bitmap.h"

Note: Included in the source file that can be downloaded from the last section.

In user code begin 2 in main function, initlialize the GLCD and start displaying some text and graphics as following:

ST7920_init();

  	ST7920_SendString(0,0, "HELLO WORLD");
  	ST7920_SendString(1,0, "FROM");
  	ST7920_SendString(2,0, "EmbeddedExpertIO");
  	ST7920_SendString(3,0, "1234567890!@#$%^");
  	HAL_Delay(2000);
  	ST7920_Clear();
  	ST7920_GraphicMode(1);
  	ST7920_DrawBitmap(logo);
  	HAL_Delay(2000);
  	ST7920_Clear();
  	DrawCircle(110, 31, 12);

  	DrawCircle(110, 31, 16);

  	DrawLine(3, 60, 127, 33);

  	ST7920_Update();

  	HAL_Delay(2000);

  	DrawRectangle (100, 12, 20, 14);

  	ST7920_Update();

  	HAL_Delay(2000);

  	DrawFilledRectangle(30, 20, 30, 10);

  	ST7920_Update();

  	HAL_Delay(2000);

  	DrawFilledCircle(15, 30, 6);

  	ST7920_Update();

  	DrawFilledTriangle(1,5,10,5,6,15);

  	ST7920_Update();

  	HAL_Delay(2000);

  	ST7920_Clear();

In while 1 loop, we shall display horse animation as following:

		ST7920_DrawBitmap(horse1);
		ST7920_DrawBitmap(horse2);
		ST7920_DrawBitmap(horse3);
		ST7920_DrawBitmap(horse4);
		ST7920_DrawBitmap(horse5);
		ST7920_DrawBitmap(horse6);
		ST7920_DrawBitmap(horse7);
		ST7920_DrawBitmap(horse8);
		ST7920_DrawBitmap(horse9);
		ST7920_DrawBitmap(horse10);

Save the project, build it and run it on your board as following:

You should get the following:

9. Source Code Download:

You can download the source code from here:

Happy coding 😉

Add Comment

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