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

🐱作者:一只大喵咪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.rhkb.cn/news/149487.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决报错: require is not defined in ES module scope

用node启动mjs文件报错:require is not defined in ES module scope 现象如下: 原因: 文件后缀是mjs, 被识别为es模块,但是node默认是commonjs格式,不支持也不能识别es模块。 解决办法:把文件后缀从.mjs改…

Javascript文件上传

什么是文件上传 文件上传包含两部分, 一部分是选择文件,包含所有相关的界面交互。一部分是网络传输,通过一个网络请求,将文件的数据携带过去,传递到服务器中,剩下的,在服务器中如何存储&#xf…

清除浮动的方法

为什么需要清除浮动? 父级的盒子不能把height定死这样,浮动子类就没有了(行内块元素的特点),父类高度为零。故引用清除浮动 1、父级没有高度 2、子盒子浮动了 3、影响下面的布局了,我们就应该清除浮动了…

string类的使用方式的介绍

目录 前言 1.什么是STL 2. STL的版本 3. STL的六大组件 4.STL的缺陷 5.string 5.1 为什么学习string类? 5.1.1 C语言中的字符串 5.2 标准库中的string类 5.3 string类的常用接口的使用 5.3.1 构造函数 5.3.2 string类对象的容量操作 5.3.3 string类对象…

re学习(38)HGAME2020-re-Level-Week1-maze

题目描述 You won’t figure out anything if you give in to fear. 学习资料: https://ctf-wiki.github.io/ctf-wiki/reverse/maze/maze-zh/ 附加说明:请走最短路线 题解 分析题目 一看题目:maze 可以确定是一个迷宫题 void __fastcall __noreturn…

邮件注册(一)验证码发送

通过邮箱实现注册&#xff0c;用户请求验证码完成注册操作。 导入依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><g…

消息队列技术选型:这 7 种消息场景一定要考虑!

大家好&#xff0c;我是君哥。 我们在做消息队列的技术选型时&#xff0c;往往会结合业务场景进行考虑。今天来聊一聊消息队列可能会用到的 7 种消息场景。 1 普通消息 消息队列最基础的功能就是生产者发送消息、Broker 保存消息&#xff0c;消费者来消费消息&#xff0c;以…

Stm32_标准库_6_八种输入出模式

上拉输入与下拉输入 上拉输入&#xff1a;电平默认为高电平&#xff0c;只有当外部输入为低电平时&#xff0c;此IO口电平才会被拉低&#xff0c;经过触发器&#xff0c;再到寄存器&#xff0c;最后传入CPU GPIO_Mode_IPU&#xff1b;下拉输入&#xff1a;电平默认为低电平&am…

机器学习小知识--面试得一塌糊涂

机器学习中需要归一化的算法有SVM, 逻辑回归&#xff0c;神经网络&#xff0c;KNN, 线性回归&#xff0c;而树形结构的不需要归一化&#xff0c;因为它们不关心变量的值&#xff0c;而是关心变量分布和变量之间的条件概率&#xff0c;如决策树&#xff0c;随机森林&#xff0c;…

ExoPlayer架构详解与源码分析(3)——Timeline

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player 文章目录 系列文章目录前言Timeline单文件或者点播流媒体文件播放列表或者点播流列表有限可播的直播流无限可播的直播流有多个P…

速度轴模拟量控制FB(博途SCL+三菱ST代码)

利用模拟量实现变频器的正反转直接控制具体方法,请参考下面文章链接: 模拟量0-10V信号控制变频器实现正反转速度随动_RXXW_Dor的博客-CSDN博客比例随动专栏有系列文章介绍,大家可以查看相关文章,链接如下:绕线机-排线伺服比例随动功能块(梯形图+SCL代码)_RXXW_Dor的博客…

Python如何实现数据驱动的接口自动化测试

大家在接口测试的过程中&#xff0c;很多时候会用到对CSV的读取操作&#xff0c;本文主要说明Python3对CSV的写入和读取。下面话不多说了&#xff0c;来一起看看详细的介绍吧。 1、需求 某API&#xff0c;GET方法&#xff0c;token,mobile,email三个参数 token为必填项mobil…

比特米盒子刷CoreELEC

CoreELEC就晶辰定制的Kodi版本&#xff0c;比特米盒子在刷入ATV后通过切换卡载系统可以安装CoreELEC即可安装&#xff0c;实现影音播放自由 1、U盘启动CoreELEC 1.1 、安装【安卓】切换卡载系统 通过U盘在已经刷好atv6.0的比特米盒子安装“切换卡载系统”。比特米盒子刷atv6.…

uni-app:js修改元素样式(宽度、外边距)

效果 代码 1、在<view>元素上添加一个ref属性&#xff0c;用于在JavaScript代码中获取对该元素的引用&#xff1a;<view ref"myView" id"mybox"></view> 2、获取元素引用 &#xff1a;const viewElement this.$refs.myView.$el; 3、修改…

【Zookeeper专题】Zookeeper特性与节点数据类型详解

目录 前言前置知识课程内容一、Zookeeper介绍二、Zookeeper快速开始2.1 Zookeeper安装2.2 客户端命令行操作2.3 GUI工具 三、Zookeeper数据结构3.1 ZNode节点分类3.2 ZNode状态信息3.3 监听机制详解3.3.1 永久性Watch 3.4 节点ZNode特性总结3.5 应用场景详解3.5.1 统一命名服务…

广西建筑模板厂家-能强优品木业

广西作为中国西南地区的重要省份&#xff0c;建筑业蓬勃发展&#xff0c;建筑模板作为建筑施工的核心材料之一&#xff0c;在广西也有着广泛的需求。如果您正在寻找广西的建筑模板厂家&#xff0c;广西贵港市能强优品木业有限公司是一家备受认可的供应商。广西贵港市能强优品木…

八、【快速选择工具组】

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

5分钟入门卷积算法

大家好啊&#xff0c;我是董董灿。 深度学习算法中&#xff0c;尤其是计算机视觉&#xff0c;卷积是无论如何都绕不过去的槛。 初学者看到这个算法后&#xff0c;很多是知其然不知其所以然&#xff0c;甚至不知道这个算法是做什么的&#xff0c;或者很疑惑&#xff0c;为什么…

在vue2中,v-model和.sync的区别

最近在封装一个弹窗组件时&#xff0c;用了比较复杂的逻辑去做显示和隐藏的逻辑&#xff0c;在查看同事的代码之后&#xff0c;才知道还有更简单的方法&#xff0c;自己已经忘了一些API. popup组件里统一的template&#xff1a; <div v-ifisShowPopup> // 弹窗内容 <…

oringin的x轴(按x轴规定值)绘制不规律的横坐标

1.双击x轴 2.选择刻度线标签 3.选择刻度