news 2026/10/7 15:43:09

ROS2 + Gazebo阿克曼小车搭载Livox MID-360雷达仿真搭建指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ROS2 + Gazebo阿克曼小车搭载Livox MID-360雷达仿真搭建指南

做机器人的朋友应该都有这种感觉:硬件还没到齐,算法却已经等不及要跑起来了。尤其像阿克曼底盘和Livox MID-360这种组合,真机少说也要几千块,等采购走流程的时间,足够在仿真里把整套系统先搭一轮。这篇文章就把我搭建“ROS2 + Gazebo 阿克曼小车 + Livox MID-360 雷达仿真”的完整流程整理出来,从环境安装到URDF建模、传感器配置、launch启动、rviz2看点云,一条线走通。适合准备做SLAM、导航验证,或者第一次接触阿克曼仿真的开发者参考。文章里我会把容易踩坑的小细节都标注出来,跳过这些坑,你就能把更多时间留给真正的算法调试。

1. 为什么选这套组合做仿真

1.1 阿克曼小车 vs 差速小车,仿真差异在哪

很多新手一开始接触ROS2仿真,用的都是差速小车,也就是两轮独立驱动,通过左右轮速差实现转向。阿克曼小车不一样,它模仿的是真实汽车底盘,前轮负责转向,后轮负责驱动,转向时内外侧车轮的转角并不相同,而是围绕同一个瞬时转向中心转动。

这个差异在仿真里特别重要。差速小车可以原地旋转,阿克曼小车做不到;阿克曼小车的最小转弯半径由轴距和前轮最大转角决定,这意味着同一套导航算法,在这两种底盘上的表现会差很多。如果你之后要做的项目是无人配送车、巡检车这类产品,底盘大概率是阿克曼结构,那么尽早用阿克曼模型做仿真,能提前暴露很多规划和控制问题。

Gazebo恰好是验证这类底盘逻辑最顺手的环境。它内置了物理引擎,可以模拟轮胎与地面的摩擦、转向关节的约束、车身质量对运动的影响。Gazebo的ros2插件生态也比较成熟,从控制器到传感器都有现成方案,搭建一套阿克曼仿真比从零写物理模型要省太多时间。

1.2 为什么是Gazebo而不是Webots或Isaac Sim

聊仿真器,免不了被问:现在Webots、Isaac Sim也挺火,为什么还用Gazebo?我的观点是,不同阶段选不同工具。

Gazebo的优势在于和ROS2的集成度最高。gazebo_ros_pkgs、gazebo_ros2_control这些包基本都是跟着ROS2发行版一起维护的,安装完ROS2之后,稍微补几个包就能用。而且网上关于Gazebo的报错案例、教程数量远超其他仿真器,遇到问题搜索一下基本都能找到答案。这一点对于新手来说比“画质更好”重要得多。

Webots在物理引擎和传感器模型上不弱,但和ROS2的桥接配置相对繁琐,社区资料也少一些。Isaac Sim的渲染和物理精度确实强,但硬件门槛高,动辄需要一块不错的GPU,配置也更复杂。对大多数做算法验证的人而言,Gazebo是投入产出比最高的选择。

1.3 在仿真阶段为什么要“提前预演”Livox MID-360

MID-360是一款非重复扫描的固态激光雷达,和传统的机械多线雷达不太一样。它通过棱镜旋转形成类似花瓣状的扫描轨迹,单帧点云看起来比较稀疏,但随着时间累积,视场覆盖会越来越密。它的垂直视场角很大,从向下7度到向上52度,这对感知车顶、近处障碍物和低矮物体都很有帮助。

但问题也在这里,很多人在拿到真机之前,对它的点云形态、FOV范围、盲区大小没有概念。仿真阶段先把雷达装上,感受一下“雷达在车顶0.25米高度时,近处哪些区域测不到”,后续处理实车数据会从容很多。仿真当然无法逐点还原非重复扫描的轨迹,我们可以用规则扫描的Ray传感器模拟它的FOV和量程,把精力先放在算法链路上。

2. 环境准备与技术选型

2.1 ROS2版本和Gazebo版本怎么搭配

我这次用的是Ubuntu 22.04 + ROS2 Humble + Gazebo Classic 11,这是当前最稳的组合。Humble是长期支持版本,官方源里的gazebo_ros_pkgs、gazebo_ros2_control、ros2_controllers都是现成的,apt直接装,省去编译时间。网上教程也基本围绕这个版本组合,遇到问题容易找到对应方案。

安装命令如下:

sudo apt update sudo apt install ros-humble-desktop -y sudo apt install ros-humble-gazebo-ros-pkgs -y sudo apt install ros-humble-gazebo-ros2-control -y sudo apt install ros-humble-ros2-controllers -y sudo apt install ros-humble-xacro ros-humble-robot-state-publisher -y sudo apt install ros-humble-joint-state-publisher-gui -y

如果你用的是Ubuntu 24.04,官方推荐的是ROS2 Jazzy + Gazebo Harmonic,插件名和部分配置会有差异,不建议新手一上来就挑战。先把Humble这条路线跑通,再迁移也不迟。

提示:国内用户安装ROS2时,如果apt速度不理想,建议先把系统源和ROS2源都换成国内镜像,不要边装边等,心态容易崩。

2.2 工作空间结构怎么设计

我做仿真项目习惯用一个功能包把URDF、world、launch全装进去,先跑通再拆分。功能包结构大概是这样:

src/ └── ackermann_vehicle/ ├── config/ │ └── ackermann_controller.yaml ├── launch/ │ └── gazebo_sim.launch.py ├── urdf/ │ ├── ackermann_vehicle.xacro │ └── mid360_sensor.xacro ├── worlds/ │ └── simple_world.world ├── CMakeLists.txt └── package.xml

URDF相关文件放在urdf目录,控制器参数放在config目录,launch文件负责把模型、仿真器和传感器整个串起来。这样后续增加地图、导航、SLAM模块,只需要再扩展对应的目录。

功能包创建命令:

mkdir -p ros2_ws/src cd ros2_ws/src ros2 pkg create ackermann_vehicle --build-type ament_cmake

创建后记得在package.xml里加上gazebo_ros_pkgs、xacro、urdf等依赖声明,否则colcon build时可能因为依赖缺失报错。

2.3 控制方案选型:ros2_control还是Gazebo自带插件

这是搭建流程中最容易犹豫的一步。Gazebo里有现成的阿克曼驱动插件libgazebo_ros_ackermann_drive.so,配置简单,几行XML就能让车动起来。另一个方案是ROS2官方的ackermann_steering_controller,配合gazebo_ros2_control使用,配置稍微复杂,但更贴近真实机器人控制链路。

我最终选了ros2_control方案,因为阿克曼小车后续往往要接导航、运动控制、里程计融合,ros2_control的硬件接口抽象和控制器管理方式,和实车控制架构是一致的。仿真里养成用controller manager的习惯,后面切换真实电机驱动时会顺滑很多。

对比项Gazebo自带插件ros2_control + ackermann_steering_controller
配置复杂度低,改XML即可中,需要controller yaml和URDF ros2_control块配合
里程计/TF插件负责发布controller负责发布,可控性强
后续扩展实车需要重写可直接替换hardware interface
教程资源相对少ROS2官方维护,长期稳定

3. 阿克曼小车URDF建模与驱动

3.1 关节层级怎么定义

阿克曼小车的URDF核心在于关节层级。我的模型分四层:底盘base_link、前轮转向关节、后轮驱动关节、雷达挂载点。每个前轮都有一个绕Z轴旋转的steering joint,后轮则用continuous joint,驱动方式为velocity。

简化版本的xacro结构如下:

<xacro:macro name="ackermann_vehicle"> <!-- 底盘 --> <link name="base_link"> <visual> <geometry> <box size="0.5 0.3 0.1"/> </geometry> </visual> </link> <!-- 左前轮转向关节 --> <link name="front_left_wheel"> <visual> <geometry> <cylinder radius="0.075" length="0.04"/> </geometry> </visual> </link> <joint name="front_left_steer_joint" type="revolute"> <parent link="base_link"/> <child link="front_left_wheel"/> <origin xyz="0.2 0.16 0" rpy="0 0 0"/> <axis xyz="0 0 1"/> <limit lower="-0.6" upper="0.6" effort="10" velocity="1.0"/> </joint> <!-- 后轮驱动关节 --> <joint name="rear_left_wheel_joint" type="continuous"> <parent link="base_link"/> <child link="rear_left_wheel"/> <origin xyz="-0.2 0.16 0" rpy="0 0 0"/> <axis xyz="0 1 0"/> </joint> </xacro:macro>

这里有个关键细节:前轮转向关节的轴是Z轴,也就是车轮绕垂直方向转动;后轮驱动关节的轴是Y轴,车轮绕自身中心旋转。如果把转向关节的轴写错成Y轴,就会出现车轮“低头”或者“抬头”的奇怪姿态,车根本走不直。

3.2 物理属性配置

Gazebo里模型能不能稳定跑起来,物理参数比视觉模型更重要。每个link都要配置碰撞体collision和惯性inertial,否则小车放到世界里的瞬间就会乱跳或者直接穿透地面。

惯量计算这块,新手最容易偷懒,直接用默认惯量。对于底盘这样0.5 * 0.3 * 0.1米的盒子,质量假设3kg,惯量可以用公式粗略估算。不过更省事的办法是参照SolidWorks导出的URDF模板,或者用xacro里的cylinder和box宏来自动计算。网上有很多现成的惯量计算xacro宏,拿来直接用就行。

轮胎和地面的摩擦系数也很关键。仿真中如果摩擦力太小,阿克曼小车转弯时会严重侧滑;摩擦力太大,转向关节会受力过大。我把轮胎link的<mu1>和<mu2>都设为1.0,实测转向和直线稳定性都还可以。你可以根据自己的物理引擎微调。

3.3 用ros2_control驱动四个车轮

要在仿真里使用ackermann_steering_controller,URDF里需要添加<ros2_control>块,声明哪些关节需要命令接口和状态接口。示例:

<ros2_control name="GazeboSystem" type="system"> <hardware> <plugin>gazebo_ros2_control/GazeboSystem</plugin> </hardware> <joint name="front_left_steer_joint"> <command_interface name="position"/> <state_interface name="position"/> </joint> <joint name="front_right_steer_joint"> <command_interface name="position"/> <state_interface name="position"/> </joint> <joint name="rear_left_wheel_joint"> <command_interface name="velocity"/> <state_interface name="velocity"/> </joint> <joint name="rear_right_wheel_joint"> <command_interface name="velocity"/> <state_interface name="velocity"/> </joint> </ros2_control>

然后配置控制器参数文件config/ackermann_controller.yaml:

controller_manager: ros__parameters: update_rate: 50 use_sim_time: true ackermann_steering_controller: ros__parameters: type: ackermann_steering_controller/AckermannSteeringController rear_axle_wheel_radius: 0.075 wheelbase: 0.4 front_axle_track: 0.32 rear_axle_track: 0.32 left_front_steering_joint: front_left_steer_joint right_front_steering_joint: front_right_steer_joint left_rear_wheel_joint: rear_left_wheel_joint right_rear_wheel_joint: rear_right_wheel_joint open_loop: false enable_odom_tf: true

这里wheelbase是前后轴的距离,front_axle_track和rear_axle_track是左右轮距,这几个参数决定了阿克曼转向模型的计算。open_loop: false表示控制器会使用里程计反馈,如果只想先让车动起来,也可以先设为true。

注意:不同ROS2版本对controller参数的名称略有调整,如果报unknown parameter,可以去ros2 pkg prefix ros2_controllers目录下的config示例文件里对照一下。这是正常的,别慌。

3.4 里程计和TF从哪来

用ros2_control方案的好处是,ackermann_steering_controller会根据轮速和转向角推算里程计,并发布odom话题以及odom -> base_footprint的TF变换。也就是说,你不需要在Gazebo里再额外挂里程计插件。

小车的关节状态TF,比如base_link -> front_left_wheel这些,由robot_state_publisher负责。它读取URDF中的joint state,把每个link的坐标系发布出来。所以launch文件里需要启动robot_state_publisher,并给它传入robot_description参数。

4. 搭载Livox MID-360仿真传感器

4.1 雷达的安装位置和盲区估算

MID-360的垂直FOV是从向下7度到向上52度,这个特性对安装高度非常敏感。假设雷达装在车顶0.25米高度,以水平方向为0度,向下只有7度的余量,那么最近能打到地面的距离大约是:

最近可视距离 = 0.25 / tan(7°) ≈ 2.03米

也就是说,车周围两米内是一个渐变盲区,越靠近车身越测不到。这个结论在实车布置雷达时很有参考价值。如果雷达安装得太低,盲区会更大;如果希望近距离感知好一些,可以考虑让雷达略微前倾或者选择更高安装点。

4.2 用GPU Ray模拟MID-360点云

仿真里我用的传感器是gpu_ray,它靠GPU加速射线计算,比CPU的ray传感器快很多。在URDF中给雷达本体添加传感器配置:

<gazebo reference="mid360_link"> <sensor type="gpu_ray" name="mid360_sensor"> <pose>0 0 0 0 0 0</pose> <visualize>true</visualize> <update_rate>10</update_rate> <ray> <scan> <horizontal> <samples>360</samples> <resolution>1</resolution> <min_angle>-3.14159265358979</min_angle> <max_angle>3.14159265358979</max_angle> </horizontal> <vertical> <samples>8</samples> <resolution>1</resolution> <min_angle>-0.12</min_angle> <max_angle>0.92</max_angle> </vertical> </scan> <range> <min>0.1</min> <max>40.0</max> <resolution>0.01</resolution> </range> <noise> <type>gaussian</type> <mean>0.0</mean> <stddev>0.01</stddev> </noise> </ray> <plugin name="gazebo_ros_gpu_ray" filename="libgazebo_ros_gpu_ray_sensor.so"> <ros> <remapping>~/out:=/livox/lidar</remapping> </ros> <output_type>sensor_msgs/msg/PointCloud2</output_type> </plugin> </sensor> </gazebo>

水平方向360度扫描,360个采样点;垂直方向大概8线,覆盖-7度到+52度,对应弧度大约是-0.12到0.92。最大量程40米,和MID-360的标称规格保持一致。

这里有两个容易踩的坑。第一个是gpu_ray需要GPU支持,如果你跑在虚拟机或者没有独立显卡的机器上,Gazebo可能直接黑屏或闪退,这时可以把sensor type改成ray,退回CPU计算。第二个是output_type一定别漏,默认输出的是LaserScan,而我们后面做SLAM、导航更习惯直接用PointCloud2。

4.3 话题命名与真机驱动对齐

既然挂着Livox的牌子,我建议话题名、坐标系名尽量向真机驱动livox_ros_driver2靠拢。真机驱动发布的话题通常是/livox/lidar,点云消息类型是sensor_msgs/PointCloud2,同时还会发布IMU数据。仿真里把点云topic映射成/livox/lidar,雷达坐标系命名成livox_frame或mid360_link,这样后续把仿真代码迁移到实车时,改动的代码量会非常小。

坐标系命名不要随便起。我见过有人把雷达叫laser_frame,真机上驱动发布的名字却是livox_frame,导致SLAM算法里所有关于雷达的TF都得改一遍。仿真阶段就统一命名,省得后面返工。

5. 联调启动

5.1 launch文件怎么编排

launch文件是整个流程的“总指挥”,我习惯用Python写。它需要完成四件事:加载URDF模型、启动Gazebo、把小车模型加载到世界里、启动controller和TF发布。

核心launch文件gazebo_sim.launch.py:

import os from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, ExecuteProcess, IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node from ament_index_python.packages import get_package_share_directory from launch.substitutions import LaunchConfiguration def generate_launch_description(): pkg_share = get_package_share_directory('ackermann_vehicle') robot_desc_path = os.path.join(pkg_share, 'urdf', 'ackermann_vehicle.xacro') world_path = os.path.join(pkg_share, 'worlds', 'simple_world.world') controller_config = os.path.join(pkg_share, 'config', 'ackermann_controller.yaml') robot_description = {'robot_description': Command(['xacro ', robot_desc_path])} gazebo = IncludeLaunchDescription( PythonLaunchDescriptionSource([ os.path.join(get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py')]), launch_arguments={'world': world_path}.items(), ) spawn_entity = Node( package='gazebo_ros', executable='spawn_entity.py', arguments=['-topic', 'robot_description', '-entity', 'ackermann_vehicle'], output='screen', ) robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', parameters=[robot_description], ) controller_manager = Node( package='controller_manager', executable='ros2_control_node', parameters=[robot_description, controller_config], output='screen', ) spawn_controller = Node( package='controller_manager', executable='spawner', arguments=['ackermann_steering_controller'], output='screen', ) return LaunchDescription([ DeclareLaunchArgument('use_sim_time', default_value='true'), gazebo, robot_state_publisher, spawn_entity, controller_manager, spawn_controller, ])

这段launch里值得注意的点:spawn_entity直接从robot_description话题读取模型,所以robot_state_publisher必须先启动。controller_manager需要同时拿到URDF和controller配置,否则它不知道要管理哪些joint、加载哪个controller。

5.2 启动顺序和话题检查

编译工作空间:

cd ros2_ws colcon build source install/setup.bash ros2 launch ackermann_vehicle gazebo_sim.launch.py

启动后不要急着看画面,有时候Gazebo窗口还没刷新,后台话题其实已经通了。打开另一个终端依次检查:

ros2 topic list ros2 topic hz /livox/lidar ros2 topic echo /odom --once ros2 run tf2_tools view_frames

如果/livox/lidar有频率输出,说明雷达传感器已经在跑;/odom能echo出数据,说明控制器和里程计链路正常;view_frames生成的frames图里能看到odom -> base_link -> mid360_link的TF树,说明坐标系发布完整。

5.3 用rviz2看点和开车测试

rviz2看点云有一个特别容易忽略的地方:QoS设置。Gazebo雷达传感器默认是Best Effort可靠性策略,而rviz2默认使用Reliable,两者对不上,话题会一直显示No Data。添加PointCloud2插件后,把Topic的Reliability Policy改成Best Effort,点云才会显示出来。这个坑我踩过很多次,每次换新环境都会忘。

Fixed Frame要设成base_link或mid360_link,这样点云才跟车体坐标系对齐。如果你设成map,而当前没有map坐标系,画面里就会出现一个红叉报错。

让车动起来,发一条Twist指令:

ros2 topic pub /ackermann_steering_controller/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.5}, angular: {z: 0.2}}" --once

观察点云是否随着车辆移动而发生变化,同时看/odom中twist.twist.linear.x是否接近0.5。如果车动了但odom不变,大概率是controller参数里joint名字和URDF对不上,回头逐个检查。

6. 常见问题与避坑记录

6.1 Gazebo界面一直闪烁或黑屏

这个问题的出现频率非常高。常见原因是Gazebo的OpenGL渲染和当前显卡驱动不兼容,尤其是笔记本双显卡环境。可以尝试在启动前设置:

export LIBGL_ALWAYS_SOFTWARE=1 ros2 launch ackermann_vehicle gazebo_sim.launch.py

如果环境变量能解决闪烁,但画面变卡,说明GPU渲染没吃上,需要检查显卡驱动是否正常。另一个办法是降低Gazebo的渲染设置,比如关闭Shadows和环境光反射。

还有一种情况是gpu_ray传感器导致的闪烁。无GPU或虚拟机环境建议把URDF中的sensor type改成ray,虽然CPU射线计算慢一些,但至少不会闪。

6.2 rviz2里看不到雷达点云

先确认ros2 topic hz /livox/lidar是否有频率输出,有频率说明数据在发,问题基本出在rviz2的QoS或者Fixed Frame。点开PointCloud2插件的Topic属性,把Reliability Policy改成Best Effort;然后把Fixed Frame从map改成base_link。这两步改完,九成以上的“看不到点云”都能解决。

如果是rviz2里根本没有/livox/lidar这个topic,检查launch里的remapping是否生效。有时候Gazebo插件命名空间带前缀,topic会变成/ackermann_vehicle/livox/lidar这种形式,用ros2 topic list确认一下实际话题名。

6.3 小车启动后不动

小车不动的原因主要有几种。controller没启动成功、joint名字不匹配、cmd_vel话题发错,都可能导致车不动。先运行:

ros2 control list_controllers

如果列表里没有ackermann_steering_controller,说明controller manager没加载成功。检查URDF的<ros2_control>块和yaml配置里的joint名称是否完全一致;名称有一个字母不对,controller就找不到关节。

还有一个隐蔽问题:如果发布命令后/odom有变化但模型在Gazebo里不动,说明模型上可能存在两个controller在抢同一个joint,或者还有一个旧的gazebo_ros2_control节点重复加载。关掉多余的launch进程,重新启动一套完整的launch。

6.4 点云频率太低或太高

雷达点云的更新频率由update_rate控制。如果是为了建图,10Hz通常够用;如果做实时避障,可能需要20Hz以上。频率太高会增加CPU/GPU负载,导致Gazebo整体变卡。我的建议是先在低频率下调通算法,再逐步提高传感器频率,找到性能和精度的平衡点。

6.5 常见问题速查表

现象可能原因处理方法
Gazebo启动闪屏显卡驱动或gpu_ray不支持设置LIBGL_ALWAYS_SOFTWARE,换用ray传感器
rviz2看不到点云QoS不匹配或frame设置错误Topic改为Best Effort,Fixed Frame改为base_link
小车不动controller未启动或joint名称不匹配ros2 control list_controllers检查,核对URDF与yaml
点云频率异常update_rate设置不合适调整sensor的update_rate参数
车穿透过地面缺少collision或mass设置不合理补充碰撞体,检查惯性参数
点云有大量空洞射线数量太少或遮挡增加horizontal/vertical samples,检查雷达安装位置

做仿真最忌讳的就是“看起来在动,但没有数据流”。我的习惯是每一步都确认话题和数据,URDF改完先看robot_state_publisher有没有报错,launch启动后先看topic list,再点开rviz2。流程通了,后面接SLAM、接导航都是水到渠成的事。

最后再分享一个小技巧:如果你手头有真机MID-360的bag包,可以在Gazebo仿真跑通之后,把仿真点云和真机点云放在同一个rviz2里对比观察。两边的话题结构保持一致,你就会发现数据形态差异主要在点云密度和噪声分布,这对理解仿真到实车的迁移非常有帮助。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 15:42:54

显示驱动调试工具全解析:从内核日志到总线信号的分层实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:42:39

基于YOLOv5的道路交通标识识别:从数据集到训练避坑全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:41:29

DRV8301无刷电机驱动板设计全流程:原理图、PCB布局与STM32代码实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:40:31

InternVL端侧多模态推理:高通QNN部署实战与性能优化路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:39:59

PADS Layout实战:元件摆放与地线处理的10个关键技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:39:57

LDO稳定性分析:环路增益、零极点与相位裕度调试指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华