《从零到一:基于定制 FOC 的双驱轮式机器人底盘全栈构建指南》
《从零到一基于定制 FOC 的双驱轮式机器人底盘全栈构建指南》导言告别黑盒拥抱纯粹的底层控制做机器人底盘最怕的就是底层硬件不听话。通过采购通用双驱无刷 FOC 控制板和 36V 轮毂电机结合底层驱动参考方案我们直接跳过了痛苦的“硬件逆向”阶段。这套方案的绝对优势即插即用硬件引脚在底层固件中已完美定义无需跑线。物理白盒真正的 FOC 矢量控制提供纯粹的速度/力矩闭环响应0 延迟。超大杯动力10寸直驱相比普通的 6.5 寸电机10 寸 350W 轮毂电机拥有更大的轮胎外径这意味着在相同转速下它的直线行驶极速更高、越野通过性更强同时得益于直驱外转子设计它依然保持着惊人的低速大扭矩配合 36V 动力载重百斤轻松自如是目前具备极高性价比的强力 AGV 底盘方案。第一阶段硬件集结钢铁之躯拼装手撕黑盒的第一步是把这堆散件变成一辆真正的“车”。1. 机械组装动力轮安装将两个 350W 轮毂电机固定在准备好的车架钢板两侧。注意确保两个电机的轴心在同一条水平线上否则车子走直线会跑偏。万向轮就位在车架前后或单前/单后安装万向轮形成稳定的三点或四点支撑结构。主板固定将通用双驱无刷 FOC 控制板固定在钢板中央注意主板底部要做好绝缘垫上绝缘柱防止引脚短路到钢板上。2. 神经与血管接驳相线连接将左右电机的粗线黄、绿、蓝分别接入主板两侧的电机接线柱。霍尔线连接将电机的 5 芯霍尔线红、黑、黄、绿、蓝插入主板对应的白色端子中。电源准备准备一块 36V 动力锂电池满电 42V接入主板的 XT60 接口注意此时先不要通电。第二阶段灵魂重铸固件烧录与环境搭建通用控制板出厂通常带有其他应用固件我们需要用 ST-Link 将其“洗脑”。1. 武器库准备硬件ST-Link V2 下载器、杜邦线。软件安装 STM32CubeProgrammer 和 Visual Studio Code并安装 PlatformIO IDE 插件。源码获取准备好基于底层驱动参考方案定制的 FOC 固件源码。2. 破壁解锁擦除原厂固件在主板上找到 4 针 SWD 调试接口通常丝印为GND,3.3V,SWDIO,SWCLK。将 ST-Link 对应连接到主板。警告全程断开 36V 电池仅靠 ST-Link 的 3.3V 供电即可打开 STM32CubeProgrammer点击 Connect。如果提示芯片被锁Read Out Protection进入Option Bytes菜单将RDP改为Level 0(AA)点击 Apply。芯片将被瞬间清空主板进入“白纸”状态。第三阶段FOC 引擎调校参数配置打开 VS Code进入 PlatformIO 工程我们需要针对 36V 系统和机器人需求进行定制。1. 锁定通信变体在platformio.ini文件中找到[platformio]项修改为串口控制模式default_envs VARIANT_USART这告诉编译器我们将通过主板侧边的通用排线口使用 UART 接收外部指令。2. 核心参数标定 (Inc/config.h)这是最关键的一步必须与您的硬件物理特性匹配控制模式机器人底盘最稳妥的选择是速度闭环。#define CTRL_MOD_REQ SPD_MODE电池电压适配极其重要您的电机是 36V 系统10串锂电池必须修改电压阈值防止低压误报或过压炸管。#define BAT_CELLS 10 // 36V 电池通常是 10 串#define BAT_LVL1_ENABLE 1 // 开启低压报警#define BAT_LVL2_ENABLE 1 // 开启低压断电保护电机参数匹配10寸专属说明底层固件默认的 PID 参数是基于 6.5 寸电机调校的。您的10寸电机内部极对数Pole Pairs大概率也是 15 对可以直接跑起来。但因为轮胎变大、转动惯量变大如果启动或低速时电机出现轻微抖动/异响后续可能需要在BLDC_controller_data.c中稍微微调一下电流环或速度环的 PID 参数。编译代码并点击 Upload 烧录至主板。第四阶段上位机接管Teensy 4.1 点火测试现在主板已经是一台等待串口指令的纯粹伺服驱动器了。我们将使用性能强悍的 Teensy 4.1 作为大脑来接管它。1. 硬件通信连接在主板边缘找到用于通讯的 4 芯扩展排线插座引脚定义通常为GND,5V/15V,TX,RX。将您的Teensy 4.1与主板进行连接Teensy 的 TX 接主板 RXTeensy 的 RX 接主板 TXGND 接 GND。极其危险的避坑绝对不要盲猜电源脚主板排线里通常有一根是 15V 高压供电。Teensy 4.1 的 IO 口最高只能承受 3.3V 逻辑电平它不耐 5V 更不耐 15V。请务必只接 TX、RX 和 GND 这三根线Teensy 4.1 使用单独的 USB 或降压模块供电千万别把主板的 15V 接进 Teensy2. 协议下发与解析参考底层驱动提供的串口通信示例这套通信协议的运作逻辑如下发送指令控制每次发送一个结构体数据包。注意数据包内不是直接发左右轮速度而是发送Steer(转向幅度)和Speed(总线速度/油门)固件底层会自动进行差速解算。此外还包含0xABCD帧头和 XOR 校验和Checksum以防止数据被干扰。测试逻辑例如发送Send(0, 100);底盘就会直线前进发送Send(50, 0);底盘就会原地打转。接收反馈状态监测Teensy 4.1 必须在主循环中不断运行一个非阻塞的Receive()状态机函数。它会通过寻找0xABCD包头并核对校验和精准剥离出主板传回的实时数据包括左右轮真实转速、电池实时电压、主板温度。这对于您构建机器人的里程计Odom和电量监测至关重要。第五阶段跨文件解耦的现代机器人软件架构设计摒弃把所有代码揉在一坨的“意大利面条式”写法将底层电机运动学/控制逻辑与上层 ROS 2 (micro-ROS) 通信组件完全解耦。这样一来main.cpp成了纯粹的底盘控制大脑而外围的 DDS 通信只作为数据管道存在。Teensy 4.1 拥有 600MHz 的强劲算力和 8 个全硬件串口咱们彻底抛弃孱弱的SoftwareSerial直接起飞。 项目目录结构规划├── include/ │ ├── chassis_protocol.h # 底层通信协议数据包定义 │ └── ros2_interface.h # ROS 2 接口声明 ├── src/ │ ├── ros2_interface.cpp # micro-ROS / XRCE-DDS 核心封装不污染主干 │ └── main.cpp # 动作控制逻辑、加速/刹车/转向实现、主循环 └── platformio.ini # 编译配置文件1. 数据包协议头文件 (include/chassis_protocol.h)这个文件只存放最纯粹的数据结构体两边通信的基石。#pragma once #include Arduino.h #define CHASSIS_START_FRAME 0xABCD // 发送给底盘的控制帧 typedef struct { uint16_t start; int16_t steer; int16_t speed; uint16_t checksum; } SerialCommand; // 底盘返回的反馈帧 typedef struct { uint16_t start; int16_t cmd1; int16_t cmd2; int16_t speedR_meas; int16_t speedL_meas; int16_t batVoltage; int16_t boardTemp; uint16_t cmdLed; uint16_t checksum; } SerialFeedback;2. ROS 2 接口隔离层 (include/ros2_interface.hsrc/ros2_interface.cpp)这里将 micro-ROS (基于 micro-XRCE-DDS) 的初始化、内存分配、节点注册全部封装只向main.cpp暴露极简接口。include/ros2_interface.h#pragma once #include Arduino.h #include chassis_protocol.h // 定义回调函数指针类型用于将 ROS 下发的 cmd_vel 传给 main.cpp typedef void (*CmdVelCallback)(float linear_x, float angular_z); // 暴露给 main.cpp 的三个纯净接口 void ros2_setup(CmdVelCallback callback); void ros2_spin_loop(); void ros2_update_feedback(const SerialFeedback feedback);src/ros2_interface.cpp#include ros2_interface.h #include micro_ros_platformio.h #include rcl/rcl.h #include rclc/rclc.h #include rclc/executor.h #include geometry_msgs/msg/twist.h #include sensor_msgs/msg/battery_state.h // ROS 2 内部变量 rclc_support_t support; rcl_allocator_t allocator; rcl_node_t node; rclc_executor_t executor; rcl_subscription_t twist_subscriber; geometry_msgs__msg__Twist twist_msg; rcl_publisher_t battery_publisher; sensor_msgs__msg__BatteryState battery_msg; CmdVelCallback app_cmd_vel_callback nullptr; // ROS 2 接收到 cmd_vel 时的回调 void twist_subscription_callback(const void * msgin) { const geometry_msgs__msg__Twist * twist (const geometry_msgs__msg__Twist *)msgin; if (app_cmd_vel_callback ! nullptr) { // 将解包后的线速度和角速度传回给 main.cpp app_cmd_vel_callback(twist-linear.x, twist-angular.z); } } void ros2_setup(CmdVelCallback callback) { app_cmd_vel_callback callback; // Teensy 4.1 通过主 USB (Serial) 与上位机的 micro-ROS agent 通信 set_microros_serial_transports(Serial); delay(2000); allocator rcl_get_default_allocator(); rclc_support_init(support, 0, NULL, allocator); rclc_node_init_default(node, teensy_chassis_node, , support); // 订阅 cmd_vel rclc_subscription_init_default( twist_subscriber, node, ROSIDL_GET_MSG_TYPE_SUPPORT(geometry_msgs, msg, Twist), /cmd_vel); // 发布电池状态 rclc_publisher_init_default( battery_publisher, node, ROSIDL_GET_MSG_TYPE_SUPPORT(sensor_msgs, msg, BatteryState), /chassis/battery); // 初始化执行器 rclc_executor_init(executor, support.context, 1, allocator); rclc_executor_add_subscription(executor, twist_subscriber, twist_msg, twist_subscription_callback, ON_NEW_DATA); } void ros2_spin_loop() { rclc_executor_spin_some(executor, RCL_MS_TO_NS(10)); // 非阻塞自旋 } void ros2_update_feedback(const SerialFeedback feedback) { // 将底层发来的 36V 电池数据转化为 ROS 标准格式发布 battery_msg.voltage feedback.batVoltage / 100.0f; // 假设反馈是整数 mV转化为 V rcl_publish(battery_publisher, battery_msg, NULL); }3. 底盘控制大脑 (src/main.cpp)这里是动作执行的绝对主场包含加速、刹车、减速、停止、左转、右转、原地打转的完整逻辑。引入了动态转矩平滑控制算法防止高扭矩电机瞬间电流过大损伤刚性结构。#include Arduino.h #include chassis_protocol.h #include ros2_interface.h // 配置区 #define CHASSIS_SERIAL Serial1 // Teensy 4.1 硬件串口1 (RX1Pin0, TX1Pin1) 连主板 #define CHASSIS_BAUD 115200 // 波特率 #define MAX_SPEED 800 // 最大行进速度极限 #define MAX_STEER 400 // 最大转向极限 // 全局状态 int16_t target_speed 0; // 期望速度 int16_t target_steer 0; // 期望转向 int16_t current_speed 0; // 当前平滑输出速度 int16_t current_steer 0; // 当前平滑输出转向 SerialCommand cmd; SerialFeedback feedback; SerialFeedback new_feedback; // 运动学状态插值与柔性过渡封装 // 核心作用抑制瞬态电流突变保护高扭矩输出下的底盘刚性结构完整性 void Apply_Motion_Ramp_Filter(int16_t* c_speed, int16_t* c_steer, int16_t t_speed, int16_t t_steer) { // 内部算法基于特定的平滑算法逼近目标值 // ... [核心缓冲逻辑脱敏隐藏] ... } // 动作执行 API // 1. 加速 (给定增量) void chassis_accelerate(int16_t increment) { target_speed increment; if (target_speed MAX_SPEED) target_speed MAX_SPEED; } // 2. 减速/倒车 (给定增量) void chassis_decelerate(int16_t decrement) { target_speed - decrement; if (target_speed -MAX_SPEED) target_speed -MAX_SPEED; // 支持倒车加速 } // 3. 左转 / 右转 (行进中差速转向) void chassis_turn_left(int16_t steer_val) { target_steer steer_val; } void chassis_turn_right(int16_t steer_val) { target_steer -steer_val; } // 4. 原地打转 (类似坦克调头) void chassis_spin_in_place(int16_t speed, bool clockwise) { target_speed 0; // 必须切断前进动力 target_steer clockwise ? -speed : speed; } // 5. 停止 (平滑停下带惯性) void chassis_stop() { target_speed 0; target_steer 0; } // 6. 刹车 (紧急制动不给惯性) void chassis_brake() { target_speed 0; target_steer 0; // 瞬间拉平当前速度强行输出0给电机跨过 Ramping 缓冲 current_speed 0; current_steer 0; } // ROS 2 指令对接回调 void on_ros_cmd_vel(float linear_x, float angular_z) { // 映射 ROS 物理单位 (m/s, rad/s) 到底层驱动的 PWM/Speed 量纲 int16_t mapped_speed (int16_t)(linear_x * 300.0f); int16_t mapped_steer (int16_t)(angular_z * 150.0f); target_speed constrain(mapped_speed, -MAX_SPEED, MAX_SPEED); target_steer constrain(mapped_steer, -MAX_STEER, MAX_STEER); } // 底层串口通信 void send_to_chassis() { // 调用封装好的动态转矩平滑控制算法 Apply_Motion_Ramp_Filter(current_speed, current_steer, target_speed, target_steer); cmd.start CHASSIS_START_FRAME; cmd.steer current_steer; cmd.speed current_speed; cmd.checksum (uint16_t)(cmd.start ^ cmd.steer ^ cmd.speed); CHASSIS_SERIAL.write((uint8_t *) cmd, sizeof(cmd)); } void receive_from_chassis() { static byte idx 0; static byte incomingBytePrev 0; while (CHASSIS_SERIAL.available()) { byte incomingByte CHASSIS_SERIAL.read(); uint16_t bufStartFrame ((uint16_t)(incomingByte) 8) | incomingBytePrev; byte* p (byte*)new_feedback; if (bufStartFrame CHASSIS_START_FRAME) { p[0] incomingBytePrev; p[1] incomingByte; idx 2; } else if (idx 2 idx sizeof(SerialFeedback)) { p[idx] incomingByte; } if (idx sizeof(SerialFeedback)) { uint16_t checksum (uint16_t)(new_feedback.start ^ new_feedback.cmd1 ^ new_feedback.cmd2 ^ new_feedback.speedR_meas ^ new_feedback.speedL_meas ^ new_feedback.batVoltage ^ new_feedback.boardTemp ^ new_feedback.cmdLed); if (new_feedback.start CHASSIS_START_FRAME checksum new_feedback.checksum) { memcpy(feedback, new_feedback, sizeof(SerialFeedback)); ros2_update_feedback(feedback); // 将反馈数据推回给 ROS 2 } idx 0; } incomingBytePrev incomingByte; } } // 主程序 unsigned long last_send_time 0; void setup() { Serial.begin(115200); // USB 串口用于 micro-ROS 通信 CHASSIS_SERIAL.begin(CHASSIS_BAUD); // 硬件串口1接驱动主板 ros2_setup(on_ros_cmd_vel); // 初始化 ROS 2并绑定控制回调 } void loop() { unsigned long now millis(); ros2_spin_loop(); // 刷新 ROS 2 收发管道 (轻量化) receive_from_chassis(); // 处理来自底板的实时数据 // 以 100Hz 的固定频率下发平滑控制帧 if (now - last_send_time 10) { last_send_time now; send_to_chassis(); } } 架构解说动态转矩平滑控制机制机器人的上位机如 Nav2发送cmd_vel经常是阶跃信号瞬间从 0 变到 1.5m/s。直接传给底层会让轮子瞬间抽搐。我们在send_to_chassis()中调用了封装好的动态转矩平滑控制算法Apply_Motion_Ramp_Filter实现物理平滑过渡保护了 10 寸大电机和电池的寿命。紧急刹车chassis_brake()注意这个函数的实现它强行跨过了平滑缓冲算法直接把current_speed暴降为 0。这是为了防撞/急停场景设计的保命操作。彻底隔离的 ROS 2main.cpp对 ROS 的存在仅仅知道ros2_setup()和回调函数on_ros_cmd_vel。即使以后您不用 ROS 2改用蓝牙手柄也只需要写一个新模块去修改target_speed即可底盘核心控制逻辑一行代码都不用改

相关新闻

AE高级合成技巧:色彩校正、景深与光效打造电影感AMV

AE高级合成技巧:色彩校正、景深与光效打造电影感AMV

这次我们来看一套针对 AMV(Anime Music Video)制作的 After Effects 高级合成技巧。AMV 的核心在于将不同动画片段无缝融合,并注入强烈的情绪与风格,这远不止是简单的剪辑拼接。色彩校正、背景深度与氛围光效,正是决定…

2026/10/12 11:07:38 阅读更多 →
Python字符串转列表通用方法:从JSON到eval的安全实践

Python字符串转列表通用方法:从JSON到eval的安全实践

1. 引言 在Python编程中,我们经常会遇到需要将字符串表示的列表转换为实际列表对象的情况。比如从配置文件读取的字符串["qwen-turbo","qwen-plus","deepseek"],或者包含对象实例的字符串"[Person(Alice), Person(Bo…

2026/10/8 16:18:00 阅读更多 →
Unity游戏开发:工业级坐骑召唤系统设计与实现全解析

Unity游戏开发:工业级坐骑召唤系统设计与实现全解析

1. 项目概述:从需求到实现的完整拆解在游戏开发中,尤其是MMORPG、ARPG或者开放世界冒险类游戏里,“坐骑”系统几乎是标配。它不仅仅是玩家从一个点快速移动到另一个点的交通工具,更是角色成长、身份象征和游戏沉浸感的重要组成部分…

2026/10/10 1:08:00 阅读更多 →

最新新闻

AI做PPT终于不丑了!开源gpt-image2-ppt-skills让你自动生成PPT

AI做PPT终于不丑了!开源gpt-image2-ppt-skills让你自动生成PPT

AI做PPT终于不丑了!开源gpt-image2-ppt-skills让你自动生成PPT 大家好,我是星哥。 最近后台有不少朋友跟我吐槽:“星哥,现在市面上的 AI 做 PPT 工具,怎么做出来的东西都那么‘AI味’啊?” 说实话&#…

2026/10/13 7:05:56 阅读更多 →
毛坯房装修的正确流程是什么?

毛坯房装修的正确流程是什么?

现在温度越来越低,虽然不适合动工,可以趁着淡季活动多、优惠大、工程少,尽快定下装修公司、设计方案和需要定制的装修材料,不仅省钱,还不耽误工期。1)装修的准备工作,2)毛坯房装修的…

2026/10/13 7:05:56 阅读更多 →
宝塔面板管理

宝塔面板管理

在阿里云平台扩展程序中找到宝塔面板登录入口,在数据库/Mysql添加数据库,在数据库项上点导入进行导入数据结构备份文件,在导入的本地文件备份上再点导入,则把数据从文件导入到数据库中。

2026/10/13 7:05:56 阅读更多 →
【Bug已解决】PyTorch - RuntimeError: [enforce fail at inline_container.cc:209] . file not found: arc 解决方案

【Bug已解决】PyTorch - RuntimeError: [enforce fail at inline_container.cc:209] . file not found: arc 解决方案

【Bug已解决】PyTorch - RuntimeError: [enforce fail at inline_container.cc:209] . file not found: archive/data.pkl 解决方案 问题描述 在 PyTorch 中使用 torch.jit.save() 和 torch.jit.load() 保存和加载 TorchScript 模型时,开发者可能会遇到以下错误&…

2026/10/13 7:05:56 阅读更多 →
【Bug已解决】How to move PyTorch model to GPU on Apple M1 chips? 解决方案

【Bug已解决】How to move PyTorch model to GPU on Apple M1 chips? 解决方案

【Bug已解决】How to move PyTorch model to GPU on Apple M1 chips? 解决方案 问题描述 Apple Silicon(M1/M2/M3/M4)芯片的推出为机器学习开发带来了新的硬件选择。这些芯片内置了强大的 GPU(通过统一内存架构),但它…

2026/10/13 7:05:56 阅读更多 →
Java 项目部署之 Docker工具快速入门: 自定义镜像的分层结构与写时复制

Java 项目部署之 Docker工具快速入门: 自定义镜像的分层结构与写时复制

概述 自己写的 Spring Boot 服务,DockerHub 上不会有人替你构建镜像,必须自己来。而要自己构建,先得搞清楚一个镜像内部到底是怎么堆起来的——这一篇只讲结构,不讲语法。 纲要 从"镜像是什么"这句话里拆出组成与顺序…

2026/10/13 7:04:56 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →