huangcunxiake 发表于 2025-7-17 09:00

OLED驱动时候使用的字库,如何考虑存放的数据类型可以更好的兼容不同的单片机。

在OLED驱动中,字库(通常是大数组形式的点阵数据)的存储和访问是跨平台移植的关键挑战。以下是如何设计兼容不同单片机架构的字库存储方案:

兼容性设计原则

统一存储位置:确保字库始终存放在Flash中
抽象访问方式:隐藏底层架构差异
最小化平台依赖:集中处理编译器特定代码

实现方案(分步骤)
步骤1:创建统一的Flash存储定义头文件
// flash_storage.h
#ifndef FLASH_STORAGE_H
#define FLASH_STORAGE_H

#include <stdint.h>

// 平台检测 ========================
#if defined(__C51__) || defined(SDCC)       // 8051
#define PLATFORM_8051
#elif defined(__XC8__) || defined(__XC16__) // PIC
#define PLATFORM_PIC
#elif defined(__AVR__)                     // AVR
#define PLATFORM_AVR
#elif defined(__MSP430__) || defined(__TI_COMPILER_VERSION__) // MSP430
#define PLATFORM_MSP430
#endif

// Flash存储修饰符定义 ================
#if defined(PLATFORM_8051)
#define FLASH_STORAGE code const
#elif defined(PLATFORM_PIC)
#define FLASH_STORAGE const __flash
#elif defined(PLATFORM_AVR)
#include <avr/pgmspace.h>
#define FLASH_STORAGE const PROGMEM
#else // MSP430及其他
#define FLASH_STORAGE const // 默认const足够
#endif

// Flash数据访问宏 ==================
#if defined(PLATFORM_AVR)
#define FLASH_READ_BYTE(addr) pgm_read_byte(addr)
#define FLASH_READ_WORD(addr) pgm_read_word(addr)
#else
// 统一编址架构可直接访问
#define FLASH_READ_BYTE(addr) (*(const uint8_t *)(addr))
#define FLASH_READ_WORD(addr) (*(const uint16_t *)(addr))
#endif

#endif // FLASH_STORAGE_H
步骤2:定义字库数据结构

// font_data.h
#ifndef FONT_DATA_H
#define FONT_DATA_H

#include "flash_storage.h"

// 通用字库结构(示例:16x16点阵汉字)
typedef struct {
    uint16_t unicode;      // 字符编码
    uint8_t width;         // 实际宽度
    uint8_t height;      // 实际高度
    uint8_t data;      // 点阵数据(16x16/8=32字节)
} FontChar;

// 声明字库(实际定义在.c文件)
extern FLASH_STORAGE FontChar font_lib[];
extern const uint16_t font_lib_size;

#endif // FONT_DATA_H步骤3:实现字库数据(平台无关)
// font_data.c
#include "font_data.h"

// 使用统一修饰符定义字库
FLASH_STORAGE FontChar font_lib[] = {
    {0x4E2D, 16, 16, { // 汉字"中"
      0x00,0x40,0x00,0x40,0x7F,0xFE,0x40,0x02,
      0x5F,0xFA,0x50,0x0A,0x50,0x0A,0x5F,0xFA,
      0x50,0x0A,0x50,0x0A,0x5F,0xFA,0x40,0x02,
      0x7F,0xFE,0x00,0x40,0x00,0x40,0x00,0x00}},
    // ...其他字符
};

const uint16_t font_lib_size = sizeof(font_lib)/sizeof(FontChar);步骤4:实现跨平台字库访问函数
// oled_font.c
#include "font_data.h"
#include "flash_storage.h"

// 获取字符点阵数据(兼容所有平台)
bool font_get_char_data(uint16_t unicode, uint8_t* buffer, uint8_t buf_size) {
    for (uint16_t i = 0; i < font_lib_size; i++) {
      // 平台安全的Unicode读取
      uint16_t current_unicode;
      
      #if defined(PLATFORM_AVR)
            current_unicode = pgm_read_word(&font_lib.unicode);
      #else
            current_unicode = font_lib.unicode; // 直接访问
      #endif

      if (current_unicode == unicode) {
            // 复制点阵数据
            uint8_t data_size = font_lib.height * ((font_lib.width + 7) / 8);
            if (buf_size < data_size) return false;
            
            for (uint8_t j = 0; j < data_size; j++) {
                #if defined(PLATFORM_AVR)
                  buffer = pgm_read_byte(&font_lib.data);
                #else
                  buffer = font_lib.data; // 直接访问
                #endif
            }
            return true;
      }
    }
    return false; // 未找到字符
}

// 优化版本:直接访问(非AVR平台)
const FontChar* font_get_char(uint16_t unicode) {
    #if !defined(PLATFORM_AVR)
    for (uint16_t i = 0; i < font_lib_size; i++) {
      if (font_lib.unicode == unicode) {
            return &font_lib; // 直接返回Flash中的指针
      }
    }
    #endif
    return NULL; // AVR不支持直接返回指针
}步骤5:OLED驱动中使用字库(示例)
// oled_driver.c
#include "font_data.h"

void oled_draw_char(uint16_t unicode, uint8_t x, uint8_t y) {
    uint8_t char_data; // 足够存放最大字符
   
    // 通用访问方式(全平台兼容)
    if (font_get_char_data(unicode, char_data, sizeof(char_data))) {
      // 使用char_data绘制...
    }
   
    // 优化路径(非AVR平台)
    #if !defined(PLATFORM_AVR)
    const FontChar* font_char = font_get_char(unicode);
    if (font_char) {
      // 直接访问font_char->data绘制...
      // 示例:绘制一个字节
      uint8_t pixel_data = font_char->data;
      // ...
    }
    #endif
}


huangcunxiake 发表于 2025-7-17 09:01

关键设计考量
三级访问抽象:

层1:FLASH_STORAGE 统一存储修饰符

层2:FLASH_READ_* 统一读取宏

层3:font_get_char_data() 平台无关接口

huangcunxiake 发表于 2025-7-17 09:02

AVR特殊处理:
// AVR必须使用pgm_read_*函数
#if defined(PLATFORM_AVR)
buffer = pgm_read_byte(&font_lib.data);
#endif

性能优化路径:
// 非AVR平台可直接访问Flash数据
#if !defined(PLATFORM_AVR)
const FontChar* pChar = &font_lib;
uint8_t pixel = pChar->data; // 直接访问
#endif

数据结构设计技巧:

将元数据(宽高)与点阵数据打包存储

使用unicode代替索引便于国际化

固定高度变量宽度节省存储空间

huangcunxiake 发表于 2025-7-17 09:02

不同平台对比表

平台存储修饰符访问方式直接指针访问特殊要求
8051code const编译器自动处理✅需Keil/SDCC特定语法
PICconst __flash编译器自动处理✅XC编译器特定修饰符
AVRconst PROGMEMpgm_read_byte()函数❌必须使用访问宏
MSP430const直接访问✅无特殊要求
ARM Cortexconst直接访问✅无特殊要求

huangcunxiake 发表于 2025-7-17 09:03

分层隔离:
应用层 (oled_driver.c)

服务层 (oled_font.c) → 平台抽象层 (flash_storage.h)

数据层 (font_data.c)

huangcunxiake 发表于 2025-7-17 09:03

条件编译集中化:

所有平台相关代码集中在flash_storage.h

避免在业务逻辑中散布#ifdef

huangcunxiake 发表于 2025-7-17 09:04

双模式访问:
// 在头文件中提供两种访问模式
#ifdef __cplusplus
extern "C" {
#endif

// 安全但较慢的通用接口
bool font_get_char_data(uint16_t code, uint8_t* buf, uint8_t size);

// 快速访问(非AVR平台)
#if !defined(PLATFORM_AVR)
const FontChar* font_get_char(uint16_t code);
#endif

#ifdef __cplusplus
}
#endif


性能关键代码:
// 对于需要高性能的场景
#if defined(PLATFORM_AVR)
// AVR专用优化代码
#else
// 通用优化代码
#endif
字库生成工具:

开发Python工具自动生成font_data.c

自动添加平台修饰符

示例命令:
python font_converter.py font.ttf -s 16 -o font_data.c --platform auto
这种设计允许:

在MSP430/ARM上获得最佳性能

保持AVR平台的兼容性

最小化8051/PIC的特殊处理

添加新平台只需修改一个头文件

通过集中处理平台差异和提供统一的抽象接口,可以创建真正可移植的OLED字库实现,显著提高代码复用率并降低维护成本。

破晓战神 发表于 2025-7-18 11:50

这个方案很全面,通过统一Flash存储和访问方式,确实可以提高字库的兼容性。

wanduzi 发表于 2025-7-26 15:00

还有一个就是把所有的控制放到数组里,方便用DMA。

xinpian101 发表于 2025-7-27 22:42

一直找不到好用的液晶屏驱动库。

mintspring 发表于 2025-7-28 09:10

这是个难题啊,没有最好用的。

小岛西岸来信 发表于 2025-8-27 11:11

采用无符号字节型(uint8_t)存储 OLED 字库数据,按行列扫描顺序排列像素。统一使用 ASCII 码映射字符,汉字用 GB2312 编码索引。数据结构用二维数组,避免指针和动态分配。字库文件独立,通过宏定义适配不同单片机的存储地址,确保跨平台兼容性

桃乐丝 发表于 2025-8-27 12:18

OLED 驱动字库选择数据类型时,需优先考虑通用二进制格式,避免依赖特定单片机的自定义数据类型(如某些芯片的 “sfr” 特殊功能寄存器类型),以兼容 8 位、16 位、32 位不同架构单片机。
建议采用标准无符号整数类型(如 uint8_t、uint16_t)定义字库数据,通过头文件(如 stdint.h)统一类型声明,消除不同编译器对 “char”“int” 的位数差异影响。
字库存储结构选用数组形式,按 “行 / 列扫描顺序”(如横向 8 点 / 16 点编码)组织数据,避免使用指针或结构体嵌套,降低不同单片机内存寻址方式的适配难度。
同时,预留数据长度标识位,方便不同单片机通过通用 IO 口或 SPI/I2C 接口读取时,自动识别字符宽度 / 高度,减少硬件差异导致的兼容性问题。

桃乐丝 发表于 2025-9-4 15:03

使用无符号字符型(uint8_t)存储 OLED 字库数据,兼容多数单片机。按字节数组形式组织,每个字符占固定字节(如 16×16 字符占 32 字节),用 const 修饰存程序区。避免依赖特定架构类型,通过宏定义字符尺寸,便于不同单片机移植。
页: [1]
查看完整版本: OLED驱动时候使用的字库,如何考虑存放的数据类型可以更好的兼容不同的单片机。