发帖
4 0 0

解决带字库的OLED显示UTF-8乱码问题--助力第九期DIY活动

起个名字好难啊
论坛元老

38

主题

68

回帖

6049

积分

论坛元老

积分
6049
技术杂谈 131 4 昨天 14:11

解决带字库的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


代码实现:utf8_to_gb2312.c

#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;
}


代码实现:utf8_to_gb2312.h

#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


OLED 实现显示

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表情和字幕


──── 0人觉得很赞 ────

使用道具 举报

莫工这排版就很独特
WT_0213 发表于 2025-11-6 14:33
莫工这排版就很独特

眼前一亮
下一期DIY主打这个吗
昨天 22:43
bzhou830 发表于 2025-11-6 17:12
下一期DIY主打这个吗

莫哥暗示的还不够明显吗😎
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 30098 个
  • 话题数: 44203 篇