You need to drive the 128x64 pixel grid with a microcontroller, update a framebuffer in RAM, and refresh the LCD via SPI at a rate that matches your desired visual smoothness. For a 3.18 inch 128x64 COG LCD display, the typical controller is the ST7565 or similar, which uses a 4-wire SPI interface. The key to a progress bar is not just drawing a rectangle, but managing the trade-off between update speed, memory usage, and visual artifacts like ghosting. Let's break down the hard facts: the display has a resolution of 128 columns by 64 rows, and each pixel is controlled by a single bit in the controller's RAM (1 for ON, 0 for OFF). The total framebuffer size is 128 * 64 / 8 = 1024 bytes. You need to allocate this buffer in your microcontroller's RAM (e.g., on an Arduino Uno, that's 1KB out of 2KB total, so you have to be careful). The SPI clock speed for these displays typically maxes out at 2-4 MHz, but you can run at 1 MHz for stability. To draw a horizontal progress bar, you calculate the number of pixels to fill based on a percentage (e.g., 0% to 100% corresponds to 0 to 128 pixels wide). Then, you set the corresponding bits in the framebuffer. For a 1-pixel-high bar, you only modify the row that the bar sits on. For a thicker bar (e.g., 8 pixels high), you modify 8 rows. The simplest approach is to clear the entire bar area and then draw the filled portion. But that causes flicker if you update too slowly. A better method is to use a double buffer: you have two 1024-byte buffers, write the new bar state to the back buffer, then copy the changed bytes to the front buffer, and only send the changed bytes to the LCD. This reduces SPI traffic. For example, if you only change the pixels in the bar's row range (say rows 30 to 37, which is 8 rows * 128 columns / 8 = 128 bytes), you can send just those 128 bytes instead of the full 1024 bytes. This cuts update time by 87.5%. The SPI transfer time for 128 bytes at 1 MHz is about 1.024 milliseconds (128 bytes * 10 bits per byte / 1,000,000 bits per second = 1.28 ms with overhead). So you can update the bar at over 700 Hz, which is far beyond what the human eye can perceive (60 Hz is smooth). But the LCD's response time (typically 100-200 ms for COG LCDs) limits the actual visible update rate. So you don't need to update faster than 10-20 Hz. The real challenge is the contrast and viewing angle. COG LCDs have a fixed contrast voltage (V0) generated by an internal charge pump. You can adjust it via software by setting the contrast register (e.g., for ST7565, the register is 0x81 followed by a value from 0 to 63). A common value is 0x20 (32) for a 3.3V supply. If you set it too high, the bar will look washed out; too low, it will be too dark and ghosting appears. For a progress bar, you want high contrast (dark black on light gray background). The LCD's bias voltage (1/9 bias for 64 rows) is fixed, but you can tweak the electronic volume (EV) register. I've found that setting EV to 0x1F (31) gives a good balance for most 3.18 inch panels. Now, let's talk about the graphic design of the bar itself. A 128-pixel-wide bar can be drawn as a single row of pixels, but it's hard to see. A practical height is 8 to 16 pixels. For a 16-pixel-high bar, you need to set 16 rows of pixels. The bar's position can be centered vertically. For example, if the display is 64 rows high, a 16-pixel bar can be placed from row 24 to row 39 (center). The background can be left untouched (transparent) or you can clear the area to white (all pixels OFF). If you clear the background, you need to write 0x00 to the entire bar area before drawing the filled portion. This adds extra SPI commands. A smarter way is to use an XOR drawing mode: instead of clearing, you toggle the pixels. But this requires that the background is known. For a progress bar, the simplest is to draw a rectangular outline (border) first, then fill the inside proportionally. The border can be drawn using the same pixel-setting logic. For example, the border is 1 pixel wide on all four sides. So the bar area is 128 pixels wide by 16 pixels high, but the inner fill area is 126 pixels wide by 14 pixels high. The fill percentage is applied to the inner width. So at 50%, you fill 63 pixels of the inner width (126 * 0.5 = 63). This gives a clean look. The pixel data for the border and fill are stored in the framebuffer. You can precompute the border pattern for the entire bar area and then modify the fill bytes. For a 16-pixel-high bar, the border occupies the top and bottom rows (row 24 and row 39) and the left and right columns (column 0 and column 127). So you set bits for those rows and columns. The fill is done by setting bits for rows 25 to 38 and columns 1 to (1 + fill_width - 1). This is a lot of bit manipulation, but it's fast if you use byte-level operations. For example, you can define a byte array for the bar area (16 rows * 128 columns / 8 = 256 bytes). Then you update the fill bytes in a loop. A common mistake is to update the entire 256 bytes every time the percentage changes. Instead, you can track the previous fill width and only update the bytes that changed. For example, if the bar goes from 50% to 60%, you only need to set the new pixels (from column 64 to column 76) and leave the rest unchanged. This minimizes SPI traffic. The SPI command sequence for updating a specific area is: set the column address range (0x10 + high nibble, 0x00 + low nibble) and the page address range (0xB0 + page number). For a 128x64 display, the pages are 8 rows each (page 0: rows 0-7, page 1: rows 8-15, etc.). So a 16-pixel-high bar spans 2 pages (e.g., page 3 and page 4 for rows 24-39). You can set the column start and end (0 to 127) and page start and end (3 to 4). Then you send 128 bytes per page, total 256 bytes. But if you only update the changed columns, you can set the column range to just those columns (e.g., 64 to 76) and send 13 bytes per page, total 26 bytes. This is a 90% reduction in data. The SPI overhead for setting the column and page addresses is 4 bytes per command (2 for column, 2 for page). So the total SPI transaction for a partial update is 4 + 26 = 30 bytes, which at 1 MHz takes about 0.3 ms. This is negligible. The real bottleneck is the microcontroller's CPU time to compute the new pixel states. For a simple progress bar, you can precompute a lookup table for the fill pattern. For example, for each possible fill width (0 to 126), you can store a byte array of 16 rows * 126 columns / 8 = 252 bytes? No, that's too much. Instead, you can compute the fill on the fly using bitwise operations. For a given fill width, you calculate which bytes are fully filled (all 8 bits set to 1) and which are partially filled (the last byte). For example, if the fill width is 63 pixels, that's 7 full bytes (56 bits) plus 7 bits in the next byte. So you set 7 bytes to 0xFF and the 8th byte to 0xFE (binary 11111110) for the top row. For the bottom row, you do the same. This is fast. The data for the border is static. You can store the border pattern for the 16 rows as a 256-byte array. Then you overlay the fill on top of it. For example, for the top row (row 24), the border has pixels at column 0 and 127 set to 1. The fill starts at column 1. So you set the fill bits for columns 1 to fill_width. This is done by OR-ing the fill bytes with the border bytes. For the bottom row (row 39), same logic. For the inner rows (rows 25-38), the border only has pixels at column 0 and 127. So you set the fill bits for columns 1 to fill_width. This is straightforward. The 3.18 inch 128x64 cog lcd display has a built-in controller that handles the pixel addressing, but you need to initialize it correctly. The initialization sequence for the ST7565 includes: reset the display (pin RST low for 1 µs, then high), turn off the display (0xAE), set the bias (0xA2 for 1/9 bias), set the ADC select (0xA1 for normal), set the SHL select (0xC0 for normal), set the power control (0x2F), set the V0 voltage regulator (0x81, 0x20), set the contrast (0x81, 0x1F), set the display start line (0x40), set the page address (0xB0), set the column address (0x10, 0x00), then turn on the display (0xAF). This sequence is critical. If you skip a step, the display may not work or the contrast will be off. I've seen many projects where the progress bar looks faint because the contrast is set too low. The typical V0 value for a 3.3V supply is 0x20 to 0x30. For a 5V supply, you can go up to 0x40. But check the datasheet for your specific panel. The 3.18 inch COG LCD typically has a 1/9 bias and a 1/65 duty cycle. The internal charge pump generates the negative voltage for the LCD. The power consumption is about 1-2 mA for the backlight (if you use an LED backlight) and 0.5 mA for the LCD itself. So total current is around 2.5 mA. This is important for battery-powered projects. The SPI speed can be increased to 2 MHz if your microcontroller supports it. At 2 MHz, the SPI transfer time for 256 bytes is 1.28 ms, which is still fast. But the LCD's internal update rate is limited by the frame frequency. The ST7565 has a frame frequency of about 65 Hz (1/65 duty cycle * 1/65 second = 65 Hz). So you can't update the display faster than 65 Hz, but you can send data faster and the controller will buffer it. The practical update rate for a progress bar is 10-30 Hz, which is smooth. For a smoother animation, you can use a linear interpolation of the fill width over time. For example, if the target percentage is 80%, you can increment the fill width by 1 pixel every 10 ms. This gives a smooth transition. The formula is: fill_width = (percentage / 100) * 126. For a 10% step, that's 12.6 pixels, so you can round to 13 pixels. The human eye can perceive changes of 1 pixel at 128 pixels wide, so you can use integer arithmetic. The microcontroller's timer can be used to generate an interrupt every 10 ms. In the interrupt, you update the fill width and redraw the bar. This is non-blocking. The main loop can handle other tasks. The memory for the framebuffer can be stored in the microcontroller's RAM or in external SRAM. For the Arduino Uno, 1024 bytes is half of the available RAM, so you need to be efficient. You can reduce the framebuffer to only the bar area (256 bytes) if you don't need to display other graphics. But if you have other text or icons, you need the full 1024 bytes. In that case, you can use a smaller microcontroller like the STM32F103 (64KB RAM) or the ESP32 (520KB RAM). The SPI interface on these microcontrollers can run at 10-20 MHz, so the update time is negligible. The temperature range of the COG LCD is typically -20°C to +70°C. At low temperatures, the contrast drops. You can compensate by increasing the V0 value. For example, at -10°C, you might need to set the contrast register to 0x30. At high temperatures, you might need to lower it to 0x10. This is done in software by reading a temperature sensor. The progress bar can be used to show battery level, file transfer progress, or sensor readings. The visual design can include a percentage text next to the bar. For example, you can draw the text "80%" in a 5x7 font. The font data is stored in a bit array. You can calculate the position of the text (e.g., right-aligned at column 100). The text update is independent of the bar update. You can update the text every time the percentage changes. The SPI traffic for the text is small (e.g., 5 characters * 7 rows * 8 columns / 8 = 35 bytes). So the total update for the bar and text is about 300 bytes. This is fine. The power consumption for the SPI bus is about 0.1 mA per MHz. So at 1 MHz, the SPI bus consumes 0.1 mA. The total system power is about 3 mA. This is acceptable for a battery-powered device. The display's viewing angle is 6:00 (meaning the best view is from the bottom edge). If you mount the display upside down, you can change the SHL and ADC settings to mirror the image. For example, set SHL to 0xC8 (reverse) and ADC to 0xA0 (reverse). This will flip the display. The progress bar will then be drawn from right to left. This is useful if the display is mounted in a different orientation. The physical dimensions of the 3.18 inch COG LCD are typically 84.0 mm x 41.0 mm (with a 0.5 mm tolerance). The active area is 73.4 mm x 38.9 mm. The pixel pitch is 0.574 mm. So the bar width is 128 * 0.574 = 73.4 mm. This is large enough to see from a distance of 1 meter. The bar height of 16 pixels is 9.2 mm. This is visible. The contrast ratio of the COG LCD is typically 5:1 to 10:1. This is lower than an OLED, but sufficient for indoor use. The backlight can be white, yellow-green, or blue. The yellow-green backlight has the best contrast. The white backlight is brighter. The blue backlight is less common. The backlight is driven by a series resistor. The typical forward voltage is 3.0V at 20 mA. So for a 3.3V supply, you need a 15 ohm resistor. For a 5V supply, you need a 100 ohm resistor. The backlight can be turned on/off via a GPIO pin. This can save power. The progress bar can be used in a menu system. For example, you can have a main menu with a progress bar showing the battery level. The bar is updated every 10 seconds. The microcontroller reads the battery voltage via an ADC. The voltage is converted to a percentage. The formula is: percentage = (voltage - 3.0) / (4.2 - 3.0) * 100. For a lithium-ion battery. The ADC resolution is 10 bits (0-1023). The reference voltage is 3.3V. So the voltage is (ADC_value / 1023) * 3.3. This is accurate to 3.2 mV. The bar can be updated with a hysteresis to avoid flicker. For example, only update if the percentage changes by more than 1%. This reduces SPI traffic. The bar can also be used to show a countdown timer. For example, a 10-minute timer. The bar fills from 0% to 100% over 10 minutes. The update rate is every 100 ms. The fill width is calculated as (elapsed_time / total_time) * 126. This is a linear interpolation. The timer can be implemented using a hardware timer interrupt. The interrupt fires every 100 ms, increments the elapsed time, and updates the bar. This is accurate to 100 ms. The bar can be used in a data logging application. For example, logging temperature data to an SD card. The bar shows the percentage of the SD card used. The SD card capacity is 2 GB. The bar is updated every time a file is written. The file size is 1 KB. So the bar is updated every 1 KB. This is a fine granularity. The bar can be used in a user interface for a 3D printer. The bar shows the print progress. The print time is estimated. The bar is updated every 1% of the print. The bar can be used in a medical device. The bar shows the battery level of a portable pulse oximeter. The bar is updated every 5 seconds. The bar can be used in a smart home device. The bar shows the water level in a tank. The bar is updated every 1 minute. The bar can be used in a car dashboard. The bar shows the fuel level. The bar is updated every 10 seconds. The bar can be used in a gaming console. The bar shows the health of a character. The bar is updated every 100 ms. The bar can be used in a weather station. The bar shows the barometric pressure. The bar is updated every 1 hour. The bar can be used in a fitness tracker. The bar shows the step count. The bar is updated every 1 minute. The bar can be used in a robot. The bar shows the battery level.
Filed by the RevOps desk
How to display a progress bar on a 3.18 inch 128x64 COG LCD?
Stop shipping average.
Book a working demo with a sales engineer. Bring a deal you actually want to close — we will show you the coaching nudges that would fire on it, live.
Book a demo →