GuiLite C语言实现版本

简介

本项目是idea4good/GuiLite的C语言实现版本,基于2024-06-20节点的版本(提交ID:e9c4b57)。
本项目仓库:GuiLite_C

需求说明

作为芯片从业人员,国产芯片普遍资源有限(ROM和RAM比较少-都是成本,CPU速度比较高-100MHz),需要在512KB ROM,20KB左右RAM资源上实现手环之类的GUI操作(要有触摸),CPU可以跑96MHz。

第一次搞嵌入式GUI,问了一圈朋友,LVGL直接放弃(太绚丽了,个人觉得也不可能跑得动,而且代码应该也比较复杂,魔改会比较困难),有人建议手撸,那要死人了。最终有朋友推荐了idea4good/GuiLite,看了下介绍,GUI简单直接,所需的ROM和RAM也比较少,效果图里面也有很多所需的场景,持续有更新, Apache-2.0 license,比较符合我的需求。

尝试放到芯片上跑,受限于芯片资源和使用场景(基本没Heap,开发环境基本都是C)。由于要支持C++环境,带进来一堆系统库,作为搞嵌入式裸机环境的程序员,完全无法接受各种调用系统库操作,此外加上C++环境后,code size一下子也膨胀了很多。

关键是调试麻烦!!!调试麻烦!!!调试麻烦!!!看反汇编的时候太痛苦了(因为我是C语言小白)。

没什么说的,一共也就几千行代码,手撸成C语言(没现成的,问下来有人干了这个事情,但是没开源)。

所需资源分析

资源分GUI代码和控件所需的资源以及Framebuffer。Framebuffer是固定的,各个GUI有专门的优化处理。

GUI代码和控件所需资源

对于嵌入式环境而言,code size和ram size至关重要。所以以典型的cm0嵌入式开发环境为例,对code size和ram size进行分析。编译出来的大小见下表。

可以看到不同的例程所需资源差异巨大,这个涉及到GUI用到了哪些控件,字库,图片等。

注意:由于不同lib库对于printf、malloc等接口影响较大,库这些接口都不实现。资源紧张的场景可以按需简易实现。

注意:在porting\cm0\GCC路径下运行porting\cm0\GCC\utils_analysis_elf_size.py脚本,可以打印下面的表格保存在porting\cm0\GCC\output\README.md

appCode(Bytes)RAM(Bytes)
Hello3D193001124
Hello3Ddonut230965824
Hello3Dwave228485240
HelloAnimation1939652680
HelloCircle1886029636
HelloExSimple16332644
HelloFont1646292584
HelloKeypad115961068
HelloLayers14440328
HelloMario387324392
HelloMolecule97641736
HelloNets1692426128
HelloParticle119242304
HelloPendulum31144368
HelloScroll12100762160
HelloSlide39418641512
HelloStar73643848
HelloTimer87820712
HelloTransparent7115963128
HelloWave139247652
HelloWidgets272366696
HelloWindows24617441084

HelloExSimple为例,实现1个button+1个label只需要16332的code size和644的ram size字节,这里面字体占用的code size接近1半。

image-20240627205351143

Framebuffer

其实GuiLite所需的Code Size和Ram Size都比较小,但是其对Framebuffer需要很大。单个页面,不支持动效之类的场景还好,如果需要支持Scroll、Dialog、Slice等功能,就需要很多Framebuffer了。这块优化并不好,如果没有复杂动效的话,不需要提供Framebuffer,所需的资源就上面列表的值。

不然就是要n个Framebuffer了,一个240*320的RGB565的屏幕,需要153,600Bytes=150KBytes,这很可怕了,更别说部分场景还需要乘以n。

代码架构

没什么东西,也就是源代码,例程和编译配置。

  • example:各种GUI例程,基本是照搬GuiLite的来。
  • porting:程序的主入口,根据平台不同,有一些不同实现。
  • src:GuiLite代码实现部分,结构参考GuiLite处理。
  • build.mk和Makefile:Makefile文件。
GuiLite_C├── build.mk├── Makefile├── example│   ...├── porting│   ...└── src...

使用说明

环境搭建

目前暂时只支持Windows编译,最终生成exe,可以直接在PC上跑。

目前需要安装如下环境:

  • GCC环境,笔者用的msys64+mingw,用于编译生成exe,参考这个文章安装即可。Win7下msys64安装mingw工具链 - Milton - 博客园 (cnblogs.com)。

编译说明

本项目都是由makefile组织编译的,编译整个项目只需要执行make all即可,调用make run可以运行。

根据具体需要可以调整一些参数,目前Makefile支持如下参数配置。

  • APP:选择example中的例程,默认选择为Hello3D
  • PORT:选择porting中的环境,也就是当前平台,默认选择为windows,cm0只用于评估code size和ram size需要专门编译。

也就是可以通过如下指令来编译工程:

make all APP=Hello3D

执行make run后,在windows环境就会弹出一个窗口,演示GUI效果了。

image-20240627173328170

改动说明

编译运行

为了方便维护,将idea4good/GuiLite和idea4good/GuiLiteSamples两个合在一起。

这样修改源码和Sample可以同时进行。

windows环境也不再使用Visual Studio编译,直接调用系统API进行窗口绘制。

代码结构

保留原本的代码结构不变,不过将源码和例程合并在一起。

原本只有一个.h文件,变成一个.h和一个.c

GuiLite.h原本是所有代码都放在这里,虽然代码看起来清爽了,但是可维护性和可阅读性比较差。这里只是将所有控件都放进来,应用层只需要引用这个头文件即可。

各个平台实现目前也不做过多考虑了,全部命令行操作。只关心code size和基本使用。目前只实现了windows和cm0的版本。

代码组织方式用Makefile,简单直接。

C++改动说明

还好作者用的C++特性较少,改起来比较轻松,下面对改动进行说明。

所有类用struct来实现,为方便使用,都用typedef声明下。

// C++ class impl
class AAA
{
}// C struct impl
typedef struct AAA AAA;
struct AAA
{}

类-成员

成员有结构体成员来实现,有一些类里面static处理,通过外部定义变量来处理,写代码的人自己控制操作空间。

// C++ class impl
class AAA
{int m_aaa;static int m_bbb;
}// C struct impl
typedef struct AAA AAA;
struct AAA
{int m_aaa;
}static int AAA_m_bbb;

类-构造函数、方法

public、protect和private就不区分了,软件自己控制操作空间。直接在方法名前面加入class_来区分。第一个传参调整为类对象的指针,名称为self。

对于构造函数(析构也一样,不过本项目没有),定义函数class_init来实现,因为编译器不会帮你调用,所以需要自己手动调用

// C++ class impl
class AAA
{AAA(aaa) : m_aaa(aaa) {}
public:void func_1(void){}protect:void func_2(void){}private:void func_3(void){}int m_aaa;
}// C struct impl
typedef struct AAA AAA;
struct AAA
{
}
void AAA_func_1(AAA *self)
{}void AAA_func_2(AAA *self)
{}void AAA_func_3(AAA *self)
{}
void AAA_init(AAA *self, int aaa)
{self->m_aaa = aaa;
}

类-同名函数(重载)

函数有多个名称一样的函数,传参不同,或者部分参数有默认值。对于这种情况最全的参数列表为默认名称。最简单为func_simple,有其他参数,使用func_with_xxx

对于构造函数(析构也一样,不过本项目没有),定义函数class_init来实现,因为编译器不会帮你调用,所以需要自己手动调用

// C++ class impl
class AAA
{void func_1(int a=10){}
}// C struct impl
typedef struct AAA AAA;
struct AAA
{
}
void AAA_func_1(AAA *self, int a)
{}void AAA_func_1_simple(AAA *self)
{AAA_func_1(self, 10);
}

类-默认传参

最全的参数列表为默认名称。

最简单为func_simple

有其他参数,使用func_with_xxx

类-虚函数

这里最麻烦的就是虚函数了处理了,因为涉及到类继承,函数覆盖等处理。简单的处理就是一个虚函数一个函数指针,但是这样当类里面的虚函数比较多时,所以RAM就很多了。

所以这里用一个麻烦的处理,用函数列表来做,所有集成类的构造函数(也就是class_init)需要重新赋值虚函数表。

为区分,虚函数的函数命令需要在函数前面加入class_vir_。还要声明一个结构体为struct class_vir_api来定义虚函数表,同时类的成员加入const class_vir_api *m_api来存储函数列表指针。

// C++ class impl
class AAA
{AAA(aaa) : m_aaa(aaa) {}virtual void func_virtual_1(void){}int m_aaa;
}// C virtual api impl
typedef struct AAA_vir_api AAA_vir_api;
struct AAA_vir_api
{void (*func_virtual_1)(AAA *self);
}// C struct impl
typedef struct AAA AAA;
struct AAA
{const AAA_vir_api* m_api; // virtual api
}
void AAA_vir_func_virtual_1(AAA *self)
{}static const AAA_vir_api AAA_vir_api_table = {AAA_vir_func_virtual_1,
};void AAA_init(AAA *self, int aaa)
{self->m_aaa = aaa;self->m_api = &AAA_vir_api_table; // set virtual api.
}

类-继承

暂时只考虑只继承一个父类,不考虑继承多个父类的处理。

子类需要定义第一个成员为父类base,构造函数需要先调用父类的构造函数,有虚函数重写的,需要重新定义虚函数表,并覆盖。

所有涉及虚函数,使用基类作为函数self传参。虚函数实现的api接口,传参为基类的api,需要转一下BBB *b= (BBB*)self;

// C++ class impl
class AAA
{virtual void func_virtual_1(void){}
}class BBB : public AAA
{virtual void func_virtual_1(void){}
}// C virtual api impl
typedef struct AAA_vir_api AAA_vir_api;
struct AAA_vir_api
{void (*func_virtual_1)(AAA *self);
}// C struct impl
typedef struct AAA AAA;
struct AAA
{const AAA_vir_api* m_api; // virtual api
}
void AAA_vir_func_virtual_1(AAA *self)
{}static const AAA_vir_api AAA_vir_api_table = {AAA_vir_func_virtual_1,
};void AAA_init(AAA *self)
{self->m_api = &AAA_vir_api_table; // set virtual api.
}// C struct impl
typedef struct BBB BBB;
struct BBB
{AAA base; // base class
}
void BBB_vir_func_virtual_1(AAA *self)
{BBB *bbb = (BBB *)self;
}static const AAA_vir_api BBB_vir_api_table = {BBB_vir_func_virtual_1,
};void BBB_init(BBB *self)
{AAA_init(&self->base); // call base init funcself->base.m_api = &BBB_vir_api_table; // set virtual api.
}

下一步计划

Framebuffer全面移除

对于嵌入式GUI而言,GUI自身所需的Code Size其实完全比不上字体、图片等资源所需的空间(虽然这些可以放在外部存储中)。Ram其实是很关键的,而大头更多是Framebuffer。

目前Surface的设计,多个Surface就需要多个Framebuffer的设计,对于嵌入式而言还是太不友好了。

后续要么只保留一个Framebuffer,要么全部移除。

资源外部加载接口实现

目前想JPG和MP4这些,作者希望大家自己写控件实现,后续考虑提供专门的接口函数,方便MCU使用。当然现在这样也完全没问题,只是跨平台写代码麻烦点。

注释完善

用AI加一些注释,不然对于新人太不友好了。

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

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

相关文章

[Vulnhub] wallabysnightmare LFI+RCE+Irssi聊天服务RCE

信息收集 Server IP AddressOpening Ports192.168.8.105TCP:22,80,6667,60080 $ nmap -p- -sC -sV 192.168.8.105 --min-rate 1000 -Pn 基础Shell http://192.168.8.105/?page../../../../../etc/shadow 当再次尝试访问已经关闭 $ nmap -p- -sC -sV 192.168.8.105 --min-rat…

【PTA】7-1 网红点打卡攻略(C/C++)代码实现 反思

解题细节分析: 0.比较图的两种存储方法,通过邻接矩阵存储更便于查找给定两点之间的关系 1.注意理解清楚题义:“访问所有网红点”中所有不是指攻略中所有,而是存在的全部的网红点 代码见下:// 需要注明的是&#xff…

锦江丽笙酒店稳步拓局海内外酒店市场 签约及意向合作20个新项目

(中国上海,2024年6月27日)民族品牌的国际化发展已日趋成为推动经济和文化交流的重要力量。作为民族品牌与国际品牌的融合发展,锦江丽笙酒店顺应市场趋势有序推进旗下品牌矩阵的全面布局;2024年上半年,已达成…

简易深度学习(1)深入分析神经元及多层感知机

一、神经元 单个神经元结构其实可以认为是一个线性回归模型。例如下图中 该神经元输入为三个特征(x1,x2,x3),为了方便理解,大家可以认为每条线上都有一个权重和特征对应(w1,w2&…

从源码到上线:直播带货系统与短视频商城APP开发全流程

很多人问小编,一个完整的直播带货系统和短视频商城APP是如何从源码开发到最终上线的呢?今天,笔者将详细介绍这一全过程。 一、需求分析与规划 1.市场调研与需求分析:首先需要进行市场调研,了解当前市场的需求和竞争情…

入职必备-Git 2种方式拉取代码

【SSH方式】: 1.复制电子邮箱 2.git bash 打开窗口 ssh-keygen -t rsa -C liuchangprimecare.group 3.一路回车,然后查看C:\Users\LiuChang.ssh里面的文件 打开id_rsa.pub文件,复制代码 4.添加到GitLab的公钥输入框 5.然后 git clone gitgitlab.pr…

使用容器配置文件构建任意应用镜像_并将应用镜像推送到公共仓库共享_应用分享与启动---分布式云原生部署架构搭建012

上面我们编写好了应用,并且,安装好了redis 现在我们把应用打包成镜像. 以前是这样做的,不方便,因为需要在服务器上,安装jdk什么的,现在有了 镜像就不用,给服务器安装镜像什么的了 以后所有机器都安装docker以后,就直接运行就可以了 首先看一下,安装java应用,需要 用到openjd…

指纹浏览器是什么?跨境多账号安全如何保证?

随着电子商务的蓬勃发展,越来越多的商家选择开设多店来扩大经营规模。然而多店运营也带来了一系列的挑战,其中之一就是账号安全。 1. 了解反检测浏览器和代理服务器 在我们开始讨论如何有效地使用反检测浏览器之前,我们首先需要了解这两个工…

openlayer 我的标注功能

背景: 通过openlayer库,可以在地图上实现绘制点、线、面。 并把绘制的结果添加到我的标注的弹框。 我的标注功能,包括:我的标注查询结果的数据展示;添加分组;添加我的标注;编辑分组、删除分组&a…

经典神经网络(13)GPT-1、GPT-2原理及nanoGPT源码分析(GPT-2)

经典神经网络(13)GPT-1、GPT-2原理及nanoGPT源码分析(GPT-2) 2022 年 11 月,ChatGPT 成功面世,成为历史上用户增长最快的消费者应用。与 Google、FaceBook等公司不同,OpenAI 从初代模型 GPT-1 开始,始终贯彻只有解码器&#xff0…

【机器学习300问】134、什么是主成分分析(PCA)?

假设你的房间堆满了各种各样的物品,书籍、衣服、玩具等等,它们杂乱无章地散落各处。现在,你想要清理房间,但又不想扔掉任何东西,只是希望让房间看起来更整洁,更容易管理。 你开始思考,能否将物品…

以数治税时代来临,企业如何应对?

全电发票是数字经济时代发票的新形态,顺应了数字经济潮流。现如今,国家正全力推动行业数字化进程,预计,2025年将基本实现发票全领域、全环节、全要素电子化,实现税务执法、服务、监管与大数据智能化应用深度融合、高效…

Spring事务的源码底层实现

文章目录 事务理论执行过程EnableTransactionManagement底层实现 事务 在线流程图 理论执行过程 通过事务管理器创建一个连接对象connection1设置事务隔离级别、是否只读等conn1.autocommit(false)将conn1存入ThreadLocal中Map<DataSource,Connection>执行目标方法、多…

Session会话与请求域的区别

session会话和请求域&#xff08;也称为request域&#xff09;都是用于存储和管理用户特定信息的重要概念&#xff0c;但它们在作用范围和生命周期上有显著的不同。 请求域 (Request Domain) 作用范围&#xff1a;请求域是面向单次请求的。每次HTTP请求都会创建一个新的request…

Java中的程序异常处理介绍

一、异常处理机制 Java提供了更加优秀的解决办法&#xff1a;异常处理机制。 异常处理机制能让程序在异常发生时&#xff0c;按照代码的预先设定的异常处理逻辑&#xff0c;针对性地处理异常&#xff0c;让程序尽最大可能恢复正常并继续执行&#xff0c;且保持代码的清晰。 Ja…

算法刷题日志 —— 数组和位运算

文章目录 [461. 汉明距离](https://leetcode.cn/problems/hamming-distance/submissions/542447020/)[448. 找到所有数组中消失的数字](https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array/submissions/)[136. 只出现一次的数字](https://leetcode.cn/pro…

最长回文串

描述&#xff1a; 最长回文串 思路&#xff1a; 统计每个字母出现次数&#xff0c;如果是偶数&#xff0c;ret x;如果是存在奇数的话&#xff0c;就可以放在中间&#xff0c;ret 1. 代码&#xff1a; class Solution { public:int hash[200];int longestPalindrome(str…

AI智能修复视频,垃圾画质也变高清 HD——牛小影

很多时候&#xff0c;从网上下载的视频或监控视频都是模糊的。有什么方法或者软件可以让哪些模糊的视频恢复清晰吗&#xff1f;今天就给大家推荐一个可以使模糊的视频变清晰的软件。 我们都知道用PS或者一些修复工具可以修复模糊的图片&#xff0c;但是很多人不知道的是视频也可…

构建LangChain应用程序的示例代码:46、使用 Meta-Prompt 构建自我改进代理的 LangChain 实现

Meta-Prompt 实现 摘要&#xff1a; 本文介绍了 Noah Goodman 提出的 Meta-Prompt 方法的 LangChain 实现&#xff0c;该方法用于构建能够自我反思和改进的智能代理。 核心思想&#xff1a; Meta-Prompt 的核心思想是促使代理反思自己的性能&#xff0c;并修改自己的指令。…

上班族要怎么挑选智能猫砂盆?今年最受欢迎的牌子都在这里了!

对于上班族来说&#xff0c;猫砂盆里的猫屎到底该如何是好&#xff0c;放到下班回来再铲&#xff0c;猫砂的臭味早就飘满屋子&#xff0c;想立刻铲掉吧&#xff0c;班不要上啦&#xff1f;可是不铲就会生细菌&#xff0c;谁也不想花个几千块去给猫咪看病吧&#xff0c;谁不希望…