解决带字库的OLED显示UTF-8乱码问题
GT20L16S 是OLED屏幕常用的一款字库芯片,内置 GB2312 编码的字库,但是不支持 UTF-8 编码,导致在 OLED 屏幕上显示中文字符时出现乱码。小智AI 下发的字幕编码格式为UNICODE,直接显示在 OLED 屏幕上会出现乱码。
- GT20L16S 字库芯片内置 GB2312 编码的字库,并且具备 UNICODE->GB2312 的映射表
- 先把 UTF-8 编码的字符转换为 UNICODE 编码
- 由 UNICODE 编码计算对应的 GB2312 编码的地址
- 通过地址从 GT20L16S 字库芯片中读取 GB2312 编码的字符
- 把读取的 GB2312 编码从 GT20L16S 读出显示在 OLED 屏幕上
flowchart TD
A( UTF-8 字符) --> B{判断是否为 UTF-8}
B -->|是|C[UTF-8]
C -->|通过计算|D[Unicode编码]
D -->|计算偏移量|E[GT20L16S 地址]
E -->|读取地址内容|F[GB2312 编码]
F -->OLED(OLED显示)
B -->|否|OLED
#include "utf8_to_gb2312.h"
/**
* @brief 判断字符串是否为UTF-8编码
*
* @param str
* @param len
* @return uint8_t
*/
uint8_t isStrUTF8(const char *str, uint16_t len){
if (str == NULL || len < 1 || len > 4) {
return 0; // 长度无效
}
switch (len) {
case 1:
// 1字节:0xxxxxxx
return (str[0] & 0x80) == 0;
case 2:
// 2字节:110xxxxx 10xxxxxx
return (str[0] & 0xE0) == 0xC0 // 首字节前3位为110
&& (str[1] & 0xC0) == 0x80; // 后续字节前2位为10
case 3:
// 3字节:1110xxxx 10xxxxxx 10xxxxxx
return (str[0] & 0xF0) == 0xE0 // 首字节前4位为1110
&& (str[1] & 0xC0) == 0x80 // 后续字节前2位为10
&& (str[2] & 0xC0) == 0x80;
case 4:
// 4字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
return (str[0] & 0xF8) == 0xF0 // 首字节前5位为11110
&& (str[1] & 0xC0) == 0x80 // 后续字节前2位为10
&& (str[2] & 0xC0) == 0x80
&& (str[3] & 0xC0) == 0x80;
default:
return 0;
}
}
// --------------------------
// UTF-8解码为Unicode(标准实现)
// --------------------------
uint32_t utf8_to_unicode(const char *str, uint8_t *byte_len) {
uint8_t c = (uint8_t)*str;
if (c < 0x80) { // 单字节ASCII
*byte_len = 1;
return c;
} else if ((c & 0xE0) == 0xC0) { // 双字节(110xxxxx)
*byte_len = 2;
return ((c & 0x1F) << 6) | (str[1] & 0x3F);
} else if ((c & 0xF0) == 0xE0) { // 三字节(1110xxxx)
*byte_len = 3;
return ((c & 0x0F) << 12) | ((str[1] & 0x3F) << 6) | (str[2] & 0x3F);
} else { // 无效字符
*byte_len = 1;
return 0;
}
}
// --------------------------
// Unicode转GB2312(精确映射表,覆盖常用字)
// 说明:从Unicode 0x4E00开始,前3755个汉字对应GB2312一级汉字
// --------------------------
uint32_t unicode_to_gb2312_fontaddr(uint32_t unicode) {
uint32_t baseAddr, decodeAddr = 0;
if(unicode <= 0x3017 && unicode >= 0x3000) baseAddr = 0x1d9e5;
else if(unicode <= 0x9fa5 && unicode >= 0x4e00) baseAddr = 0x1bfbb;
else if(unicode <= 0xfe6b && unicode >= 0xfe30) baseAddr = 0x16131;
else if(unicode <= 0xff5e && unicode >= 0xff01) baseAddr = 0x1609c;
else if(unicode <= 0xffe5 && unicode >= 0xffe0) baseAddr = 0x1601b;
else baseAddr = 0;
if(baseAddr != 0){
decodeAddr = (unicode + baseAddr) * 2;
}
return decodeAddr;
}
#ifndef UTF8_TO_GB2312_H
#define UTF8_TO_GB2312_H
#include "stm32f1xx_hal.h"
uint8_t isStrUTF8(const char *str, uint16_t len);
// UTF-8解码为Unicode
uint32_t utf8_to_unicode(const char *str, uint8_t *byte_len);
uint32_t unicode_to_gb2312_fontaddr(uint32_t unicode);
#endif
void OLED_Display_UTF8(uint8_t x, uint8_t y, const char *text) {
uint8_t i = 0;
uint8_t byte_len;
uint8_t fontbuf[2];
while (text[i] != '\0') {
if (isStrUTF8(&text[i], 3)) {
uint32_t unicode = utf8_to_unicode(&text[i], &byte_len);
if (unicode == 0) {
i++;
continue;
}
uint32_t fontaddr = unicode_to_gb2312_fontaddr(unicode);
if (fontaddr == 0) {
i += byte_len;
continue;
}
OLED_get_data_from_ROM(fontaddr>>16&0XFF, fontaddr>>8&0XFF, fontaddr&0XFF, fontbuf, 2);
OLED_Display_GB2312_string(x, y, (char*)fontbuf);
x += 16;
i += byte_len;
} else if ((text[i] >= 0x20) && (text[i] <= 0x7e)){
unsigned char fontbuf[16];
uint8_t addrHigh, addrMid, addrLow;
uint32_t fontaddr ;
fontaddr=(text[i]-0x20);
fontaddr=(unsigned long)(fontaddr*16);
fontaddr=(unsigned long)(fontaddr+0x3cf80);
addrHigh=(fontaddr&0xff0000)>>16;
addrMid=(fontaddr&0xff00)>>8;
addrLow=fontaddr&0xff;
OLED_get_data_from_ROM(addrHigh,addrMid,addrLow,fontbuf,16);
OLED_Display_8x16(x,y,fontbuf);
x += 8;
i+=1;
}
}
}
- OLED 的显示 GB2312 中文厂家已经写好,可以正常显示 GB2312 编码的中文,VSCode 默认的编码格式是UTF-8,如果出现乱码,可以把编码格式改为 GB2312
- 这种方式可以替代手写映射表的方式,给MCU节省了大量的内存空间
- 本文给网络上缺失的 OLED 显示 GB2312 中文的方法,给有需要的人提供帮助
- 第9期DIY活动已经开始,大家可以挑战一下使用Ai-WV01-32S+带字库OLED屏,实现显示小智AI表情和字幕
|