How to display a custom font on a 0.95 inch OLED?
How to Display a Custom Font on a 0.95 Inch OLED
To display a custom font on a 0.95 inch OLED, you need to convert your font into a bitmap array, load it into the microcontroller’s memory, and then use a graphics library to render the characters pixel by pixel. The most common approach is using the Adafruit GFX library with an SSD1306 or SH1106 driver, but for a 96x64 color OLED, you’ll typically work with a different driver like the SSD1331 or similar SPI-based controller. For example, the 0.95 inch 96x64 color oled display uses a 16-bit color interface, so you’ll need to adapt the font rendering to handle RGB565 color data. This means each character is stored as a 2D array of pixel values, not just on/off bits. The key is understanding the display’s resolution: 96 columns by 64 rows, with each pixel requiring 2 bytes for color. So a full frame buffer is 96 * 64 * 2 = 12,288 bytes, which is manageable on most microcontrollers like the ESP32 or STM32. But for custom fonts, you’re only storing the character glyphs, which are typically 8x8 or 8x16 pixels. An 8x8 monochrome character takes 8 bytes, but for color, it’s 128 bytes. So you need to compress or store the font data efficiently.
First, you need to generate the font bitmap. Tools like FontForge or GLCD Font Creator can export a font as a C array. For a 0.95 inch OLED, you’ll want a font size that’s readable at the display’s pixel density. The pixel pitch of a 0.95 inch OLED is roughly 0.21 mm per pixel (since 96 pixels across 20.3 mm width). So a 8x8 font is about 1.7 mm tall, which is tiny but readable for status icons. For text, a 12x16 or 16x16 font is more practical. The data sheet for the 0.95 inch OLED from DisplayModule shows a 96x64 resolution with a 0.21 mm pitch, so a 16x16 character covers about 3.4 mm, which is legible at arm’s length. The color depth is 16-bit (65,536 colors), so you can define each character’s pixels with RGB565 values. For example, a white character on a black background uses 0xFFFF for white and 0x0000 for black. But if you want a custom font with antialiasing, you’ll need to store grayscale values and map them to the OLED’s color space.
Now, let’s talk about the microcontroller side. The ESP32 is popular for this because it has 520 KB of SRAM, which can hold the entire frame buffer plus a custom font table. For a 16x16 font with 256 characters, you’d need 256 * 16 * 16 * 2 = 131,072 bytes, which is about 128 KB. That fits in the ESP32’s flash, but you need to load it into RAM during rendering. Alternatively, you can store the font in PROGMEM (flash memory) on an Arduino Uno, but that only has 32 KB of flash, so you’d have to limit the character set. A better approach is to use a microcontroller with external SPI flash, like the ESP32 with a 4 MB flash chip. The 0.95 inch OLED itself uses SPI at 8 MHz to 20 MHz, so you can send data quickly. The typical SPI clock for the SSD1331 driver is 8 MHz, which gives a theoretical pixel rate of 8 million pixels per second. But in practice, with the overhead of font rendering, you’ll get around 30 frames per second for a full screen update. For custom fonts, you only update the changed characters, so it’s faster.
Let’s break down the actual code steps. You’ll use the Adafruit_GFX library, but it doesn’t support custom fonts out of the box for color displays. You need to modify the drawChar() function. The standard approach is to define a font struct like this:
typedef struct { uint16_t *bitmap; uint8_t width; uint8_t height; uint8_t firstChar; uint8_t lastChar; } FontDef;
Then, for each character, you store the bitmap as a 1D array of 16-bit colors. For example, a 16x16 character ‘A’ might have a bitmap array of 256 elements. To render it, you loop through the rows and columns, and for each pixel, if the value is not transparent, you write it to the display’s frame buffer. The OLED’s SPI command set includes 0x15 for column address range and 0x75 for row address range. So you set the window to the character’s position, then send the pixel data. The window size is 96x64, so you can place characters anywhere. For a custom font, you need to handle kerning and spacing. Typically, you add a 1-pixel gap between characters. For a 0.95 inch OLED, the total width of 96 pixels means you can fit 6 characters of 16x16 size with 1 pixel spacing (6*16 + 5 = 101, which is too many, so you need 5 characters with 2 pixels spacing: 5*16 + 4*2 = 88 pixels, leaving 8 pixels for margins). So a practical line of text is 5 characters for a 16x16 font, or 9 characters for a 10x16 font.
Now, let’s get into the data format. The 0.95 inch 96x64 color oled display uses a 16-bit color depth, which means each pixel is represented by 2 bytes. The color format is RGB565: 5 bits for red, 6 bits for green, 5 bits for blue. So a pixel value of 0xF800 is pure red, 0x07E0 is green, and 0x001F is blue. When you create a custom font, you can define each character’s pixels with these values. For a monochrome font, you’d use 0xFFFF for white and 0x0000 for black. But if you want a gradient or shadow, you can store multiple colors. For example, a font with a 3D effect might have a light gray pixel at 0xCE79 and a dark gray at 0x8410. The font file is typically a C header file with a large array. For a 256-character ASCII set at 16x16, the array size is 256 * 256 * 2 = 131,072 bytes. That’s within the flash of an ESP32 (4 MB), but you can compress it using run-length encoding (RLE). For a typical font, the compression ratio is about 2:1 because many pixels are the same color. For example, a horizontal line of white pixels can be encoded as a count and color. The RLE algorithm for font data is simple: store a byte for the count and two bytes for the color. So a run of 10 white pixels becomes 0x0A, 0xFF, 0xFF. This reduces the font size to around 60-70 KB for a full set.
Let’s look at the hardware specifics. The 0.95 inch OLED from DisplayModule has a resolution of 96x64 pixels, and it uses the SSD1331 driver IC. The SPI interface requires 4 pins: CS, DC, MOSI, and SCK. The typical operating voltage is 3.3V, and the current draw is about 20 mA for full white. The pixel clock is 8 MHz, so the theoretical data rate is 8 million pixels per second, but the actual throughput is lower due to command overhead. For font rendering, you’ll send a command to set the write window, then send the pixel data. The command sequence for setting a window is: 0x15 (column address), then two bytes for start and end columns; 0x75 (row address), then two bytes for start and end rows. Then you send the pixel data with 0x5C (write RAM). For a 16x16 character, you send 256 pixels, each 2 bytes, so 512 bytes total. At 8 MHz, that takes about 512 * 8 / 8,000,000 = 0.512 ms per character. For a 10-character string, that’s 5.12 ms, plus command overhead. So you can update the entire display with 10 characters in under 10 ms, which is fast enough for real-time data.
But there’s a catch: the frame buffer. If you’re using a microcontroller with limited RAM, like an Arduino Uno (2 KB RAM), you can’t store a full frame buffer. Instead, you render directly to the display using the windowing feature. This is called “partial updates.” For a 0.95 inch OLED, you can update only the region where the text changes. For example, if you’re displaying a counter that increments every second, you only update the 16x16 area for the digit. This reduces the data transfer to 512 bytes per update, which is efficient. The SSD1331 supports hardware scrolling, but for custom fonts, you’ll need to handle scrolling in software. You can shift the frame buffer by one row and redraw the new line. This is common in scrolling text displays.
Now, let’s talk about the font conversion tool. FontForge is open-source and can export a font as a bitmap. You set the size to 16 pixels, then export as a C array. The output is a monochrome bitmap, but you can convert it to color by mapping each bit to a 16-bit color. For example, if the bit is 1, set the pixel to 0xFFFF; if 0, set to 0x0000. But if you want antialiasing, you need a grayscale bitmap. FontForge can export with 8-bit grayscale, so each pixel is 0-255. You then map that to a color gradient. For a white font on a black background, you can use a linear interpolation: color = (gray * 0xFFFF) / 255. This gives a smooth antialiased font. The downside is that the font data size increases by 8 times (from 1 bit to 8 bits per pixel). For a 16x16 character, that’s 256 bytes per character, or 65,536 bytes for 256 characters. That’s still manageable on an ESP32.
Another approach is to use TrueType Font (TTF) rendering on the microcontroller. Libraries like U8g2 support TTF fonts, but they require a lot of RAM. For a 0.95 inch OLED, you can use the U8g2 library with a custom font file. The library supports the SSD1331 driver, so you can call u8g2.drawStr() with a font handle. The font data is stored in flash, and the library handles the rendering. The downside is that U8g2 is designed for monochrome displays, so for color, you need to modify the library to handle RGB565. But there’s a fork called U8g2_color that supports color. You can download it from GitHub. The font file for U8g2 is a binary format that includes glyph metrics. For a 16x16 font, the file size is about 10 KB for a small set. You can use the u8g2_font_helvB16_tf font, which is a bold Helvetica at 16 pixels. This gives you a professional look without manual bitmap creation.
Let’s look at a real-world example. Suppose you’re building a weather station with a 0.95 inch OLED. You want to display temperature, humidity, and a custom icon for sunny weather. The temperature is a number like “72°F”, which uses 4 characters (7, 2, °, F). For a 16x16 font, that’s 64 pixels wide, plus spacing. The icon is 16x16 as well. So you need to update a 64x16 area for the temperature and a 16x16 area for the icon. The total data is 80 * 16 * 2 = 2,560 bytes. At 8 MHz, that’s 2.56 ms. If you update every second, the display is very responsive. The custom font for the degree symbol is a special character. You need to include it in your font set. Most font tools include the degree symbol at ASCII code 176. So you generate the font with all 256 characters, including the degree symbol. For the icon, you can create a bitmap of a sun using a 16x16 grid. The sun icon might have a yellow circle (0xFFE0) and rays (0xFFFF). You store this as a separate array in flash.
Now, let’s discuss the color calibration. The 0.95 inch OLED has a gamma correction feature. The SSD1331 driver has a 0x81 command for contrast control. You can set the contrast for red, green, and blue separately using commands 0x82, 0x83, and 0x84. The default values are 0x80 for each, but you can adjust them to match your font’s color. For example, if your font is white, you want all three colors at maximum. But if you’re using a custom color like orange (0xFD20), you might need to boost the red and green. The gamma correction is set via 0xB8 to 0xBE commands. These are 7-bit values for each color’s gamma curve. For a linear response, set all to 0x00. But for better contrast, you can set a gamma of 2.2. The datasheet for the SSD1331 provides a table of gamma values. For a custom font, you want the colors to be accurate, so you might need to calibrate using a colorimeter. But for most hobby projects, the default settings are fine.
Let’s talk about performance. The SPI bus speed is critical for font rendering. The 0.95 inch OLED from DisplayModule supports up to 20 MHz, but many microcontrollers can’t sustain that due to SPI peripheral limitations. The ESP32 can handle 20 MHz, but the Arduino Uno’s SPI is limited to 8 MHz. At 8 MHz, the theoretical pixel rate is 8 million pixels per second, but the actual throughput is about 4-5 million pixels per second due to command overhead. For a 16x16 character, that’s 256 pixels, so you can send about 19,500 characters per second. That’s more than enough for a 96x64 display. But if you’re using a software SPI implementation, the speed drops to 1-2 MHz. So it’s better to use hardware SPI. The pinout for the 0.95 inch OLED is standard: CS (chip select), DC (data/command), MOSI (master out slave in), SCK (serial clock). You also need a reset pin (RST). The typical connection is: CS to GPIO5, DC to GPIO2, MOSI to GPIO23, SCK to GPIO18, RST to GPIO4 on an ESP32. This is a common configuration for SPI displays.
Now, let’s look at the font storage in flash. For the ESP32, you can use the SPIFFS or LittleFS file system to store the font file. This allows you to update the font without recompiling the firmware. You can upload a .bin file with the font data to the flash, and then read it at runtime. The font file format is simple: a header with the number of characters, character width, height, and then the pixel data. For example, the header might be: uint16_t numChars; uint8_t charWidth; uint8_t charHeight; uint16_t firstChar; uint16_t lastChar; Then for each character, you store the bitmap as a 2-byte per pixel array. This is a portable format that can be used on any microcontroller. The file size for a 256-character 16x16 font is about 131 KB plus 10 bytes for the header. That fits in the ESP32’s 4 MB flash easily. You can also compress the file using gzip, but then you need a decompression library.
Let’s discuss the rendering algorithm. The core function is drawChar(int16_t x, int16_t y, uint16_t charCode, uint16_t color, uint16_t bgColor). This function reads the font data from flash, gets the bitmap for the character, and then writes it to the display’s frame buffer. The algorithm is:
for (uint8_t row = 0; row < charHeight; row++) { for (uint8_t col = 0; col < charWidth; col++) { uint16_t pixel = getPixelFromFont(charCode, row, col); if (pixel != transparentColor) { drawPixel(x + col, y + row, pixel); } } }
The getPixelFromFont() function reads the font data from an array. For a color font, the pixel is already a 16-bit color. For a monochrome font, you convert the bit to a color. The transparent color is used for background. If you want a transparent background, you skip drawing the pixel. Otherwise, you draw the background color first. This is important for overlaying text on
Ready to hear your track mastered?
Drop a stereo bounce. Pick a target — Spotify, Apple Music, Netflix, BBC iPlayer, or CD. Get a broadcast-loud, release-ready master back in under 90 seconds.
Start a Free Master →