GuiLite C语言实现版本

news2024/12/26 2:53:43

简介

本项目是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 func
    
    self->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.coloradmin.cn/o/1870352.html

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

相关文章

Str.format()方法

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 在Python2.6之后,提供了字符串的format()方法对字符串进行格式化操作。format()功能非常强大,格式也比较复杂&…

深度学习论文撰写实验对比分析时复现其它论文方法的问题

💪 专业从事且热爱图像处理,图像处理专栏更新如下👇: 📝《图像去噪》 📝《超分辨率重建》 📝《语义分割》 📝《风格迁移》 📝《目标检测》 📝《暗光增强》 &a…

BERT论文略读

《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》 (https://arxiv.org/abs/1810.04805) 摘要:前人优秀工作仅用了单向信息且不能很好的应用到各类下游任务,本文提出一种基于Transformer的双…

如何理解AKM?

关于Wi-Fi的加密认证过程,我们前面已经讲解:WLAN数据加密机制_tls加密wifi-CSDN博客 今天我们来理解下AKM,AKM(Authentication and Key Management)在Wi-Fi安全中是指认证和密钥管理协议。它是用于确定Wi-Fi网络中的认…

【Linux】Linux下使用套接字进行网络编程

🔥博客主页: 我要成为C领域大神🎥系列专栏:【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 用于网络应用开…

揭秘数据合并的秘密:一文掌握一对一、多对一、多对多合并技巧与实战!

使用pd.merge()合并 类似 MySQL 中表和表直接的合并merge与concat的区别在于,merge需要依据某一共同的行或列来进行合并使用pd.merge()合并时,会自动根据两者相同column名称的那一列,作为key来进行合并每一列元素的顺序不要求一致1. 一对一合并 df1 = pd.DataFrame({"…

搜维尔科技:SenseGlove Nova2国内首款支持手掌心力回馈手套开售

《SenseGlove Nova 2》现正全球发行中! 搜维尔科技独家代理最新上市的 SenseGlove Nova 2 是世上首款,也是目前市面上唯一一款提供手掌力回馈的无缐VR力回馈手套,它结合了三种最先进的反馈技术,包括主动反馈、强力反馈及震动反馈&#xff0c…

k8s学习笔记(一)

configMap 一般用来存储配置信息 创建configMap 从文件中获取信息创建:kubectl create configmap my-config --from-file/tmp/k8s/user.txt 直接指定信息: kubectl create configmap my-config01 --from-literalkey1config1 --from-literalkey2confi…

小九首度回应与小水分手传闻揭秘

#小九首度回应!与小水分手传闻揭秘#近日,泰国娱乐圈掀起了一股热议的狂潮!传闻中的“金童玉女”组合——“小水”平采娜与“小九”NINE疑似分手的消息,如同巨石投入平静的湖面,激起了千层浪花。而在这股狂潮中&#xf…

CesiumJS【Basic】- #020 加载glb/gltf文件(Primitive方式)

文章目录 加载glb/gltf文件(Primitive方式)1 目标2 代码实现3 资源文件加载glb/gltf文件(Primitive方式) 1 目标 使用Primitive方式加载glb/gltf文件 2 代码实现 import * as Cesium from "cesium";const viewer = new Cesium.Viewer

x264 码率控制 VBV 算法原理:数学模型与数据流转

x264 码率控制 VBV 算法原理 关于 VBV原理的分析可以参考:x264 码率控制 VBV 原理。关于 VBV 算法的源码分析可以参考:x264 码率控制中实现 VBV 算法源码分析。VBV算法介绍 x264中的VBV(Video Buffering Verifier)算法是H.264编码标准的一部分,主要用于码率控制,确保视频…

C语言实战 | “俄罗斯方块”游戏重构

之前的游戏中,为了方便大家掌握框架,在“贪吃蛇”游戏中使用了大量的全局变量。全局变量空间利用率不高,全局变量在程序的执行过程中一直占用存储单元,而不是仅在需要时才开辟单元。另外,全局变量降低了通用性,程序执行时还需要依赖全局变量。例如,显示“食物”和“球”…

计算机的错误计算(十四)

摘要 解读 GPU和CPU计算上的精度差异:GPU 的 3个输出的相对误差分别高达 62.5%、50%、62.5%。 例1. 计算下列两个矩阵的乘积: 显然,其结果为第一列: 这个结果是准确的。 例2. 已知上面 3 个矩阵是由下面代码产生或输出&…

Zynq7000系列FPGA中的定时器详细介绍

每个Cortex-A9处理器都有自己的专用32位定时器和32位看门狗定时器。两个处理器共享一个全局64位定时器。这些定时器总是以CPU频率(CPU_3x2x)的1/2进行计时。 在系统级,有一个24位看门狗定时器和两个16位三重定时器/计数器。 系统看门狗定时器…

免费分享一套SpringBoot+Vue在线水果(销售)商城管理系统【论文+源码+SQL脚本】,帅呆了~~

大家好,我是java1234_小锋老师,看到一个不错的SpringBootVue在线水果(销售)商城管理系统,分享下哈。 项目视频演示 【免费】SpringBootVue在线水果(销售)商城管理系统 Java毕业设计_哔哩哔哩_bilibili【免费】SpringBootVue在线水果(销售)商…

【海思Hi3403V100】多目拼接相机套板硬件规划方案

海思Hi3403V100 是专业超高清智能网络摄像头 SoC。该芯片最高支持四路 sensor 输入,支持最高 4K60fps 的 ISP 图像处理能力,支持 3F 、WDR、多级降噪、六轴防抖、硬件拼接、多光谱融合等多种传统图像增强和处理算法,支持通过AI 算法对输入图像…

CAN-bus总线在冷链运输中的应用

CAN-bus总线在冷链运输中的应用 如图1所示,疫苗冷链是指为保证疫苗从疫苗生产企业到接种单位运转过程中的质量而装备的存储、运输冷藏设施、设备。由于疫苗对温度敏感,从疫苗制造的部门到疫苗使用的现场之间的每一个环节,都可能因温度过高而失效。在储运过程中,一旦温度超…

全面解读OA系统:功能、价值及应用

反复沟通、来回跑腿,还易出错; 纸笔记录、excel统计,效率低耽误事; 档案、物资,查不清记录、看不了实时; 部门各做各的、各管各的,沟通配合难…… 你有没有经历过诸如上述的繁琐办公流程&am…

Transformer教程之神经网络和深度学习基础

在当今的人工智能领域,Transformer已经成为了一个热门的词汇。它不仅在自然语言处理(NLP)领域取得了巨大的成功,还在计算机视觉等其他领域展现出了强大的潜力。然而,要真正理解Transformer,我们首先需要扎实…

用FFmpeg合并音频和视频

使用FFmpeg合并音频和视频是一个相对直接的过程。可以通过以下一些基本的步骤和命令示例完成这个任务: 安装FFmpeg:首先,确保你的系统中已经安装了FFmpeg。你可以从[FFmpeg官网](Download FFmpeg)下载并安装它。 准备素材:确保你…