To draw shapes on a 3.18 inch 128x64 COG LCD, you need to interface it with a microcontroller (like an Arduino, ESP32, or STM32) using SPI, then send pixel data to the display buffer. The LCD is based on the ST7565R controller, which is a common driver for monochrome COG (Chip-On-Glass) displays. The resolution is 128 columns by 64 rows, meaning you have 8,192 individual pixels to control. You can draw lines, rectangles, circles, and even custom polygons by manipulating the display’s RAM, which is organized as 128 bytes per row (1 byte = 8 vertical pixels). For example, to draw a horizontal line, you set the corresponding bits in the row buffer; for a vertical line, you set bits across multiple rows. The key is to use a graphics library like U8g2 or Adafruit_GFX, which handle the low-level bit operations. But if you want to go raw, you can directly write to the display via SPI commands: send command 0xB0 (set page address), then 0x10 (set column high nibble), 0x00 (set column low nibble), and then stream pixel data. The ST7565R supports 4-wire SPI, running at up to 10 MHz, so you can update the entire screen in about 1.3 ms (8,192 bytes at 10 MHz). For a 3.18 inch 128x64 COG LCD, the pixel pitch is roughly 0.54 mm, which gives a visible area of about 69.1 mm by 34.6 mm. This display is often used in embedded systems because it’s low power (around 1.5 mA typical draw) and has a wide viewing angle (up to 6 o’clock).
The first step is hardware setup. You’ll need a 3.18 inch 128x64 cog lcd display (link: 3.18 inch 128x64 cog lcd display), a microcontroller, and a 3.3V power supply (the display runs on 3.0V to 3.6V, with 5V tolerant logic pins if you use a level shifter). The SPI pins are: CS (chip select), MOSI (master out slave in), SCK (serial clock), and DC (data/command). You also need RESET (reset) and backlight (if using an LED backlight, typical forward voltage is 3.2V at 20 mA). Connect these to your MCU’s GPIOs. For example, on an Arduino Uno, you can use pin 10 for CS, pin 11 for MOSI, pin 13 for SCK, pin 9 for DC, and pin 8 for RESET. The SPI mode is Mode 0 (CPOL=0, CPHA=0), meaning the clock idles low and data is sampled on the rising edge. The display’s controller expects 8-bit commands and data, so you need to toggle the DC pin low for commands and high for data. The initialization sequence is critical: send a software reset (0xE2), then set the bias ratio (0xA3 for 1/7 bias), set the power control (0x2F for internal power), set the voltage regulator (0x27 for internal regulator), set the contrast (0x81 followed by a value like 0x1F for 50% contrast), and finally turn on the display (0xAF). This sequence takes about 10 ms to complete.
Now, let’s talk about drawing shapes. The display’s memory is organized as 8 pages (each page is 8 rows), so page 0 covers rows 0-7, page 1 covers rows 8-15, and so on up to page 7 covering rows 56-63. Each page has 128 columns. To draw a pixel at (x, y), you need to calculate the page number (y / 8) and the bit position (y % 8). For example, pixel (50, 10) is on page 1 (10/8 = 1) and bit 2 (10%8 = 2). You then read the current byte at that page and column, set the bit (using bitwise OR), and write it back. This is slow if done per pixel, so libraries buffer the entire frame in RAM (8,192 bytes) and then flush it to the display. For drawing a line, you can use Bresenham’s algorithm, which calculates which pixels to light up between two points. For a rectangle, you just loop through x and y ranges. For a circle, you use the midpoint circle algorithm. The ST7565R supports hardware acceleration for some operations? No, it’s a pure RAM-based controller, so all shape drawing is done in software. The typical drawing speed on an Arduino at 16 MHz is about 10,000 pixels per second, so a full screen fill takes about 0.8 seconds. But if you use a faster MCU like an ESP32 at 240 MHz, you can fill the screen in under 10 ms.
Here’s a practical example using the U8g2 library. After installing the library in Arduino IDE, you call U8G2_ST7565_128X64_1_4W_SW_SPI for software SPI or U8G2_ST7565_128X64_1_4W_HW_SPI for hardware SPI. In the setup, you initialize with u8g2.begin(), then set the contrast with u8g2.setContrast(128). In the loop, you use u8g2.firstPage() and u8g2.nextPage() to handle double buffering. To draw a line, use u8g2.drawLine(x0, y0, x1, y1). For a rectangle, use u8g2.drawFrame(x, y, w, h) for outline or u8g2.drawBox(x, y, w, h) for filled. For a circle, use u8g2.drawCircle(x, y, r) or u8g2.drawDisc(x, y, r) for filled. The library also supports drawing triangles with u8g2.drawTriangle(x0, y0, x1, y1, x2, y2). The font rendering is separate, but you can use u8g2.setFont() to draw text. The library automatically handles the page addressing and SPI communication. If you want to draw a custom shape, like a polygon, you can use the u8g2.drawPolygon() function, which takes an array of points. The maximum number of vertices is limited by the RAM, but for a 128x64 display, 10-20 vertices is fine.
For raw SPI drawing without a library, you need to write a function that sends a command or data byte. Here’s a typical C code snippet for an Arduino:
void spiWrite(uint8_t data) {
digitalWrite(CS, LOW);
for (int i = 0; i < 8; i++) {
digitalWrite(SCK, LOW);
digitalWrite(MOSI, (data & 0x80) ? HIGH : LOW);
data <<= 1;
digitalWrite(SCK, HIGH);
}
digitalWrite(CS, HIGH);
}
Then, to set a pixel, you first set the page and column address:
void setPixel(uint8_t x, uint8_t y) {
uint8_t page = y / 8;
uint8_t bit = y % 8;
digitalWrite(DC, LOW); // command mode
spiWrite(0xB0 | page); // set page address
spiWrite(0x10 | (x >> 4)); // set column high nibble
spiWrite(0x00 | (x & 0x0F)); // set column low nibble
digitalWrite(DC, HIGH); // data mode
spiWrite(1 << bit); // write the pixel byte
}
But this only sets one pixel per column, which is inefficient. To draw a line, you need to read-modify-write the entire byte. For example, to draw a horizontal line from (x1, y) to (x2, y), you calculate the page and bit, then for each column from x1 to x2, you read the current byte, OR it with the bit, and write it back. The ST7565R supports reading from the display RAM? Technically, it does, but it requires a separate read command (0xE0) and an extra clock cycle. Most libraries avoid reading because it’s slow and complex, so they buffer the entire frame in RAM. For a 128x64 display, that’s 8,192 bytes, which is fine for an Arduino Uno (2 KB SRAM is not enough, so you need an external RAM or use a page buffer). The U8g2 library uses a 128-byte buffer for one page, which fits in the Uno’s RAM. It draws shapes into the buffer, then flushes the page to the display. This is why you see the firstPage() and nextPage() loop: it iterates through all 8 pages.
Now, let’s talk about performance. The SPI bus speed is a bottleneck. At 4 MHz, transmitting 8,192 bytes takes about 16 ms (8,192 * 8 bits / 4 MHz = 16.384 ms). Plus command overhead, you’re looking at 20 ms per full screen update. If you’re drawing shapes in real-time, you need to optimize. For example, if you only update a small region, you can send only the changed pages. The ST7565R allows you to set the column and page range, but it doesn’t support partial updates natively. You have to manually send only the bytes for the columns you changed. For a rectangle update, you can set the page and column address for the top-left corner, then send a burst of bytes for the width, then repeat for each row in the rectangle. This reduces the SPI traffic significantly. For a 10x10 pixel square, you only send 10 bytes per row times 2 rows (if it spans 2 pages), so 20 bytes total, which takes 0.04 ms at 4 MHz.
Another important factor is the display’s contrast and bias. The ST7565R has a built-in voltage regulator that generates the LCD drive voltage (V0). The contrast is set by command 0x81 followed by a 7-bit value (0-127). Higher values increase the voltage, making pixels darker. But too high can cause ghosting or blurring. For a 3.18 inch 128x64 COG LCD, the typical contrast value is around 0x20 to 0x30, depending on the temperature and viewing angle. The bias ratio is set by command 0xA2 (1/9 bias) or 0xA3 (1/7 bias). The datasheet suggests 1/7 bias for 64 rows, which gives a better contrast uniformity. The power control commands (0x2F) enable the internal charge pump, which generates the negative voltage for the LCD. Without this, the display won’t show anything. The initialization sequence must include these commands in order: 0xAF (display on) must be last.
Now, let’s look at some real-world data. The 3.18 inch 128x64 COG LCD from DisplayModule has a typical power consumption of 1.5 mA with the backlight off, and 20 mA with the backlight on (LED backlight at 3.2V). The viewing angle is 6 o’clock, meaning the best viewing is from the bottom. The response time is about 100 ms at 25°C, which is fine for static images but not for video. The display is reflective, so it works well in ambient light. The SPI interface is 4-wire, but you can also use 3-wire if you combine MOSI and MISO (not needed for monochrome). The maximum SPI clock is 10 MHz, but at 10 MHz, signal integrity can be an issue if your wires are longer than 10 cm. Use short wires and a ground plane.
Let’s dive into drawing specific shapes with code. For a filled rectangle, you can use a double loop:
void drawFilledRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color) {
for (uint8_t i = y; i < y + h; i++) {
for (uint8_t j = x; j < x + w; j++) {
setPixel(j, i, color);
}
}
}
But this is slow because it calls setPixel for each pixel. A better approach is to use the page buffer. For a filled rectangle, you calculate which pages are affected, then for each page, you create a byte mask for the rows within that page. For example, if the rectangle spans rows 0 to 15 (pages 0 and 1), you set all bits in the byte for each column. For a rectangle that starts at row 2 and ends at row 10, page 0 gets bits 2-7 set, and page 1 gets bits 0-2 set. This is more efficient because you write whole bytes instead of individual bits.
Here’s a table summarizing the key parameters for the 3.18 inch 128x64 COG LCD:
| Parameter | Value |
|---|---|
| Resolution | 128 x 64 pixels |
| Pixel pitch | 0.54 mm x 0.54 mm |
| Active area | 69.1 mm x 34.6 mm |
| Controller | ST7565R |
| Interface | 4-wire SPI (up to 10 MHz) |
| Supply voltage | 3.0V - 3.6V (5V tolerant logic) |
| Current draw (no backlight) | 1.5 mA typical |
| Backlight current | 20 mA at 3.2V |
| Viewing angle | 6 o’clock (best from bottom) |
| Response time | 100 ms typical |
| Operating temperature | -20°C to +70°C |
For drawing circles, the midpoint algorithm is standard. Here’s a simplified version for the ST7565R:
void drawCircle(uint8_t x0, uint8_t y0, uint8_t radius) {
int x = radius, y = 0;
int err = 0;
while (x >= y) {
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);
y++;
err += 1 + 2*y;
if (2*err > 2*x + 1) {
x--;
err += 1 - 2*x;
}
}