top of page

Part 3 LCD : Drawing Shapes (Without Library)

In this tutorial, we will be looking at how to draw shapes such as lines, rectangles and circles in our LCD.


Let’s begin by drawing a line. To draw a line, simply include the below void in your program.


void drawLine(float x1, float y1, float x2, float y2, uint16_t color) {
 if (x2 > x1) {
   for (int xinc = x1; xinc < x2; xinc++) { //xinc = x=incremental
     float m = (y2 - y1) / (x2 - x1); //m=gradient
     float yinc = m * (xinc - x1) + y1;
     //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
     Address_set(y1, yinc, x1, xinc);
     LCD_data_write(color >> 8);
     LCD_data_write(color);
     y1 = yinc;
     x1 = xinc;
   }
 }
 else {
   float newx1 = x2; float newx2 = x1; float newy1 = y2; float newy2 = y1;
   for (int xinc = newx1; xinc < newx2; xinc++) { //xinc = x=incremental
     float m = (newy2 - newy1) / (newx2 - newx1); //m=gradient
     float yinc = m * (xinc - newx1) + newy1;
     //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
     Address_set(newy1, yinc, newx1, xinc);
     LCD_data_write(color >> 8);
     LCD_data_write(color);
     newy1 = yinc;
     newx1 = xinc;
   }
 }
}

The code above is pretty straightforward to understand as it only uses the straight line formula to deduce the next pixel in drawing a straight line.


m = (y2 - y1) / (x2 - x1); //m=gradient

As an example, you can try running the below code by including it in the void setup as we would only want this code to run once :


 uint16_t color = selectColour(100, 0, 0);
 drawLine(20,20,300,220,color);
 drawLine(20,220,300,20,color);

The above code would give you 2 diagonal lines drawn in color of your choice.


To draw a hollow rectangle, you can make use of the below void :


void hollowRectangle(int16_t rectx1, int16_t recty1, int16_t wide, int16_t tall, uint16_t color) {
 //draw left line
 int16_t lefty1 = recty1;
 int16_t leftx1 = rectx1;
 int16_t lefty2 = recty1 + tall;
 int16_t leftx2 = rectx1 + 1;
 for (int i = lefty1; lefty1 < lefty2; lefty1++) {
   //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
   Address_set(lefty1, lefty2, leftx1, leftx2);
   LCD_data_write(color >> 8);
   LCD_data_write(color);
 }

 //draw left line
 int16_t righty1 = recty1;
 int16_t rightx1 = rectx1;
 int16_t righty2 = recty1 + tall;
 int16_t rightx2 = rectx1 + wide;
 for (int i = righty1; righty1 < righty2; righty1++) {
   //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
   Address_set(righty1, righty2, rightx2, rightx2 + 1);
   LCD_data_write(color >> 8);
   LCD_data_write(color);
 }

 //draw top line
 int16_t topy1 = recty1 + tall;
 int16_t topx1 = rectx1;
 int16_t topy2 = recty1 + tall;
 int16_t topx2 = rectx1 + wide;
 for (int i = topx1; topx1 < topx2 + 1; topx1++) {
   //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
   Address_set(topy1, topy2 + 1, topx1, topx2 + 1);
   LCD_data_write(color >> 8);
   LCD_data_write(color);
 }

 //draw bottom line
 int16_t bottomy1 = recty1;
 int16_t bottomx1 = rectx1;
 int16_t bottomy2 = recty1;
 int16_t bottomx2 = rectx1 + wide;
 for (int i = bottomx1; bottomx1 < bottomx2 + 1; bottomx1++) {
   //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
   Address_set(bottomy1, bottomy2 + 1, bottomx1, bottomx2 + 1);
   LCD_data_write(color >> 8);
   LCD_data_write(color);
 }
}

The above void is built on simple mathematics to construct 4 lines or sides of a rectangle which will construct a rectangle in whole. You can try out the below examples to understand how these codes work:


//Basic Rectangle
uint16_t color = selectColour(100, 0, 0);
hollowRectangle(20, 20, 280, 200, color);

The above example will draw a basic rectangle.


//Example 1

 for(int i=20; i < 280; i = i +20){
 for(int j=20; j < 200; j = j +20){
 uint16_t color = selectColour(100, 0, 0);
 hollowRectangle(20, 20, i, j, color);
 }
 }

The above example will draw rectangle stacked on top of each other.


//Example2

 for(int i=20; i < 280; i = i +20){
 
 uint16_t color = selectColour(100, 0, 0);
 hollowRectangle(0, 0, i, i, color);
 }

The above example will draw multiple rectangle in increasing width and height.


Next, we will be looking at how to draw filled rectangles. To draw a rectangle filled with color, you can use the below function :


void filledRectangle(int16_t rectx1, int16_t recty1, int16_t wide, int16_t tall, uint16_t color) {
 //draw left line
 int16_t y1 = recty1;
 int16_t x1 = rectx1;
 int16_t y2 = recty1 + tall;
 int16_t x2 = rectx1 + wide;

 Address_set(y1, y2, x1, x2);
 for (int i = 0; i < wide + 1; i++)
   for (int j = 0; j < tall + 1; j++) {
     LCD_data_write(color >> 8);
     LCD_data_write(color);
   }
}

Once included the above void in your program, you can try out the below examples which draw three rectangles in different colors and size.


//Example 1
 uint16_t color = selectColour(100, 0, 0);
 filledRectangle(0, 0, 320, 240, color);

 filledRectangle(40, 40, 240, 160, selectColour(0, 100, 0));

 filledRectangle(80, 80, 160, 80, selectColour(0, 0, 100));

Then, let’s look at drawing hollow circles. As usual, include the below void functions if you need to draw hollow circle:


void hollowCircle(float x1, float y1, float radius, uint16_t color) {
 float leftx1 = x1 - radius;
 float rightx1 = x1 + radius;
 float bottomy1 = y1 - radius;
 float topy1 = y1 + radius;

 for ( float i = leftx1; i < rightx1; i = i + 0.5) {
   //circle formula (x-h)^2 + (y-k)^2 = r^2
   float movingy = y1 + sqrt(pow(radius, 2) - pow((i - x1), 2));
   drawPixel(i, movingy, color);
   movingy = y1 - sqrt(pow(radius, 2) - pow((i - x1), 2));
   drawPixel(i, movingy, color);
 }

 for ( float j = bottomy1; j < topy1; j = j + 0.5) {
   float movingx = x1 + sqrt(pow(radius, 2) - pow((j - y1), 2));
   drawPixel(movingx, j, color);
   movingx = x1 - sqrt(pow(radius, 2) - pow((j - y1), 2));
   drawPixel(movingx, j, color);
 }
}

Once done, you can try out the below examples.

//Example 1
 uint16_t color = selectColour(100, 100, 100);
 hollowCircle(160, 120, 120, color);

The above example draws a single hollow circle.


//Example 2
 uint16_t color = selectColour(100, 100,100);
 filledRectangle(0, 0, 320, 240, color);

 for(int i=0; i < 120; i = i +20){
 uint16_t color = selectColour(50, 35, 80);
 hollowCircle(160, 120, i, color);
 hollowCircle(160, 120, i+1, color);
 }

The example above draws hollow circles in increasing radius from the center point of my LCD Display which is x = 160 and y = 120.


Finally, we will be looking at how to draw filled circles.


void filledCircle(float x1, float y1, float radius, uint16_t color) {
 for ( float i = 0; i < radius; i = i + 1) {
   hollowCircle(x1, y1, i, color);
 }
}

The above void draws filled circle by using void hollowCircle while increasing its radius to the desired length.


Try out the below examples :

//Example 1
 uint16_t color = selectColour(100, 100, 0);
 filledCircle(160, 120, 80, color);

The above example draws a simple filled circle in your desired color.


//Example 2
 uint16_t color = selectColour(83, 84,83);
 filledRectangle(0, 0, 320, 240, color);
 
 color = selectColour(0, 0, 100);
 filledCircle(160, 120, 120, color);

 color = selectColour(100, 0, 0);
 filledCircle(160, 120, 80, color);

 color = selectColour(100, 100, 100);
 filledCircle(160, 120, 40, color);

Whereas this example draws 3 filled circles inside of one another.


With that, we have come to the end of our tutorial. For more detailed explanation, check out my YouTube vide below :


Entire Code :

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define RED2    0x4000
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define GREEN2  0x2FA4
#define CYAN2   0x07FF

#define LCD_RD   A0 //Serves as read signal/MCU read data at the rising edge. Pg11 Datasheet
#define LCD_WR   A1 //Serves as write signal/command at the rising edge
#define LCD_RS   A2 //D/CX (0=Command/1=Data)       
#define LCD_CS   A3 //Chip Select Pin : Active Low
#define LCD_RST  A4 //Shield Reset

void LCD_write(uint8_t d) {
  // Serves as write signal/command at the rising edge
  digitalWrite(LCD_WR, LOW); // WR 0

  // LCD Pins |7|6|5|4|3|2|1|0|
  // Uno Pins |7|6|5|4|3|2|9|8|

  //Arduino Port D : Pin 0 - 7, but Pin 0,1 not used in the LCD Shield
  //Arduino Port B : Pin 8 - 13,but Pin, 10,11,12,13 not used in the LCD Shield
  //Arduino Port C : Analog Pins

  PORTD = (PORTD & B00000011) | ((d) & B11111100);
  PORTB = (PORTB & B11111100) | ((d) & B00000011);

  digitalWrite(LCD_WR, HIGH); // WR 1
}

void LCD_command_write(uint8_t command) {
  // LCD_RS = 0, A2=0, D/CX (0=Command/1=Data) | DataSheet Page 24
  digitalWrite(LCD_RS, LOW);
  LCD_write(command);
}

void LCD_data_write(uint8_t data) {
  // LCD_RS = 1, A2=1, D/CX (0=Command/1=Data) | DataSheet Page 24
  digitalWrite(LCD_RS, HIGH);
  LCD_write(data);
}

void Lcd_Init(void) {
  //void does not return any value
  //void only execute instruction within it
  //similar to void setup and loop
  //This function will have LCD initialization measures
  //Only the necessary Commands are covered
  //Eventho there are so many more in DataSheet

  //Reset Signal is Active LOW
  digitalWrite(LCD_RST, HIGH);
  delay(5);
  digitalWrite(LCD_RST, LOW); //Actual Reset Done Here
  delay(15);
  digitalWrite(LCD_RST, HIGH);
  delay(15);

  //MCU Interface Mode : Page 27
  digitalWrite(LCD_CS, HIGH); //Chip-Select Active Low Signal
  digitalWrite(LCD_WR, HIGH);
  digitalWrite(LCD_RD, HIGH);
  digitalWrite(LCD_CS, LOW);  //Chip-Select Active Low Signal

  LCD_command_write(0xC5);    //Test this Out | VCOM Control 1 : Colour Contrast Maybe
  LCD_data_write(0x7F);       //VCOM H 1111111
  LCD_data_write(0x00);       //VCOM L 0000000

  LCD_command_write(0x36);    //Memory Access Control | DataSheet Page 127
  LCD_data_write(0x48);       //Adjust this value to get right color and starting point of x and y
  //LCD_data_write(B00001000);     //Example

  LCD_command_write(0x3A);    //COLMOD: Pixel Format Set | DataSheet Page 134
  LCD_data_write(0x55);       //16 Bit RGB and MCU

  LCD_command_write(0x11);    //Sleep Out | DataSheet Page 245
  delay(10);                  //Necessary to wait 5msec before sending next command

  LCD_command_write(0x29);    //Display on.

  LCD_command_write(0x2c);    //Memory Write | DataSheet Page 245
}

void Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2) {
  LCD_command_write(0x2a);  //Column Address Set | DataSheet Page 110
  LCD_data_write(y1 >> 8);  //8 Bit Shift Right of x1
  LCD_data_write(y1);       //Value of x1
  LCD_data_write(y2 >> 8);  //8 Bit Shift Right of x2
  LCD_data_write(y2);       //Value of x2

  LCD_command_write(0x2b);  //Page Address Set | DataSheet Page 110
  LCD_data_write(x1 >> 8);  //8 Bit Shift Right of y1
  LCD_data_write(x1);       //Value of y1
  LCD_data_write(x2 >> 8);  //8 Bit Shift Right of y2
  LCD_data_write(x2);       //Value of y2

  LCD_command_write(0x2c); // REG 2Ch = Memory Write
}

void drawPixel(int16_t x, int16_t y, uint16_t color) {
  digitalWrite(LCD_CS, LOW);// Chip Select active
  Address_set(y, y + 1, x, x + 1);
  LCD_command_write(0x2C);
  LCD_data_write(color >> 8);
  LCD_data_write(color);
}

uint16_t selectColour(float redpercentage, float greenpercentage, float bluepercentage) {
  //use uint16_t instead of void as we need some values in return
  //int does not work with division
  //hence, we're using float instead of int

  redpercentage   = (redpercentage / 100) * 31;
  greenpercentage = (greenpercentage / 100) * 63;
  bluepercentage  = (bluepercentage / 100) * 31;

  int red = round(redpercentage);
  int green = round(greenpercentage);
  int blue = round(bluepercentage);

  uint16_t color_16_bit = 0;
  color_16_bit = (red << 11) + (green << 5) + blue;
  return color_16_bit;
}

void drawLine(float x1, float y1, float x2, float y2, uint16_t color) {
  if (x2 > x1) {
    for (int xinc = x1; xinc < x2; xinc++) { //xinc = x=incremental
      float m = (y2 - y1) / (x2 - x1); //m=gradient
      float yinc = m * (xinc - x1) + y1;
      //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
      Address_set(y1, yinc, x1, xinc);
      LCD_data_write(color >> 8);
      LCD_data_write(color);
      y1 = yinc;
      x1 = xinc;
    }
  }
  else {
    float newx1 = x2; float newx2 = x1; float newy1 = y2; float newy2 = y1;
    for (int xinc = newx1; xinc < newx2; xinc++) { //xinc = x=incremental
      float m = (newy2 - newy1) / (newx2 - newx1); //m=gradient
      float yinc = m * (xinc - newx1) + newy1;
      //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
      Address_set(newy1, yinc, newx1, xinc);
      LCD_data_write(color >> 8);
      LCD_data_write(color);
      newy1 = yinc;
      newx1 = xinc;
    }
  }
}

void hollowRectangle(int16_t rectx1, int16_t recty1, int16_t wide, int16_t tall, uint16_t color) {
  //draw left line
  int16_t lefty1 = recty1;
  int16_t leftx1 = rectx1;
  int16_t lefty2 = recty1 + tall;
  int16_t leftx2 = rectx1 + 1;
  for (int i = lefty1; lefty1 < lefty2; lefty1++) {
    //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
    Address_set(lefty1, lefty2, leftx1, leftx2);
    LCD_data_write(color >> 8);
    LCD_data_write(color);
  }

  //draw right line
  int16_t righty1 = recty1;
  int16_t rightx1 = rectx1;
  int16_t righty2 = recty1 + tall;
  int16_t rightx2 = rectx1 + wide;
  for (int i = righty1; righty1 < righty2; righty1++) {
    //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
    Address_set(righty1, righty2, rightx2, rightx2 + 1);
    LCD_data_write(color >> 8);
    LCD_data_write(color);
  }

  //draw top line
  int16_t topy1 = recty1 + tall;
  int16_t topx1 = rectx1;
  int16_t topy2 = recty1 + tall;
  int16_t topx2 = rectx1 + wide;
  for (int i = topx1; topx1 < topx2 + 1; topx1++) {
    //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
    Address_set(topy1, topy2 + 1, topx1, topx2 + 1);
    LCD_data_write(color >> 8);
    LCD_data_write(color);
  }

  //draw bottom line
  int16_t bottomy1 = recty1;
  int16_t bottomx1 = rectx1;
  int16_t bottomy2 = recty1;
  int16_t bottomx2 = rectx1 + wide;
  for (int i = bottomx1; bottomx1 < bottomx2 + 1; bottomx1++) {
    //Address_set(int16_t y1, int16_t y2, int16_t x1, int16_t x2)
    Address_set(bottomy1, bottomy2 + 1, bottomx1, bottomx2 + 1);
    LCD_data_write(color >> 8);
    LCD_data_write(color);
  }
}

void filledRectangle(int16_t rectx1, int16_t recty1, int16_t wide, int16_t tall, uint16_t color) {
  //draw left line
  int16_t y1 = recty1;
  int16_t x1 = rectx1;
  int16_t y2 = recty1 + tall;
  int16_t x2 = rectx1 + wide;

  Address_set(y1, y2, x1, x2);
  for (int i = 0; i < wide + 1; i++)
    for (int j = 0; j < tall + 1; j++) {
      LCD_data_write(color >> 8);
      LCD_data_write(color);
    }
}

void hollowCircle(float x1, float y1, float radius, uint16_t color) {
  float leftx1 = x1 - radius;
  float rightx1 = x1 + radius;
  float bottomy1 = y1 - radius;
  float topy1 = y1 + radius;

  for ( float i = leftx1; i < rightx1; i = i + 0.5) {
    //circle formula (x-h)^2 + (y-k)^2 = r^2
    float movingy = y1 + sqrt(pow(radius, 2) - pow((i - x1), 2));
    drawPixel(i, movingy, color);
    movingy = y1 - sqrt(pow(radius, 2) - pow((i - x1), 2));
    drawPixel(i, movingy, color);
  }

  for ( float j = bottomy1; j < topy1; j = j + 0.5) {
    float movingx = x1 + sqrt(pow(radius, 2) - pow((j - y1), 2));
    drawPixel(movingx, j, color);
    movingx = x1 - sqrt(pow(radius, 2) - pow((j - y1), 2));
    drawPixel(movingx, j, color);
  }
}

void filledCircle(float x1, float y1, float radius, uint16_t color) {
  for ( float i = 0; i < radius; i = i + 1) {
    hollowCircle(x1, y1, i, color);
  }
}

void setup() {
  // Setting Pin 2-7 as Output, DDR is PinMode Commnand, Pin0,1 Untouched
  DDRD = DDRD | B11111100;
  // Setting Pin 8-9 as Output
  DDRB = DDRB | B00000011;
  //Setting Analog Pins A4-A0 as Output
  DDRC = DDRC | B00011111;
  //Setting Analog Pins A4-A0 as HIGH
  PORTC = PORTC | B00011111;
  Lcd_Init();
  Serial.begin(9600);

//Example 2
  uint16_t color = selectColour(83, 84,83);
  filledRectangle(0, 0, 320, 240, color);
  
  color = selectColour(0, 0, 100);
  filledCircle(160, 120, 120, color);

  color = selectColour(100, 0, 0);
  filledCircle(160, 120, 80, color);

  color = selectColour(100, 100, 100);
  filledCircle(160, 120, 40, color);

}

void loop() {

}


bottom of page