【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL

一 LVGL简介
最近emwin用的比较烦躁,同时被LVGL酷炫的界面吸引到了,所以准备换用LVGL试试水。
LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用。
【1】主要特性
丰富且强大的模块化图形组件:按钮 (buttons)、图表 (charts)、列表 (lists)、滑动条 (sliders)、图片 (images) 等
高级的图形引擎:动画、抗锯齿、透明度、平滑滚动、图层混合等效果
支持多种输入设备:触摸屏、 键盘、编码器、按键等
支持多显示设备
不依赖特定的硬件平台,可以在任何显示屏上运行
配置可裁剪(最低资源占用:64 kB Flash,16 kB RAM)
基于UTF-8的多语种支持,例如中文、日文、韩文、阿拉伯文等
可以通过类CSS的方式来设计、布局图形界面(例如:Flexbox、Grid)
支持操作系统、外置内存、以及硬件加速(LVGL已内建支持STM32 DMA2D、NXP PXP和VGLite)
即便仅有单缓冲区(frame buffer)的情况下,也可保证渲染如丝般顺滑
全部由C编写完成,并支持C++调用
支持Micropython编程,参见:LVGL API in Micropython
支持模拟器仿真,可以无硬件依托进行开发
丰富详实的例程
详尽的文档以及API参考手册,可线上查阅或可下载为PDF格式
在 MIT 许可下免费和开源
【2】配置要求

【3】与其他显示工具的对比

二 LVGL移植前准备
【1】参考资料
LVGL官网:https://lvgl.io/
LVGL源码:https://gitee.com/my_lvgl/lvgl
百问网LVGL中文开发手册:http://lvgl.100ask.net/8.2/index.html
一些关于移植的经验资料:
LITTLEVGL (LVGL)入门之移植到STM32芯片
LVGL学习笔记 | 02 - 移植LVGL 8.2到STM32F407开发板(MCU屏)
STM32移植LVGL(LittleVGL)
在STM32F4上移植LVGL8.2
【2】硬件资源
硬件资源包括:
嵌入式平台(我用的MCU是华大的HC32F460芯片)
实现了屏幕驱动的液晶屏(我用的是3.2寸320*240的8080并口屏,屏幕驱动可以移步【嵌入式】MCU(HC32F460)+并口LCD液晶屏ILI9341 移植emWin记录)
输入设备(非必要,包括触摸屏、鼠标、键盘、编码器、按键,我自己使用的是磁控旋钮编码器)
【3】其他
推荐一款不错的LVGL设计工具GUI Guider,是由恩智浦开发的,通过NXP官网(https://www.nxp.com.cn/design/software/development-software/gui-guider:GUI-GUIDER)即可下载,具体使用后面有机会专门写一篇文章介绍。

准备好了以上的内容,接下来就可以进行移植了。
三 LVGL移植
【1】源码下载
首先下载LVGL源码,链接见上一章的参考资料。有很多版本,我这边没有选择最新的版本,而是用了V1.5.0-GA版本GUI Guider支持的V8.3.2版本:


下载完解压后是这样的:

【2】源码添加工程
将上面解压完成的lvgl-v8.3.2文件夹中的下列文件复制到自己keil工程自建文件夹GUI_LVGL中:

复制进来后,删除proting文件夹以及lv_conf_template文件中_template后缀:

在keil中建立管理目录:

其中:
GUI/LVGL用来存放src及其子目录中所有.c文件。这一步比较考验耐心和细致,每一个子目录下都有不少源文件。(要是不想自己keil工程中有许多看起来凌乱的源文件,可以参考STM32/keil把多个c文件编译为静态库lib中的方法,预先将这许许多多的.c文件编译为lib库,那么GUI/LVGL中放一个lib文件即可。但是这种方法要注意创建lib的工程和使用lib的工程芯片型号要一致才可以)。我是使用的lib库的形式。
GUI/LVGL/PORT用来存放porting下的三个源文件,分别是lv_port_disp.c(显示接口文件)、lv_port_indev(输入设备接口文件)、lv_port_fs(文件系统接口文件)。
GUI/LVGL用来存放GUI Guider生成的图形界面文件,这边暂时还没有用到,可以不用管。
包含头文件路径:

最后还有两个重要的小操作,一个是要选用C99 Mode:

另一个是加大栈空间,方法是打开.s启动文件,修改其中的Stack_Size值,我这边配置的是8KB(根据上面LVGL运行配置要求的推荐值确定):

至此,可以编译一下,由于还没有使用显示接口等,所以这边应该是编译无误的。
【3】添加心跳接口
在timer.c中包含头文件"lvgl.h",并在1ms定时器中调用lv_tick_inc(1)启用心跳:
/**************************************************************************
* 函数名称: TimeraUnit2_IrqCallback
* 功能描述: 定时器A 单元2初始化
* 输入参数:
* 输出参数:
* 返 回 值:
* 其它说明: 1ms定时器中断
**************************************************************************/
static void TimeraUnit2_IrqCallback(void)
{lv_tick_inc(1);TIMERA_ClearFlag(TIMERA_UNIT2, TimeraFlagOverflow);
}当然还有其他方式让LVGL开始心跳,这个可以自行选用,控制心跳周期为1ms即可。
【4】显示接口对接屏幕驱动
修改lv_conf.h头文件中的#if 0为#if 1,同时根据适配的显示设备设置颜色格式:

补充,如果想屏幕刷新快一点,可以将FPS做如下调整(30ms-->10ms,即FPS由30-->100):

把lv_port_disp.c、lv_port_disp.h、lv_port_indev.c、lv_port_indev.h四个文件的#if 0 都改成#if 1 ,这四个文件包含的头文件名字还需根据编译报错信息修改。:


注意,若头文件#include "lvgl/lvgl.h"包含报错,可以添加宏定义LV_CONF_INCLUDE_SIMPLE。
修改lv_port_disp.c中适配底层屏幕驱动分辨率(我用的就是320*240):
/********************** DEFINES*********************/
#ifndef MY_DISP_HOR_RES#warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.#define MY_DISP_HOR_RES 320
#endif#ifndef MY_DISP_VER_RES#warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now.#define MY_DISP_VER_RES 240
#endif修改lv_port_disp.c中lv_port_disp_init函数,该函数包含两个部分,一个是初始化disp_init(),一个是绘制缓冲区。
其中初始化函数disp_init()中加入自己的屏幕初始化程序:
void LCD_AllInit(void)
{LCD_InitGPIO(); //初始化几个GPIO口,包括CS\RS\WR\RD\RES\DB0-15LCD_HardwareReset(); //LCD复位LCD_ConfigureREG(); //RGB配置初始化LCD_Clear(WHITE); //清屏白色LCD_DisplayDir(data_inter.showDirect); //默认为横屏
}static void disp_init(void)
{/*You code here*/LCD_AllInit();
}修改lv_port_disp.c中lv_port_disp_init函数绘制缓冲区的部分,删除不用的Example for 2/3,只留Example for 1即可:
void lv_port_disp_init(void)
{/*-------------------------* Initialize your display* -----------------------*/disp_init();/*-----------------------------* Create a buffer for drawing*----------------------------*//*** LVGL requires a buffer where it internally draws the widgets.* Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.* The buffer has to be greater than 1 display row** There are 3 buffering configurations:* 1. Create ONE buffer:* LVGL will draw the display's content here and writes it to your display** 2. Create TWO buffer:* LVGL will draw the display's content to a buffer and writes it your display.* You should use DMA to write the buffer's content to the display.* It will enable LVGL to draw the next part of the screen to the other buffer while* the data is being sent form the first buffer. It makes rendering and flushing parallel.** 3. Double buffering* Set 2 screens sized buffers and set disp_drv.full_refresh = 1.* This way LVGL will always provide the whole rendered screen in `flush_cb`* and you only need to change the frame buffer's address.*//* Example for 1) */static lv_disp_draw_buf_t draw_buf_dsc_1;static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*//*-----------------------------------* Register the display in LVGL*----------------------------------*/static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/lv_disp_drv_init(&disp_drv); /*Basic initialization*//*Set up the functions to access to your display*//*Set the resolution of the display*/disp_drv.hor_res = MY_DISP_HOR_RES;disp_drv.ver_res = MY_DISP_VER_RES;/*Used to copy the buffer's content to the display*/disp_drv.flush_cb = disp_flush;/*Set a display buffer*/disp_drv.draw_buf = &draw_buf_dsc_1;/*Required for Example 3)*///disp_drv.full_refresh = 1;/* Fill a memory array with a color if you have GPU.* Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.* But if you have a different GPU you can use with this callback.*///disp_drv.gpu_fill_cb = gpu_fill;/*Finally register the driver*/lv_disp_drv_register(&disp_drv);
}补充,如果想屏幕刷新快一点,在SRAM足够的条件下,可以修改默认的buf_1[MY_DISP_HOR_RES * 10]为buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 4]:
static lv_disp_draw_buf_t draw_buf_dsc_1;static lv_color_t buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 4]; /*A buffer for 10 rows*/lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * MY_DISP_VER_RES / 4); /*Initialize the display buffer*/修改lv_port_disp.c中的刷屏函数disp_flush,默认的刷屏用打点的方式速度太慢,改用刷块的方式:
void LCD_Fill_LVGL(U16 sx,U16 sy,U16 ex,U16 ey,U16 *color)
{uint16_t height, width;uint16_t i, j;width = ex - sx + 1; /* 得到填充的宽度 */height = ey - sy + 1; /* 高度 */for (i = 0; i < height; i++){LCD_SetCursor(sx, sy + i); /* 设置光标位置 */LCD_WriteRAM_Prepare(); /* 开始写入GRAM */for (j = 0; j < width; j++)LCD_WriteRAM(color[i * width + j]); /* 写入数据 */}
} /*Flush the content of the internal buffer the specific area on the display*You can use DMA or any hardware acceleration to do this operation in the background but*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{LCD_Fill_LVGL(area->x1,area->y1,area->x2,area->y2,(U16 *)color_p);/*IMPORTANT!!!*Inform the graphics library that you are ready with the flushing*/lv_disp_flush_ready(disp_drv);
}至此,显示接口对接屏幕驱动完成。
【5】输入设备接口对接编码器
如果没有使用到诸如触摸屏、鼠标、键盘、编码器、按键这几类的外部设备,只要显示界面不用操作的话,可以跳过这一节,直接到LVGL测试章节继续。
我这边用到的是磁控旋钮编码器外设。
修改lv_port_indev.c中lv_port_indev_init函数,包含两个部分,一个是初始化encoder_init(),一个是注册编码器外部设备。
其中初始化函数encoder_init()中加入自己的屏幕初始化程序:
/*Initialize your keypad*/
extern STRU_KNOB knob_old;
extern char knob_state_now;
static void encoder_init(void)
{/*Your code comes here*/MLX_InitGPIO();MLX_InitSPI();knob_old = knobPosInit(); //旋钮位置初始化
}删除不用的外部设备,只留下编码器作为唯一外部输入:
void lv_port_indev_init(void)
{/*** Here you will find example implementation of input devices supported by LittelvGL:* - Touchpad* - Mouse (with cursor support)* - Keypad (supports GUI usage only with key)* - Encoder (supports GUI usage only with: left, right, push)* - Button (external buttons to press points on the screen)** The `..._read()` function are only examples.* You should shape them according to your hardware*/static lv_indev_drv_t indev_drv;/*------------------* Encoder* -----------------*//*Initialize your encoder if you have*/encoder_init();/*Register a encoder input device*/lv_indev_drv_init(&indev_drv);indev_drv.type = LV_INDEV_TYPE_ENCODER;indev_drv.read_cb = encoder_read;indev_encoder = lv_indev_drv_register(&indev_drv);/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,*add objects to the group with `lv_group_add_obj(group, obj)`*and assign this input device to group to navigate in it:*`lv_indev_set_group(indev_encoder, group);`*/
}注册外部设备,修改encoder_read函数,encoder_handler可以不用管,encoder_diff的加减表示左旋和右旋,encoder_state赋值LV_INDEV_STATE_PR或者LV_INDEV_STATE_REL表示按下或松开:
/*Will be called by the library to read the encoder*/
static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{if(knob_state_now == 9 || knob_state_now == 15) //按下动作和按下状态都算按下encoder_state = LV_INDEV_STATE_PR; //按下elseencoder_state = LV_INDEV_STATE_REL; //松开if(knob_state_now == 19)encoder_diff ++;else if(knob_state_now == 17)encoder_diff --;data->enc_diff = encoder_diff;data->state = encoder_state;encoder_diff = 0;
}knob_state_now表示当前旋钮的状态,其状态的获得在主循环中获取,将其映射到encoder_diff和encoder_state这两个变量:
#define KNOB_ESCAPE 8 //旋钮抬起状态,xy不变
#define KNOB_UP 17 //z不变,正向旋转
#define KNOB_DOWN 19 //z不变,反向旋转
#define KNOB_ENTER_BEF 15 //按下动作四 LVGL测试
至此便完成了LVGL的显示接口以及输入设备接口的移植。
在主任务中添加上面的初始化,这边简单做了一个小界面方便展示:
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
static void lv_ex_label_1(void)
{lv_obj_t * label2 = lv_label_create(lv_scr_act());lv_label_set_recolor(label2, true);lv_label_set_long_mode(label2, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/lv_obj_set_width(label2, 120);// Hello world ! Trisuborn.lv_label_set_text(label2, "#ff0000 Hello# #00ff00 world ! Trisuborn.#");lv_obj_align(label2, LV_ALIGN_CENTER, 0, 0);
}void GUI_Task(void)
{//LVGL初始化lv_init();//显示器初始化lv_port_disp_init();//外部输入初始化lv_port_indev_init();//界面生成lv_ex_label_1();while(1){knob_now = knobPosNow(); //确定旋钮当前位置knob_state_now = Knob_State_Entry(&knob_old, &knob_now, knob_state_old); //确定旋钮当前状态if(KNOB_ESCAPE == knob_state_now){knob_state_old = knob_state_now;}else{knob_old = knob_now;knob_state_old = knob_state_now;}lv_task_handler();os_dly_wait(5);}
}效果如下:

相关文章:
【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL
一 LVGL简介最近emwin用的比较烦躁,同时被LVGL酷炫的界面吸引到了,所以准备换用LVGL试试水。LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效…...
Transformer学习笔记
Transformer学习笔记1. 参考2. 模型图3.encoder部分3.1 Positional Encoding3.2 Muti-Head Attention3.3 ADD--残差连接3.4 Norm标准化3.5 单个Transformer Encoder流程图4.decoder部分4.1 mask Muti-Head Attention4.2 Muti-Head Attention5 多个Transformer Encoder和多个Tra…...
vue-cli引入wangEditor、Element,封装可上传附件的富文本编辑器组件(附源代码直接应用,菜单可调整)
关于Element安装引入,请参考我的另一篇文章:vue-cli引入Element Plus(element-ui),修改主题变量,定义全局样式_shawxlee的博客-CSDN博客_chalk variables 1、安装wangeditor npm i wangeditor --savewangE…...
移动办公时代,数智化平台如何赋能企业管理升级?
在传统的办公模式下,企业组织办公不仅时效低,周期长、成本高,且各办公系统相互独立。随着社会经济的发展,人们的工作生活变得多样化,对于办公的需求也越来越多,存在明显弊端的传统办公模式已不能满足企业对…...
2023“拼夕夕”为什么可以凭借简单的拼团做这么大?
2023“拼夕夕”为什么可以凭借简单的拼团做这么大? 2023-02-24 梦龙 大家好,我是你们熟悉而又陌生的好朋友梦龙,一个创业期的年轻人 大家都知道,拼夕夕背后的商业模式是拼团,但是大家知道为什么简单的拼团可以让拼夕…...
sqlmap工具
sqlmap Sqlmap是一个开源的渗透测试工具,可以用来自动化的检测,利用SQL注入漏洞,获取数据库服务器的权限。目前支持的数据库有MySQL、Oracle、PostgreSQL、Microsoft SQL Server、Microsoft Access等大多数据库 Sqlmap采用了以下5种独特的SQ…...
高/低压供配电系统设计——安科瑞变电站电力监控系统的应用
摘 要:在电力系统的运行过程中,变电站作为整个电力系统的核心,在保证电力系统可靠的运行方面起着至关重要的作用,基于此需对变电站监控系统的特点进行分析,结合变电站监控系统的功能需求,对变电站电力监控系…...
Tapdata 和 Databend 数仓数据同步实战
作者:韩山杰https://github.com/hantmacDatabend Cloud 研发工程师基础架构在云计算时代也发生着翻天地覆的变化,对于业务的支持变成了如何能利用好云资源实现降本增效,同时更好的支撑业务也成为新时代技术人员的挑战。 本篇文章通过…...
单核CPU, 1G内存,也能做JVM调优吗?
最近,笔者的技术群里有人问了一个有趣的技术话题:单核CPU, 1G内存的超低配机器,怎么做JVM调优?这实际上是两个问题。单核CPU的超低配机器,怎么充分利用CPU?单核CPU, 1G内存的超低配机器,怎么做J…...
《计算机应用研究》投稿经历和时间节点
记录四川计算机研究院《计算机应用研究》期刊投稿经历和时间节点。 日期状态周期2022.11.09上传稿件当天显示编辑部已接收稿件,开始初审2022.11.09 – 2022.11.15初审6天2022.11.15 – 2022.12.21外审36天2022.12.21收到退修意见(邮件形式)编…...
mars3d获取视窗的范围
期望效果 :1.我现在想获取到当前视窗的地图范围,请问有什么⽅法可以拿到吗 2.⽐如当前视窗地图范围的边界点,每个边界点的经纬度 回复:1.mars3d的API⽂档中有相关的⽅法 2.具体使⽤可以参考⽂档地址:http://mars3d.cn/api/Map.htm…...
《高性能MySQL》读书笔记(上)
目录 MySQL的架构 MySQL中的锁 MySQL中的事务 事务特性 隔离级别 事务日志 多版本并发控制MVCC 影响MySQL性能的物理因素 InnoDB缓冲池 MySQL常用的数据类型以及优化 字符串类型 日期和时间类型 数据标识符 MySQL的架构 默认情况下,每个客户端连接都…...
05-代理模式
代理模式 代理模式使用代理对象来代替真实对象的访问,在不修改原有对象的前提下,提供额外的操作,扩展目标对象的功能。代理模式分为静态代理和动态代理。 静态代理 手动为目标对象中的方法进行增强,通过实现相同接口重写方法进…...
RocketMQ源码分析之消费队列、Index索引文件存储结构与存储机制-上篇
RocketMQ 存储基础回顾: 源码分析RocketMQ之CommitLog消息存储机制 本文主要从源码的角度分析 Rocketmq 消费队列 ConsumeQueue 物理文件的构建与存储结构,同时分析 RocketMQ 索引文件IndexFile 文件的存储原理、存储格式以及检索方式。RocketMQ 的存储…...
基于Java的浏览器的设计与实现毕业设计
技术:Java等摘要:当今世界是一个以计算机网络为核心的信息时代,互联网为人们快速获取、发布和传递信息提供了便捷,而浏览器作为互联网上查找信息的重要工具,给人们提供了巨大而又宝贵的信息财富,受到了大家…...
手把手教你使用vite打包自己的js代码包并推送到npm
准备 要有npm账号,没有的铁子去npm官网注册一个,又不要钱。 使用vite创建项目 一行代码搞定 npm create vite viet-demo框架选择Others 模板选择library 选择ts 这样项目就创建完了 这个项目默认有一个函数,用来记录按钮的点击次数并…...
Tomcat源码分析-关于tomcat热加载的一些思考
在前面的文章中,我们分析了 tomcat 类加载器的相关源码,也了解了 tomcat 支持类的热加载,意味着 tomcat 要涉及类的重复卸装/装载过程,这个过程是很敏感的,一旦处理不当,可能会引起内存泄露 卸载类 我们知…...
DataWhale 大数据处理技术组队学习task4
五、分布式并行编程模型MapReduce 1. 概述 1.1 分布式并行编程 背景:摩尔定律已经开始逐渐失效,提升数据处理计算能力刻不容缓。传统的程序开发与分布式并行编程 传统的程序开发:以单指令、单数据流的方式顺序执行,虽然这种方式…...
Oracle 12C以上统计信息收集CDB、PDB执行时间不一致问题
文章目录前言一、统计信息窗口期调查二、时区调查三、查询alert记录四、why Database Statistic Collection Job is running two times inside a Maintenance Window?五、Default Scheduler Timezone Value In PDB$SEED Different Than CDB六、总结前言 在实际工作中发现一个…...
用Python获取弹幕的两种方式(一种简单但量少,另一量大管饱)
前言 弹幕可以给观众一种“实时互动”的错觉,虽然不同弹幕的发送时间有所区别,但是其只会在视频中特定的一个时间点出现,因此在相同时刻发送的弹幕基本上也具有相同的主题,在参与评论时就会有与其他观众同时评论的错觉。 在国内…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
