当前位置: 首页 > news >正文

MuJoCo新手必看:从XML配置到PD控制器的完整机器人仿真指南

MuJoCo新手必看:从XML配置到PD控制器的完整机器人仿真指南

如果你刚开始接触机器人仿真,面对MuJoCo这个强大的物理引擎,可能会觉得有点无从下手。我刚开始用的时候,也是对着那些XML标签和C API文档发愁,感觉每个参数都像是一个谜题。但别担心,这正是每个机器人学和控制理论学习者都会经历的过程。MuJoCo的魅力在于,一旦你掌握了它的核心逻辑,就能用它构建出从简单摆臂到复杂人形机器人的各种仿真场景,而且仿真精度和速度都相当出色。

这篇文章就是为你准备的。我不会只给你一堆零散的知识点,而是会带你走一遍完整的流程:从最基础的XML模型搭建开始,一步步深入到如何为你的机器人编写一个能实际工作的PD控制器。我们会重点解决那些新手最容易卡壳的地方,比如惯性参数到底该怎么设置、坐标系转换背后的逻辑是什么、传感器噪声怎么处理才合理。这些细节往往决定了你的仿真是否“真实”,控制器是否有效。

准备好了吗?让我们从零开始,构建一个属于你自己的仿真世界。

1. 理解MuJoCo的核心:模型、数据与仿真循环

在动手写任何代码之前,你得先理解MuJoCo是怎么“想问题”的。它把整个仿真世界抽象成三个核心部分:模型(mjModel)数据(mjData)仿真循环(Simulation Loop)。这个架构是理解一切的基础。

模型(mjModel)是你的机器人或物理场景的“蓝图”。它定义了所有静态的、不会随时间改变的属性。想象一下,你要造一辆车,模型就是这辆车的设计图纸:它有多重?轮子有多大?发动机能输出多大扭矩?这些信息在仿真开始前就固定了。在MuJoCo里,模型通常用一个XML文件(MJCF格式)来描述,里面定义了物体的几何形状、质量、惯性、关节类型、执行器参数等等。模型一旦被加载,在单次仿真运行中就不会再改变。

数据(mjData)则是仿真运行时的“状态记录本”。它包含了所有动态的、随时间变化的信息。还是那辆车的例子,数据记录的是:车现在开到了哪里?速度是多少?方向盘打了多少度?发动机当前的转速呢?在MuJoCo中,mjData结构体保存了广义坐标位置(qpos)、广义速度(qvel)、执行器控制信号(ctrl)、传感器读数(sensordata)等等。每次仿真步进(mj_step)后,这些数据都会被更新。

仿真循环是让一切动起来的引擎。一个典型的MuJoCo主循环长这样:

// 加载模型 mjModel* m = mj_loadXML("your_model.xml", NULL, NULL, 0); mjData* d = mj_makeData(m); // 主循环 while (!glfwWindowShouldClose(window)) { // 1. 获取控制输入(例如从键盘、鼠标或你的控制器算法) myController(m, d); // 2. 前向动力学计算(根据模型、当前状态和控制输入,计算加速度) mj_step(m, d); // 3. 渲染更新(在GUI中显示) mjv_updateScene(m, d, &opt, NULL, &cam, mjCAT_ALL, &scn); mjr_render(rect, &scn, &con); glfwSwapBuffers(window); glfwPollEvents(); }

这个循环清晰地展示了数据流:你的控制器函数根据当前状态(d->qpos,d->qvel)计算出控制指令(d->ctrl),然后mj_step函数根据这些指令和物理定律,计算出下一时刻的状态,并更新mjData。理解了这个流程,你就掌握了MuJoCo仿真的脉搏。

提示:新手常犯的一个错误是试图在仿真循环中修改mjModel里的参数(比如质量、惯性)。记住,模型是静态的,要改只能重新加载。而mjData里的状态是动态的,你可以在每个循环里随意读取和修改。

2. 构建你的第一个MJCF模型:从单摆开始

理论说再多,不如动手建个模型。我们就从一个最简单的单连杆摆(One-Link Pendulum)开始。这个模型虽然简单,但包含了MuJoCo模型定义的所有核心要素:世界、物体、几何体、关节、执行器。我会带你逐行解析一个可用的XML文件,并解释每个关键参数背后的物理意义。

首先,创建一个名为pendulum.xml的文件。MJCF格式是层次化的,最外层是<mujoco>标签。

<mujoco model="simple_pendulum"> <!-- 编译器选项:定义角度单位、坐标系等全局设置 --> <compiler angle="radian" inertiafromgeom="true"/> <!-- 默认设置:为后续元素提供默认值,避免重复 --> <default> <joint type="hinge" axis="0 0 1"/> <geom type="cylinder" size="0.02 0.2" rgba="0.7 0.5 0.3 1"/> </default> <!-- 世界:定义仿真环境的基本属性,如重力 --> <worldbody> <!-- 地面 --> <geom name="ground" type="plane" pos="0 0 0" size="2 2 0.1" rgba="0.8 0.9 0.8 1"/> <!-- 摆杆的基座(固定在世界坐标系) --> <body name="base" pos="0 0 0.5"> <geom type="sphere" size="0.05" rgba="0.5 0.5 0.5 1"/> <!-- 摆杆本体,通过铰链关节与基座连接 --> <body name="arm"> <joint name="hinge_joint"/> <geom name="arm_geom" pos="0 0 -0.2"/> <!-- 几何体中心相对于关节偏移 --> </body> </body> </worldbody> <!-- 执行器:定义驱动关节的“肌肉” --> <actuator> <motor name="motor_hinge" joint="hinge_joint" gear="1.0"/> </actuator> </mujoco>

现在,我们来拆解其中几个容易出错的点:

<compiler angle="radian">:这个设置决定了模型中角度相关参数(如关节位置qpos)的单位。MuJoCo默认使用度(degree),但对于控制理论中的大多数公式(如PID控制器),使用弧度(radian)更为方便。我强烈建议在项目开始时就将单位统一为弧度,避免后续单位转换的混乱。

inertiafromgeom="true":这是一个非常实用的选项。当设置为true时,MuJoCo会根据几何体(<geom>)的形状和密度自动计算其质量属性(质量和惯性张量)。对于简单形状(如盒子、球体、圆柱体),这能省去你手动计算惯性的麻烦。但请注意,对于复杂组合体或不规则形状,自动计算可能不准确,此时你需要手动指定<inertia>标签。

惯性张量的手动设置:如果你需要精确控制,或者几何体无法自动计算惯性,就必须手动设置<inertia>标签。这是新手的一大痛点。惯性张量描述了物体绕其质心旋转的难易程度,是一个3x3的矩阵。但对于主轴对齐的简单情况,我们可以使用对角惯性张量(diagonal inertia tensor),只指定三个主轴方向的惯性矩。

<body name="my_body"> <geom type="box" size="0.1 0.2 0.05"/> <inertial pos="0 0 0" mass="2.0" diaginertia="0.01 0.04 0.05"/> </body>

这里,diaginertia="0.01 0.04 0.05"分别对应绕x、y、z轴的惯性矩(Ixx, Iyy, Izz)。如何估算这些值?对于均匀密度的长方体,绕其几何中心旋转的惯性矩有公式可循。但更实际的方法是:先用inertiafromgeom="true"让MuJoCo算一个值,然后基于这个值进行微调。比如,你觉得物体旋转起来太“轻”或太“重”,就按比例增大或减小diaginertia的值。

坐标系的理解:MJCF中的每个<body>都有自己的局部坐标系。poseuler属性定义了该body相对于其父body坐标系的位置和朝向。<geom>poseuler则是相对于其所属body的坐标系。关节(<joint>)的轴(axis)方向也是在body的局部坐标系中定义的。比如上面例子中axis="0 0 1"意味着绕body局部坐标系的z轴旋转。搞不清坐标系,你的模型可能会朝意想不到的方向运动。

一个完整的、带详细注释的单摆模型可能如下所示,我增加了传感器和更多可视化选项:

<mujoco model="pendulum_with_sensors"> <compiler angle="radian" inertiafromgeom="true"/> <option timestep="0.01" iterations="50" integrator="RK4"/> <worldbody> <light pos="0 0 4" dir="0 0 -1"/> <geom name="floor" type="plane" pos="0 0 0" size="3 3 0.1" rgba="0.9 0.9 0.9 1"/> <!-- 基座:固定点 --> <body name="base" pos="0 0 0.5"> <geom type="sphere" size="0.03" rgba="0.3 0.3 0.3 1"/> <site name="base_site" type="sphere" size="0.02" rgba="1 0 0 1"/> <!-- 摆臂:通过铰链关节连接 --> <body name="pendulum"> <joint name="joint1" type="hinge" axis="0 0 1" limited="true" range="-3.14 3.14" damping="0.01"/> <geom name="arm" type="capsule" fromto="0 0 0 0 0 -0.4" size="0.02" rgba="0.2 0.6 0.8 1"/> <site name="tip" type="sphere" size="0.02" pos="0 0 -0.4" rgba="0 1 0 1"/> </body> </body> </worldbody> <actuator> <!-- 电机执行器,关联到joint1,增益为1 --> <motor name="torque_actuator" joint="joint1" gear="1.0" ctrlrange="-2 2"/> </actuator> <sensor> <!-- 关节位置传感器 --> <jointpos name="joint_pos" joint="joint1"/> <!-- 关节速度传感器 --> <jointvel name="joint_vel" joint="joint1"/> <!-- 执行器力/扭矩传感器 --> <actuatorfrc name="motor_torque" actuator="torque_actuator"/> </sensor> </mujoco>

这个模型已经具备了被控制的基本条件:一个可旋转的关节、一个驱动它的执行器,以及测量其状态(位置、速度)和输出(扭矩)的传感器。接下来,我们就要让这个摆臂动起来。

3. 深入执行器与传感器:让模型感知与被驱动

模型建好了,但它现在还是个“植物人”。我们需要给它安装“肌肉”(执行器)和“神经”(传感器),它才能动起来,并感知自身的状态。MuJoCo的执行器系统非常灵活,支持多种类型,但最常用的是motorposition伺服。

执行器(Actuator)详解:在<actuator>部分定义的每个执行器,都必须通过joint属性关联到一个具体的关节。gear参数是关键,它代表了传动比。你可以把它理解为一个放大器:gear="50"意味着你输入1单位的控制信号,执行器会试图在关节上施加50单位的扭矩(对于motor类型)或产生50倍的位置跟踪力(对于position类型)。这个参数对于匹配仿真中的力/扭矩尺度与现实电机参数至关重要。

<actuator> <!-- 类型1:力矩电机 - 直接控制关节扭矩 --> <motor name="my_motor" joint="hinge_joint" gear="30.0" ctrlrange="-5 5" forcerange="-100 100"/> <!-- 类型2:位置伺服 - 控制关节到达目标位置(内部使用PD控制) --> <position name="my_servo" joint="slider_joint" kp="200" gear="1.0" ctrlrange="-1.57 1.57"/> </actuator>
  • motor:最简单直接。你给d->ctrl[i]赋值多少,它就试图在关节上施加gear * ctrl的扭矩。但它不保证位置,如果外力很大,关节可能会被推开。
  • position:更高级。你给d->ctrl[i]赋值一个目标位置(例如弧度),MuJoCo内部会用一个PD控制器来计算所需的扭矩,使关节跟踪这个目标位置。这里的kp就是内部控制器的比例增益。position执行器让你不用自己写PD控制律就能实现位置跟踪,非常适合简单的定点控制任务。

传感器(Sensor)配置与噪声:传感器是你的机器人的“眼睛”和“耳朵”。在<sensor>部分,你可以添加各种传感器来测量模型的状态。但现实世界的传感器都是有噪声的,为了仿真更逼真,MuJoCo允许你为传感器添加噪声。

<sensor> <!-- 测量关节位置,并添加高斯噪声 --> <jointpos name="encorder" joint="joint1" noise="0.001"/> <!-- 标准差为0.001 rad --> <!-- 测量关节速度 --> <jointvel name="tachometer" joint="joint1"/> <!-- 测量执行器实际输出的力 --> <actuatorfrc name="current_sensor" actuator="my_motor"/> </sensor>

在代码中,你可以通过d->sensordata[sensor_id]来读取这些传感器的值。带噪声的传感器读数对于开发鲁棒的控制算法至关重要。一个在完美传感器下工作的控制器,在现实噪声中可能会完全失效。在仿真中,你可以通过mjcb_sensor_noise回调函数自定义噪声模型,或者简单地使用XML中noise属性定义的加性高斯白噪声。

这里有一个重要的实践细节:传感器数据的索引顺序d->sensordata数组的顺序严格对应XML中<sensor>标签定义的顺序。如果你在代码里硬编码索引(比如sensordata[0]),一旦修改了XML中传感器的顺序或数量,代码就会出错。更稳健的做法是在初始化时,通过传感器名称来查找其ID:

// 在初始化阶段,根据传感器名称获取其ID int pos_sensor_id = mj_name2id(m, mjOBJ_SENSOR, "encorder"); int vel_sensor_id = mj_name2id(m, mjOBJ_SENSOR, "tachometer"); // 在控制循环中,使用ID来读取数据 double measured_pos = d->sensordata[pos_sensor_id]; double measured_vel = d->sensordata[vel_sensor_id];

同样的逻辑也适用于执行器(mjOBJ_ACTUATOR)和关节(mjOBJ_JOINT)。养成使用mj_name2id查询的习惯,能让你的代码与模型文件解耦,提高可维护性。

4. 编写你的第一个PD控制器:从理论到代码

现在,到了最核心的部分——让单摆立起来!我们将实现一个比例-微分(PD)控制器。PD控制器是机器人控制中最基础、最常用的算法之一,它的思想直观而强大:根据当前位置与目标位置的偏差(比例项P),以及当前速度(微分项D),来计算应该施加多大的控制力(扭矩),使系统稳定在目标点。

PD控制律的直观理解:想象一下用手平衡一根竖立的木棍。木棍往左倒(位置偏差),你就往右推(控制力);木棍倒得越快(速度,即偏差的变化率),你就要推得越用力。比例增益kp决定了你对位置偏差的“反应强度”,微分增益kd决定了你对速度的“阻尼力度”。kd太大系统会反应迟钝,太小则会振荡甚至失稳。

对于我们的单摆,目标位置是竖直向上(qpos = 0)。PD控制律可以写成:torque = -kp * (current_position - target_position) - kd * current_velocity

在代码中实现它:

// 假设我们的单摆模型只有一个旋转关节和一个motor执行器 void myPDController(const mjModel* m, mjData* d) { // 1. 定义控制参数 - 这些值需要根据你的模型调试 double target_angle = 0.0; // 目标:竖直向上 (0弧度) double kp = 50.0; // 比例增益 double kd = 3.0; // 微分增益 // 2. 获取当前状态 // 关节索引为0(如果只有一个关节) int joint_id = 0; double current_pos = d->qpos[joint_id]; double current_vel = d->qvel[joint_id]; // 3. 也可以从传感器读取(带噪声的)状态,更贴近真实情况 // int sensor_pos_id = mj_name2id(m, mjOBJ_SENSOR, "joint_pos"); // int sensor_vel_id = mj_name2id(m, mjOBJ_SENSOR, "joint_vel"); // double measured_pos = d->sensordata[sensor_pos_id]; // double measured_vel = d->sensordata[sensor_vel_id]; // 4. 计算控制误差 double error_pos = current_pos - target_angle; // 注意:这里我们使用速度作为微分项的近似(误差的微分 = d(error)/dt ≈ -d(current_pos)/dt = -current_vel,因为目标位置固定) double error_vel = current_vel; // 对于固定目标,误差变化率就是负的当前速度 // 5. 应用PD控制律计算所需扭矩 double torque = -kp * error_pos - kd * error_vel; // 6. 可选:施加控制限幅,防止扭矩过大 double torque_limit = 5.0; if (torque > torque_limit) torque = torque_limit; if (torque < -torque_limit) torque = -torque_limit; // 7. 将计算出的扭矩赋值给执行器 // 执行器索引为0(如果只有一个motor执行器) int actuator_id = 0; d->ctrl[actuator_id] = torque; }

把这个控制器函数放到仿真循环中mj_step之前调用,你的单摆就应该能从任意初始位置摆动并最终稳定在竖直向上的位置了。

参数调试的艺术kpkd选多少?这没有标准答案,完全取决于你的模型(质量、惯性、长度)和你想要的响应特性。我的经验是:

  1. 先调kp:将kd设为0,逐渐增大kp。你会发现摆臂开始振荡,kp越大,振荡频率越高。当kp增大到一定程度,系统会变得不稳定(发散)。
  2. 再调kd:在kp固定为一个较大值(但尚未发散)时,逐渐加入kdkd的作用是阻尼,它会抑制振荡,让系统更快地稳定下来。你会看到振荡幅度逐渐减小直至消失。
  3. 微调:在稳定和不振荡之间找到一个平衡点,响应既快速又平稳。你可以尝试不同的(kp, kd)组合,观察系统的阶跃响应。

为了更系统地理解参数影响,可以参考下面这个简化的调试指南:

参数组合现象kp过小kp适中但kd过小kp适中且kd适中kp过大
系统响应收敛极慢,或根本无法到达目标持续振荡,衰减很慢快速收敛,无超调或轻微超调剧烈振荡甚至发散
物理直觉控制力太弱,无法克服重力或惯性有足够的“推力”,但缺少“刹车”推力和刹车配合得当控制力过猛,导致系统反复过冲
调整建议逐步增加kp逐步增加kd已达到较优状态,可微调减小kp,或大幅增加kd

进阶:处理传感器噪声。如果你使用了带噪声的传感器,上面的控制器直接用d->sensordata代替d->qposd->qvel可能会因为噪声引入的高频抖动而导致控制信号剧烈波动,甚至激发系统的高频模态。一个常见的做法是加入一个低通滤波器(Low-pass Filter)来平滑传感器信号:

// 简单的单极点低通滤波器 double filtered_pos = 0.0; double filtered_vel = 0.0; double alpha = 0.2; // 滤波系数,介于0和1之间,越小越平滑但延迟越大 void myPDControllerWithFilter(const mjModel* m, mjData* d) { double target_angle = 0.0; double kp = 50.0; double kd = 3.0; // 读取带噪声的传感器数据 int sensor_pos_id = mj_name2id(m, mjOBJ_SENSOR, "joint_pos"); int sensor_vel_id = mj_name2id(m, mjOBJ_SENSOR, "joint_vel"); double raw_pos = d->sensordata[sensor_pos_id]; double raw_vel = d->sensordata[sensor_vel_id]; // 应用低通滤波: filtered_value = alpha * raw + (1-alpha) * previous_filtered filtered_pos = alpha * raw_pos + (1 - alpha) * filtered_pos; filtered_vel = alpha * raw_vel + (1 - alpha) * filtered_vel; // 使用滤波后的状态进行控制 double error_pos = filtered_pos - target_angle; double error_vel = filtered_vel; double torque = -kp * error_pos - kd * error_vel; // ... 限幅和赋值 }

滤波器的引入会带来相位滞后,可能会影响稳定性,需要重新调试kpkd。这就是为什么在仿真中集成噪声和滤波如此重要——它迫使你设计出更鲁棒、更接近实际部署的控制器。

5. 调试与可视化:让问题无处遁形

代码写完了,但摆臂可能不按你预想的方式运动,或者干脆一动不动。别慌,调试是仿真开发中不可或缺的一环。MuJoCo提供了一系列强大的工具来帮助你洞察仿真内部的状态。

利用printf与日志:最朴素的往往最有效。在控制循环中打印关键变量的值,是理解系统行为的第一步。

printf("Timestep: %d, Pos: %.3f, Vel: %.3f, Ctrl: %.3f\n", d->time, d->qpos[0], d->qvel[0], d->ctrl[0]);

你可以将数据写入文件,然后用Python的Matplotlib或MATLAB绘制曲线,直观地看到位置、速度、控制量随时间的变化。观察误差是否收敛,控制量是否饱和,振荡频率是多少。

深入探查mjModelmjData:MuJoCo的模型和数据结构非常丰富。当你遇到奇怪的行为时,检查以下数据往往能找到线索:

  • d->qfrc_applied:外部施加的广义力(包括执行器产生的力)。
  • d->qfrc_passive:被动力(如阻尼、摩擦)。
  • d->qfrc_constraint:约束力(如接触力)。
  • m->jnt_dofadr[joint_id]:获取指定关节在qpos/qvel数组中的起始地址。对于多自由度关节(如球关节),这很重要。

使用MuJoCo的调试功能:MuJoCo的GUI(simulate)本身就是一个强大的调试工具。

  • 实时调整参数:在运行仿真时,你可以按Ctrl(或Cmd)+鼠标点击模型中的物体、关节或执行器,会弹出属性编辑窗口。你可以实时修改质量、惯性、阻尼、刚度、控制增益等参数,并立即看到效果。这对于参数调试来说是无价之宝。
  • 查看受力:在Visualization菜单中,开启Contact ForceActuator Force等选项,可以在3D视图中用箭头直观地显示力和力矩的方向与大小。
  • 传感器可视化:开启Sensor显示,可以在GUI侧边栏实时看到所有传感器读数的数值。
  • 时间缩放与暂停:使用,.键减慢或加快仿真速度,空格键暂停。这让你可以仔细观察快速或瞬时的现象。

生成并分析MJMODEL.TXT:在运行simulate时,MuJoCo会在二进制文件同目录下生成一个MJMODEL.TXT文件。这个文件是模型编译后所有内部数据的完整文本转储,信息量巨大。当你怀疑XML中的某个参数没有被正确解析时(比如惯性矩阵、坐标系转换),查看这个文件是最终的验证手段。你可以在这里找到每个物体的精确质量、惯性张量、关节自由度映射等底层信息。

一个常见的调试案例:控制器没反应。你写了PD控制器,但摆臂毫无反应。可能的原因和排查步骤:

  1. 执行器索引错误:确认d->ctrl[actuator_id]中的actuator_id是否正确对应了XML中定义的执行器。使用mj_name2id来获取ID。
  2. 执行器类型不匹配:如果你定义的是position伺服,却给它赋值扭矩,那是无效的。position执行器期望的是目标位置信号。
  3. 控制量太小:计算出的torque值可能远小于模型的惯性力或重力。尝试将kpkd放大几个数量级看看。
  4. 关节被锁定或受限:检查XML中关节的limitedrange属性,或者是否有其他约束(如equality)限制了运动。
  5. 查看d->ctrl是否被覆盖:确保你的控制器函数在仿真循环中被正确调用,并且没有其他地方(比如另一个回调函数)在后面覆盖了d->ctrl的值。

调试是一个假设-验证的过程。利用好MuJoCo提供的工具,耐心地缩小问题范围,你总能找到那个让机器人“活”起来的关键参数或代码行。

http://www.jsqmd.com/news/444607/

相关文章:

  • Kubernetes如何自动识别资源瓶颈?
  • Qwen-Image-2512-Pixel-Art-LoRA商业应用:独立设计师接单用像素插画快速交付流程
  • Nunchaku-flux-1-dev企业应用:为内部知识库生成技术架构图解
  • PostgreSQL存储空间优化指南:如何精准分析表和索引占用情况
  • 美胸-年美-造相Z-Turbo效果实测:看看AI能画出多美的人像
  • AI Coder Agent 技术方案研究报告
  • 对ai的想象,是否能完成物理上的任务?
  • Kubernetes如何优化资源使用效率?
  • GNSS-INS松组合导航:从KF-GINS源码看卡尔曼滤波实现
  • 2026年分子筛转轮选购指南:深度解析TOP服务商与选型策略 - 2026年企业推荐榜
  • 2026年贵阳一站式建材公司推荐与选择指南 - 2026年企业推荐榜
  • 梦幻动漫魔法工坊保姆级教程:从安装到生成第一张动漫图
  • gte-base-zh嵌入模型入门实战:信息检索、语义相似度计算场景应用
  • K8s核心原理及注意事项
  • 空论视野下的全球智能治理
  • 【硬件片内测试】基于FPGA的完整QPSK链路测试,含频偏锁定,帧同步,定时点,Viterbi译码,信道,误码统计
  • 2026年最新:不锈钢精密铸造厂家联系电话推荐(附河北光德详细资料) - 品牌推荐
  • 3D 互动实验室:10 款极简小游戏 Prompt 教学
  • 郑州律师电话更新(2026年最新版):刘艳伟律师联系方式公布 - 品牌推荐
  • 【仿真测试】基于FPGA的完整QPSK通信链路实现,含频偏锁定,帧同步,定时点,Viterbi译码,信道,误码统计
  • Obsidian+OpenClaw:9分钟重构AI知识管理,再也不用当“信息搬运工”啦!
  • 尚巨网络18载深耕AI搜索+GEO精准赋能,全链路营销靠谱之选 - 品牌企业推荐师(官方)
  • C++的数组指针的类型
  • K8s
  • 基于OFDM+QPSK调制解调的通信链路matlab性能仿真,包含同步模块,信道估计和编译码
  • 树莓派安装openclaw小龙虾
  • IEaseCore 工业通讯模块
  • 树莓派pico使用无源蜂鸣器播放小星星
  • Pandas数据处理(3): 数据分箱与行列名修改
  • Pandas数据处理(4):时间数据处理与分组聚合