关于studywolf_control动态运动原语

news2024/12/27 22:04:11

链接:

​​​​​Dynamic movement primitive | studywolf

GitHub - studywolf/control: A repository for control benchmarking code

GitHub - studywolf/pydmps

以下内容讲解这个指令下的逻辑:python3 run.py arm3 dmp write 

目录

(一)  run.py

1、导入模块

2、构建任务与控制器

3、 执行与画图

(二)write.py

Task函数

1、检查控制器类型

2、根据arm类型设置不同的参数

3、生成要跟随的路径

4、实例化control_shell

(a)controller创建操作空间控制器实例

(b)control_pars参数

5、生成runner_pars参数

(三)dmp.py

Shell类(dmp.py)

1、初始化

2、control函数(trajectoy.py)

3、check_pen_up函数

4、gen_path函数(传入trajectory参数,DMP学习阶段)

5、set_next_seq函数

6、set_target()函数(DMP运用阶段)

(四)sim_and_plot.py

Runner类

1、定义参数

2、run函数

3、anim_animate更新函数

(五)osc.py

Control类(osc.py)

1、control函数

2、gen_target函数


(一)  run.py

1、导入模块

通过importlib.import_module()函数将arms.three_link.arm...模块、controllers.dmps模块以及task.write模块导入其中(之所以是模块,必须有__init__.py文件,尽管为空)

arm_module=arms.three_link.arm...模块

controller_class=controllers.dmps模块

task_module=task.write模块

2、构建任务与控制器

主要依据arm,controller_class与要跟踪的序列sequence,调用task_module.Task(),本质是调用write.py中的Task函数,主要完成路径生成和控制器的创建与初始化,见下面(二)的介绍

control_shell, runner_pars = task(
    arm, controller_class,
    sequence=args['--sequence'], scale=args['--scale'],
    force=float(args['--force']) if args['--force'] is not None else None,
    write_to_file=bool(args['--write_to_file']))

3、 执行与画图

调用Runner.run()来依据control_shell类(已根据任务-主要是trajector,完成参数设置和初始化)进行执行和画图

//实例化
runner = Runner(dt=dt, **runner_pars)
//执行
runner.run(arm=arm, control_shell=control_shell,
           end_time=(float(args['--end_time'])
                     if args['--end_time'] is not None else None))
//画图
runner.show()

(二)write.py

Task函数

参数:arm机械臂, controller_class控制器(只能是dmp或track), sequence=None要跟踪的序列, scale=None缩放,force=None外力, write_to_file=False写入文件, **kwargs

返回:control_shell, runner_pars

头文件:

import controllers.osc as osc # 操作空间控制器(Operational Space Controller, OSC)
import controllers.forcefield as forcefield  #力场控制器(Forcefield Controller)
import tasks.write_data.read_path as rp # 导入用于生成路径的函数。

1、检查控制器类型

只能是'dmp'或'trace',否则报错

2、根据arm类型设置不同的参数

PD控制器的位置误差增益kp、阈值、writebox书写范围(和机械臂的可达空间有关)

3、生成要跟随的路径

设定sequence与scale的default值;生成要跟随的路径:

trajectory = rp.get_sequence(sequence, writebox, spaces=True)
# 其中rp来源于“import tasks.write_data.read_path as rp”

# sequence 要跟随的序列
# writebox 是一个列表,定义了数字序列将要被绘制的矩形区域的最小和最大 x、y 坐标
# spaces 控制数字在给定 writebox(写框)中的布局方式,特别是数字之间的间距,如果为False是紧凑型,如果是True是宽松型

在get_sequence的内部

1、num = get_raw_data(nn, num_writebox)调用了路径中预先存好的字符路径(012345678hello):studywolf/control/studywolf_control/tasks/write_data/'+input_name+'.txt'

之后想用自己的路径,可以从此处修改

2、num = get_raw_data(nn, num_writebox)中将字符路径数据计算调整到num_writebox区域内部,成为机械臂可达空间上的序列。

3、将num堆叠在nums上,同时在num和nums之间以及nums的末尾保留一行nans作为间隔。

4、返回nums,即是trajectory

4、实例化control_shell

control_shell = controller_class.Shell(controller=controller, **control_pars)

这个controller_class就是controllers.dmps模块,所以就是实例化dmp.py中的Shell类,这个类的继承关系为:dmp.py(Shell)<-trajectory.py(Shell)<-shell.py(Shell)

关于Shell中传入的两个参数:

(a)controller创建操作空间控制器实例

controller = osc.Control(kp=kp, kv=np.sqrt(kp), write_to_file=write_to_file)

        继承关系osc.py(Control类)<-control.py(Control类)

        shell.py父类Shell中control方法也用到了Control类中的control函数

(b)control_pars参数

(也就是Shell类需要的参数们)

  • additions——附加到输出控制信号的加法类列表
  • gain——轨迹跟踪的PD增益
  • pen_down——当末端在画画时为true
  • threshold——系统必须多接近初始目标
  • trajectory——np.array要遵循的点的时间序列。[DOF,时间]。none表示笔抬起
  • add_to_goals——用于在目标位置基础上进行空间偏移调整,便于轨迹放缩或变换
  • bfs——DMP中的基函数数量,影响轨迹精度和灵活性。
  • tau——时间尺度放缩因子
  • 未提及:pattern:指定 DMP 模式为“离散”或“周期性”.....(还有一些参数)

这里只是实例化dmp.Shell类,完成了初始化,dmp.py(Shell)中做了什么呢,见下面(三)的介绍

真正执行里面的函数是在sim_and_plot.py(Runner)模块中执行的。

5、生成runner_pars参数

'infinite_trail': True,

'title': 'Task: Writing numbers',

'trajectory': trajectory


(三)dmp.py

上面提到Shell的继承关系:dmp.py(Shell)<-trajectory.py(Shell)<-shell.py(Shell)

作用:

⭐就是实例化Shell后(在write.py中实例化了),会执行gen_path(传入轨迹,分段学习)以及执行set_target(运用dmp进行step单步生成轨迹,这个轨迹保存到osc.Control类中target属性)

⭐在sim_and_plot中调用了control(arm)本质是调用的osc.Control类中的control()函数,用于控制机械臂运动

Shell类(dmp.py)

1、初始化

(a)------hell.Shell-------
#定义参数:
self.controller = controller  # 是osc.Control()类型的
self.pen_down = pen_down
self.kwargs = kwargs
#定义了control函数
(b)------trajectory.Shell-------
#定义参数:
self.done = False
self.gain = gain
self.not_at_start = True
self.num_seq = 0
self.tau = tau
self.threshold = threshold
self.x = None
#执行
self.gen_path(trajectory)
self.set_target()
#定义了control函数;声明了check_pen_up、gen_path、set_next_seq、set_target函数
(c)------dmp.Shell-------
#定义参数:
self.bfs = bfs
self.add_to_goals = add_to_goals
self.pattern = pattern
#如果提供了目标偏移量,则更新DMP目标位置.作用:允许动态调整目标位置以适应新的环境或任务需求
if add_to_goals is not None:
    for ii, dmp in enumerate(self.dmp_sets): 
        dmp.goal[0] += add_to_goals[ii*2]
        dmp.goal[1] += add_to_goals[ii*2+1] 
#定义了check_pen_up、gen_path、set_next_seq、set_target函数

add_to_goals就是用于设置目标值(偏差量)

2、control函数(trajectoy.py)

这个函数在sim_and_plot中调用了,作用是控制机械臂

  1. 保存当前末端执行器位置:使用 np.copy(arm.x) 将当前末端执行器的位置复制到 self.x 中。这确保了即使后续操作修改了 arm.x,原始值仍然被保留下来以供参考。

  2. 检查与目标的距离:调用 self.controller.check_distance(arm) 来计算末端执行器当前位置与目标位置之间的距离,并将其与阈值 (self.threshold) 进行比较。如果距离小于阈值,则将 self.not_at_start 设置为 False,表示机械臂已经到达目标位置附近。

  3. 判断是否应用控制信号:如果 self.not_at_start 或者 self.done 为 True,则直接调用控制器的 control 方法生成控制信号并返回。否则,进入下一步逻辑。

  4. 设置新目标:如果机械臂尚未到达起始位置(即 sef.not_at_start 和 self.done 都为 False),则调用 self.set_target() 方法来设置新的目标位置。

  5. 检查是否需要抬起笔:调用 self.check_pen_up() 方法来决定是否应该抬起笔。如果是最后一次动态运动基元 (DMP) 并且需要抬起笔,则结束程序(通过 sys.exit())。如果不是最后一次 DMP,则更新序列编号 (self.num_seq),准备下一个 DMP,并重新设置目标位置。

  6. 确定控制类型和计算期望位置:根据控制器类型(osc.Control 或 gc.Control),选择使用末端执行器位置 (arm.x) 或关节角度 (arm.q) 作为当前位置 (pos)。计算期望位置偏差 (pos_des),这是基于目标位置 (self.controller.target) 和当前位置 (pos) 的差异,并乘以增益 (self.gain)。

  7. 生成并返回控制信号:最后,调用控制器的 control 方法,传入机械臂模型和期望位置偏差 (pos_des),生成最终的控制信号 (self.u) 并返回。

    pos_des = self.gain * (self.controller.target - pos)
    self.u = self.controller.control(arm, pos_des)

    这里本质调用的osc.Control类中的control()函数,参数arm, x_des

3、check_pen_up函数

基于 DMP 的相位系统 (canonical system) 判断时间是否已接近轨迹末端,如果是,则返回 True

4、gen_path函数(传入trajectory参数,DMP学习阶段)

根据输入轨迹生成 DMP 模型,每段轨迹可能包含多个子轨迹(如绘图中的笔划分段)

输入:trajectory参数

返回:每段轨迹的DMP学习结果

  1. 判断输入轨迹维度,确保统一格式。
  2. 找出trajectory轨迹中的中断点(NaN 或 None 标记的断点)
  3. 根据分段轨迹使用多个 DMP 模型。
  4. 对每段轨迹建立一个DMP模型,并把这段的轨迹(seq从trajectory切片出来的)用于DMP学习,最后把学习后的结果添加到dmp_sets序列中。
    dmps = DMP_discrete.DMPs_discrete(n_dmps=num_DOF, n_bfs=self.bfs)
    dmps.imitate_path(y_des=seq)
    self.dmp_sets.append(dmps)

5、set_next_seq函数

切换到下一段轨迹(多段轨迹的控制)

self.dmps = self.dmp_sets[self.num_seq]

6、set_target()函数(DMP运用阶段)

   def set_target(self):
        """Get the next target in the sequence.动态调整目标,适应环境变化
        """
        error = 0.0
        if self.controller.target is not None:
            error = np.sqrt(np.sum((self.x - self.controller.target)**2)) * 1000
        self.controller.target, _, _ = self.dmps.step(tau=self.tau, error=error)

Shell类中有一个controller属性,在write.py实例化control_shell的时候给到了osc.Control类型,因此set_target()函数这里是为了设置osc.Control类的target属性,是由dmps单步计算出来的。

关于osc.Control类的介绍见下面(五)


(四)sim_and_plot.py

用的时候这么用的

runner = Runner(dt=dt, **runner_pars)
runner.run(arm=arm, control_shell=control_shell,
           end_time=(float(args['--end_time'])
                     if args['--end_time'] is not None else None))
runner.show()

Runner类

用于模拟和可视化机械臂的运动

1、定义参数

  • title: 设置绘图窗口的标题。
  • dt: 模拟的时间步长,默认为 1×10−31×10−3 秒。
  • control_steps: 控制信号更新的频率,默认每一步更新一次。
  • display_steps: 显示更新的频率,默认每一步更新一次。
  • t_target: 目标位置更新的时间间隔,默认为1秒。
  • control_type: 控制类型的标识符,例如 'random' 或其他自定义类型。
  • trajectory: 预定义的轨迹数据,默认为 None
  • infinite_trail: 是否保持无限轨迹,默认为 False
  • mouse_control: 是否启用鼠标控制,默认为 False

2、run函数

  1. 设置绘图框:根据机械臂的自由度 (DOF) 设置绘图框的边界 (box)。

  2. 创建图形窗口:使用 Matplotlib 创建一个图形窗口,并设置标题和网格线。添加子图并设置坐标轴范围,确保绘图区域是正方形。

  3. 设置绘图元素:初始化轨迹、机械臂线条、目标线条和信息文本。

  4. 绘制轨迹:如果提供了轨迹数据,则在图中绘制出来作为参考。

  5. 连接鼠标事件:如果启用了鼠标控制,则将 move_target 函数绑定到鼠标移动事件上,允许用户通过鼠标动态设置目标位置。

  6. 创建动画:使用 FuncAnimation 创建动画,其中 anim_animate 是每一帧的更新函数,而 anim_init 用于初始化动画状态。.

anim = animation.FuncAnimation(fig, self.anim_animate,init_func=self.anim_init, frames=5000, interval=0, blit=True)

除了run还有这些函数:

  • make_info_text: 构建包含当前仿真时间和关节角度 (q) 及控制信号 (u) 的文本信息。
  • anim_init: 初始化动画状态,清空所有绘图元素的数据。
  • anim_animate: 更新每一帧的动画内容,包括机械臂位置、目标位置、信息文本和轨迹。(下面重点讲)
  • show: 展示图形窗口。

3、anim_animate更新函数

  • (1)检查结束条件:如果设置了结束时间 (end_time),并且当前仿真时间超过了该时间,则停止动画并关闭图形窗口。

  • (2)更新目标位置:根据 control_type 更新目标位置。如果控制类型为 'random',则在每个目标更新时间间隔后生成新的随机目标;否则使用控制器中的目标。

        if self.control_type == 'random':
            # update target after specified period of time passes
            if self.sim_step % (self.target_steps*self.display_steps) == 0:
                self.target = self.shell.controller.gen_target(self.arm)
        else:
            self.target = self.shell.controller.target

其中的self.shell.controller是osc.Control()类型的

  • (3)应用控制信号:在每个显示步骤内,根据控制步骤更新控制信号 (tau) 并应用到机械臂上。

  • (4)更新绘图元素:更新机械臂线条、信息文本、轨迹和目标线条以反映最新的状态。

  • (5)更新手部轨迹:如果笔处于放下状态 (pen_down),则更新轨迹数据;如果笔抬起,则在轨迹数据中插入断点。


(五)osc.py

上面提到继承关系osc.py(Control类)<-control.py(Control类)

Control类(osc.py)

1、初始化

这里有个参数target可以修改目标值?

有个参数DOF可以修改任务空间维度

(1)-----control.py(Control)------
# 定义参数
        self.u = np.zeros((2,1)) # control signal
        self.additions = additions ########
        self.kp = kp # 位置误差增益
        self.kv = kv # 速度误差增益
        self.task = task
        self.target = None  ###########目标值!#######
        self.write_to_file = write_to_file
        self.recorders = []
# 定义函数
    def check_distance(self, arm):
        """Checks the distance to target"""
        return np.sum(abs(arm.x - self.target)) + np.sum(abs(arm.dq))
# 声明函数
        control(self)

(2)-----osc.py(Control)-----
# 定义参数
        self.DOF = 2 # task space dimensionality任务空间维度
        self.null_control = null_control
        if self.write_to_file is True:
            from recorder import Recorder
            # set up recorders
            self.u_recorder = Recorder('control signal', self.task, 'osc')
            self.xy_recorder = Recorder('end-effector position', self.task, 'osc')
            self.dist_recorder = Recorder('distance from target', self.task, 'osc')
            self.recorders = [self.u_recorder,
                            self.xy_recorder,
                            self.dist_recorder]
# 定义函数
    def control(self, arm, x_des=None)
    def gen_target(self, arm)
    

1、control函数

参数:机械臂模型 (arm) ;可选的期望末端执行器位置 (x_des)

返回:控制信号 (self.u),该信号将被应用于机械臂以实现所需的运动。

  1. 计算期望的末端执行器加速度:如果 x_des 是 None,那么使用当前机械臂末端执行器的位置 (arm.x) 和目标位置 (self.target) 来计算期望的末端执行器加速度。使用比例增益 (self.kp) 计算误差并放大,得到期望的末端执行器加速度。
  2. 生成任务空间中的质量矩阵:使用机械臂模型的方法 (gen_Mq 和 gen_Mx) 分别生成关节空间和任务空间的质量矩阵 (Mq 和 Mx)。
  3. 计算作用力 (Fx):根据任务空间的质量矩阵 (Mx) 和期望的末端执行器加速度 (x_des) 计算作用力 (Fx)。
  4. 计算雅可比矩阵 (JEE):雅可比矩阵描述了关节空间速度与末端执行器速度之间的关系。
  5. 计算控制信号 (self.u):控制信号是通过将雅可比矩阵转置乘以任务空间的作用力 (Fx) 并减去一个基于关节速度补偿项来获得的。这里假设重力补偿已经包含在机械臂模型中,因此不需要额外添加。
  6. 处理空控件 (Null Space Control):如果启用了空控件 (self.null_control) 并且任务空间的自由度 (DOF) 少于机械臂的自由度,则计算一个附加的控制信号 (u_null),该信号试图将机械臂移动到其休息位置。计算空控件滤波器 (null_filter) 并应用到附加控制信号上,确保它只影响机械臂的空控件。
  7. 记录数据:如果设置了写入文件 (self.write_to_file),则记录控制信号、末端执行器位置以及目标位置与当前位置之间的距离。
  8. 添加额外信号:如果有其他需要添加的信号(例如来自外部模块),它们也会被加入到最终的控制信号中。

2、gen_target函数

用于需要动态或随机目标的应用场景

    def gen_target(self, arm):
        """Generate a random target"""
        gain = np.sum(arm.L) * .75
        bias = -np.sum(arm.L) * 0
        self.target = np.random.random(size=(2,)) * gain + bias
        return self.target.tolist()

(六)pydmps库函数

未完待续.....

Tip:

  1. kwargs一般表示其他参数传递给基类初始化函数
  2. super(Shell, self).__init__(**kwargs) # 调用父类 Shell 的初始化方法

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

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

相关文章

对Python中队列三种实现方式的测试

一、结论 本程序模拟比较队列的三种情况&#xff1a; 一、deque当作队列使用&#xff1b; 二、queue当作队列使用&#xff1b; 三、list当作队列使用。 结论&#xff1a; Python队列可以用deque、queue、list实现&#xff0c;其中list极慢、queue较慢&#xf…

【VScode】第三方GPT编程工具-CodeMoss安装教程

一、CodeMoss是什么&#xff1f; CodeMoss是一款集编程、学习和办公于一体的高效工具。它兼容多种主流平台&#xff0c;包括VSCode、IDER、Chrome插件、Web和APP等&#xff0c;支持插件安装&#xff0c;尤其在VSCode和IDER上的表现尤为出色。无论你是编程新手还是资深开发者&a…

Unity中如何修改Sprite的渲染网格

首先打开SpriteEditor 选择Custom OutLine,点击Genrate 则在图片边缘会出现边缘线&#xff0c;调整白色小方块可以调整边缘 调整后&#xff0c;Sprite就会按照调整后的网格渲染了。 如何在UI中使用&#xff1f; 只要在UI的Image组件中选择Use Sprite Mesh 即可 结果&#xff1…

【机器学习】探索机器学习与人工智能:驱动未来创新的关键技术

探索机器学习与人工智能&#xff1a;驱动未来创新的关键技术 前言&#xff1a;人工智能的核心技术深度学习&#xff1a;自然语言处理&#xff08;NLP&#xff09;&#xff1a;计算机视觉&#xff1a; 机器学习与人工智能的驱动创新医疗健康领域金融行业智能制造与工业互联网智慧…

Visual Studio Code(VS Code)配置C/C++环境

一、Visual Studio Code安装 Visual Studio Code&#xff0c;下文中简称为VS Code的详细安装方法请参考VSCode安装教程&#xff08;超详细&#xff09;-CSDN博客 二、MinGW编译器下载与配置 1、MinGW介绍 MinGW(Minimalist GNU for Windows)是一款用于Windows 平台的轻…

Postman接口测试01|接口测试基础概念、http协议、RESTful风格、接口文档

目录 一、接口测试基础概念 1、什么是接口 2、接口的类型 3、什么是接口测试 4、为什么要做接口测试 5、接口测试的实现方式 6、什么是自动化接口测试&#xff1f; 二、接口返回的数据格式 1、三种格式 2、Json 三、接口协议 1、webservice协议 2、dubbo协议 3、…

HDR视频技术之十一:HEVCH.265 的 HDR 编码方案

前文我们对 HEVC 的 HDR 编码优化技术做了介绍&#xff0c;侧重编码性能的提升。 本章主要阐述 HEVC 中 HDR/WCG 相关的整体编码方案&#xff0c; 包括不同应用场景下的 HEVC 扩展编码技术。 1 背景 HDR 信号一般意味着使用更多比特&#xff0c;一般的 HDR 信号倾向于使用 10…

ThinkPHP 8开发环境安装

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《ThinkPHP 8高效构建Web应用 夏磊 编程与应用开发丛书 清华大学出版社》【摘要 书评 试读】- 京东图书 1. 安装PHP8 Windows系统用户可以前往https://windows.php.net/downloads/releases/archives/下载PHP 8.0版本&am…

CAN201 Introduction to Networking(计算机网络)Pt.2 传输层

文章目录 3. Transport Layer&#xff08;传输层&#xff09;3.1 Multiplexing and demultiplexing&#xff08;多路复用和多路分解&#xff09;3.2 Connectionless transport&#xff1a;UDP3.3 Principles of reliable data transfer3.4 Pipelined communication3.5 TCP: con…

linux系统上SQLPLUS的重“大”发现

SQL plus版本&#xff1a; [oraclepg-xc2 ~]$ sqlplus -v SQL*Plus: Release 19.0.0.0.0 - Production Version 19.3.0.0.0 操作系统&#xff1a;CentOS Linux 7 (Core) 数据库&#xff1a;Oracle 19c Version 19.3.0.0.0 同样的SQL脚本在windos CMD sqlplus 执行没问题。…

Unity中实现人物残影效果

今天火柴人联盟3公测了&#xff0c;看到一个残影的效果&#xff0c;很有意思&#xff0c;上网查询了一下实现方式&#xff0c; 实现思路&#xff1a; 将角色的网格复制出来&#xff0c;然后放置到新建的物体的MeshFilter组件上&#xff0c;每隔几十毫秒在玩家的位置生成一个&a…

mysql mmm和mha对比

本文简单介绍MySQL的两个high availability方案&#xff0c;MMM和MHA。 MMM MMM(Master-Master replication managerfor Mysql)的基本组成如下&#xff0c; 主节点master1&#xff1a;承载写流量备主节点master2&#xff1a;replicate主节点master1的写流量&#xff0c;在主节…

探秘“香水的 ChatGPT”:AI 开启嗅觉奇幻之旅!

你没有看错&#xff0c;AI也能闻到味道了&#xff01;这是一家名为Osmo公司公布的信息&#xff0c;他们成功创造出了由AI生成的李子味道&#xff0c;快跟着小编一探究竟吧~ 【图片来源于网络&#xff0c;侵删】 Osmo公司的这项技术&#xff0c;通过分析香味的化学成分和人类嗅…

uniapp实现为微信小程序扫一扫的功能

引言 随着微信小程序的快速发展,越来越多的开发者开始关注和学习微信小程序的开发。其中,微信小程序的扫一扫功能是非常常用且实用的功能之一。通过扫描二维码,用户可以获取到相关的信息或者实现特定的功能。 正文 在过去,开发者需要使用微信开发者工具以及相关的开发文档…

网络ip地址改成什么比较好

在数字化时代&#xff0c;网络IP地址作为设备在网络中的唯一标识&#xff0c;扮演着至关重要的角色。然而&#xff0c;随着网络环境的日益复杂和网络安全问题的频发&#xff0c;许多用户开始考虑更改自己的网络IP地址&#xff0c;以期获得更好的网络体验和安全保障。那么&#…

iOS从Matter的设备认证证书中获取VID和PID

设备认证证书也叫 DAC, 相当于每个已经认证的设备的标识。包含了 VID 和 PID. VID: Vendor ID &#xff0c;标识厂商 PID: Product ID&#xff0c; 标识设备的 根据 Matter 对于设备证书的规定&#xff0c;DAC证书subject应该包含VID 和 PID. 可通过解析 X509 证书读取subject…

聊一聊 C#线程池 的线程动态注入

提高注入速度的两种方法 1. 降低GateThread的延迟时间 上一篇跟大家聊过 Result 默认情况下GateThread每秒会注入4个&#xff0c;底层逻辑是由 Blocking.MaxDelayMs250ms 变量控制的&#xff0c;言外之意就是能不能减少这个变量的值呢&#xff1f;当然可以的&#xff0c;这里我…

【嵌入式C语言】指针数组结构体

指针与数组 指针与数组指针数组数组指针 多维数组数组名的保存 结构体定义结构体定义结构体变量使用typedef简化结构体声明访问结构体成员结构体内存分配字节对齐位域定义位域位域的限制示例 指针与数组 指针数组和数组指针是两个不同的概念&#xff0c;它们涉及到指针和数组的…

HarmonyOS NEXT 实战之元服务:静态案例效果(二)

背景&#xff1a; 前几篇学习了元服务&#xff0c;后面几期就让我们开发简单的元服务吧&#xff0c;里面丰富的内容大家自己加&#xff0c;本期案例 仅供参考 先上本期效果图 &#xff0c;里面图片自行替换 效果图代码案例如下&#xff1a; Index里面实现 import { authent…

Android Studio超级详细讲解下载、安装配置教程(建议收藏)

博主介绍&#xff1a;✌专注于前后端、机器学习、人工智能应用领域开发的优质创作者、秉着互联网精神开源贡献精神&#xff0c;答疑解惑、坚持优质作品共享。本人是掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战&#xff0c;深受全网粉丝喜爱与支持✌有…