【智能家居项目】裸机版本——字体子系统 | 显示子系统

🐱作者:一只大喵咪1201
🐱专栏:《智能家居项目》
🔥格言:你只管努力,剩下的交给时间!
图

图
今天实现上图整个项目系统中的字体子系统和显示子系统。

目录

  • 🀄设计思路
  • 🀄字体子系统
    • 🃏管理层
    • 🃏子系统层
    • 🃏字库层
  • 🀄显示子系统
    • 🃏编程
  • 🀄测试

🀄设计思路

在显示设备上显示字体其实也是比较复杂的,显示的字体有点阵字体,矢量字体等方式。

  • 使用点阵绘制文字时:每个文字的大小一样,前后文字互不影响:
    图
    如上图所示,点阵字体中的每个字体的点阵大小都是固定的,也就是需要的像素点个数是固定的,例如8*16就是宽用8个像素点,长用16个像素点,无论是汉字,字母,数字甚至是一个标点符合,都用8*16个像素点。
  • 点阵方式的字体并不连续,字体与字体之间分隔较远,看上去并不是那么美观。
  • 使用Freetype(矢量字体)绘制文字时:大小可能不同,前面文字会影响后面文字:

图
如上图所示,矢量字体中的每个字体的大小是不一样的,根据字体的类型,是汉字还是字母甚至是标点符合,以及前一个字体的位置,会对显示的字体做适当的调整。

  • 矢量字体的排列更加紧凑合理,看起来也更美观,符合我们的生活经验。

描述点阵字体:

图
对于普通的点阵字体,描述该字体需要:

  • X、Y方向大小及原点坐标
  • 每个像素点的值

所以可以用如下结构体来表示一个点阵字体:

struct dot_font
{int iX;	int iY;		//字体坐标int iWidth;//宽度int iHeight;//高度unsigned char* dots;//用于显示该字体的16字节数组(字模)
}

通过坐标以及长度和宽度可以确定字体的轮廓,将dots数组中的数据发送给显存,一个完整的字体就显示在屏幕上了。


描述矢量字体:

图

如上图所示,对于矢量字体,每个字体的大小可能不一样,前一个字体会影响下一个字体,其中有两黑点非常重要:

  • 左边的黑点:当前字符的原点。
  • 右边的黑点:下一个字符的原点。

下一个字符的位置和上一个字符息息相关,还有其他要素,如宽度,高度,绘制的起始点等等组合在一起才可以确定一个字符。

  • 绘制起点和原点不是一个,原点是相当于是整个字符的标点,字符的位置由原点决定。
  • 绘制起点也就是显示开始的位置,一般是字符的左上角。

所以可以用如下结构体来表示一个矢量字体:

typedef struct FontBitMap
{int iLeftUpX;		/* 位图左上角X坐标 */	int iLeftUpY;		/* 位图左上角Y坐标 */int iWidth;			/* 字体宽度 */int iHeight;		/* 字体高度 */int iCurOriginX;	/* 原点X坐标 */int iCurOriginY;	/* 原点Y坐标 */int iNextOriginX;	/* 下一个字符X坐标 */ int iNextOriginY;	/* 下一个字符Y坐标 */ unsigned char* Buffer;/* 字符点阵 */
}FontBitMap, *pFontBitMap;

包含矢量字体的绘制左边(左上角坐标),字体的宽度和高度,当前字体的原点,下一个字体的原点,以及一个字模数组。


现在我们要做的就是抽象出一个结构体,既能描述点阵字体,也能描述矢量字体。

  • 能用来描述矢量字体的结构体必然也能够描述点阵字体。

绘制起始坐标以及宽度和高度和点阵字体中的坐标位置以及x和y方向的长度一样,当前字符原点和下一个字符原点,点阵字体也可以通过计算得到,所以无论是点阵字体还是矢量字体,都可以共用这一个结构体。

图
如上图,整个字体系统并不涉及到内核或者芯片,它是属于软件层面的,所以并不需要分那么多层,都放在一层中即可。

无论是点阵字体还是矢量字体,它们都必须有字库,将字符在字库中对应的数据发送给显示设备才能显示出相应字符,常见的字库有ASCII码字库,GBK字符,以及FreeType字库。

  • 字库也要被描述,也要被管理起来。

🀄字体子系统

🃏管理层

先来实现对字符位图以及字库的管理。

图
如上图头文件中代码所示,结构体FontBitLib是用来描述一个要显示字符的位图的,每一个字符都会创建一个结构体对象,而成员中的Buffer中存放的就是该字符的字母数据,将这些数据发送给显示设备就能显示出对应字符。

结构体FontLib是用来描述字库的,本喵这里只会实现ASCII码字库,其中的成员函数有很多在这里是用不到的,但是为了符合所有字库,这里本喵仍然写了,方便以后的扩展和维护。

还有一个注册字库的函数声明和一个获取字库的函数声明,获取字库的函数有__表明该函数在另一层被调用,这样也是为了避免重复包含的问题。

图
如上图所示源文件代码,创建了一个用来管理字库的全局链表,以及实现了注册字库和获取字库的函数。


🃏子系统层

字库的管理已经实现了,下面该实现一下子系统层调用这些管理函数的字体系统了:

图
如上图所示头文件代码,提供了对字库进行一系列操作的函数声明。

图
如上图源文件代码所示,由于在同一时刻只能使用一个字库,所以创建了一个全局的默认字库变量,还提供了操作字库所用方法的具体实现,在初始化默认字库的时候,需要判断该字库的初始化方法是否为空,为空说明不用初始化。


🃏字库层

此时对字库的管理以及各种操作都已经实现了,但是字库还没有,所以接下来就需要实现ASCII码字库:

图
如上图所示是ASCII字库的头文件,只有一个增加ASCII码字库的函数声明。

图

如上图所示源文件中代码,包含一个ASCII码字库,这是一个全局的二维数组,字库中的数据是通过字模制作软件生成的,在本喵的文章I2C通信协议 | OLED屏中详细讲解过,有兴趣的小伙伴可以移步。

创建了全局的ASCII码字库结构体变量并进行了初始化,本喵这里的ASCII码大小是固定的,就是8*16的,所以就在函数中就直接给了定值,对于获取字符的位图函数本喵单独讲解一下:

tu
仍然是这个图,在显示该字符的时候,是通过原点坐标来确定位置的,也就是图中坐标的黑点,这个黑点的坐标是由用户指定的,所以在函数中该坐标是已知的。

左上角的绘画起始坐标和原点在x方向上相同,在y方向上相差字体的高度,ASCII码字符中就是16,所以可以通过当前原点坐标计算出左上角坐标。

下一个字符的原点坐标,在x方向上和当前字符原点坐标相差字体的宽度,ASCII码字符中就是8,所以也可以通过当前原点坐标计算出下一个字符的原点坐标。

字符的高度和宽度是固定的,也就是8*16的,最重要的字模数组Buffer中的内容就来自前面的字库ascii_font二维数组,如果用户没有向Buffer中存放数据,那么就直接返回字库中对应的字模数据,如果用户存放了,那么就将字库中的字模数据复制到Buffer中。

图
如上图代码就是用来从字库中获取指定字符的位图数据的。


虽然将字体系统分为了三层,但是它们仍然属于系统层,只是在系统层中又细分出来的三层。

图

如上图所示便是这三小层各种的功能和互相之间的调用关系,子系统层只负责使用字库,并不关心字库的维护和管理,管理层则要做到细节处的管理,管理多个字库,但是并不用知道每个字库中的内容,字库层则需要详细实现自己所代表的字库,包括所有字模数据,以及字库结构体中的那些成员方法。

🀄显示子系统

显示子系统和设备子系统中的显示设备并不是一回事。

图
如上图,文字子系统会将从字体子系统中取出的点阵发送给设备子系统中的显示设备绘制点阵从而显示出来。

编码集:

图
如上图所示,字符串“ABC中国”使用不同的编码集在内存中的数据不一样,使用Unicode编码集中的UTF-8(左边内存窗口),每一个汉字占用3个字节,使用GB2312编码集(右边内存窗口),每一个汉字占用2个字节。

  • 无论什么编码集,对ASCII码都是用一个字节表示。

编码格式:

拿常用的Unicode编码集举例,它包含三种编码格式,其中最常用的就是UTF-8编码格式:

Unicode数值范围(16进制)UTF-8编码方式(二级制)
0000 0000-0000 007F0xxxxxxx
0000 0080-0000 07FF110xxxxx 10xxxxxx
0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

第一列表示Unicode编码集支持的编码范围,第二列表示UTF-8编码格式,该编码格式中每个字节中包含有其他信息:

  • 每个字节中,从高位到地位,遇到第一个0之前,有几个1就表示这个字符有几个字节共同表示,如果是10xxxxxx10表示当前字节和前一个字节共同表示一个字符。
  • 去掉表示字节个数的位以后,将剩余的位放在一起,组成的数值就是UTF-8编码值。

1110xxxx 10yyyyyy 10zzzzzz这三个字节表示的字符,这串二进制序列表示该字符用3个字节表示,组合成UTF-8编码值为xxxxyyyy yyzzzzzz两个字节大小。

除了UTF-8编码格式外,还有UTF-16LE,UTF-16BE等编码格式,不同的编码格式,表示同一个字符的数据也会不一样。

点阵:

从默认字库中取出字符的点阵数据,然后再选择显示设备,将字符在LCD或者OLED上显示。

图
如上图所示便是整个显示的过程,先确定编码集为Unicode,再确定编码格式为UTF-8,然后算出编码值,取出对应的点阵数据,最后在显示设备上显示。

🃏编程

显示子系统是在使用字体子系统和设备子系统中的显示设备,所以它并不用分很多层,只工作在显示子系统层(应用层)。

tu
如上图,要想在显示设备上显示字符,有三要素,分别是具体的显示设备,字符的坐标,已经要显示的字符。

图
如上图代码便是整个显示系统中的核心代码,里面有几个被调用的函数本喵会单独拿出来讲解。


调用该函数显示字符串的时候,首先做的第一步就是获得字符的编码值,这里调用了GetCodeForStr函数来获得编码值:

图

如上图代码所示,专门创建一个文件encoding来处理编码值,在该函数中,可以获取指定字符不同编码集下的编码值,这里本喵就只用ASCII编码集。

使用该函数获取的是一个字符的编码值,所以对于ASCII编码集来说,直接返回该字符的ASCII码值作为该字符的编码值即可,因为ASCII编码集默认就是支持的。


得到字符的编码值以后,就要获取该编码值对应的点阵数据。
图
如上图,根据编码值获取点阵数据是从字体子系统中获取,该函数在ascii_font.c中已经实现了,调用默认字库中的GetFontBitMap即可从默认字库(ASCII码字库)中获得点阵数据。


点阵数据有了以后,就需要将点阵数据写入到显示设备在RAM中的显存中去:

图

如上图代码所示,该函数的就是将点阵数据写入到RAM中的显存中的,点阵数据是在FontBitMap对象中的,将该字符的所有像素点数据都获取到并写入到显存中。


本喵这里使用的OLED显示,所以按照OLED显示方式来分析如何获取像素点的位置:

图

如上图所示,OLED显示的字符是8*16的,前一页显示一个字符点阵的前八8字节,后一也显示后8个字节,每个字节中的一个比特位就是一个像素点,所以要获取的是每一个比特位中的值。

tu
如上图代码所示,获取(iX,iY)坐标像素点的像素值时,按照OLED显示方式图,通过横坐标x确定像素点所在字节在Buffer中的地址,然后根据像素点的y坐标确定是该字节的哪个比特位,然后通过按位与和左移操作返回该像素点的值。


图

如上图所示,整个显示子系统的调用关系,首先调用显示子系统中的ShowTextInDisplayDevice函数去显示字符str,应用层只用关心这一个函数怎么调用,不比管它的实现,站在应用层的角度,此时就可以显示出指定字符了。

显示函数又调用显示子系统中编码集层中的GetCodeStr得到该字符的编码值,再用编码值去字体子系统中的默认字库中得到点阵数据,数据放在FontBitMap结构体对象中。

显示子系统再调用DrawBitMapOnFrameBuffer将点阵数据写到设备子系统中显示设备的RAM显存中,在这个函数内部,显示子系统会先调用GetPixelColorFromBitMap函数获取像素点的颜色数据,然后再调用显示设备自带的SetPixel方法将颜色数据写到RAM显存中。

最后会调用Flush函数将RAM显存中的数据发送到显示设备自带的显存中,至此整个显示流程就结束了。

  • 在获取编码值的时候,根据不同的编码集和编码方式获得编码值,这里可以进行扩展维护。
  • 在获取像素点颜色数据的时候,显示设备的显示规则不同,获取编码值的方式也就不同,这里也可以扩展维护。

🀄测试

最后就是测试显示子系统,设备子系统和字体子系统三个系统能否成功配合在OLED上显示字符了:

图

如上图代码所示,在测试函数中,先将字体系统中的字库初始化完毕,包括添加字库设置默认字库等等,然后再初始化显示设备,包括指定显示设备,初始化等等步骤。

  • 显示字符只用调用一个函数ShowTextInDidsplayDevice(ptDev,16,16,str)即可在作为为(16,16)处显示指定字符str

图
可以看到,成功显示字符,源代码中的字符串太长,涉及到了换行,所以本喵在仅显示了A Big MiaoMi字符串,没有实现换行。

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

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

相关文章

【网络安全-信息收集】网络安全之信息收集和信息收集工具讲解

一,域名信息收集 1-1 域名信息查询 可以用一些在线网站进行收集,比如站长之家 域名Whois查询 - 站长之家站长之家-站长工具提供whois查询工具,汉化版的域名whois查询工具。https://whois.chinaz.com/ 可以查看一下有没有有用的信息&#xf…

【熬夜爆肝版】JAVA基础入门专栏——1.JAVA开发入门

JAVA开发入门 1、Java概述1)起源2)特点3)应用领域 2、JDK1)定义2)作用3)组成4)JDK版本与兼容性5)JDK的安装与配置6)JDK的发行版 3、系统环境变量1)定义2&…

回归预测 | MATLAB实现PSO-SVR粒子群优化支持向量机回归多输入单输出预测

回归预测 | MATLAB实现PSO-SVR粒子群优化支持向量机回归多输入单输出预测 目录 回归预测 | MATLAB实现PSO-SVR粒子群优化支持向量机回归多输入单输出预测预测效果基本介绍模型描述程序设计预测效果 <

Chrome(谷歌浏览器)如何关闭搜索栏历史记录

目录 问题描述解决方法插件解决&#xff08;亲测有效&#xff09;自带设置解决步骤首先打开 地址 输入&#xff1a;chrome://flags关闭浏览器&#xff0c;重新打开Chrome 发现 已经正常 问题描述 Chrome是大家熟知的浏览器&#xff0c;但是搜索栏的历史记录如何自己一条条的删…

时序预测 | MATLAB实现EMD-iCHOA+GRU基于经验模态分解-改进黑猩猩算法优化门控循环单元的时间序列预测

时序预测 | MATLAB实现EMD-iCHOAGRU基于经验模态分解-改进黑猩猩算法优化门控循环单元的时间序列预测 目录 时序预测 | MATLAB实现EMD-iCHOAGRU基于经验模态分解-改进黑猩猩算法优化门控循环单元的时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 EMD-iCHOAGR…

LVGL_基础控件滚轮roller

LVGL_基础控件滚轮roller 1、创建滚轮roller控件 /* 创建一个 lv_roller 部件(对象) */ lv_obj_t * roller lv_roller_create(lv_scr_act()); // 创建一个 lv_roller 部件(对象),他的父对象是活动屏幕对象// 将部件(对象)添加到组&#xff0c;如果设置了默认组&#xff0c…

MFC文本输出学习

void CTxttstView::OnDraw(CDC* pDC) {CTxttstDoc* pDoc GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for native data hereCString str1;pDC->SetBkColor(RGB(0,0,0));pDC->TextOut(50, 50, "一段文字");pDC->SetBkColor(RGB(255,255,255))…

Linux Vi编辑器基础操作指南

Linux Vi编辑器基础操作指南 Linux中的Vi是一个强大的文本编辑器&#xff0c;虽然它有一些陡峭的学习曲线&#xff0c;但一旦掌握了基本操作&#xff0c;它就变得非常高效。以下是Vi编辑器的一些基本用法&#xff1a; 打开Vi编辑器&#xff1a; vi 文件名退出Vi编辑器&#xff…

kafka初体验基础认知部署

kafka 基础介绍 Apache Kafka是一个分布式流处理平台&#xff0c;最初由LinkedIn开发并于2011年开源。它主要用于解决大规模数据的实时流式处理和数据管道问题。 Kafka是一个分布式的发布-订阅消息系统&#xff0c;可以快速地处理高吞吐量的数据流&#xff0c;并将数据实时地分…

【状态估计】将变压器和LSTM与卡尔曼滤波器结合到EM算法中进行状态估计(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

数据挖掘(3)特征化

从数据分析角度&#xff0c;DM分为两类&#xff0c;描述式数据挖掘&#xff0c;预测式数据挖掘。描述式数据挖掘是以简介概要的方式描述数据&#xff0c;并提供数据的一般性质。预测式数据挖掘分析数据建立模型并试图预测新数据集的行为。 DM的分类&#xff1a; 描述式DM&#…

剑指offer——JZ26 树的子结构 解题思路与具体代码【C++】

一、题目描述与要求 树的子结构_牛客题霸_牛客网 (nowcoder.com) 题目描述 输入两棵二叉树A&#xff0c;B&#xff0c;判断B是不是A的子结构。&#xff08;我们约定空树不是任意一个树的子结构&#xff09; 假如给定A为{8,8,7,9,2,#,#,#,#,4,7}&#xff0c;B为{8,9,2}&…

第一章 visual studio下载安装

一、官网下载 地址&#xff1a;https://visualstudio.microsoft.com/zh-hans/ 点击免费visual studio 二、安装 运行下载好的exe文件&#xff0c;自定义安装目录 三、选择需要的组件安装 只需要选择标记组件&#xff0c;然后点击安装 等待安装完成就行 四、重启电脑 安装完之后…

【PyCharm】SSH连接远程服务器,代码能跑但导入的库被标红的解决方案

文章目录 一、问题描述二、解决方案一三、解决方案二 一、问题描述 在 PyCharm 中修改SSH连接的远程服务器的 Python 解释器后&#xff0c;导入的第三方库会被标红&#xff0c;如图1所示&#xff1a; 图1 但此时程序仍然可以正常执行&#xff1a; 图2 二、解决方案一 在 Py…

第三章、运输层

文章目录 3.1 概述和运输层服务3.1.1 运输层和网络层的关系3.1.2 因特网运输层概述 3.2 多路复用与多路分解3.3 无连接运输&#xff1a;UDP3.4 可靠数据传输原理3.4.1构造可靠数据传输协议rdt1.0rdt2.xrdt3.0 3.4.2 流水线可靠数据传输协议3.4.3 回退N步3.4.4选择重传 3.5 面向…

4.MySql安装配置(更新版)

MySql安装配置 无论计算机是否有安装其他mysql&#xff0c;都不要卸载。 只要确定大版本是8即可&#xff0c;8.0.33 8.0.34 差别不大即可。 MySql下载安装适合电脑配置属性有关&#xff0c;一次性安装成功当然是非常好的&#xff0c;因为卸载步骤是非常麻烦的 如果第一次安装…

面试高频手撕算法 - 01背包系列

1. 前言 为什么要专门去搞一下这个背包问题呢 ? 因为作者已经在两场面试中吃了这个亏, 尤其是在面深信服的测开岗的时候, 一面的难度适中, 加上面试官也没为难我, 侥幸让我过了. (以下是一面问题) 二面的时候, 主要问了项目和手撕算法. 当时项目个人觉得面的还不错, 因为本人是…

基于SpringBoot的电影评论网站

目录 前言 一、技术栈 二、系统功能介绍 电影信息管理 电影评论回复 电影信息 用户注册 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了电影评…

八、【快速选择工具组】

文章目录 对象选择工具快速选择工具魔棒工具 对象选择工具 当我们选择对象选择工具时&#xff0c;需要先注意上边有一个循环的圆&#xff0c;它会进行内容识别&#xff0c;当识别完成会停止旋转。这个时候我们按住n键&#xff0c;或者将鼠标放上对应的图形时会出现选中的颜色。…