
In this second part of the guide, we move from configuration to implementation by developing the GC9A01 driver header and source files for STM32. We will initialize the LCD controller and implement a basic draw-pixel function, using it to render randomly colored pixels as a first functional test of the display.
In this guide, we shall cover the following:
- Creating source and header file.
- Developing the header file.
- Developing the source file.
- Main.c code.
- Results.
5. Creating Source and Header File:
irst, we need to create new source and header files.
To create Source 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 GC9A01.c as following:

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

6. Developing the Header File:
Within GC9A01.h file, we start by including the following header files:
#include "stdint.h" #include "main.h" #include "spi.h"
- stdint allows us to access uint8_t etc variable type.
- main.h for main STM32 header file to access HAL basic functions like HAL_Delay etc.
- spi.h allows to access to SPI peripheral related function.
Next, define the size of the display:
#define LCD_1IN28_HEIGHT 240 #define LCD_1IN28_WIDTH 240
Orientation of the display:
#define HORIZONTAL 0 #define VERTICAL 1
Functions related to display initialization and draw pixel:
void LCD_Init(uint8_t Scan_dir); void Draw_Pixel(uint8_t X, uint8_t Y, uint16_t Color);
Hence, the header file as follows:
#ifndef INC_GC9A01_H_ #define INC_GC9A01_H_ #include "stdint.h" #include "main.h" #include "spi.h" #define LCD_1IN28_HEIGHT 240 #define LCD_1IN28_WIDTH 240 #define HORIZONTAL 0 #define VERTICAL 1 void LCD_Init(uint8_t Scan_dir); void Draw_Pixel(uint8_t X, uint8_t Y, uint16_t Color); #endif /* INC_GC9A01_H_ */
7. Developing the Source File:
We start by including GC9A01 header file as follows:
#include "GC9A01.h"
Data structure to hold some required data:
typedef struct{
uint16_t WIDTH;
uint16_t HEIGHT;
int8_t SCAN_DIR;
}LCD_ATTRIBUTES;Declare the data structure:
LCD_ATTRIBUTES lcdAttr;
Next, we need some functions to handle the CS, CD and RST pins:
static void CS_Deselect()
{
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, SET);
}
static void CS_Select()
{
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, RESET);
}
static void RST_HIGH()
{
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, SET);
}
static void RST_LOW()
{
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, RESET);
}
static void DC_HIGH()
{
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, SET);
}
static void DC_LOW()
{
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, RESET);
}These are standardized for any color LCD.
Next, function that will hardware reset the display:
static void Reset(void)
{
RST_HIGH();
HAL_Delay(100);
RST_LOW();
HAL_Delay(100);
RST_HIGH();
HAL_Delay(100);
}The reset sequence as follows:
- Set RST pin to high and wait for 100ms.
- Set it low and wait for 100ms.
- Set it high again and wait for 100ms.
Next, function that will write data, command and 16-bit data as follows:
static void WriteData(uint8_t data)
{
CS_Select();
DC_HIGH();
HAL_SPI_Transmit(&hspi1, &data, 1, 10);
CS_Deselect();
}
static void WriteCommand(uint8_t Command)
{
CS_Select();
DC_LOW();
HAL_SPI_Transmit(&hspi1, &Command, 1, 10);
CS_Deselect();
}
static void WriteData16Bit(uint16_t data)
{
CS_Select();
DC_HIGH();
uint8_t data16_bit[2]={(data>>8)&0xFF, data&0xFF};
HAL_SPI_Transmit(&hspi1, data16_bit, 2, 10);
CS_Deselect();
}Next, register initialization of the display:
static void LCD_InitReg(void)
{
WriteCommand(0xEF);
WriteCommand(0xEB);
WriteData(0x14);
WriteCommand(0xFE);
WriteCommand(0xEF);
WriteCommand(0xEB);
WriteData(0x14);
WriteCommand(0x84);
WriteData(0x40);
WriteCommand(0x85);
WriteData(0xFF);
WriteCommand(0x86);
WriteData(0xFF);
WriteCommand(0x87);
WriteData(0xFF);
WriteCommand(0x88);
WriteData(0x0A);
WriteCommand(0x89);
WriteData(0x21);
WriteCommand(0x8A);
WriteData(0x00);
WriteCommand(0x8B);
WriteData(0x80);
WriteCommand(0x8C);
WriteData(0x01);
WriteCommand(0x8D);
WriteData(0x01);
WriteCommand(0x8E);
WriteData(0xFF);
WriteCommand(0x8F);
WriteData(0xFF);
WriteCommand(0xB6);
WriteData(0x00);
WriteData(0x20);
WriteCommand(0x36);
WriteData(0x08);//Set as vertical screen
WriteCommand(0x3A);
WriteData(0x05);
WriteCommand(0x90);
WriteData(0x08);
WriteData(0x08);
WriteData(0x08);
WriteData(0x08);
WriteCommand(0xBD);
WriteData(0x06);
WriteCommand(0xBC);
WriteData(0x00);
WriteCommand(0xFF);
WriteData(0x60);
WriteData(0x01);
WriteData(0x04);
WriteCommand(0xC3);
WriteData(0x13);
WriteCommand(0xC4);
WriteData(0x13);
WriteCommand(0xC9);
WriteData(0x22);
WriteCommand(0xBE);
WriteData(0x11);
WriteCommand(0xE1);
WriteData(0x10);
WriteData(0x0E);
WriteCommand(0xDF);
WriteData(0x21);
WriteData(0x0c);
WriteData(0x02);
WriteCommand(0xF0);
WriteData(0x45);
WriteData(0x09);
WriteData(0x08);
WriteData(0x08);
WriteData(0x26);
WriteData(0x2A);
WriteCommand(0xF1);
WriteData(0x43);
WriteData(0x70);
WriteData(0x72);
WriteData(0x36);
WriteData(0x37);
WriteData(0x6F);
WriteCommand(0xF2);
WriteData(0x45);
WriteData(0x09);
WriteData(0x08);
WriteData(0x08);
WriteData(0x26);
WriteData(0x2A);
WriteCommand(0xF3);
WriteData(0x43);
WriteData(0x70);
WriteData(0x72);
WriteData(0x36);
WriteData(0x37);
WriteData(0x6F);
WriteCommand(0xED);
WriteData(0x1B);
WriteData(0x0B);
WriteCommand(0xAE);
WriteData(0x77);
WriteCommand(0xCD);
WriteData(0x63);
WriteCommand(0x70);
WriteData(0x07);
WriteData(0x07);
WriteData(0x04);
WriteData(0x0E);
WriteData(0x0F);
WriteData(0x09);
WriteData(0x07);
WriteData(0x08);
WriteData(0x03);
WriteCommand(0xE8);
WriteData(0x34);
WriteCommand(0x62);
WriteData(0x18);
WriteData(0x0D);
WriteData(0x71);
WriteData(0xED);
WriteData(0x70);
WriteData(0x70);
WriteData(0x18);
WriteData(0x0F);
WriteData(0x71);
WriteData(0xEF);
WriteData(0x70);
WriteData(0x70);
WriteCommand(0x63);
WriteData(0x18);
WriteData(0x11);
WriteData(0x71);
WriteData(0xF1);
WriteData(0x70);
WriteData(0x70);
WriteData(0x18);
WriteData(0x13);
WriteData(0x71);
WriteData(0xF3);
WriteData(0x70);
WriteData(0x70);
WriteCommand(0x64);
WriteData(0x28);
WriteData(0x29);
WriteData(0xF1);
WriteData(0x01);
WriteData(0xF1);
WriteData(0x00);
WriteData(0x07);
WriteCommand(0x66);
WriteData(0x3C);
WriteData(0x00);
WriteData(0xCD);
WriteData(0x67);
WriteData(0x45);
WriteData(0x45);
WriteData(0x10);
WriteData(0x00);
WriteData(0x00);
WriteData(0x00);
WriteCommand(0x67);
WriteData(0x00);
WriteData(0x3C);
WriteData(0x00);
WriteData(0x00);
WriteData(0x00);
WriteData(0x01);
WriteData(0x54);
WriteData(0x10);
WriteData(0x32);
WriteData(0x98);
WriteCommand(0x74);
WriteData(0x10);
WriteData(0x85);
WriteData(0x80);
WriteData(0x00);
WriteData(0x00);
WriteData(0x4E);
WriteData(0x00);
WriteCommand(0x98);
WriteData(0x3e);
WriteData(0x07);
WriteCommand(0x35);
WriteCommand(0x21);
WriteCommand(0x11);
HAL_Delay(120);
WriteCommand(0x29);
HAL_Delay(20);
}This function sends the required initialization sequence to the LCD controller so the panel can operate correctly.
It configures internal power circuits, voltage levels, timing parameters, gamma curves, and panel-driving waveforms using a series of command/data writes. The 0x36 command sets the screen orientation, while 0x3A selects the pixel format (RGB565). Other grouped commands adjust refresh behavior and image quality.
At the end, 0x11 brings the display out of sleep mode, the delay allows power stabilization, and 0x29 turns the screen on so it is ready to receive pixel data.
Next, setting display attributes as follows:
static void LCD_SetAttributes(uint8_t Scan_dir)
{
//Get the screen scan direction
lcdAttr.SCAN_DIR = Scan_dir;
uint8_t MemoryAccessReg = 0x08;
//Get GRAM and LCD width and height
if(Scan_dir == HORIZONTAL) {
lcdAttr.HEIGHT = LCD_1IN28_HEIGHT;
lcdAttr.WIDTH = LCD_1IN28_WIDTH;
MemoryAccessReg = 0XC8;
} else {
lcdAttr.HEIGHT = LCD_1IN28_WIDTH;
lcdAttr.WIDTH = LCD_1IN28_HEIGHT;
MemoryAccessReg = 0X68;
}
// Set the read / write scan direction of the frame memory
WriteCommand(0x36); //MX, MY, RGB mode
WriteData(MemoryAccessReg); //0x08 set RGB
}This will allow the firmware to adjust the behaviour according to the orientation.
Next, LCD initialization function:
void LCD_Init(uint8_t Scan_dir)
{
// Hardware reset
Reset();
// Set the resolution and scanning method of the screen
LCD_SetAttributes(Scan_dir);
// Set the initialization register
LCD_InitReg();
}Next, set address window function:
void LCD_SetWindows(uint8_t Xstart, uint8_t Ystart, uint8_t Xend, uint8_t Yend)
{
//set the X coordinates
WriteCommand(0x2A);
WriteData(0x00);
WriteData(Xstart);
WriteData(0x00);
WriteData(Xend);
//set the Y coordinates
WriteCommand(0x2B);
WriteData(0x00);
WriteData(Ystart);
WriteData(0x00);
WriteData(Yend);
WriteCommand(0X2C);
}This function defines the active drawing window (addressing region) inside the GC9A01’s internal GRAM before any pixel data is written. It uses the Column Address Set (0x2A) and Row Address Set (0x2B) commands to specify the start and end coordinates of the region where subsequent pixel data will be applied.
Finally draw pixel function:
void Draw_Pixel(uint8_t X, uint8_t Y, uint16_t Color)
{
LCD_SetWindows(X, Y, X, Y);
WriteData16Bit(Color);
}Hence, the entire source file as follows:
#include "GC9A01.h"
typedef struct{
uint16_t WIDTH;
uint16_t HEIGHT;
int8_t SCAN_DIR;
}LCD_ATTRIBUTES;
LCD_ATTRIBUTES lcdAttr;
static void CS_Deselect()
{
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, SET);
}
static void CS_Select()
{
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, RESET);
}
static void RST_HIGH()
{
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, SET);
}
static void RST_LOW()
{
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, RESET);
}
static void DC_HIGH()
{
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, SET);
}
static void DC_LOW()
{
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, RESET);
}
static void Reset(void)
{
RST_HIGH();
HAL_Delay(100);
RST_LOW();
HAL_Delay(100);
RST_HIGH();
HAL_Delay(100);
}
static void WriteData(uint8_t data)
{
CS_Select();
DC_HIGH();
HAL_SPI_Transmit(&hspi1, &data, 1, 10);
CS_Deselect();
}
static void WriteCommand(uint8_t Command)
{
CS_Select();
DC_LOW();
HAL_SPI_Transmit(&hspi1, &Command, 1, 10);
CS_Deselect();
}
static void WriteData16Bit(uint16_t data)
{
CS_Select();
DC_HIGH();
uint8_t data16_bit[2]={(data>>8)&0xFF, data&0xFF};
HAL_SPI_Transmit(&hspi1, data16_bit, 2, 10);
CS_Deselect();
}
static void LCD_InitReg(void)
{
WriteCommand(0xEF);
WriteCommand(0xEB);
WriteData(0x14);
WriteCommand(0xFE);
WriteCommand(0xEF);
WriteCommand(0xEB);
WriteData(0x14);
WriteCommand(0x84);
WriteData(0x40);
WriteCommand(0x85);
WriteData(0xFF);
WriteCommand(0x86);
WriteData(0xFF);
WriteCommand(0x87);
WriteData(0xFF);
WriteCommand(0x88);
WriteData(0x0A);
WriteCommand(0x89);
WriteData(0x21);
WriteCommand(0x8A);
WriteData(0x00);
WriteCommand(0x8B);
WriteData(0x80);
WriteCommand(0x8C);
WriteData(0x01);
WriteCommand(0x8D);
WriteData(0x01);
WriteCommand(0x8E);
WriteData(0xFF);
WriteCommand(0x8F);
WriteData(0xFF);
WriteCommand(0xB6);
WriteData(0x00);
WriteData(0x20);
WriteCommand(0x36);
WriteData(0x08);//Set as vertical screen
WriteCommand(0x3A);
WriteData(0x05);
WriteCommand(0x90);
WriteData(0x08);
WriteData(0x08);
WriteData(0x08);
WriteData(0x08);
WriteCommand(0xBD);
WriteData(0x06);
WriteCommand(0xBC);
WriteData(0x00);
WriteCommand(0xFF);
WriteData(0x60);
WriteData(0x01);
WriteData(0x04);
WriteCommand(0xC3);
WriteData(0x13);
WriteCommand(0xC4);
WriteData(0x13);
WriteCommand(0xC9);
WriteData(0x22);
WriteCommand(0xBE);
WriteData(0x11);
WriteCommand(0xE1);
WriteData(0x10);
WriteData(0x0E);
WriteCommand(0xDF);
WriteData(0x21);
WriteData(0x0c);
WriteData(0x02);
WriteCommand(0xF0);
WriteData(0x45);
WriteData(0x09);
WriteData(0x08);
WriteData(0x08);
WriteData(0x26);
WriteData(0x2A);
WriteCommand(0xF1);
WriteData(0x43);
WriteData(0x70);
WriteData(0x72);
WriteData(0x36);
WriteData(0x37);
WriteData(0x6F);
WriteCommand(0xF2);
WriteData(0x45);
WriteData(0x09);
WriteData(0x08);
WriteData(0x08);
WriteData(0x26);
WriteData(0x2A);
WriteCommand(0xF3);
WriteData(0x43);
WriteData(0x70);
WriteData(0x72);
WriteData(0x36);
WriteData(0x37);
WriteData(0x6F);
WriteCommand(0xED);
WriteData(0x1B);
WriteData(0x0B);
WriteCommand(0xAE);
WriteData(0x77);
WriteCommand(0xCD);
WriteData(0x63);
WriteCommand(0x70);
WriteData(0x07);
WriteData(0x07);
WriteData(0x04);
WriteData(0x0E);
WriteData(0x0F);
WriteData(0x09);
WriteData(0x07);
WriteData(0x08);
WriteData(0x03);
WriteCommand(0xE8);
WriteData(0x34);
WriteCommand(0x62);
WriteData(0x18);
WriteData(0x0D);
WriteData(0x71);
WriteData(0xED);
WriteData(0x70);
WriteData(0x70);
WriteData(0x18);
WriteData(0x0F);
WriteData(0x71);
WriteData(0xEF);
WriteData(0x70);
WriteData(0x70);
WriteCommand(0x63);
WriteData(0x18);
WriteData(0x11);
WriteData(0x71);
WriteData(0xF1);
WriteData(0x70);
WriteData(0x70);
WriteData(0x18);
WriteData(0x13);
WriteData(0x71);
WriteData(0xF3);
WriteData(0x70);
WriteData(0x70);
WriteCommand(0x64);
WriteData(0x28);
WriteData(0x29);
WriteData(0xF1);
WriteData(0x01);
WriteData(0xF1);
WriteData(0x00);
WriteData(0x07);
WriteCommand(0x66);
WriteData(0x3C);
WriteData(0x00);
WriteData(0xCD);
WriteData(0x67);
WriteData(0x45);
WriteData(0x45);
WriteData(0x10);
WriteData(0x00);
WriteData(0x00);
WriteData(0x00);
WriteCommand(0x67);
WriteData(0x00);
WriteData(0x3C);
WriteData(0x00);
WriteData(0x00);
WriteData(0x00);
WriteData(0x01);
WriteData(0x54);
WriteData(0x10);
WriteData(0x32);
WriteData(0x98);
WriteCommand(0x74);
WriteData(0x10);
WriteData(0x85);
WriteData(0x80);
WriteData(0x00);
WriteData(0x00);
WriteData(0x4E);
WriteData(0x00);
WriteCommand(0x98);
WriteData(0x3e);
WriteData(0x07);
WriteCommand(0x35);
WriteCommand(0x21);
WriteCommand(0x11);
HAL_Delay(120);
WriteCommand(0x29);
HAL_Delay(20);
}
static void LCD_SetAttributes(uint8_t Scan_dir)
{
//Get the screen scan direction
lcdAttr.SCAN_DIR = Scan_dir;
uint8_t MemoryAccessReg = 0x08;
//Get GRAM and LCD width and height
if(Scan_dir == HORIZONTAL) {
lcdAttr.HEIGHT = LCD_1IN28_HEIGHT;
lcdAttr.WIDTH = LCD_1IN28_WIDTH;
MemoryAccessReg = 0XC8;
} else {
lcdAttr.HEIGHT = LCD_1IN28_WIDTH;
lcdAttr.WIDTH = LCD_1IN28_HEIGHT;
MemoryAccessReg = 0X68;
}
// Set the read / write scan direction of the frame memory
WriteCommand(0x36); //MX, MY, RGB mode
WriteData(MemoryAccessReg); //0x08 set RGB
}
void LCD_Init(uint8_t Scan_dir)
{
// Hardware reset
Reset();
// Set the resolution and scanning method of the screen
LCD_SetAttributes(Scan_dir);
// Set the initialization register
LCD_InitReg();
}
void LCD_SetWindows(uint8_t Xstart, uint8_t Ystart, uint8_t Xend, uint8_t Yend)
{
//set the X coordinates
WriteCommand(0x2A);
WriteData(0x00);
WriteData(Xstart);
WriteData(0x00);
WriteData(Xend);
//set the Y coordinates
WriteCommand(0x2B);
WriteData(0x00);
WriteData(Ystart);
WriteData(0x00);
WriteData(Yend);
WriteCommand(0X2C);
}
void Draw_Pixel(uint8_t X, uint8_t Y, uint16_t Color)
{
LCD_SetWindows(X, Y, X, Y);
WriteData16Bit(Color);
}
8. Main.c Code:
In user code includes in main.c file, include the following:
#include "GC9A01.h" #include "stdlib.h"
In user code begin 2 in main function, initialize the LCD in vertical orientation:
LCD_Init(VERTICAL);
In user code begin 3 in while loop:
File each pixel with random color as follows:
for(int i=0;i<LCD_1IN28_HEIGHT;i++)
{
for (int j=0;j<LCD_1IN28_WIDTH;j++)
{
Draw_Pixel(i,j,random()%0xFFFF);
}
}
HAL_Delay(500);Thats all.
Save the project, build it and run it on your board as following:

9. Results:
Once the IDE finish flashing, you should get random colors on you display, for example:

In part 3, we shall display an image.
Stay tuned.
Happy coding 😉
Add Comment