[PIC®/AVR®/dsPIC®产品] OLED驱动时候使用的字库,如何考虑存放的数据类型可以更好的兼容不同的单片机。

[复制链接]
 楼主| huangcunxiake 发表于 2025-7-17 09:00 | 显示全部楼层 |阅读模式
在OLED驱动中,字库(通常是大数组形式的点阵数据)的存储和访问是跨平台移植的关键挑战。以下是如何设计兼容不同单片机架构的字库存储方案:

兼容性设计原则

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

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

  4. #include <stdint.h>

  5. // 平台检测 ========================
  6. #if defined(__C51__) || defined(SDCC)       // 8051
  7.   #define PLATFORM_8051
  8. #elif defined(__XC8__) || defined(__XC16__) // PIC
  9.   #define PLATFORM_PIC
  10. #elif defined(__AVR__)                     // AVR
  11.   #define PLATFORM_AVR
  12. #elif defined(__MSP430__) || defined(__TI_COMPILER_VERSION__) // MSP430
  13.   #define PLATFORM_MSP430
  14. #endif

  15. // Flash存储修饰符定义 ================
  16. #if defined(PLATFORM_8051)
  17.   #define FLASH_STORAGE code const
  18. #elif defined(PLATFORM_PIC)
  19.   #define FLASH_STORAGE const __flash
  20. #elif defined(PLATFORM_AVR)
  21.   #include <avr/pgmspace.h>
  22.   #define FLASH_STORAGE const PROGMEM
  23. #else // MSP430及其他
  24.   #define FLASH_STORAGE const // 默认const足够
  25. #endif

  26. // Flash数据访问宏 ==================
  27. #if defined(PLATFORM_AVR)
  28.   #define FLASH_READ_BYTE(addr) pgm_read_byte(addr)
  29.   #define FLASH_READ_WORD(addr) pgm_read_word(addr)
  30. #else
  31.   // 统一编址架构可直接访问
  32.   #define FLASH_READ_BYTE(addr) (*(const uint8_t *)(addr))
  33.   #define FLASH_READ_WORD(addr) (*(const uint16_t *)(addr))
  34. #endif

  35. #endif // FLASH_STORAGE_H

步骤2:定义字库数据结构

  1. // font_data.h
  2. #ifndef FONT_DATA_H
  3. #define FONT_DATA_H

  4. #include "flash_storage.h"

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

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

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

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

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

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

  15.         if (current_unicode == unicode) {
  16.             // 复制点阵数据
  17.             uint8_t data_size = font_lib[i].height * ((font_lib[i].width + 7) / 8);
  18.             if (buf_size < data_size) return false;
  19.             
  20.             for (uint8_t j = 0; j < data_size; j++) {
  21.                 #if defined(PLATFORM_AVR)
  22.                     buffer[j] = pgm_read_byte(&font_lib[i].data[j]);
  23.                 #else
  24.                     buffer[j] = font_lib[i].data[j]; // 直接访问
  25.                 #endif
  26.             }
  27.             return true;
  28.         }
  29.     }
  30.     return false; // 未找到字符
  31. }

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

  3. void oled_draw_char(uint16_t unicode, uint8_t x, uint8_t y) {
  4.     uint8_t char_data[64]; // 足够存放最大字符
  5.    
  6.     // 通用访问方式(全平台兼容)
  7.     if (font_get_char_data(unicode, char_data, sizeof(char_data))) {
  8.         // 使用char_data绘制...
  9.     }
  10.    
  11.     // 优化路径(非AVR平台)
  12.     #if !defined(PLATFORM_AVR)
  13.     const FontChar* font_char = font_get_char(unicode);
  14.     if (font_char) {
  15.         // 直接访问font_char->data绘制...
  16.         // 示例:绘制一个字节
  17.         uint8_t pixel_data = font_char->data[0];
  18.         // ...
  19.     }
  20.     #endif
  21. }



 楼主| huangcunxiake 发表于 2025-7-17 09:01 | 显示全部楼层
关键设计考量
三级访问抽象:

层1:FLASH_STORAGE 统一存储修饰符

层2:FLASH_READ_* 统一读取宏

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

 楼主| huangcunxiake 发表于 2025-7-17 09:02 | 显示全部楼层
AVR特殊处理:
  1. // AVR必须使用pgm_read_*函数
  2. #if defined(PLATFORM_AVR)
  3. buffer[j] = pgm_read_byte(&font_lib[i].data[j]);
  4. #endif


性能优化路径:
  1. // 非AVR平台可直接访问Flash数据
  2. #if !defined(PLATFORM_AVR)
  3. const FontChar* pChar = &font_lib[index];
  4. uint8_t pixel = pChar->data[0]; // 直接访问
  5. #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 | 显示全部楼层
双模式访问:
  1. // 在头文件中提供两种访问模式
  2. #ifdef __cplusplus
  3. extern "C" {
  4. #endif

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

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

  11. #ifdef __cplusplus
  12. }
  13. #endif



性能关键代码:
  1. // 对于需要高性能的场景
  2. #if defined(PLATFORM_AVR)
  3. // AVR专用优化代码
  4. #else
  5. // 通用优化代码
  6. #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 | 显示全部楼层
这是个难题啊,没有最好用的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

222

主题

3700

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部