2023年3月

本文档参考了Git提交规范,旨在规范使用SVN进行代码版本管理时的提交操作。

提交前的准备

1. 检查代码

在提交代码前,请先进行必要的代码检查,确保代码的正确性、可读性和可维护性。可以使用代码质量管理工具进行自动化检查,也可以手动检查。

2. 编写提交信息

在提交代码时,需要编写提交信息来描述本次提交的内容。提交信息应包括以下内容:

  • 本次提交的目的或原因
  • 本次提交的变更内容
  • 其他有关本次提交的说明信息

提交信息应遵循以下格式:

<type>(<scope>): <subject>

<body>

<footer>

其中,
<type>
代表本次提交的类型,包括以下几种:

  • feat:新功能(feature)
  • fix:修补 Bug
  • docs:文档(documentation)
  • style:格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)
  • test:增加测试
  • chore:构建过程或辅助工具的变动

<scope>
代表本次提交的影响范围,可以省略。

<subject>
代表本次提交的简要说明。

<body>
代表本次提交的详细说明,可以省略。

<footer>
代表本次提交的备注信息,可以省略。

例如:

feat: 添加登录功能

为网站添加了登录功能,并引入了 OAuth2.0 认证。

Closes #123

提交操作的规范

1. 提交频率

请根据实际情况适当控制提交的频率。过于频繁的提交会增加版本控制系统的负担,也会给其他开发人员带来不必要的干扰。

2. 提交文件

请只提交与本次提交相关的文件,不要提交无关文件或者未经修改的文件。在提交前,请进行必要的代码差异比较,确保只提交了必要的文件和修改。

3. 提交信息

请按照前面所述的规范编写提交信息,确保信息的完整、准确和易于理解。

4. 提交顺序

请按照提交的逻辑顺序进行提交操作。如果本次提交需要依赖于其他提交,请确保其他提交已经完成并通过测试。

饥汉模式

package com.cz.single;

/**
 * @author 卓亦苇
 * @version 1.0
 * 2023/3/11 21:31
 */
public class Hungry {

    private byte[] data1 = new byte[1024];
    private byte[] data2 = new byte[1024];
    private byte[] data3 = new byte[1024];
    private byte[] data4 = new byte[1024];
    private Hungry(){

    }

    private final static Hungry hungry = new Hungry();
    public static Hungry getInstance(){
        return hungry;
    }

    public static void main(String[] args) {
     .   Hungry.getInstance();
    }
}

会浪费内存,执行代码,其4个对象已经被创建,浪费空间。

懒汉式单例

package com.cz.single;

/**
 * @author 卓亦苇
 * @version 1.0
 * 2023/3/11 21:35
 */
public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    private volatile static LazyMan lazyMan;

    public static LazyMan getLazyMan(){
        if ((lazyMan==null)){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getLazyMan();
            }).start();
        }
    }
}

懒汉式为使用该对象才创建新对象,但是初始代码有问题,单线程初始没有问题,多线程会造成,非单例。

解决办法,首先加锁,先判断对象是否为空,如果为空则将class对象进行上锁,然后需再判断,锁是否为空,如果为空再创建新对象。

同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码。第二层,是因为使用同步代码块才加上的,有的可能过了第一个if,没到同步代码块

为双层检测的懒汉式单例,也称
DCL懒汉式

第二个问题

lazyMan = new LazyMan();

代码为非原子性操作

创建新对象的底层操作分为3步

1.分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
但如果不是原子操作,那132的状况式可能发现的,如果在A还没完成构造是,线程B进来,则不会执行if语句,发生错误

让lazyMan加上volatile参数

反射会破坏单例模式

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                LazyMan.getLazyMan();
//            }).start();
//        }
        LazyMan lazyMan1 = LazyMan.getLazyMan();
        //获得空参构造器
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //反射的setAccessible(true)参数,无视私有构造器
        declaredConstructor.setAccessible(true);
        //通过反射建造对象
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }

image-20230314155639972

解决办法,在无参构造器添加异常

private LazyMan(){
    synchronized (LazyMan.class){
        if (lazyMan!=null){
            throw new RuntimeException("不要试图反射破坏");
        }
    }
    System.out.println(Thread.currentThread().getName()+"  OK");
}

image-20230314160149602

但依然存在问题, if (lazyMan!=null)为非原子性操作,依然存在两个反射对象导致出现非单例的状况

//可以采用红绿灯解决,定义一个密钥
private static boolean key = false;
private LazyMan(){
    synchronized (LazyMan.class){
        if (key==false){
            key=true;
        }else {

                throw new RuntimeException("不要试图反射破坏");
             
        }
    }
    System.out.println(Thread.currentThread().getName()+"  OK");
}

但是如果仍然用反射破环,假设获取到了密钥的情况

//通过反射修改静态参数
Field key1 = LazyMan.class.getDeclaredField("key");
key1.setAccessible(true);

//获得空参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//反射的setAccessible(true)参数,无视私有构造器
declaredConstructor.setAccessible(true);
//通过反射建造对象
LazyMan lazyMan1 = declaredConstructor.newInstance();

//修改对象的密钥参数
key1.set(lazyMan1,false);

LazyMan lazyMan2 = declaredConstructor.newInstance();

System.out.println(lazyMan1);
System.out.println(lazyMan2);

单例仍然会被破坏

真实有效的方式
枚举

package com.cz.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author 卓亦苇
 * @version 1.0
 * 2023/3/14 16:26
 */
public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        //EnumSingle instance2 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

image-20230314163414487

至此才得以真正解决

环境准备

准备两台服务器环境,配置相同,分别安装Centos7,mysql8.0,docker mysql 8.0

准备测试代码

    public class Chat
    {
        public static long Count = 0;
        public Chat() {
        }
        public static void AddChat()
        {
            var context = new IM.Data.MySQLContext();
            while (true)
            {
                var chat = new IM.Data.ChatRecordTb
                {
                    UserId = "F62E97C5-98E0-4473-B933-08D7787382C",
                    UserHeadPortrait = "http://www.baidu.com/2023-3-13/952824521354142234544.jpg",
                    UserName = "测试用户",
                    TextType = 1,
                    ChatText = "*****************很长的文字**********************",
                    FileSrc = "",
                    ThumbnailSrc = "",
                    CreateTime = DateTime.Now
                };
                context.ChatRecordTb.Add(chat);
                context.SaveChanges();
                context.ChatRecordTb.Entry(chat).State = EntityState.Detached;

                //+1
                Interlocked.Increment(ref Count);
            }
        }
    }

使用ORM框架(EF)进行数据库操作,ORM部分就不展示,在控制台进行调用

//多线程添加,10个线程
for (var i = 0; i < 10; i++)
{
    var task = new Task(ConsoleApp1.Chat.AddChat);
    task.Start();
}

while (true)
{
    var startCount = ConsoleApp1.Chat.Count;
    System.Threading.Thread.Sleep(10000);
    var endCount = ConsoleApp1.Chat.Count;
    Console.WriteLine($"10秒一共写入{endCount - startCount}行条数据,总行数:{endCount}");
}

祼机mysql性能

mysql的性能是随着配置不同,逞现不同性能,这是的性能是指当前配置下的性能。

应该是硬盘IO达到瓶颈,线程多与少己经没不能引起写入性能的大性幅提升,平均性能大概在2000条/S

Docker mysql 性能

相同的代码在docker下的mysql 相比之下似乎稳定很多, 无论是控制台数据统计,ESXI的数据统计,都可以看到docker下的mysql 很顺滑,同样CPU消耗明显略高,数据库写入速度大概为2500条/S

结尾

两者的mysql版本相同均为:8.0.32,相比之下感觉dokcer下写入更稳定,性能之间似乎没有什么明显的区别。

前端性能优化——采用高效的缓存策略提供静态资源

一、发现性能问题

通过 Chrome 开发者工具的 Lighthouse 工具对目标站点的某个页面进行分析,其生成的报告如图所示:

img

由分析报告可知,该目标站点存在多项待优化的性能问题,如
减少未使用的 JavaScript

采用高效的缓存策略提供静态资源
等问题,本文主要分析并解决
采用高效的缓存策略提供静态资源
问题。

二、分析性能问题

Lighthouse 会标记所有未被缓存的静态资源:

img

Lighthouse 认为一个资源是可缓存的,当且仅当以下所有条件都满足:

  • 该资源是字体、图像、媒体文件、脚本或样式表。
  • 该资源具有 200、203 或 206 HTTP 状态码。
  • 该资源没有显式的 no-cache 策略。

当页面未通过审计时,Lighthouse 会将结果列在一个具有三列的表格中:

列名 说明
网址 可缓存资源的位置
缓存 TTL 资源当前的缓存持续时间
传输文件大小 如果被标记的资源已经被缓存,用户可以节省多少数据的估算值

通过分析 Lighthouse 工具对目标站点生成的检测报告,发现
采用高效的缓存策略提供静态资源
这一项中的缓存 TTL 列,有一些静态资源文件的值为 None,也就是说该目标站点有部分静态资源未被缓存,那么这部分资源在重复访问时,会重复向服务器发送请求,因此该情况将明显影响目标站点性能。

三、解决性能问题

为了使静态资源缓存,需要在服务器端配置 HTTP 响应标头。标头控制浏览器缓存的行为。以下是一些有关缓存的重要标头:

  • Cache-Control:用于控制资源的缓存机制。通过设置值为 public 或 private 来指定资源是否可以被缓存,并通过设置 max-age 指定资源应该被缓存的时间长度。
  • Expires:允许指定一个过期时间,过期后,浏览器必须重新获取资源。
  • ETag:允许服务器标识资源,并使用 If-None-Match 请求头进行缓存验证。
  • Last-Modified:允许服务器指定资源最后修改的时间,并使用 If-Modified-Since 请求头进行缓存验证。

请配置你的服务器返回 Cache-Control HTTP 响应头:

Cache-Control: max-age=31536000

max-age 参数告诉浏览器它应该将资源缓存多长时间,单位为秒。以下示例将持续时间设置为 31536000,对应于 1 年:60 秒 × 60 分钟 × 24 小时 × 365 天= 31536000 秒。

以在 Nginx 中配置 Cache-Control 响应头为例:

http {
    server {
         location / {
            root /html;
            index index.html index.htm;

            # # 设置缓存时间为 1 天
            # expires 1d;

            # 开启缓存,并设置一年的缓存期
            add_header Cache-Control "public,max-age=31536000";
        }
    }
}

可以通过配置 expires 参数和 Cache-Control 的 max-age 来指定缓存期。

add_header Cache-Control "public,max-age=31536000";
开启了缓存,并指定了缓存类型为 public,表示响应可以被客户端和代理服务器缓存,max-age 指定了资源的缓存期为 31536000 秒。

Nginx 服务器端设置
Cache-Control
响应头后,访问静态资源的响应如图所示:

img

img

需要注意的是,长时间的缓存期存在一个问题:用户可能看不到静态文件的更新。但是可以通过配置构建工具来解决该问题,在静态资源文件名中嵌入哈希来避免这个问题,以便每个版本都是唯一的,从而促使浏览器从服务器获取新版本。(要了解如何使用 webpack 嵌入哈希,请参阅 webpack 的
缓存
指南。)

如果资源经常更新且实时性很重要,那么可以将其缓存设置为 no-cache,但浏览器仍会缓存该资源,不过首先会与服务器进行检查,以确保资源仍然是最新的。

此外,并不是缓存期越长越好。而是需要根据实际需求进行权衡,以决定资源的最佳缓存期。

通过在 Nginx 开启缓存,并设置合适的缓存期,目标站点的性能检测报告如图所示:

img

由上图可知,性能评分并没有改变,但在
诊断结果
下已没有
采用高效的缓存策略提供静态资源
这一项,这一项已经被移入
已通过的审核
,也就是该项通过在服务器设置 Cache-Control 的方式得到了性能上的优化。

前言

念春时已夏,恋冬雪已融。
总是感叹时光匆匆,便努力在在平凡中挣扎,在平庸中努力,在平淡中积累。奈何时代飞速发展,时间又被工作占用,外加生活中的诱惑又太多了,很多想学、想做、想超越的事,都被抛之一旁,渐渐的跟不上时代了,当年对兴趣爱好的激情,也下降了好多。
上一篇博客还是几年前的,虽然中间进步不少,但是却少了许多当年写博客时的激情飞扬,真是感慨诸多。
按公司要求,以后需要每周交一篇知识分享文档,索性就继续写一下博客,算是总结,也算是对自己的积累。
由于文档允许在大框架内自由发挥,所以决定在我的兴趣点上找一些还说的过去的东西写一写,而不是单纯的复制一些网上随便就能找到的东西。
以后会努力在机械电机、三维重建、图像视觉、深度学习等兴趣范围内做一些比较综合的整理,而不是仅限于嵌入式的方向。当然,他们的实现都离不开底层,所以肯定是包含嵌入式方向的。

一:说明

BL808芯片是三核异构的RISC-V CPU,参数如下:
三核异构RISC-V CPUs:

  • RV64GCV 480MHz
  • RV32GCP 320MHz
  • RV32EMC 160MHz

AI NN 通用硬件加速器 —— BLAI-100 用于视频/音频检测/识别
内置 768KB SRAM + 64MB UHS PSRAM

编解码:

  • MJPEG and H264(Baseline/Main)
  • 1920x1080@30fps + 640x480@30fps
    接口:
  • 摄像头接口 :DVP 和 MIPI-CSI
  • 显示接口:SPI、DBI、DPI(RGB)
    无线:
  • 支持 Wi-Fi 802.11 b/g/n
  • 支持 Bluetooth 5.x Dual-mode(BT+BLE)
  • 支持 Wi-Fi / 蓝牙 共存
    USB 2.0 HS OTG (引出到 USB Type-C 接口)
    具体参数可以查看Sipeed科技的
    M1s DOCK 开发板
    说明页。
    可以看到,这款芯片的能力还是很强的,完全可以媲美之前非常火的入门级Linux芯片 F1C100S,另外,它支持FreeRTOS,所以可以当做裸机进行开发,代替单片机。但是可能由于是新出的产品,或是由于其他原因,目前这款产品只有Sipeed科技生产的模组,没有纯芯片售卖。目前体验设备便是Sipeed科技推出的全功能测试开发板:【
    Sipeed M1s DOCK 开发板

开始使用体验

1:环境搭建

  • --Linux开发环境搭建
    参考Sipeed科技的【
    上手使用
    】教程的1-4大节,搭建linux使用环境后,使用模拟U盘的方式将bin文件拖拽进去即可体验。如果有问题,请自行查找解决办法。
    这里我使用VMware软件运行ubuntu16.04环境进行测试。支持快速内外复制等功能。如果是别的虚拟机环境,自行判断是否支持。
    由于购买时间较早,所以需要根据教程的 3.2【串口烧录】 来更新板载BL702固件来实现稳定的下载,在使用图形化界面烧录最新的固件,来实现 3.1【U盘烧录】。后面编译生成的bin文件都可以通过U盘烧录来快速更新。
    目前主要测试程序都是基于三个核心中的C906核心的。
  • --SDK下载和编译测试
    参考【
    上手使用
    】教程的第5大节,下载SDK并配置好编译工具链,然后就可以编译并下载程序测试。
    可以跟着操作一遍【lvge_demo】的编译和下载操作,熟悉整个流程。
    新手小提示

    • 1:程序需要两个文件夹,一个是 M1s_BL808_SDK ,另一个是 M1s_BL808_example ,可以用 VSCode 打开这两个文件夹所在的文件夹,就可以使用 【Ctrl + 单击】需要了解的函数就能进行快速追踪跳转了。如果只打开例子文件夹是不会跳转的,或是文本会包含所在文本的文件夹地址。
    • 2:修改【lvgl_demo】的示例内容方法如下:在配置头文件 M1s_BL808_SDK/compnets/lvgl/lvgl/lv_conf.h 中,LV_BUILD_EXAMPLES 配置部分进行示例切换,默认是 LV_USE_DEMO_BENCHMARK 例子。将想要的例子后面的0切换为1,其他的切换为0,然后在 M1s_BL808_example/c906_app/lvgl_demo/main.c 的主文件中将之前的 lv_demo_benchmark() 函数屏蔽,更换为开启的demo的函数,比如music例子是 M1s_BL808_SDK/compnets/lvgl/lvgl/demos/lv_demo_music.h中的lv_demo_music()函数,其他几个例子类似,都在M1s_BL808_SDK/compnets/lvgl/lvgl/demos文件夹下对应的文件夹内。
    • 3:编译其他几个例子需要开启对应的字体(具体开启那些字体,可以按照报错提示来),字体开关在配置头文件 M1s_BL808_SDK/compnets/lvgl/lvgl/lv_conf.h 中,快速搜索LV_FONT_MONTSERRAT_ 即可跳转到,后面的数字是你需要开启的字体包。
    • 4:export BL_SDK_PATH 这一步每次重开都要重新来一次,否则会找不到编译链。有长效修改的方法,可自行搜索尝试。

2:LVGL体验

  • LVGL
    是由来自匈牙利首都布达佩斯的 Gábor Kiss-Vámosi最早于2016年编写并开源的一款可运行于低资源的MCU设备上的开源嵌入式GUI库(轻量级通用型图形库)。
  • 其GitHub地址为:【
    LVGL Git
  • LVGL当时叫 LittlevGL而不是LVGL,后来作者统一修改为 LVGL 甚至连仓库地址都改了。目前最新版本已经发展到了 v9.0.0 版本,在6.0版本时,我还体验过移植和简单使用。当时还没有拖拽式GUI设计软件,所有界面都要手写。如今,LVGL已经有了如SquareLine Studio、GUI-Guider等GUI设计软件,可以直接PC上进行界面设计和仿真运行,得到【所见即所得】的方便效果。而且随着LVGL的发展壮大,其中文教程资源等都有了极大的发展,比如【
    百问网
    】就有包含文档和视频教程在内的丰富的教程资源,而且以前【
    正点原子
    】就有编写过专门的LVGL教程。
  • 现在,首先下载和体验【
    SquareLine Studio
    】软件。
    SquareLine Studio是LVGL官方提供的GUI设计软件,可以在LVGL官网直接点击到下载页面。目前最新版本是V1.2.1。按照官网的说明,它只有30天的全功能体验时间。
    按照教程下载安装后(具体安装步骤可以参考其他网友的教程,很多的),即可新建一个工程进行GUI设计了。
    在新建界面,可以看到,SquareLine Studio支持的LVGL版本只有8.3.3和8.3.4两个版本:

【M1s DOCK】开发板的屏幕是 1.69 寸 240x280 电容触摸屏,所以我们需要在新建界面将Resolution设置为 (280,240) ,Color depth 要设置为 (16 bit swap)模式 。设置好名字和要保存的地址后就可以点击 CREATE 新建工程了。其实也可以点击上方的 Example 来查看官方提供的几个比较丰富的demo示例,跑起来很绚丽。不过由于开发板官方提供的LVGL例程所使用的的版本是8.2的,而SquareLine Studio只能选择两个更高的版本,所以下载demo后,程序并不能正常运行。以后有时间再移植成最新的版本再测试吧。
GUI设计界面如图:

(具体含义和操作可以自行搜索教程使用,也可以自己摸索)
我们可以点击一个【Button】到界面。

然后点击左上角的【Export】,选择【Export UI Files】生成代码到指定文件夹。
生成成功后,可以看到指定文件夹下有几个生成的文件:

将这几个文件复制到linux环境中的 M1s_BL808_example/c906_app/lvgl_demo/ 文件夹下(此文件夹下除了原本的 bouffalo.mk 和 main.c,其他的都可以删掉)。
我用的是
VMware虚拟机
,所以支持win与ubuntu环境的快速复制。如果是别的虚拟机环境,自行处理文件复制问题。
将 ui.h 文件中的 #include "lvgl/lvgl.h" 改为 #include "lvgl.h",不然会报错,据说这个是 SquareLine Studio 的BUG。
将 main.c中的 demo测试相关的代码替换为ui.h中的ui_init()函数。
编译通过后,将开发板配置为U盘下载模式(USB插在OTG口,按住两侧按键再按RST按键),将 M1s_BL808_example/c906_app/build_out/lvgl_demo.bin 文件拖拽到U盘中即可。
可以看到,所见即所得:

LVGL升级体验

完成基础示例后,既可以进行更复杂一些的基础示例了。
首先进行一次页面切换。
点击做下角的【Screen】图标即可增加一个显示界面了。
在界面2上增加一个【Arc】控件。

下面,就应该思考该如何做界面切换了。首先,计划需要实现的功能:点击按钮进入第二页面,右划第二界面回到第一页面。
实现方法如下:

  • 选中第一页面的按钮,可以看到右边的属性设置栏,可以对按钮进行各种属性配置。

  • 点击最下面的【ADD EVENT】,可以看到,有几个事件类型选项,我们做如下选择:


    • 【Trigger】选择 【CLICKED】,即点击事件
    • 【Action】选择【CHANGE SCREEN】,即切换屏幕

      然后点击【ADD】,增加这个点击事件,然后就可以对此事件进一步配置:
    • 【Screen to】选择【Screen2】,即切换到屏幕2(名称可以自行修改,对应到自己的屏幕的名称)
    • 其他选项默认(可自行测试功能,或是查看手册查找功能说明)

      此时就算配置完成了第一步,实现点击按钮切换屏幕。可以点击【运行】三角号按钮进行模拟测试。
  • 后面可以进行同样的配置,点击第二页的屏幕,选中屏幕,右侧进行配置
    * 【Trigger】选择 【GESTURE_RIGHT】,即右划事件
    * 【Screen to】选择【Screen1】,即切换到屏幕1

    然后点击【ADD】,添加事件,进入到事件配置界面。
    * 【Screen to】选择【Screen1】,即切换到屏幕1
    此时完成预定目标的配置,实现右划切换回屏幕1,可以点击【运行】三角号按钮进行模拟测试。

  • 点击【Export】->【Export UI Files】生成代码。

  • 复制替换生成的代码到linux对应环境下,然后编译下载,进行实际运行查看,可以看到实现了既定目标,在设备上跑起来了,按钮和滑动也能正常切换。

LVGL操控设备

目前我们已经完成了LVGL的简单绘制,基本事件绑定等体验功能,但具体来说,也只是使用SquareLine Studio本身实现的,不需要编写一行代码就能实现(需要移植好的设备环境),并没有涉及LVGL之外的交互。所以下面尝试一下,将LVGL与硬件交互联系起来,然后就能举一反三,实现完整复杂的逻辑功能了。

  • 首先,计划需要实现的功能:将屏幕2上的【Arc】控件于设备背面的LED的亮度绑定,实现拖拽【Arc】的角度就能控制LED的亮度。

  • 然后,我们来看一下LED的PWM驱动例子。
    打开 M1s_BL808_example/c906_app/pwm_demo 中的main.c,查看PWM的例子。
    可以看到,PWM操作背面的LED灯,只需要几个特定的函数。

  • 再然后,我们考虑怎么将LVGL的【Arc】拖动事件与PWM调值联系起来。


    • 选中屏幕2的【Arc】控件,点击右下角的【ADD EVENT】,增加一个事件。
    • 【Trigger】选择 【VALUE_CHANGED】,即值改变事件
    • 【Action】选择【CALL_FUNCTION】,即调用函数


    • 点击【ADD】,进入配置
    • 给调用函数起一个名字,在【Action】->【Function name】文本框中输入起的名字,如[Arc_PWMControl] ,注意,名字要够一定字数
      配置完成,开始生成文件。
      将生成的文件复制替换到linux环境下,打开文件夹,可以看到 ui_events.c 中有一个函数:
void Arc_PWMControl(lv_event_t * e)
{
	// Your code here
}

里面没有任何功能。所以我们需要在本函数中添加回调函数对应的处理,即获取改变后的值,然后设置对应脉宽给PWM,实现调节亮度。
使用如下函数即可获取到【Arc】控件的当前值:

int16_t ArcValue = 0;
lv_obj_t* arc = lv_event_get_target(e);//获取目标控件指针
ArcValue = lv_arc_get_value(arc) ;//获取目标值

然后就可以使用这个值,调用 pwm_demo 中的 m1s_xram_pwm_set_duty 函数进行占空比配置。我们可以在main函数中整合一下PWM操作相关的函数,包括初始化和占空比调节,进行调用。在main.c中增加如下两个函数:

```
#include "m1s_c906_xram_pwm.h"
#define PWM_PORT (0)
#define PWM_PIN (8)
#define PWM_FREQ 2000  //PWM频率设置
//PWM驱动LED初始化代码
void LED_PWMInit(int freq,int duty){
    m1s_xram_pwm_init(PWM_PORT, PWM_PIN, freq, duty);
    m1s_xram_pwm_start(PWM_PORT, PWM_PIN);
}
//设置占空比
void LED_PWMDutySet(int duty){
    m1s_xram_pwm_set_duty(PWM_PORT, PWM_PIN, PWM_FREQ, duty);   
}
```

将LED_PWMInit()函数放置到main函数中,将LED_PWMDutySet()函数使用extern关键字引用到ui_events.c中。
查看pwm_demo中的m1s_xram_pwm_set_duty()函数,可以看到,占空比为0-99,我们的【Arc】范围为0-100,所以需要限制【Arc】的范围。两种方法:

  • 1:是在【Arc】属性配置控件中将【Range max】最大值属性设置为99
  • 2:在程序中增加判断,大于99的都设置为99
    也可以两种都用上,增加保险。
    然后在Arc_PWMControl()函数中调用,将ArcValue 值传给m1s_xram_pwm_set_duty()函数即可。
    另外,由于LED灯电路上的驱动电路的不同(高电平端控制或是低电平端控制),所以PWM脉宽与LED亮度不一定成正比,可以加上一句 ArcValue = 99-ArcValue; 来换算。
    具体程序如下(仅供参考):
    main.c
    :
/* FreeRTOS */
#include <FreeRTOS.h>
#include <task.h>

/* bl808 c906 std driver */
#include <bl808_glb.h>

#include "demos/lv_demos.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lvgl.h"

#include "ui.h"
#include "m1s_c906_xram_pwm.h"
static void lvgl_task(void *param)
{
    while (1) {
        lv_task_handler();
        vTaskDelay(1);
    }
    vTaskDelete(NULL);
}

#define PWM_PORT (0)
#define PWM_PIN (8)
#define PWM_FREQ 2000

//PWM驱动LED初始化代码
void LED_PWMInit(int freq,int duty){
    m1s_xram_pwm_init(PWM_PORT, PWM_PIN, freq, duty);
    m1s_xram_pwm_start(PWM_PORT, PWM_PIN);
}
//设置占空比
void LED_PWMDutySet(int duty){
    m1s_xram_pwm_set_duty(PWM_PORT, PWM_PIN, PWM_FREQ, duty);   
}

void main()
{
    LED_PWMInit(PWM_FREQ,99);//PWM初始化,默认占空比设为99
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();//触摸相关
    ui_init();
    lv_task_handler();
    xTaskCreate(lvgl_task, (char *)"lvgl task", 512, NULL, 15, NULL);
}

ui_events.c
中程序如下:

#include "ui.h"

extern void LED_PWMDutySet(int duty);

void Arc_PWMControl(lv_event_t * e)
{
	// Your code here
	int16_t ArcValue = 0;
	lv_obj_t* arc = lv_event_get_target(e);//获取目标控件指针
	ArcValue = lv_arc_get_value(arc) ;//获取目标值
	if(ArcValue>99)ArcValue=99;
	
	ArcValue = 99-ArcValue;//占空比对应灯的亮度;由于LED驱动方式的不同,亮度与占空比不一定是正比
							//这样处理后便将亮度与占空比换算为正比了
	
	LED_PWMDutySet(ArcValue); 
}

将程序编译,然后将 build_out文件夹下生成的 lvgl_demo.bin 拖拽到模拟U盘中更新程序,可以看到已经实现了既定目标:

  • 点击【button】切换到第二页面
  • 右划第二页面回到第一页面
  • 拖拽【Arc】控件实现控制背面LED的亮度


(LED在背面,此处不再展示)

以上只是一些简单的例程,用于实现LVGL的操作体验和与硬件的交互实现的流程,更多功能便可以很轻松的拓展了。其实还有很多优化空间的,比如【Arc】控件的左右滑动会激发页面滑动效果从而回到第一页面,右下角的参数窗口没去掉,点击指示图标是一个USB图标,SDK的lvgl版本与编辑器的版本不同导致复杂界面卡死等。这些都可以自行深入学习并完善。
关于LVGL深入的学习,还请自行查看相关教程。而且例程是基于freertos嵌入式操作系统的,在main函数中可以看到,lv_task_handler()句柄是在 lvgl_task 任务中循环执行的,所以可以创建其他线程执行lvgl之外的功能,而不是仅仅依靠lvgl控件的回调函数实现。后面有时间会再写一些好玩的东西,比如深入理解一下LVGL的移植,然后实现其他的绘图库的移植,比如之前写过的旋转立方体:

或是基于此算法的旋转时钟:

  • 随梦,随心,随愿,恒执念,为梦执战,执战苍天 ----执念执战 (好久没写我这很中二的座右铭了,哈哈)