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…

c++实现web服务器数据收发

利用微软标准API实现web服务器数据的发送和接受,遇到的问题点: 1.句柄创建 CString strMsg; int iError 0; HINTERNET hint; HINTERNET hftp; HINTERNET hconnect; HINTERNET Openhconnect; hint InternetOpen(0, INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY/INTERNET_O…

7、广告-用户识别与ID映射

一、用户识别原理 程序化广告生态系统是以数据为核心的生态系统,要实现精准的受众定向,首先需要进行单用户的识别。在PC端,常用Cookie作为用户标识,为用户打上标签的技术被称为“种Cookie”。 Cookie的作用与局限性 生命周期短&a…

ms17-010 ms12-020 ms-08-067

MS17-010是一个由微软发布的安全公告编号,它指代了一个严重级别的安全漏洞,该漏洞存在于Microsoft Windows的Server Message Block 1.0 (SMBv1)协议处理中。这个漏洞被命名为“永恒之蓝”(EternalBlue),因为它最初是由…

安装samba服务

说明: 1、根据业务场景需要,要求将linux生成的图片文件,共享到windows服务器。 2、研发从共享文件夹中读取图片并且在应用web页面展示。 3、故要求安装搭建samba服务器,然后将共享文件夹挂载到windows服务器指定路径。 一、安装samba服务 1、安装samba服务 说明:请在linu…

java web中解决浏览器下载后文件中文乱码问题

解决Java Web应用中浏览器下载文件时中文乱码的问题,通常需要在HTTP响应头中正确设置Content-Disposition字段,以指示浏览器如何处理文件名中的非ASCII字符。 以下是一个通用的方法,适用于包括IE、Chrome、Firefox、Safari在内的多种浏览器&…

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

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

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

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

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

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

11-NumPy遍历数组

NumPy遍历数组 NumPy 提供了一个 nditer 迭代器对象,它可以配合 for 循环完成对数组元素的遍历。 下面看一组示例,使用 arange() 函数创建一个 3*4 数组,并使用 nditer 生成迭代器对象。 示例1: import numpy as np a np.ara…

Java列表转树形结构的工具

不废话,直接上代码 一、工具函数 可以直接使用list2tree()实现列表转树形结构 package com.server.utils.tree;import org.springframework.beans.BeanUtils;import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Bi…

从源码到上线:直播带货系统与短视频商城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)?

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

深入解读 ThreadLocal 源码及其在 ThreadLocalContext 中的使用

深入解读 ThreadLocal 源码及其在 ThreadLocalContext 中的使用 ThreadLocal 是 Java 中用于提供线程局部变量的一种机制,通过为每个线程提供独立的变量副本,保证了线程之间的数据隔离性。本文将深入解读 ThreadLocal 的源码,并展示其在 Thr…

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

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