精简体积的OLED 基础驱动库 - OLED_BASIC

news2024/11/15 5:07:44

打算用一个存储空间不大的Arduino 芯片做点简单的文字和图形显示,屏幕芯片SSD1316,感觉u8g2 占用还是太大,想裁剪别人的现成代码又感觉无从下手,所以就基本上重写了一个OLED 显示库,仓库地址:gitee.com/etberzin/oled_basic。

先说重点:

  1. 目前支持SPI 接口的SSD1316 和I2C 接口的SSD1306,想扩展支持相似的其他SSD 这一家子型号难度应该不大,似乎主要是初始化代码上有区别;
  2. 不是u8g2 库的替代;
  3. 不需要显示缓冲区,几乎没有额外的RAM 占用;
  4. 只支持文本和整数输出函数,不支持矩形以外的绘图功能;
  5. 想显示图片的话,只要把图片当作大一点字符来用就行,自定义一个单独的字库放进去;
  6. 自定义字库的部分特意做的很简单,不用像U8G2 库那样还要整一堆编码上的劳什子,取模软件输出的数组直接放进代码就能用;
  7. 没用到的内置字库和底层驱动不会占地方;
  8. 只适合驱动单个屏幕的场合,想驱动多个也不是不行,得付出一点效率上的代价;

安装

如果使用PlatformIO 环境,安装库只要在platformio.ini 配置文件里加上:

lib_deps = 
	oled_basic=https://gitee.com/etberzin/oled_basic.git

用Arduino IDE 的话,就把文件夹下载下来,然后扔到库的路径底下。

性能

用Arduino 的硬件SPI 库驱动SSD1316;6x8 的字体,覆盖字母大小写、数字和大部分标点符号;基于8 位AVR 架构的ATmega128 单片机,在屏幕上显示几个字母,程序编译后总大小约2.2kB。内置了裁剪掉小写字母部分的字库,可以再精简到2kB。加上整数显示功能,从2kB 增加到了2.5kB,以后可能会再优化一下,但是应该没什么余地。

硬件I2C 驱动库Wire 本身体积比较大,所以用I2C 驱动SSD1306 屏幕,体积膨胀了不少。其他条件不变,编译后大约4kB。想精简体积,可以考虑换用极简的软件I2C,不检查ACK,就愣发送的那种。

示例代码

以下是硬件SPI 驱动SSD1316 的代码,效果是在屏幕上显示几个8x16 的大写字母,以及12x24 的大号数字。显示字母用的8x16 字库裁剪到只剩下大写字母,数字对应的12x24 字库离只有十个数字加负号、小数点和斜杠。编译下来大小2.9k。核心功能就这么些,代码上的注释写的应该够详细了。

// Copyright (c) 2023 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#include <Arduino.h>

#include "oled_basic_v1.h"

// 具体硬件的底层驱动,只选择一个要用的include 进来,其他驱动的依赖就不会被引入。比如只用SPI 通信,就不需要Wire 库。
#include "oled_basic_1316_arduino_spi.h"
// #include "oled_basic_1306_arduino_wire.h"


// 定义SPI 操作引脚,SCK 和MOSI引脚由硬件SPI 自己管理
constexpr auto OLED_CS = 7;
constexpr auto OLED_DC = 8;

// 选择具体屏幕的底层驱动,同时传入SPI 引脚。ArduinoSpi1316_12832 对应12832 的屏幕,
// 如果有多个驱动方式相同的不同尺寸屏幕,都会放在同一个头文件里
using LowLevel = oled_basic::ArduinoSpi1316_12832<OLED_CS, OLED_DC>;

// 实例化顶层驱动
using Oled = oled_basic::OledDriver<LowLevel>;

// 如果用I2C 总线,就不需要传入其他的引脚作为参数,全部由Wire 库管理
// using Oled = oled_basic::OledDriver<oled_basic::ArduinoWire1306_12864>;


// 选择待会用来显示文本的字体,用一个常量表示,方便修改。
// ascii_8x16_upper_case_only 是ascii_8x16 字库裁剪掉的只剩下26 个大写字母的部分。
// 字体的命名规则是三段式,ascii 表示名称,8x16 表示单个符号的宽度和高度,其中高度必须是8 的倍数。
// 如果字体还有一些别的特征,而且没有体现在字体名称中,则用后缀标记。upper_case_only 就是只有大写字母。
constexpr auto &TEXT_FONT = oled_basic::ascii_8x16_upper_case_only;

void setup() {
    pinMode(OLED_CS, OUTPUT);
    pinMode(OLED_DC, OUTPUT);

    // 底层驱动使用SPI 库时不管初始化部分,需要手动写
    // 通信时的参数是自动设置的
    SPI.begin();

    // Wire.begin();

    // 执行屏幕对应的初始化代码
    Oled::run_init_sequence();
    
    // 显示两个文本,0,1 表示x,y 坐标,或者说是列和页地址。
    // 对于12832 的屏幕,列地址从0 到127,可以单像素点定位;
    // 页地址从0 到3,每个页代表屏幕上8 个像素高度的横条,其中的每列8 个像素就对应显存中的一个字节。
    // 向屏幕传送显示数据时,只能对应显存里的字节一个一个传,所以一次传送至少覆盖某个页中的一列8 个像素,也可以说是一行中的8 个像素。
    // 如果想实现高度上的单像素点定位,需要做不少额外的处理,可能需要个显示缓冲区,跟这个精简OLED 库的目标不符。
    Oled::put_str("NUM", TEXT_FONT, 0, 1);
    Oled::put_str("NUM", TEXT_FONT, 20, 0);
    
    int8_t num = -100;

    while (1) {
        // 用12x24 的大号数字字体显示数字,24 / 8 = 3,也就是要占用3 页的高度。
        // 数字字体里包含十个数字,加上小数点、负号和斜杠,一共13 个符号。字体本身占用的空间是3 * 12 * 13 = 468 字节。
        // 最后一个参数oled_basic::write_mode::inverse 是可选参数,表示反色显示,默认不反色。
        // put_str 最后也可以加上反色参数。
        // 返回值表示显示内容后的下一列地址。如果在第0 列显示了一个12x24 的数字,返回值就是0 + 12 = 12。
        uint8_t col = Oled::put_signed(num++, oled_basic::big_digit_12x24, 59, 1, oled_basic::write_mode::inverse);
        
        // num 范围是-128 到127,所以最多显示三位数加一个负号,文字占用12 * 4 的宽度。
        // 显示了较大数字后,再显示小数字,必须清空后面空出来的位置
        // 比如先显示-100,然后变成-99,如果不做清空,实际显示出来就是-990。
        // 整体清屏再重写会产生一个空白帧,导致闪烁,所以只清空数字后面多出来的部分
        if(col < 59 + 12 * 4) {
            Oled::fill_area(0, col, 1, 59 + 12 * 4 - col, 3);
        }

        // fill_area 的功能是用指定的一个字节数据重复填充屏幕上一块矩形区域,
        // 如果指定的数据是0,效果就是清空区域
        
        delay(500);
    }
}

void loop() {}

示例代码的显示效果如下:

反色显示数字演示 - 精简体积的Arduino OELD

自定义字体

字体描述

内置的每个字体由两部分组成:

  1. 字模,就是由取模软件直接生成的数组,包含每个字的显示数据;
  2. 描述,就是个结构体,里面放着字体的宽度、高度等信息;

描述信息是必要的,否则就只能把这些信息写死在OLED 驱动的代码里,太不灵活。上面示例代码里的oled_basic::big_digit_12x24oled_basic::ascii_8x16_upper_case_only 就是字体的描述结构体,内部代码根据描述去找字模数据。这样一来,没用到的字体也能被编译器删除掉,不会占用资源。和u8g2 库的做法不一样,描述结构体和字模是独立的,而不是整体混合编码成一个二进制。好处是结构很直观,可以轻松手写修改,而且去掉了耦合,修改描述信息不会影响字模,反之亦然。

结构体的声明在头文件oled_basic_font.h 中,里面还有几个内置字体的结构体声明:

    /**
     * @brief 存储字体的元信息,方便使用
     *
     * 字体数组中,单个符号从左到右,按列切分成多个字节,对于宽度6,高度8 的符号,在数组中切分成6 个字节。
     * 宽度8,高度16 的符号占用两页,数组中,从上到下按页拆分成2 组,显示第一页的8 个字节,然后第二页。
     *
     * 所以,如果想访问数组中的第n 个符号,先减去offset:
     * n -= offset;
     *
     * 然后乘以每个符号占用的字节数:
     * pos = n * width * height;
     * 
     * OLED 库如果想省资源,大头都在字库上,所以采用这种直观的存储方式,字库数组和元信息分开,
     * 不需要折腾编码和二进制格式,方便裁剪字库。
     *
     */
    struct FontMeta {
        const uint8_t* font_data;  // 指向字体数组的指针
        uint8_t width;             // 字体占用的列数
        uint8_t height;            // 字体占用的页数,如果字体是6x8,则宽、高分别是6,1
        uint8_t offset;  // 字体数组中第一个元素对应的ascii 码
        // uint8_t glyph_size;  // width * height
    };

举例,oled_basic::ascii_8x16_upper_case_only 的描述是:

    const FontMeta ascii_8x16_upper_case_only = {
        .font_data = reinterpret_cast<const uint8_t*>(_ascii_1608_upper_case_only),  // 指向字模数组的指针
        .width = 8,        // 字体宽度,以列为单位
        .height = 2,       // 字体高度,以页为单位,实际的像素高度为2 * 8 = 16
        .offset = 'A'};    // 字库包含的第一个符号对应的ASCII 码,这个字体被裁剪到只包含26 个大写字母,所以第一个符号就是A

如果要显示一个动画,可以先把动画按帧分解成图片,如果每个图片的尺寸是48x48,就可以把动画做成一个48x48 的字体,如下:

		// 动画字体的描述信息:动画是用gif 转换来的;尺寸48x48;占用的空间巨大。
    const FontMeta gif_48x48_巨 = {
        .font_data = reinterpret_cast<const uint8_t*>(_gif_4848),  // 指向图片数组的指针
        .width = 48,       // 宽度48
        .height = 6,       // 高度48 / 8 = 6
        .offset = 0};      // 动画当然是从第0 帧开始的,不用像ASCII 码字体那样加offset

想显示中文也可以用相似的方式。要注意,动画的总帧数不能超过256。另外还有两个不太方便的问题:

  1. 结构体里没有总帧数字段;
  2. 没有图片数据压缩功能;

如果要做循环动画,没有总帧数字段就表示要硬编码进显示动画的代码里,不过可以修改结构体的声明,尾部追加一个成员进去,不会影响已有的代码。或者自定义一个结构体,继承内置的FontMeta,尾部追加一个成员。图片压缩的话,不确定是否实用,反正显示普通图标和文字时基本没用。

字模数据

字模就是通用的数组格式,内置字模和字体描述都放在oled_basic_font.cpp 这个文件里,以6x8 字体为例:

    const unsigned char _ascii_0806[][6] PROGMEM = {
        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},  // sp
        {0x00, 0x00, 0x00, 0x2f, 0x00, 0x00},  // !
        {0x00, 0x00, 0x07, 0x00, 0x07, 0x00},  // "
        {0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14},  // #
        {0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12},  // $
        {0x00, 0x62, 0x64, 0x08, 0x13, 0x23},  // %
        {0x00, 0x36, 0x49, 0x55, 0x22, 0x50},  // &
        {0x00, 0x00, 0x05, 0x03, 0x00, 0x00},  // '
        {0x00, 0x00, 0x1c, 0x22, 0x41, 0x00},  // (
        {0x00, 0x00, 0x41, 0x22, 0x1c, 0x00},  // )
        {0x00, 0x14, 0x08, 0x3E, 0x08, 0x14},  // *
        {0x00, 0x08, 0x08, 0x3E, 0x08, 0x08},  // +
        // ...
    };

每行是对应一个符号的二维数组,字体宽度是6,对应6 列,也就是6 个字节。6x8 字体每个字占用一页,所以占用的字节数就是6 x 1 = 6。如果是8x16 的字体,宽度8,高度16 / 8 = 2,每个字就是8 x 2 = 16字节。

二维数组是直接用取模软件生成,然后复制粘贴过来的,参考其他取模教程。如果用的工具是经典的PCtoLCD2002,生成字模的选项是这样:

在这里插入图片描述

扩展OLED 型号和通讯方式

TODO:看情况,之后再更新

初始化指令序列

这招是从Arduino 库SSD1306Ascii 那里学来的,那也是个占用比较小的库,缺点是,我不想浪费很多时间读别人的陈年代码[doge]。那个库只支持SSD1306,想扩展SSD1316 的话,得研究一番他的实现;他的字库也是描述和字模混在一起的,还整了一些条件编译和宏,实在不想研究要怎么裁剪。

底层驱动

底层驱动可选功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/908145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL助你面大厂(Join家族介绍)

在学习SQL时候&#xff0c;在多表查询的时候你肯定使用过Join&#xff0c;无非就是把两表联合在一起进行多表查询&#xff0c;但是你是真的了解它们的用法么&#xff1f; Join家族一般有left Join、Rigth Join、Inner Join、Left Semi Join、Left Anti Join、Full Join为主 C…

补充1 MATLAB_GUI_修改普通按钮(PushButton)的参数创建一个长按回调按钮

目录 一、实例效果二、补充的知识点&#xff08;两种回调函数&#xff09;三、步骤  1. 先建一个空白的GUI。  2.在GUI Figure 上添加一个按钮&#xff08;PushButton&#xff09;组件&#xff0c;并设置其属性&#xff0c;例如位置、大小和文本等。  3.CtrS保存一下GUI。…

强推9个研究生必备的免费论文下载网站

一、文献党下载器 文献党下载器把庞大的中外文献数据库资源集成在一个平台&#xff0c;就是把大量的中外数据库资源整合在一个站&#xff08;目前文献资源量名列前茅&#xff09;。不论是中文还是外文文献&#xff0c;不论是哪种文献类型&#xff0c;不论是哪个学科领域该网站…

<kernel>kernel 6.4 USB-之-hub_port_connect()分析

&#xff1c;kernel&#xff1e;kernel 6.4 USB-之-hub_port_connect()分析 kernel 6.4 USB系列文章如下&#xff1a; &#xff1c;kernel&#xff1e;kernel 6.4 USB-之-hub_event()分析 &#xff1c;kernel&#xff1e;kernel 6.4 USB-之-port_event()分析 &#xff1c;kern…

全球机器传感器市场价值约为142亿美元,预计将以超过7.5%的增长率增长

机器传感器是一类用于检测、测量和感知机器环境中物理量、化学量或其他特定参数的设备。这些传感器将实际的物理现象转化为电信号或数字信号&#xff0c;以便计算机或控制系统进行处理、分析和控制。机器传感器在工业、自动化、物联网、机器人、汽车等领域有广泛应用。 根据阿…

部门来了个新同事,听说是00后,上来一顿操作给我看呆了...

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&#x…

OD动态调试exe

之前一直卡&#xff0c;好不容易搞懂一点&#xff0c;记下来记下来 分析exe文件&#xff0c;用ida打开&#xff0c;找到主函数main&#xff0c;分析主函数可以发现&#xff0c;main在判断之后调用了l02等函数 因为判断部分的逻辑还是有点复杂&#xff0c;因此想让他直接打印函…

基于PaddleOCR2.7.0发布WebRest服务测试案例

基于PaddleOCR2.7.0发布WebRest服务测试案例 #WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. #警告&#xff1a;这是一个开发服务器。不要在生产部署中使用它。请改用生产WSGI服务器。 输出结果…

smartsofthelp 5.0 最专业的数据库优化工具,数据库配置优化,数据库高并发优化,SQL 语句优化...

下载地址:百度网盘 请输入提取码 SQL操作返回历史记录&#xff1a; 2023-08-21 20:42:08:220 输入&#xff1a;select version as 版本号 2023-08-21 20:42:08:223 输出&#xff1a;当前数据库实例版本号&#xff1a;Microsoft SQL Server 2012 - 11.0.2100.60 (X64) …

司徒理财:黄金美盘继续看跌,黄金原油最新走势分析及操作建议

黄金走势分析及策略      黄金上周继续回落收跌&#xff0c;周线连续第四周走低&#xff0c;而且还破了周的低点1892&#xff0c;空头力量仍占据优势&#xff0c;暗示后市仍有继续走低的动力和空间&#xff0c;日线方面&#xff0c;黄金一直是承压5日均线下行&#xff0c;是…

@Slf4j报错:Not generating field log: A field with same name already exists

错误出处&#xff1a; 错误原因&#xff1a; 同时使用了Slf4j注解以及LittlecLogger private static final LittlecLogger log LittlecLoggerFactory.getLogger(TimeTrackController.class); 修复方法&#xff1a; 将log改为LOG&#xff0c;便于区分&#xff0c;代码即用到了…

服务运营|斯坦福大学商学院Yue Hu :基于排队论的医院急诊部人员配置策略

作者&#xff1a;李舒湉&#xff0c; 陈盈鑫&#xff0c; XinW 编者按 本次解读的文章是“Prediction-Driven Surge Planning with Application in the Emergency Department”。论文第一作者是斯坦福大学商学院&#xff08;Stanford Graduate School of Business) 的Yue Hu&a…

浅谈煤矿井下电力监控系统的应用研究

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;主要介绍了应用煤矿井下电力监控系统的重要意义&#xff0c;并对煤矿井下电力监控系统的主要原理和基本构成做了简要介绍&#xff0c;并从隔爆监控单元设计、通信协议设计和组态监控系统设计等方面提出了电力监…

小白到运维工程师自学之路 第七十八集 (安装Jenkins)

一、环境概述 随着软件开发需求及复杂度的不断提高&#xff0c;团队开发成员之间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。Jenkins自动化部署可以解决集成、测试、部署等重复性的工作&#xff0c;工具集成的效率明显高于人工操作&#xf…

【考研数学】解决求解微分方程时,常数的结合问题以及是否能直接去掉绝对值的讨论

文章目录 引言例子 1例子 2总结 引言 在微分方程&#xff0c;尤其是一阶线性微分方程的求解过程中&#xff0c;经常出现求 ∫ 1 x d x \int\frac{1}{x}dx ∫x1​dx 等原函数需要加绝对值的不定积分。如果放在不定积分里&#xff0c;肯定毫不犹豫会加上绝对值&#xff0c;也就…

OpenCV 中的色彩空间 (C++ / Python)

在本教程中,我们将了解计算机视觉中使用的流行色彩空间,并将其用于基于颜色的分割。我们还将分享 C++ 和 Python 的演示代码。

在Flutter应用内部实现分屏功能

前言 这一次被要求实现屏幕上同时展示两个页面&#xff0c;并且两个页面的逻辑&#xff0c;功能互不影响&#xff0c;通俗一点讲就是在Flutter内部实现一个类似于分屏的功能&#xff0c;这可难不倒我。 方法 要在 Flutter 中实现一个屏幕的上半部分和下半部分展示不同的页面…

【C++】C 语言 和 C++ 语言中 const 关键字分析 ② ( const 常量分配内存时机 | const 常量在编译阶段分配内存 )

文章目录 一、const 常量内存分配时机二、使用如下代码验证 const 常量内存分配时机三、分析验证结果 - const 常量在编译阶段分配内存 一、const 常量内存分配时机 在上一篇博客中 , 讲到了获取 const 常量的地址 , 代码如下 : // 定义常量// 该常量定义在了 符号表 中// 符号…

通过cpolar在外远程查看家里内网监控

通过cpolar在外远程查看家里内网监控 文章目录 通过cpolar在外远程查看家里内网监控前言1. 在cpolar官网预留一个空白隧道2. 完成空白数据隧道&#xff0c;生成地址3. 设置空白隧道的出口4. 空白数据隧道的出口设置5. 获取公网地址6. 打开本地电脑“远程桌面”7. 打开Windows自…

赶紧看看!这才是对制造业最大的优化

​随着全球商业环境的不断变化&#xff0c;资产管理系统在帮助企业实现精细化管理、提高效率和降低风险方面发挥着关键作用。 在这个数字化时代&#xff0c;资产管理系统不仅是一种管理工具&#xff0c;更是推动企业创新和增长的关键因素之一。通过充分利用这些系统&#xff0c…