Skip to content
Wauwatosa · Wisconsin

How to display Chinese characters on a 2.4 inch LCD?

admin·
About the author admin
To display Chinese characters on a 2.4 inch LCD, you need to handle the encoding and rendering of CJK (Chinese, Japanese, Korean) characters, which are not natively supported by most low-cost LCD controllers like the ILI9341 or ST7789. These controllers typically only handle 8-bit or 16-bit ASCII characters, so you must convert Chinese characters into a bitmap font format. The most practical approach is to use a pre-generated font library, such as the GB2312 or Unicode (UTF-8) encoded bitmap fonts, stored in external flash memory or embedded in your microcontroller’s program memory. For example, a common 16x16 pixel Chinese character requires 32 bytes of data (16 pixels wide x 16 pixels tall, with 2 bytes per row for monochrome). If you need to display a full set of 6,763 GB2312-80 characters, that’s about 216 KB of flash space. Many developers use tools like “FontGen” or “PCtoLCD2002” to convert TrueType fonts into C arrays. Then, you write a function that reads the bitmap data and sends it to the LCD via SPI or parallel interface, pixel by pixel. For a 2.4 inch 240x320 ips display, the resolution is 240x320 pixels, which is enough to show about 15 rows of 16x16 Chinese characters (15 rows x 16 pixels = 240 pixels height) and 20 columns (20 columns x 16 pixels = 320 pixels width), giving you a grid of 300 characters per screen. However, you must also handle the LCD’s color depth—most 2.4 inch LCDs use 16-bit RGB565 color, so each pixel is stored as 2 bytes. To display a Chinese character, you first set the background color (e.g., white: 0xFFFF) and then draw the foreground pixels (e.g., black: 0x0000) based on the bitmap data. The actual speed depends on your SPI clock frequency; a typical 40 MHz SPI bus can transfer 5 MB/s, so drawing a full screen of 240x320 pixels would take about 30 ms (240x320x2 bytes / 5 MB/s = 30.72 ms), but with Chinese character rendering, you only update specific areas, making it faster. For a real-world example, the 2.4 inch 240x320 ips display from DisplayModule uses an ILI9341 controller, which supports SPI and 8-bit parallel modes. The ILI9341 has a built-in 172x320 pixel RAM, but it does not include Chinese font support, so you must implement it in your firmware. One common method is to use the “U8g2” library, which supports Unicode and includes several Chinese font options, such as “u8g2_font_wqy12_t_chinese3” (12-point WenQuanYi bitmap font) or “u8g2_font_unifont_t_chinese2” (16-point Unifont). These fonts are stored in the library’s program memory, consuming about 150 KB for the 12-point font. For a microcontroller like the ESP32 with 4 MB of flash, this is feasible. However, for a smaller MCU like the STM32F103 (64 KB flash), you may need to store the font in an external SPI flash chip, such as the W25Q32 (4 MB). The data transfer rate from external flash to the LCD is critical; using a DMA controller can reduce CPU overhead. Another approach is to use a “font mapping” technique: you precompute the Unicode code points for the Chinese characters you need and store only those glyphs in a lookup table. For example, if you only need to display 100 common characters like “你好世界”, you can store 100 x 32 bytes = 3.2 KB of bitmap data, which is much smaller. But if you need full coverage, the GB2312 standard includes 6,763 characters, but many practical applications only require 3,500 characters for daily use. The encoding method also matters: UTF-8 is widely used in web and PC environments, but in embedded systems, you often convert UTF-8 to GB2312 or Unicode internally. For example, a UTF-8 string like “你好” (0xE4 0xBD 0xA0, 0xE5 0xA5 0xBD) can be decoded to Unicode (0x4F60, 0x597D) and then mapped to a bitmap index. The decoding process adds overhead: each UTF-8 character requires 1-3 bytes, and the lookup time depends on the font table structure. A binary search in a sorted Unicode table of 6,000 entries takes about 12 comparisons, which is acceptable on a 72 MHz Cortex-M3. The LCD’s response time is also a factor: the ILI9341 has a typical response time of 10 ms, but for static text, this is negligible. However, if you are scrolling text, you need to consider the refresh rate. A 2.4 inch LCD with 240x320 resolution can update a 16x16 character area in about 0.5 ms (16x16x2 bytes / 5 MB/s = 0.1024 ms, plus overhead for SPI commands and address setting). So, scrolling 15 rows of text at 60 Hz is possible, but the CPU load for font decoding may become a bottleneck. For example, on an ESP32 at 240 MHz, decoding a Chinese character from a bitmap font takes about 10 microseconds, so 300 characters per frame would take 3 ms, leaving plenty of time for other tasks. But on an Arduino Uno (16 MHz), the same operation could take 200 microseconds per character, making 300 characters take 60 ms, which exceeds the 16.67 ms frame time for 60 Hz. In that case, you would need to reduce the number of characters or use a smaller font, such as 12x12 pixels (18 bytes per character). Another practical consideration is color depth: Chinese characters are usually monochrome, but you can add anti-aliasing for smoother edges. Anti-aliasing requires grayscale values, which increases the bitmap data size. For example, a 16x16 anti-aliased character with 4-bit grayscale would need 16x16x0.5 bytes = 128 bytes per character, quadrupling the storage. Most embedded systems avoid this due to flash constraints. Instead, they use a 1-bit monochrome bitmap and rely on the LCD’s pixel response to create a sharp image. The ILI9341’s 16-bit color depth also allows you to set the foreground and background colors independently, so you can display Chinese characters in any color, like red (0xF800) or blue (0x001F). For a practical implementation, you can use the “Adafruit_GFX” library, which provides a basic “drawBitmap” function, but it does not include Chinese font support. You need to extend it with a custom font class. Many developers use the “TFT_eSPI” library, which is optimized for ESP32 and includes a “loadFont” function that can load Chinese fonts from SPIFFS or SD card. For example, you can store a 16-point Chinese font file (about 200 KB) on an SD card and load it into RAM. However, RAM is limited: on an ESP32, 200 KB is feasible if you have 520 KB of SRAM, but on an STM32F103 with 20 KB SRAM, you cannot load the entire font. In that case, you must use a “page-by-page” approach, where you load only the glyphs for the current screen. This requires a file system like FATFS and a buffer of a few KB. The speed of reading from an SD card via SPI is typically 10 MB/s, so loading a 200 KB font takes 20 ms, which is acceptable for a one-time load. But if you need to switch between different fonts dynamically, you may need to use a faster interface like SDIO. Another angle is the LCD’s hardware acceleration: some controllers like the ILI9488 support “window address” mode, which allows you to define a rectangular area for update. This is useful for Chinese characters because you can update only the character’s bounding box, reducing SPI traffic. For example, to draw a 16x16 character, you set the column address to (x, x+15) and the row address to (y, y+15), then send 16x16x2 bytes of pixel data. The ILI9341 supports this natively. The SPI command sequence for this is: CMD: 0x2A (column address), data: 0x00, 0x00, 0x00, 0xEF (for 240 pixels); CMD: 0x2B (row address), data: 0x00, 0x00, 0x01, 0x3F (for 320 pixels); then CMD: 0x2C (memory write), followed by pixel data. This is a standard approach. For Chinese characters, you also need to handle text alignment and line breaks. For example, a 16x16 character occupies 16 pixels width, so on a 240-pixel-wide screen, you can fit 15 characters per line (240/16 = 15). But if you use a 12x12 font, you can fit 20 characters per line (240/12 = 20). The height of the screen is 320 pixels, so you can fit 20 rows of 16-pixel-tall characters (320/16 = 20) or 26 rows of 12-pixel-tall characters (320/12 = 26.67, but you need integer rows). So, a 12x12 font gives you 20x26 = 520 characters per screen, which is more efficient for dense text. However, smaller fonts are harder to read, especially for complex Chinese characters like “龘” (three dragons). The minimum readable size for Chinese characters on a 2.4 inch LCD is typically 12x12 pixels, but 16x16 is recommended for clarity. The viewing angle of the LCD also matters: an IPS display, like the one from DisplayModule, has a 160-degree viewing angle, which ensures the characters are readable from the side. In contrast, a TN LCD may have poor contrast at angles, making small Chinese characters look blurry. The backlight brightness is another factor: a typical 2.4 inch LCD has a backlight current of 20-40 mA, and the brightness can be adjusted via PWM. For outdoor use, you may need a higher brightness, but Chinese characters with high contrast (e.g., black on white) are still readable. The power consumption of the LCD is about 50-100 mA at full brightness, which is okay for battery-powered devices if you use a low-power mode. For example, you can turn off the backlight when not in use, and only update the display when the text changes. This is common in e-reader-like applications. The data encoding for Chinese characters also affects the firmware size. If you use UTF-8 strings, you need a decoder that handles multi-byte sequences. The UTF-8 decoding algorithm is straightforward: for a 1-byte character (0x00-0x7F), it’s ASCII; for 2-byte (0xC2-0xDF), it’s a 2-byte sequence; for 3-byte (0xE0-0xEF), it’s a 3-byte sequence; for 4-byte (0xF0-0xF7), it’s a 4-byte sequence. Chinese characters in the BMP (Basic Multilingual Plane) are 3-byte UTF-8 sequences. For example, “中” is 0xE4 0xB8 0xAD, and its Unicode code point is 0x4E2D. The decoder extracts the code point and then looks up the font table. The font table can be stored as a sorted array of (Unicode, bitmap_index) pairs. For 6,000 characters, this array takes about 6,000 x 4 bytes (2 bytes for Unicode, 2 bytes for index) = 24 KB. Then the bitmap data is stored in a separate array. The total storage for a 16x16 font with 6,000 characters is 6,000 x 32 bytes = 192 KB, plus the index table 24 KB, total 216 KB. This fits in an external SPI flash chip. The speed of looking up the bitmap index is critical: a binary search on 6,000 entries takes about 12 iterations, each requiring a read from flash. If the flash has a 50 MHz SPI clock, each read takes about 0.2 microseconds, so the total lookup time is 2.4 microseconds, which is negligible. However, if the font is stored in internal flash, the read speed is faster (e.g., 100 ns per byte), but the flash size is limited. For a microcontroller with 512 KB internal flash, you can store the entire font, but you must allocate space for your program. For example, an ESP32 has 4 MB flash, so you can easily store the font. For an STM32F103 with 64 KB flash, you cannot store the full font; you need external flash. The external flash interface can be SPI or QSPI. QSPI (Quad SPI) can read 4 bits per clock, achieving up to 200 MB/s, which is faster for large fonts. But for a 2.4 inch LCD, SPI is sufficient because the data transfer to the LCD is the bottleneck. Another aspect is the use of a “font renderer” library like “FreeType” or “stb_truetype”. These libraries can render TrueType fonts on the fly, but they require significant RAM and CPU power. For example, FreeType on an ESP32 can render a 16-point Chinese character in about 1 ms, but it needs a heap of at least 50 KB for the glyph cache. This is feasible on an ESP32 with 520 KB SRAM, but on an Arduino Uno, it’s impossible. So, most embedded systems use pre-rendered bitmap fonts. The bitmap font generation process can be automated using Python scripts. For example, you can use the “Pillow” library to load a TrueType font, render each character to a bitmap, and output a C array. The script can also handle Unicode range selection. For instance, you can generate a font for the CJK Unified Ideographs block (U+4E00 to U+9FFF), which covers 20,992 characters, but you only need the first 6,763 for GB2312. The script can also optimize the bitmap by using run-length encoding (RLE) to reduce storage. For example, a 16x16 monochrome bitmap can be compressed by RLE, where consecutive identical pixels are encoded as a count and a value. For Chinese characters, which have many white pixels, RLE can reduce the size by 30-50%. But this adds decoding complexity. A simpler approach is to use a 1-bit bitmap without compression, which is easy to decode. The pixel data is sent to the LCD as 16-bit colors, so you need to convert the 1-bit bitmap to 16-bit colors. For example, for a white background, you set the pixel to 0xFFFF if the bitmap bit is 0, and 0x0000 if the bit is 1. This conversion can be done in a loop, but it’s slower. To speed it up, you can precompute a 16-bit color table for the foreground and background, and then use a lookup table for the 16-bit pixel data. For example, for a 16-pixel row, you read 2 bytes of bitmap data, then for each bit, you output the corresponding 16-bit color. This can be done with a switch statement or a lookup table of 256 entries (for 8-bit patterns). The table size is 256 x 2 bytes = 512 bytes, which is small. The actual SPI transfer can be done using DMA to avoid CPU blocking. For example, on an ESP32, you can use the SPI DMA engine to send a buffer of 16x16x2 bytes = 512 bytes in the background. While the DMA is transferring, the CPU can decode the next character. This double-buffering technique can achieve a high frame rate. The DMA transfer time for 512 bytes at 40 MHz SPI is about 512 bytes / 5 MB/s = 0.1024 ms, which is very fast. So, the bottleneck is the font decoding, not the SPI transfer. For a 240x320 screen, updating the entire screen with Chinese characters would require 300 characters, each taking 0.1 ms for SPI transfer, total 30 ms, plus 300 x 10 microseconds = 3 ms for decoding, total 33 ms. This is about 30 frames per second, which is acceptable for static text. For scrolling text, you can update only the changed lines. For example, if you scroll one line of text, you only need to update 15 characters (one row), which takes 1.5 ms for SPI transfer and 0.15 ms for decoding, total 1.65 ms, allowing 600 Hz scrolling. However, the LCD’s response time limits the visible refresh rate to about 60 Hz. The practical implementation also requires a text buffer in RAM. For example, to display 15 rows of 15 characters, you need a buffer of 15x15 = 225 characters. Each character is stored as a Unicode code point (2 bytes), so the buffer is 450 bytes. This is small. The text buffer can be updated from a UART or Wi-Fi interface. For example, you can receive UTF-8 encoded Chinese text from a serial port, decode it, and store it in the buffer. Then, a rendering function converts the buffer to bitmap data and sends it to the LCD. The rendering function can be triggered by a timer or an interrupt. The power consumption of the LCD during text update is about 50 mA, but if you only update the display when the text changes, the average power is lower. For battery-powered devices, you can use a deep sleep mode where the LCD is turned off, and only wake up when new text arrives. The LCD’s standby current is about 0.1 mA, which is negligible. Another consideration is the use of a “display buffer” in RAM. For a 240x320 screen with 16-bit color, the full frame buffer is 240x320x2 bytes = 153,600 bytes (150 KB). This is too large for most microcontrollers. For example, an ESP32 has 520 KB SRAM, so it can fit a 150 KB buffer, but it leaves less room for other tasks. An STM32F103 has 20 KB SRAM
Continue the story

Join us at the greenhouse table.

Reserve Your Table