使用水星Mecury人形机器人搭建VR遥操作控制平台!

VR遥操作机械臂是一种将虚拟现实技术与机械臂控制相结合的系统,使用户可以通过虚拟现实设备操控和交互实际的机械臂。这种技术可以应用于多个领域,包括远程操作、培训、危险环境中的工作等。

双臂人形机器人是一种模拟人体上半身结构,包括头部、躯干和双臂的机器人。这种机器人设计的目标是模仿人类的上半身动作和功能,以执行各种任务,从而在虚拟现实(VR)领域中有广泛的应用。

合虚拟现实和人型机器人可以为许多领域带来创新和改进,提高用户体验,扩展应用领域,促进技术的发展。这种结合使得虚拟和现实之间的交界更加模糊,为未来创造了令人兴奋的可能性。

硬件介绍

Mecury X1

这是一款名为“Mercury X1”的人形机器臂,由Elephant Robotics 精心研发。17个自由度(DOF),使其具有极高的灵活性和适应性。工作电压为24V,配备了一个9英寸的量子点触控屏,既现代又高效。Mercury X1最大工作半径为450毫米,最大负载能力为1公斤,净重8公斤,非常适合轻量级的操作任务。

它的重复定位精度高达±0.05毫米,意味着它在进行精密作业时非常可靠。机器人的寿命预计为5000小时,表明了其出色的耐用性。主控制单元采用了Jetson Xavier,辅助控制则由ESP32*1负责,这样的配置保证了其强大的数据处理能力和稳定的控制性能。还包含了碳纤维外壳材料,使得机器臂不仅坚固耐用,同时也轻便。它装备了Orbbec Deeyea 3D摄像头和一个具有4个麦克风的线性阵列声音模块,能够进行高效的视觉和声音处理。通信方面,支持WIFI、Ethernet、蓝牙、USB端口和RS485,兼容ROS1/ROS2,这意味着它能够轻松集成到各种智能制造和研究环境中。

VR Oculus Quest 2

是由Facebook的子公司Oculus VR开发的一款虚拟现实(VR)头戴设备。Quest 2有开发者模式,它允许开发者直接在设备上安装和测试自己开发的应用。主要是由游戏引擎unity和unreal engine平台支持。

VR控制机器人项目

软件架构和交互设计

VR遥操作首先要解决的问题就是操作者和机器人的通信问题,在这方面我选择的是基于HTTP协议的通信,服务器选择Flask。虽然选择Python可能会对性能产生不利影响,但是因为Mercury机器人提供的Python SDK——pymycobot是Python的库,因此Python作为开发语言是最方便与机器人集成的选择,非常适合验证项目的可行性。

VR端我们选择了Unity3D,丰富的社区资源使得这个项目成为可能。

服务模型为经典的C/S结构,VR端通过访问特定的URL向机器人发送不同的命令,再通过服务器转发到pymycobot,执行实际的动作。

另一方面是交互方面的设计我们采用相对移动的方式,用户按下手柄的A键标志着移动开始,松开标志着移动结束。

下面是VR遥控操作的通信流程:

实时视频流

在克服VR遥操作技术难题的过程中,确保获取低延迟的视频流一直是关键挑战之一。我们采用了一项创新性的解决方案,通过利用NVIDIA Jetson Xavier平台所提供的Accelerated GStreamer插件,成功实现了GPU加速的视频编解码,旨在在保障实时性的同时,最大程度地优化带宽利用率。

Jetson Xavier平台的Accelerated GStreamer插件是一种强大的工具,通过充分利用GPU的计算能力,对视频数据进行加速处理。这一创新性的技术手段不仅提高了视频编解码的效率,同时在大幅度减少延迟的同时,优化了网络带宽的利用效率。

Accelerated GStreamer是NVIDIA为其Jetson平台提供的一组GStreamer插件,旨在通过使用GPU(图形处理单元)加速多媒体处理任务,提高性能并降低延迟。这一套插件专为Jetson平台设计,充分利用了其强大的GPU资源,适用于视频编解码、图像处理、深度学习推理等多媒体应用。

Accelerated GStreamer — Jetson Linux Developer Guide documentation

实现过程

首先,下载编译并编译GStreamer官方提供的rtsp server源代码https://github.com/GStreamer/gst-rtsp-server/blob/1.14.5/examples/test-launch.c

编译好后会有一个test-launch的文件

通过GStreamer命令构造推流管线,这里测试采用的是


```bash
nvarguscamerasrc sensor-id=0 ! video/x-raw(memory:NVMM), format=NV12, width=3264, height=2464, framerate=21/1 ! nvv4l2h264enc insert-sps-pps=true ! h264parse ! rtph264pay name=pay0 pt=96
````提示:! 用于分隔不同的 GStreamer 元素`

nvarguscamerasrc sensor-id=0:

nvarguscamerasrc是 NVIDIA 提供的用于处理 Argus 相机的 GStreamer 插件。

sensor-id=0 指定使用 ID 为 0 的相机传感器。在多摄像头系统中,可以选择不同的相机传感器。

video/x-raw(memory:NVMM), format=NV12, width=3264, height=2464, framerate=21/1:

video/x-raw(memory:NVMM) 表示输出的是 NVIDIA 内存管理的原始视频数据。

format=NV12 指定了视频帧的格式为 NV12,这是一种 YUV 格式。

width=3264, height=2464 指定了视频帧的宽度和高度。

framerate=21/1 指定了视频的帧率为 21 帧每秒。

nvv4l2h264enc insert-sps-pps=true:

nvv4l2h264enc 是 NVIDIA 提供的 H.264 编码插件。

insert-sps-pps=true 表示在输出流中插入 SPS(序列参数集)和 PPS(图像参数集),这对于 H.264 视频流的解码是必需的。

h264parse:

h264parse 插件用于解析 H.264 数据流。

rtph264pay name=pay0 pt=96:

rtph264pay 插件用于封装 H.264 数据流为 RTP 包。

name=pay0 为该 RTP Payloader 指定了名称。

pt=96 指定了 RTP 负载类型(Payload Type),这里设置为 96。

将GST命令与RTSP Server联合使用,输入命令


```bash
./test-launch "nvarguscamerasrc sensor-id=0 ! video/x-raw(memory:NVMM), format=NV12, width=3264, height=2464, framerate=21/1 ! nvv4l2h264enc insert-sps-pps=true ! h264parse ! rtph264pay name=pay0 pt=96"
```

可以在同局域网下的另一台主机上通过RTSP协议访问当前主机来测试结果(VLC播放器可以接入RTSP协议流直接进行播放)。

最后,将test-launch.c内绑定的URL重新命名为leftright。并将开启和关闭写入脚本方便执行。

```bash
# launch-rtsp.sh
width=1920
height=1080
framerate=28
./left-rtsp-server "nvarguscamerasrc sensor-id=1 ! video/x-raw(memory:NVMM), format=NV12, width=$width, height=$height, framerate=$framerate/1 ! nvv4l2h264enc insert-sps-pps=true maxperf-enable=1 bitrate=8000000 ! h264parse ! rtph264pay name=pay0 pt=96" &
./right-rtsp-server "nvarguscamerasrc sensor-id=0 ! video/x-raw(memory:NVMM), format=NV12, width=$width, height=$height, framerate=$framerate/1 ! nvv4l2h264enc insert-sps-pps=true maxperf-enable=1 bitrate=8000000 ! h264parse ! rtph264pay name=pay0 pt=96" &
``````bash
# stop-rtsp.shps -al | grep "left\|right" | awk '{print $4}' | xargs kill
```

遥操作系统

VR遥操作系统面临的主要挑战在于要协调处理网络的不稳定性与对机械臂等硬件需要稳定输入的矛盾。这两者之间的平衡是确保远程遥操作系统有效运行的关键因素之一。

首先,网络的不稳定性可能导致延迟、数据包丢失或者不确定性的带宽情况。这种情况会直接影响到远程用户与机械臂之间的实时交互。在VR遥操作系统中,用户需要感受到虚拟环境中的实时变化,并对机械臂的运动进行即时响应。网络延迟可能导致用户的指令与机械臂的实际动作之间存在明显的滞后,降低了操作的准确性和流畅性。

另一方面,机械臂等硬件设备对输入的稳定性要求较高。由于遥操作系统需要实时传输用户输入到机械臂,输入信号的不稳定性可能导致机械臂的运动异常或失控。这对于需要高精度和可靠性的任务,如手术操作、工业维护等,可能带来严重的问题。

VR遥操作的困局(dilemma)

为了帮助你理解这个问题,首先要了解机械臂的运动方式。在传统机械臂运动中,发给机械臂一个点位,机械臂会自动规划到达目的地的加速与减速,以达到流畅顺滑的移动;但是由于机械臂会自动进行加速减速的规划,如果用这个方法去做遥操作,就会遇到机械臂在每两个采样点上频繁加速减速的过程,导致机械臂无法连续运动。而在遥操作情况下,加速减速应该由操作者的手运动来控制,因此理论上如果想要实现顺畅的遥操作,则需要机械臂有一个可以放弃自动规划,完全使用采样点来进行插值运动的接口。我们将这个接口命名为“速度融合接口”。

但是机械臂的底层插值运动接口通常对信号的稳定性有着极高的要求,必须要有毫秒级的稳定性才能保证机械臂稳定运行;这种完全采用采样点的插值底层运动接口,在Mercury机器人上的VR控制方式为在给定时间内,向指定目标点移动,举个例子,你输入了一个坐标,还有一个时间50ms,那么机械臂就会在这50ms内向着你发的坐标移动,如果到了就会停止(这也带来一个问题,后面会细说),如果机械臂没到目标位置,但是时间到了的话也会停止。

理想状态下,如果你持续且稳定地用速度融合直接给机械臂输入移动点位的命令,且你发送的间隔,刚好和你给定的运动时间片是完全匹配的,那么理论上机械臂此时在速度不超过限速的情况下,能够完全跟随人手。

但是此时如果下一条指令没有及时到来(可能因为网络带来的延迟波动),机械臂此时就会立刻急停,但是又因为没有减速的过程,此时机械臂就会因为急停而产生抖动。

一个合理的改进方案是加入缓存,缓存会平滑网络延迟带来的误差,给机械臂一个相对稳定的指令流,但是随之而来的问题就是实时性的降低。因为如果缓存发挥作用,那么实际下发的速度融合参数内给定的时间片就必须要小于实际的发送间隔,因为只有消费的指令比生产的指令更慢,才能保证缓存区内始终有一定量的指令可以下发,避免产生没有指令导致急停的尴尬。但是这样就会导致在网络稳定地情况下,缓冲区一直是满的,给整个控制系统加上了一个固定的时间片x缓冲区大小的延迟。

机械臂需要稳定地控制信号,因为它的运动本质上由电机驱动。在常规的点位移动中,机械臂会通过内部自动规划加速和减速,从而实现相对较为稳定的运动。然而,在遥操作领域,实时性是至关重要的,因此通常需要设计速度融合接口,将由VR端采样得到的点位直接下发给机械臂。这样的设计是为了确保实时性,但同时也带来了延迟和稳定性难以兼得的问题。

如果我们追求实时性,从网络传来一个点位就立刻发送,由于我们无法完全预测下一个控制信号何时到来,则提供的时间参数可能就会和实际产生偏移,如果给的太少就会造成机械臂速度连不起来,无法运动;给的太多就会造成机械臂还没运动完,下一个指令就发来了,造成指令堆积,降低实时性。如果追求稳定性,那就需要增加缓冲队列,用缓冲队列来平滑通信方式带来的延迟波动。但是同样的,这样就会增加控制延迟。这也就是遥操作系统的困局:延迟和稳定性不可兼得

VR端实现

VR端的实现采用了Unity3D + XR Interactive Toolkit, 选择Unity3D主要是因为其简单性,可以快速上手,且能支持多种VR平台。如果采用虚幻引擎可能需要花费大量时间在此工程之外的问题上。XR Interactive Toolkit是Unity3D的官方VR框架,使用这套系统而不是Oculus插件能够使项目方便的移植到其他VR设备上。

开发环境方面,除了使用Unity3D自带的Editor以外,C#编辑器使用Visual Studio 2022社区版。导入了UMP插件作为RTSP Player,以及Oculus官方的基本VR素材。

最开始的时候,我是打算采用绝对坐标一比一的转发手柄的动作,但是我发现这样操作很不方便。首先,Mercury机器人的臂展大概只相当于十几岁的小孩,如果使用完全绝对坐标1:1控制很容易导致机械臂撞到关节限位。而且手柄的初始位置也是个问题。因此最后我决定采用相对的控制方式,只有在按住特定按键的时候才允许机械臂移动,这样做的好处就是:在没有按下按钮的时候,你可以随意调整姿势;而在你开始运动的时候,机械臂永远是以你当前点为基准点进行相对运动,这样控制起来就容易得多了。

图传方面最开始的做法是,用服务器转发MJPG图片到VR端,然后以texture的方式渲染到屏幕上,这种方式的好处就是实现简单。缺点就太多了,首先就是延迟和CPU负载的问题;如果直接使用服务器转发图片,不但要至少多一次拷贝,还很难调用Nvidia自带的编解码器。而且在VR端也需要时间进行解码拷贝,整体延迟和CPU负载都很高。后来我想到了使用GStreamer+NV加速插件的方案,也就是上面说到的,利用了NV硬件加速以后,延迟和负载都得到了大幅度的改善。

除此之外,在开发过程中,我也对Unity3D+Quest 3作为遥操作平台有了更深的了解。首先在Unity3D中,几乎所有的运算都是和帧对齐的,虽然你可以开线程,但是游戏引擎给你提供的资源几乎都是按帧进行刷新的。比如我能获取到的手柄坐标,我能获取到的最大刷新率就是等于游戏帧率。因此在这个平台上,不考虑插值等操作,遥操作控制频率采样的上限其实就是帧率,这个数字通常是70-90hz每秒。因此我没有采用多线程来发送信息,而是使用了Unity3D中最普遍的做法:协程。使用协程能够保证你的操作和帧是对齐的,能够避免很多因为不同步导致的奇怪问题。

```c#
// One example of coroutine function
IEnumerator sendUpdate()
{if (!flag_origin_initialized) yield break;Vector3 pos = gameObject.transform.position;pos -= origin_pos;pos *= 1000;Vector3 rotation = gameObject.transform.rotation.eulerAngles;if (isZero(pos) || isZero(rotation))yield break;rotation = angleConvert(rotation);string content = $"[{pos.x:F2},{pos.y:F2},{pos.z:F2},{rotation.x:F2},{rotation.y:F2},{rotation.z:F2},{System.DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond}]";Debug.Log(content);using (UnityWebRequest www = UnityWebRequest.Post(updateURL, content, "application/json")){www.timeout = 1;yield return www.SendWebRequest();Debug.Log(www.result);if (www.result != UnityWebRequest.Result.Success){Debug.Log(www.error);}}
}// Use this in main thread (eg. inside Update() to sync with main loop)
StartCoroutine(sendUpdate());```
服务器端实现

服务器方面我使用的是简单可靠的Flask作为框架,因为我需要的仅仅只是作为RPC用途的控制框架。精简的Flask就能可以很好的完成这个任务。

在控制系统方面,Unity3D和机械臂平台的对齐也是一大难点,即如何将VR世界的坐标,翻译为机械臂能听懂的坐标。因为Mercury机器人的手臂,本质上是由两个Mercury单臂组成的,在单臂自己的视角下,它的坐标实际上是以它自己的底座,也就是胳膊大臂关节的位置为原点的,而不是以我们期望的——腰部为原点。因此需要设计一套坐标转换工具来实现从VR世界,到Mercury的基坐标系,再到单臂的坐标的转换。这个过程使用到了大量的线性代数工具和机器人学理论。

比如下面这段代码实现了VR坐标到基坐标系的转换


```python
def vr_to_base(posture):position = np.array(posture[:3])rotation = np.array(posture[3:])matrix = cvt_euler_angle_to_rotation_matrix(rotation)T = np.vstack((np.hstack((matrix, position.reshape(3, 1))), np.array([0, 0, 0, 1])))TRB = np.array([[0, 0, 1, 0], [-1, 0, 0, 0], [0, 1, 0, 310], [0, 0, 0, 1]])Tp = np.dot(TRB, T)rotation_matrix = Tp[:3, :3]position = Tp[:3, 3]rotation = cvt_rotation_matrix_to_euler_angle(rotation_matrix)return np.hstack((position, rotation))
```

一个有趣的问题就是左右臂互为镜像的问题,我和负责机器人算法的工程师不得不分别给两条臂使用不同的固件以兼容使用同一套转换算法。

前文提到,机械臂期望稳定且固定间隔的控制信号,而实现固定间隔就需要缓存系统和高精度的计时的下发系统。

首先是缓存问题,缓存主要考虑的一个问题就是线程安全。因为服务器本身是多线程运行的,两个网络包可能会同时到达,或者网络包到达时刚好碰上下发,因此设计一个线程安全的双端队列作为缓存区是首要任务。其次就是缓冲区的大小,下发时间的间隔,以及时间片参数的问题;这些参数也是下发系统延迟表现和稳定表现的均衡器,这些参数需要大量的实验来调试,以达到最佳表现。其中尤为难以平衡的是下发时间的间隔和时间片,因为网络延迟是不确定的,机器处理运算也需要时间,因此实际需要的时间是要比单纯下发的间隔要长的,具体长多少也是不固定的。因为这一层原因,实际上参数的设置需要更加保守才能保证系统运行稳定。

Python标准库中自带的time精度并不理想。因此单纯靠sleep来实现计时肯定是不可行的。我目前的做法是单独开一个线程,以大量占用CPU为代价高速轮询来实现低延迟下发。实测证明这种方式的延迟是要比直接给一个完整的sleep要低的。

class MyThread(Thread):...def run(self) -> None:while running:if len(self.bucket)!= 0 and time.time_ns() - self.last_time > self.time_slice:# do somethingself.last_time = time.time_ns()time.sleep(0.00001)
优化

在基本框架完成以后,还可以对下发点位进行简单的滤波,以进一步去除抖动。人手在使用VR的时候不可能确保完美的停在一个点,在做直线运动的时候也不可能保证是完美的直线。我们可以单独对每个分量进行控制,过滤掉较低的分量,以实现更稳定地运动轨迹。

a = final_base_coords[:3]
b = self.last_arm_base_coords[:3]
if self.last_arm_base_coords is not None:diff = a - b
final_base_coords[:3] = np.where(abs(diff) > 5, a, b)

另一个可能的优化是对轨迹进行平均化操作,这样可以使得轨迹更加平滑,但是可能的trade off是要进一步增加延迟。以下是一个基于双端队列的滑动窗口的实现。

class SlidingWindow:def __init__(self, maxlen=5) -> None:self.lock = Lock()self.store = deque(maxlen=maxlen)for i in range(maxlen):self.store.append(1)self.maxsize = maxlendef append(self, obj):with self.lock:self.store.append(obj)def mean(self):with self.lock:return np.mean(np.array(self.store), axis=0)def clear(self):with self.lock:self.store.clear()def size(self):with self.lock:return len(self.store)

机械臂运动控制

1.  动作捕捉:佩戴VR设备(如头盔和手套)进行实际动作。这些设备通过内置传感器捕捉用户的动作数据(如头部方向、手部位置和手势),并将这些数据实时传输到控制系统。

2.  虚拟现实界面:用户在VR环境中可以看到虚拟的机械臂模型,并通过VR设备进行操作。用户的动作被映射到虚拟模型上,实时转化为机械臂的运动指令。

3.  数据处理和传输:捕捉到的动作数据需要通过软件处理,转化为机械臂可以理解的指令。这些指令通常涉及关节角度、速度或位置信息的计算。然后,这些指令通过网络发送给机械臂控制器。

4.  机械臂执行:机械臂接收到指令后,通过其内部控制系统执行相应的动作。这可能包括移动到特定的位置、按照特定的路径移动或执行复杂的手部动作。

5.  反馈机制:为了提高操作的精确性和用户体验,系统可能包括反馈机制,如触觉反馈或视觉反馈,将机械臂的状态实时反映到VR环境中,以便用户调整其操作。

案例展示

https://youtu.be/mvpHFXcadNk

Summary

VR技术的进步正在打破物理空间的限制,使人们能够在虚拟环境中实现复杂的操作和互动,这对于远程控制、教育、医疗等领域具有重大意义。未来,随着VR技术的持续发展,我们可以预见更多创新的应用出现。例如,VR在模拟复杂手术、远程教育、灾难响应训练等领域的应用将更加广泛。

如果是你,你会怎样来使用Mercury X1呢?

大象机器人今天正式发布Mercury系列机器人,目前水星系列机器人共有三款产品,Mercury-A1七轴协作机械臂,Mercury-B1 半人形双臂机器人,Mercury-X1通用轮式人形机器人。三款产品的工业设计皆由瑞典团队精心设计而成,集成七大机器人核心算法,多种使用与开发方式。

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

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

相关文章

跨域浏览器解决前端跨域问题

1.问题背景 这是一种属于非主流的解决跨域的方案,但是也是可以正常使用而且比较简单的。如果需要使用主流的解决前端跨域方案,请参考这篇文章。 我这边其实是优先建议大家使用主流的跨域方案,如果主流的实在不行,那么就使用跨域…

多路复用IO、TCP并发模型

时分复用 CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系…

通过POST请求往Elastic批量插入数据

文章目录 引言I 请求文档请求参数请求例子引言 调试工具:Apifox 需求: 向Elasticsearch中的’test_index’索引批量插入文档 情况认证: Basic Auth 在 Header 添加参数 Authorization,其值为在 Basic 之后拼接空格,以及经过 Base64 编码的 {{Username}}:{{Password}} 示…

H3CNE(STP)

8.1 二层环路与STP的介绍 8.1.1 二层环路 8.1.2 冲突域 8.1.3 二层环路带来的问题 8.1.4 STP的基本概念:桥ID 8.1.5 STP的基本概念:根桥 8.1.6 STP的基本概念:Cost 8.1.7 STP的基本概念:Port ID 8.1.8 STP的基本概念:…

Ubuntu22.04下 MySQL8创建并使用存储过程

在Ubuntu下的MySQL 8中创建并使用存储过程: 使用mysql命令登录到MySQL服务器,例如: mysql -u root -p输入root用户的密码。 选择你想要创建存储过程的数据库,例如: CREATE DATABASE mydb;USE mydb;CREATE TABLE us…

pikachu Fileinclusion(local)

随便选择一个都试试 发现url上数字会变 发现文件名确实是file1.php~file5.php 那么会不会还有别的burp抓包选中数字 设置6-100的爆破 strat attack 678异常还有个100也是 先改一下试试看 其他的会报错 但是通过这我们可以得到路径 先写一个 下一步 读取系统文件 windows系统肯定…

【unity 新手教程 001/100】安装与窗口布局介绍

欢迎关注 、订阅专栏 【unity 新手教程】谢谢你的支持!💜💜 Unity下载与安装 👉点击跳转详细图文步骤:Unity Hub Unity 编辑器 窗口布局: Hierarchy: 层级窗口 | 默认 Sample Scene (main camera、direc…

矩阵分析——线性积分方程组的矩阵解法研究

矩阵分析——线性积分方程组的矩阵解法研究 前言线性积分方程组的矩阵解法研究 前言 “矩阵分析”是一门选修课,当时选这门课程的原因是想着图像处理就涉及到很多矩阵运算。但没想到的是这门课程吧虽然是选修,最后的结课要求是让我们写一篇“论文”&…

.dat 文件如何查看内容

第一种通过hexdump -C hexdump -C potk-00199D-0435-20000201.dat 命令查看 第二种 vscode ,将文件拖到vscode 工作区,选中要显示的乱码,点击右上角HEX图标,如下面两张图示

UM980高精度RTK定位模块支持哪些通信接口?

注 产品参数信息请以和芯星通最新版官方手册为准。

python-NLP:1中文分词

文章目录 规则分词正向最大匹配法逆向最大匹配法双向最大匹配法 统计分词语言模型HMM模型 jieba分词分词关键词提取词性标注 规则分词 基于规则的分词是一种机械分词方法,主要是通过维护词典,在切分语句时,将语句的每个字符串与词表中的词进行…

Java代码基础算法练习-字符串分类统计-2024.07.24

任务描述: 输入一行字符(字符串长度不超过255),分别统计出其中英文字母、数字、空格和其他 字符的个数。(提示,空格ASCALL码值为32) 解决思路: 输入一字符串,先判断是否…

对递归的一些理解。力扣206题:翻转链表

今天在刷力扣的时候,在写一道翻转链表的题目的过程中,在尝试使用递归解决该问题的时候,第一版代码却每次都返回的是null,这个错误让我尝试去debug了一下,最终找出了问题,并且让我对递归有了一些更深的理解&…

顶级电子合同平台推荐:2024年精选

本文将介绍以下10款工具:e签宝、法大大、上上签、金格签约、契约锁、DocuSign、Concord、PandaDoc、Agiloft、Evisort。 在当今数字化时代,选择一个合适的电子合同平台可能会让人感到困惑,如何判断哪个平台最适合企业的需求?电子合…

Java之数组应用-选择排序-插入排序

已经完全掌握了冒泡排序和二分查找的同学,可以自己尝试学习选择、插入排序。不要求今天全部掌握,最近2-3天掌握即可! 1 选择排序 选择排序(Selection Sort)的原理有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次…

超低功耗ARM Cortex-M33 TZ MCU STM32WBA54、STM32WBA55:通过提升无线性能实现更出色的用户体验

摘要 STM32WBA54、STM32WBA55产品系列同时支持多种无线标准,包括Bluetooth低功耗 5.4(已认证)、Zigbee、Thread以及可用作Thread边界路由器的Matter。 该产品系列具有出色的灵活性和更强的安全性,可帮助开发人员应对不断变化的无…

24暑假算法刷题 | Day21 | LeetCode 669. 修剪二叉搜索树,108. 将有序数组转换为二叉搜索树,538. 把二叉搜索树转换为累加树

目录 669. 修剪二叉搜索树题目描述题解 108. 将有序数组转换为二叉搜索树题目描述题解 538. 把二叉搜索树转换为累加树题目描述题解 669. 修剪二叉搜索树 点此跳转题目链接 题目描述 给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪…

vite环境下使用bootstrap

环境 nodejs 18 pnpm 初始化 pnpm init pnpm add -D vite --registry http://registry.npm.taobao.org pnpm add bootstrap popperjs/core --registry http://registry.npm.taobao.org pnpm add -D sass --registry http://registry.npm.taobao.org新建vite.config.js cons…

用三行代码“偷袭白嫖党”,保护自己的代码(多语言实现)

计算机科学专业的同学们最近遇到了一件烦心事。教授布置了一系列具有挑战性的编程作业,需要大家运用所学知识来完成。然而,一些同学却动起了歪脑筋,想要通过抄袭他人的代码来轻松完成任务。 小李、小王和小张是同宿舍的好友,他们…

四、GD32 MCU 常见外设介绍(1)RCU 时钟介绍

系统架构 1.RCU 时钟介绍 众所周知,时钟是MCU能正常运行的基本条件,就好比心跳或脉搏,为所有的工作单元提供时间 基数。时钟控制单元提供了一系列频率的时钟功能,包括多个内部RC振荡器时钟(IRC)、一个外部 高速晶体振荡器时钟(H…