Skip to content
مجلة الملوك

How to create custom fonts for a 2.42 inch OLED?

Kings of 3RB — مقال من أرشيف الاستوديو.
a بقلم admin قراءة ضمن مجلة الملوك

To create custom fonts for a 2.42 inch OLED, you need to generate bitmap font data from a standard font file, encode it into a byte array format compatible with your microcontroller, and then load it into the display's frame buffer—this is the core process. The specific display we are talking about here is the 2.42 inch 128x64 oled display, which uses a monochrome 128x64 pixel matrix driven by the SSD1306 or SH1106 controller. These controllers have a page-addressed memory layout, meaning the 64 rows are divided into 8 pages of 8 pixels each. When you design a custom font, you must account for this page structure because each character is rendered as a series of bytes, where each byte represents a vertical column of 8 pixels. For example, if you want a 5x7 font, each character occupies 5 bytes (width) by 7 pixels (height), but because the OLED uses 8-pixel pages, you typically pad the height to 8 pixels, resulting in 5 bytes per character. The total memory for a full ASCII set (95 characters) at 5 bytes each is 475 bytes, which fits easily into the 1KB internal buffer of the SSD1306. However, if you need larger fonts, like 12x16, each character takes 24 bytes (12 columns times 2 pages), and the full set jumps to 2,280 bytes, which may require external storage or dynamic loading.

The first step in creating a custom font is to choose a source font file. You can use any TrueType font (TTF) or OpenType font (OTF) from your system, but you must convert it to a bitmap. Tools like FontForge (open source) or PCtoLCD2002 (Windows) let you import a font, set the point size, and export the bitmap data. For a 2.42 inch OLED, the typical pixel dimensions are 0.96 inches wide by 0.48 inches tall, but the actual resolution is 128x64. So a font size of 8 pixels tall is readable for small text, while 16 pixels is good for headings. The physical pixel pitch of this display is approximately 0.19 mm, which is typical for 128x64 resolution at 2.42 inches diagonal. When you set the font size in points, convert to pixels: 1 point is roughly 1.333 pixels at 96 DPI, but for OLEDs, you should use the actual pixel density. For a 2.42 inch diagonal with 128x64 pixels, the DPI is about 128 / (2.42 * cos(angle)) but a simpler approach is to test sizes directly. I recommend starting with a 8x8 font for system messages, which uses 8 bytes per character (8 columns, each column is 1 byte for 8 rows). For a 12x16 font, you need 24 bytes per character (12 columns, each column is 2 bytes for 2 pages). The table below shows common font sizes and their memory requirements for a full ASCII set (95 characters):

Font Size (Width x Height)Bytes per CharacterTotal Bytes for 95 CharactersPages Used
5x754751
8x887601
8x16161,5202
12x16242,2802
16x16323,0402

Now, the actual encoding process. The SSD1306 controller expects data in column-major order, meaning each byte represents a vertical slice of 8 pixels. For a 5x7 font, you would have 5 bytes per character, where the first byte is the leftmost column, and the least significant bit (LSB) is the top pixel. But the SH1106 controller, which is also used in some 2.42 inch OLEDs, has a slightly different memory layout: it uses 132 columns instead of 128, with the first 4 columns being dummy. So if you are using an SH1106, you must shift your font data by 4 columns to the right. This is a common gotcha. To avoid this, check your display's datasheet. For the 2.42 inch 128x64 oled display from DisplayModule, they often use the SSD1306, but verify the driver IC. If you are using a library like Adafruit_SSD1306 or U8g2, they handle this automatically. But if you are writing raw SPI commands, you need to set the column address range to 0 to 127 for SSD1306, or 4 to 131 for SH1106. The font data itself is just a byte array. For example, a simple 5x7 font for the letter 'A' might look like: 0x7E, 0x11, 0x11, 0x7E, 0x00 (assuming top-to-bottom, left-to-right). But this is just an example; you need to generate actual data from a tool.

To generate the byte array, use PCtoLCD2002 (free for Windows) or FontForge with a script. In PCtoLCD2002, you select "Font" mode, choose your TTF file, set the font size (e.g., 8 for 8x8), and then export as "C51" format, which gives you a header file with an array. The tool lets you choose the byte order: "Column major" or "Row major". For OLEDs, always choose "Column major" and "Vertical orientation". The output will be a const unsigned char font[] array. For example, a 8x8 font produces 8 bytes per character. If you want proportional fonts (variable width), you also need a width table. For fixed-width fonts, you just multiply the character index by the bytes per character. For the 2.42 inch OLED, proportional fonts look better because the display is small, but they require more code. The width table is an array of 95 bytes, one for each character, indicating the number of columns. For example, 'i' might be 3 columns, while 'W' is 8 columns. The total memory for proportional fonts is higher because you store the width table plus the bitmap data. For a 5x7 proportional font, the average width is about 4 columns, so total memory is around 380 bytes for bitmaps plus 95 bytes for widths = 475 bytes, similar to fixed-width.

Now, let's talk about the hardware interface. The 2.42 inch OLED uses SPI (4-wire) or I2C, but SPI is faster for font rendering. The typical SPI clock speed is 4 MHz to 8 MHz. To send a character, you need to set the cursor position using the command 0x21 (set column address) and 0x22 (set page address), then send the bitmap data as data bytes. For a 128x64 display, the column range is 0 to 127, and the page range is 0 to 7. If you are using a 5x7 font, you can fit 25 characters per line (128 / 5 = 25.6, but you need to account for spacing). With 8 pages, you can fit 8 lines of 5x7 text (64 / 8 = 8). So the maximum text capacity is 200 characters per screen. But if you use 8x8 font, you get 16 characters per line and 8 lines, total 128 characters. For 12x16 font, you get 10 characters per line and 4 lines, total 40 characters. This is important for UI design. The table below shows the character capacity for common font sizes:

Font SizeCharacters per LineLines per ScreenTotal Characters
5x7258200
8x8168128
8x1616464
12x1610440

When you create custom fonts, you also need to consider anti-aliasing. Monochrome OLEDs cannot do grayscale, so anti-aliasing is not possible. However, you can simulate sub-pixel rendering by using a technique called "dithering" at the font level, but this is rarely done because the pixel size is small. Instead, focus on kerning and spacing. For a 2.42 inch OLED, the pixel pitch is about 0.19 mm, so two characters need at least 1 pixel gap for readability. For a 5x7 font, I recommend 1 pixel spacing between characters, which means you need to add a blank column in the font data or handle it in software. The easiest way is to include a spacing column in the font generation. In PCtoLCD2002, you can set the "Character spacing" to 1 pixel, which adds a blank column to the right of each character. This increases the bytes per character by 1. For a 5x7 font, it becomes 6 bytes per character, total 570 bytes for 95 characters. This is still small.

Now, let's get into the code. You need to write a function that takes a character and writes its bitmap to the OLED frame buffer. Here is a pseudo-code example for a fixed-width 5x7 font on SSD1306 via SPI:

void drawChar(unsigned char c, int x, int y) {
if (c < 32 || c > 126) c = 32; // space for non-printable
int index = (c - 32) * 5; // 5 bytes per character
for (int col = 0; col < 5; col++) {
// Set column address to x+col, page address to y/8
ssd1306_command(0x21); ssd1306_command(x+col); ssd1306_command(127);
ssd1306_command(0x22); ssd1306_command(y/8); ssd1306_command(7);
// Send the byte
ssd1306_data(font[index + col]);
}
}

But this is inefficient because it sends a command for each column. A better approach is to use the frame buffer. You allocate a 1024-byte buffer (128x64/8), write the font data into it, and then send the entire buffer at once. This is faster and reduces SPI overhead. The Adafruit library does this. For custom fonts, you can modify the library's font table. The library uses a structure like GFXfont which includes a pointer to the bitmap data, a pointer to the width table, and the first and last character. To add your custom font, you create a header file with the array and then call setFont(&customFont). The font data must be in the format expected by the library: for each character, the bitmap is stored as a series of bytes, where each byte is a column, and the bits are arranged from top to bottom. The width table stores the number of columns for each character. This is standard for U8g2 and Adafruit_GFX.

For the 2.42 inch 128x64 oled display, the physical dimensions are 2.42 inches diagonal, which is about 61 mm. The active area is 55.01 mm x 27.49 mm, according to typical datasheets. This means the pixel density is about 128 / 55.01 = 2.33 pixels per mm, or 59 DPI. This is low compared to a smartphone, so fonts must be at least 8 pixels tall to be readable. At 8 pixels, the character height is about 3.4 mm, which is acceptable for reading at arm's length. For a 16-pixel font, the height is 6.9 mm, which is good for headlines. The contrast ratio of OLED is >10000:1, so even small fonts are sharp. The viewing angle is 160 degrees, so no distortion.

Now, let's talk about the tools. There are several online tools for generating OLED fonts. OLED Font Generator (a web tool) lets you upload a TTF file and outputs a C array. But I prefer FontForge because it gives you control over the glyph outlines. In FontForge, you can open a font, select a character, and then export it as a bitmap using the "Export" menu. You can set the resolution to 72 DPI, but for OLED, you need to specify the exact pixel size. For example, to create a 12x16 font, you set the font size to 16 points, then export as a BMP file. Then you need to convert the BMP to a byte array. You can use a Python script to read the BMP and output the bytes. The BMP format is row-major, so you need to transpose it to column-major. Here is a simple Python snippet:

from PIL import Image
img = Image.open('char.bmp').convert('1') # 1-bit black and white
width, height = img.size
bytes_per_char = width * (height // 8)
data = []
for col in range(width):
byte = 0
for row in range(8):
if img.getpixel((col, row)): byte |= (1 << row)
data.append(byte)
print(data)

This script assumes the height is a multiple of 8. For 16-pixel height, you need two pages, so you loop over two sets of 8 rows. The output is a list of bytes for one character. You repeat for all 95 characters.

Another important factor is the font encoding. The OLED display typically uses ASCII, but if you need Unicode (e.g., Chinese characters), you need a larger font table. For a 2.42 inch OLED, Chinese characters are not practical because they require at least 16x16 pixels, which only allows 8 characters per line. But if you need them, you can use a font like wenquanyi which is 12x12 for Chinese. The memory for 2000 Chinese characters at 12x12 is 12 * 2 * 2000 = 48,000 bytes, which requires external flash storage. The SSD1306 has only 1KB internal buffer, so you cannot store the font in RAM. You would need to store it in a SPI flash chip and load it on demand. This is advanced but doable.

Now, let's discuss the SPI timing. The 2.42 inch OLED typically uses a 4-wire SPI: CS, DC, SCK, MOSI. The maximum clock frequency is 10 MHz for SSD1306, but many libraries use 4 MHz. To send a full frame of 1024 bytes, at 4 MHz, it takes 1024 * 8 / 4e6 = 2.05 ms. This is fast enough for 30 frames per second. But if you are sending font data character by character, the overhead of setting the cursor position adds maybe 100 microseconds per character, so for 200 characters, it adds 20 ms. That is why using the frame buffer is better. The frame buffer is stored in the microcontroller's RAM. For an Arduino Uno (2KB RAM), the 1024-byte buffer takes half the RAM, so you need to be careful. For a 2.42 inch OLED, I recommend using an ESP32 or STM32, which have more RAM. The ESP32 has 520KB SRAM, so no issue.

Custom fonts also require you to handle the "inverse" mode. For example, if you want to display a character on a dark background, you just write the bitmap as is. But if you want it inverted, you XOR the bitmap with 0xFF. This is useful for highlighting. The SSD1306 supports a hardware inverse mode via command 0xA7, but it inverts the entire screen. So for per-character inversion, you need to do it in software. In the frame buffer, you can XOR the byte with 0xFF before writing. This is a common technique.

Another practical detail: the font baseline. In a 5x7 font, the character 'g' has a descender that goes below the baseline. But because the OLED uses 8-pixel pages, descenders are often clipped. To fix this, you can design the font with a 1-pixel descender space, meaning the font height is 8 pixels, but the actual character is 7 pixels, with the bottom pixel reserved for descenders. In PCtoLCD2002, you can set the "Font height" to 8 and then adjust the character offset. This is standard in many OLED libraries. For example, the Adafruit 5x7 font has a 7-pixel height with a 1-pixel descender,

هل تبحث عن شريك إبداعي للعلامات التجارية الطموحة؟

أكثر من 180 علامة سعودية وخليجية تثق باستوديو الملوك لتشكيل لغتها البصرية. حان دورك.

احجز جلسة ملكية