ROS2与PX4协同控制无人机画圆:从Offboard通信到C++节点实战

发布时间:2026/9/21 7:10:06
ROS2与PX4协同控制无人机画圆:从Offboard通信到C++节点实战 很多人第一次听说“用ROS2控制PX4飞个圆”这个需求时心里想的是“写个坐标发进去就完事了”。等真正把环境装完、消息发出去、无人机在Gazebo里冲出去炸机之后才知道这条路的水有多深。这篇文章不是帮你把代码抄一遍而是把我自己踩过的坑、验证过的流程、以及每个关键决策背后的原因完整地讲给你听。这篇文章适合两类人一类是刚接触ROS2和PX4想通过一个具体任务把整个开发链路跑通的新手另一类是已经在用MAVLink或MAVSDK想切到ROS2生态但不太清楚Offboard模式在ROS2消息层应该怎么发的人。文中的所有流程都在Ubuntu 22.04 ROS2 Humble PX4 v1.14.3 Gazebo下实际跑过你可以直接照着复现。1. 项目全景ROS2、PX4、Gazebo三兄弟的协作关系1.1 为什么偏要用这个组合你可能想问控制无人机画圆的方案很多用MAVSDK写个Python脚本也行用QGC手动规划航线也行为什么非要用ROS2 C PX4 Gazebo这一套因为这几样东西组合起来恰好覆盖了从“算法设计”到“真机部署”的完整链路。ROS2提供分布式通信和丰富的感知、规划库C保证实时性PX4提供经过验证的飞行控制栈Gazebo则让你在不用炸真机的前提下验证算法。尤其是落地到实际项目里视觉避障、多机编队、室内定位这些需求基本都跑在ROS2生态上。用MAVSDK脚本控制一次飞行不难但要把视觉、路径规划、集群协同这些模块接到一起就成了“把非ROS系统硬塞进ROS架构”的别扭事。另外PX4的Offboard模式本身就是为外部计算机设计的高级控制接口。配合uXRCE-DDS协议ROS2可以直接以话题的形式向飞控发送期望位置、速度、姿态精度和实时性都比传统MAVLink串口转发好不少。可以说这个组合就是当前开源无人机二次开发的事实标准。1.2 通信链路拆解从C节点到飞控的“数据管道”先看整体数据流向我用文字画一条链路ROS2 C节点 │ ├─ 发布 /fmu/in/offboard_control_mode (OffboardControlMode 消息) └─ 发布 /fmu/in/trajectory_setpoint (TrajectorySetpoint 消息) │ ▼ Micro XRCE-DDS Agent运行在Ubuntu宿主机上 │ ▼ uXRCE-DDS Client运行在PX4固件内部 │ ▼ PX4 Offboard模式状态机 → 位置/姿态控制器 → 混控器 → 电机输出如果我们默认PX4是跑在Gazebo仿真里的SITLSoftware In The Loop模式那么PX4固件本身就是一个Linux进程它内部会启动一个uXRCE-DDS Client默认通过UDP连接宿主机的8888端口。宿主机这边运行一个Micro XRCE-DDS Agent负责把PX4发出的uORB消息转成标准DDS/ROS2话题也把ROS2发来的话题转回uORB消息。这里有个容易懵的地方ROS2节点到底是在跟谁说话实际上ROS2节点从来没有直接“接触”PX4它只是往/fmu/in/前缀的话题上发消息。Micro XRCE-DDS Agent收到后会把消息序列化通过网络传给PX4内部的ClientClient再把它翻译成PX4能读懂的uORB消息。整个流程对ROS2节点来说是透明的你只要按照PX4定义的消息格式发就行。还有一个关键点PX4的Offboard是一个“模式”不是一条指令。你发出的所有期望值都只是给Offboard状态机参考的输入。PX4要真正进入Offboard模式必须主动发送MAV_CMD_DO_SET_MODE或通过QGC、RC切换执行模式切换动作。这一点很多人会忽略结果就是消息发了一堆但无人机纹丝不动原因就是正反馈——模式根本没切进去。下面这张表可以帮你快速梳理各层的职责层级组件协议/机制主要职责决策层我们的C节点ROS2/DDS话题生成圆轨迹期望值发布控制指令桥接层Micro XRCE-DDS AgentXRCE-DDS over UDP/TCP在Linux宿主机和PX4之间转发消息飞控内部uXRCE-DDS ClientuORB将外部消息映射为PX4内部消息执行层PX4控制器位置控制、姿态控制计算电机指令驱动仿真模型这张表也对排错很有用——消息发不出去先判断节点有没有发布话题能看到但飞控不响应那大概率是Agent和Client之间的桥接问题如果飞控接收了但飞行动作怪异那是坐标系或控制参数的问题。2. 环境搭建一套能少折腾一个月的基础配置2.1 版本搭配建议环境搭建是整个流程里耗时最长、最容易放弃的一步。版本不匹配导致编译失败或者启动仿真时界面疯狂闪退这些问题十有八九是版本搭配不当。我自己实测下来比较稳的组合是这样的组件推荐版本备注Ubuntu22.04 LTS环境最成熟坑最少ROS2Humble Hawksbill和Ubuntu 22.04全兼容PX4源码v1.14.3 tag对ROS2/XRCE-DDS支持完善Gazebo Simgz-garden (新Gazebo)PX4 v1.14默认使用Micro XRCE-DDS Agent最新release用于宿主机桥接px4_msgs与PX4 v1.14.3配套ROS2侧消息定义如果你看到网上很多教程用Gazebo Classic 11那是因为PX4 v1.13之前的老教程。v1.14.3依然保留了gazebo-classic的启动方式但新版世界模型和仿真插件做得更好直接用gz_x500模型就行。这里我强烈建议使用v1.14.3而不是main分支。PX4的main分支代码变动非常快今天能编译的接口过两周可能就不兼容了学习阶段千万别追新。2.2 操作步骤从零到能起飞我按自己能稳定复现的顺序给你梳理一遍每一步都写清楚为什么要这么做。第一步安装ROS2 Humble。按照官方文档装desktop版本就行不用纠结选哪个版本。有个老生常谈但总有人踩的坑装完后一定要执行source /opt/ros/humble/setup.bash并且把这句话写进~/.bashrc否则新终端里ros2命令永远找不到。测试一下ros2 --version能输出版本号就说明ROS2本体OK。第二步安装Gazebo仿真工具链。对于PX4 v1.14.3我推荐直接用它的自动脚本把依赖一次装齐而不是手动一个个装gazebo。在源码目录下执行bash ./Tools/setup/ubuntu.sh这个脚本会检测并安装CMake、Python、Gazebo、OpenCV等一堆依赖。执行完以后最好重启一次shell让新添加的环境变量生效。第三步拉取PX4源码并切到稳定版本。git clone --recursive https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot git checkout v1.14.3 git submodule update --init --recursive--recursive非常重要。PX4有不少子模块比如mavlink、uavcan不更新子模块后面编译一定报错。第四步编译PX4 SITL。make px4_sitl gz_x500第一次编译会很久建议挂机去喝杯茶。如果编译过程中提示缺少某个Python包用pip install 包名补上就行基本不用慌。第五步创建ROS2工作空间放置px4_msgs。mkdir -p ~/ws/src cd ~/ws/src git clone https://github.com/PX4/px4_msgs.git cd ~/ws colcon build --packages-select px4_msgs source install/setup.bash这里有个细节px4_msgs的版本最好和PX4固件版本对齐。如果你用v1.14.3固件但px4_msgs是main分支消息字段可能有差异。我建议直接克隆后也切到对应release tag。第六步安装Micro XRCE-DDS Agent。这步通常有两种方式源码编译或者ros2包安装。我推荐用ROS2方式sudo apt install ros-humble-micro-ros-agent这样装完以后可以直接用ros2 run micro_ros_agent micro_ros_agent启动省去手动配置的麻烦。到这里环境基础就算打好了。启动完这些之后建议先在终端里验证一下PX4能否正常启动cd ~/PX4-Autopilot make px4_sitl gz_x500如果弹出Gazebo 3D窗口并且终端提示Ready for takeoff那就可以进入下一步了。2.3 常见的版本坑子模块缺失很多人的gazebo模型加载不出来原因是PX4的Tools/simulation-gazebo子模块没拉全。重新执行git submodule update --init --recursive就能解决。Gazebo界面狂闪如果GPU驱动不太好特别是虚拟机环境里Gazebo渲染会一直闪。临时解决方案是设置环境变量走软件渲染export LIBGL_ALWAYS_SOFTWARE1但性能会下降。最好装一下GPU驱动或换一台有独立显卡的机器跑仿真。ROS2话题从零开始没内容这通常是Agent没有启动或者PX4内部的uXRCE-DDS客户端没连上Agent。启动顺序上先启动Agent再启动PX4 SITL连接更稳定。3. C节点设计把一个“圆”翻译成飞控指令3.1 先理解Offboard模式下的控制接口在写代码前必须搞清楚PX4的Offboard模式到底接收什么格式的期望值。在ROS2接口层我们需要往两个话题上发消息/fmu/in/offboard_control_mode告知飞控本次期望值的类型位置/速度/加速度/姿态当前阶段我们需要的就是位置。/fmu/in/trajectory_setpoint具体的期望数值比如期望位置坐标、期望速度、期望偏航角。关键一点PX4在Offboard模式下会屏蔽掉所有不属于当前控制类型的其他字段。也就是说如果你的OffboardControlMode里只把position置为true那么TrajectorySetpoint里的velocity字段即使填了也不会参与控制。这点在调试时很重要不要同时开位置和速度控制容易让内环和外环打架。另外PX4内部使用的坐标系是NED北东地即X轴朝北、Y轴朝东、Z轴朝下。ROS2默认坐标系在很多场景下是ENU东北天X轴朝东、Y轴朝北、Z轴朝上。如果你在ROS2里拿到的局部位置是ENU直接塞进PX4的topic里无人机会往完全错误的方向飞。所以拿到数据后要么在节点内部统一做NED转换要么明确知道自己手里的坐标就是NED。后面我给代码时直接按NED生成圆轨迹反而少一层转换负担。3.2 圆的数学参数方程与控制频率要“画圆”本质上就是在NED坐标系下周期地给出圆的期望位置。假设圆心在起飞点上方的H高度半径R飞行角速度为ω那么NED位置满足x(t) x0 R * cos(θ(t)) y(t) y0 R * sin(θ(t)) z(t) -H // NED的Z轴向下期望高度取负 θ(t) θ0 ω * t这里θ0是初始相位决定无人机起点在圆上的哪个位置。ω的单位是rad/s想要多飞完一圈由目标线速度决定目标线速度v R * ω。更要紧的是PX4的Offboard模式允许你同时发位置和速度设定值。只发位置也能画圆但控制效果会比较“拖沓”飞机总是一点点追着目标走轨迹更像是跟随而非精确飞行。所以在TrajectorySetpoint里我建议把位置和速度都填上。速度期望可以直接用位置参数方程对时间求导得到vx(t) -R * ω * sin(θ(t)) vy(t) R * ω * cos(θ(t)) vz(t) 0飞控拿到位置和速度期望后位置环输出前馈速度速度环再跟踪跟踪效果会平滑很多。再说发布频率。PX4文档里有个硬性要求Offboard模式下设定值最低要高于某频率通常建议至少10Hz推荐20Hz~50Hz。我实测20Hz已经完全够用而且CPU占用不会太高。我个人习惯用50Hz作为默认值因为Gazebo仿真模型动力学本身比较简单50Hz下轨迹更平滑视觉上看不出锯齿。最后说一下给定参数的具体数值。如果你希望无人机在10秒内飞完一整圈半径R5m那根据圆周运动公式这个组合也可以这样拆可以先定角速度ω2π/10≈0.628 rad/s线速度就是5×0.628≈3.14m/s。这个线速度对四旋翼来说比较适中太慢会让位置控制环难以收敛太快则可能触发限幅导致轨迹打折。参数设定这块我的建议永远是“低参数起步好稳定后再加大”。3.3 写C代码核心循环下面是核心类和一个10Hz~50Hz的发布循环。这个代码我实际跑通过直接放在你创建的ROS2包里就能编译运行。为了不占太多篇幅我贴核心部分构造函数和话题创建的模板代码略。#include rclcpp/rclcpp.hpp #include px4_msgs/msg/offboard_control_mode.hpp #include px4_msgs/msg/trajectory_setpoint.hpp #include px4_msgs/msg/vehicle_local_position.hpp #include cmath class OffboardCircleNode : public rclcpp::Node { public: OffboardCircleNode() : Node(offboard_circle_node) { // 创建发布器和订阅器 offboard_mode_pub_ create_publisherpx4_msgs::msg::OffboardControlMode( /fmu/in/offboard_control_mode, 10); trajectory_pub_ create_publisherpx4_msgs::msg::TrajectorySetpoint( /fmu/in/trajectory_setpoint, 10); local_pos_sub_ create_subscriptionpx4_msgs::msg::VehicleLocalPosition( /fmu/out/vehicle_local_position, 10, [this](const px4_msgs::msg::VehicleLocalPosition::SharedPtr msg) { current_position_ *msg; }); // 定时器50Hz发布 timer_ create_wall_timer(std::chrono::milliseconds(20), [this]() { publishCircleSetpoint(); }); } private: void publishCircleSetpoint() { // 1. 先发布OffboardControlMode标记我们使用的控制类型 px4_msgs::msg::OffboardControlMode offboard_mode{}; offboard_mode.timestamp stamp(); offboard_mode.position true; // 我们使用位置控制 offboard_mode.velocity false; offboard_mode.acceleration false; offboard_mode.attitude false; offboard_mode.body_rate false; offboard_mode.thrust_and_torque false; offboard_mode.direct_actuator false; offboard_mode_pub_-publish(offboard_mode); // 2. 计算圆轨迹设定值 double time_now 0.0; // 实际项目中应从节点启动时刻或收到本地位置后开始计时 // 我这里用节点启动后的系统时间做演示 auto elapsed (this-now() - start_time_).seconds(); if (elapsed 0.0) elapsed 0.0; time_now elapsed; constexpr double radius 5.0; // 5米半径 constexpr double omega 2.0 * M_PI / 10.0; // 10秒一圈 constexpr double height 3.0; // 3米高度 constexpr double theta0 0.0; double theta theta0 omega * time_now; float x radius * std::cos(theta); float y radius * std::sin(theta); float z -height; // NEDZ轴向下 px4_msgs::msg::TrajectorySetpoint setpoint{}; setpoint.timestamp stamp(); setpoint.position[0] x; setpoint.position[1] y; setpoint.position[2] z; // 速度前馈让轨迹更平滑 setpoint.velocity[0] -radius * omega * std::sin(theta); setpoint.velocity[1] radius * omega * std::cos(theta); setpoint.velocity[2] 0.0f; setpoint.yaw 0.0f; // 让机头保持正北 trajectory_pub_-publish(setpoint); } uint64_t stamp() const { auto time_now this-now(); return static_castuint64_t(time_now.seconds() * 1e6) * static_castuint64_t(1000); // 返回纳秒级时间戳 } rclcpp::Publisherpx4_msgs::msg::OffboardControlMode::SharedPtr offboard_mode_pub_; rclcpp::Publisherpx4_msgs::msg::TrajectorySetpoint::SharedPtr trajectory_pub_; rclcpp::Subscriptionpx4_msgs::msg::VehicleLocalPosition::SharedPtr local_pos_sub_; rclcpp::TimerBase::SharedPtr timer_; px4_msgs::msg::VehicleLocalPosition current_position_; rclcpp::Time start_time_{rclcpp::Time(0)}; };代码里我故意保留了几个演示性质的简化点这里说一下哪些地方需要你自己替换成实际逻辑第一时间戳必须认真对待。OffboardControlMode和TrajectorySetpoint都要求传入PX4固件时间戳微秒或纳秒单位。我上面写的stamp()函数只演示了如何从ROS2时间转换实际运行中你会发现如果PX4主机和ROS2主机时间不同步比如通过多机通信跑时间戳会比较别扭。SITL模式下两个进程在同一台机器上时间基本一致所以问题不大。如果你跑真机建议用同步后的系统时间或PX4输出的timestamp统一管理。第二起飞前的“预置点”逻辑。代码直接从0点开始画圆但无人机在地面时不能直接从地面跳到3米高那样飞控会给出很大的期望加速度导致起飞瞬间剧烈晃动甚至翻转。稳妥做法是先发送一个当前位置的setpoint等无人机切换到Offboard模式并且高度到期望值附近后再开始画圆。我在完整工程里是把“进入Offboard模式”也封装成一个节点行为的这里篇幅有限就只展示核心循环。3.4 编译配置CMakeLists和package.xml一个干净的package.xml和CMakeLists.txt主要就是把px4_msgs依赖加进来。package.xml里需要声明dependrclcpp/depend dependpx4_msgs/dependCMakeLists.txt里则要加find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(px4_msgs REQUIRED) add_executable(offboard_circle src/offboard_circle_node.cpp) ament_target_dependencies(offboard_circle rclcpp px4_msgs) install(TARGETS offboard_circle DESTINATION lib/${PROJECT_NAME})编译cd ~/ws colcon build --packages-select your_package_name source install/setup.bash4. Gazebo仿真看着无人机把圆画出来4.1 启动PX4 SITL与DDS Agent一切就绪后启动顺序很关键。我的习惯是# 终端1启动ROS2环境 source /opt/ros/humble/setup.bash source ~/ws/install/setup.bash ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888 -v注意-v参数会输出详细日志第一次调试时务必打开能直接看到Agent和Client是否成功握手。# 终端2启动PX4 SITL cd ~/PX4-Autopilot make px4_sitl gz_x500当Gazebo窗口弹出、终端打印[SITL] Ready for takeoff后说明PX4固件已经在仿真里跑起来了。此时去终端1看一眼日志应该能看到Agent收到了来自PX4的DDS消息[info] New client [client id: 7] connected如果没看到这条多半是端口不对。默认PX4的SITL模式会往本机8888端口发UDP包Agent也要监听8888端口两边改成一致即可。4.2 运行ROS2节点验证画圆效果启动我们的控制节点# 另一个终端同样先source ros2 run your_package offboard_circle理论上无人机会垂直上升到3米高然后稳定地飞一个半径5米的圆。我建议你在验证阶段做几件事第一打开QGroundControlQGC连接到仿真。用QGC的好处是可以目视角速度和位置误差也能直接远程切换模式。注意SITL模式下QGC连接的是UDP端口14550不是USB串口。第二确认飞行模式。要让节点真正进入Offboard你可以通过QGC把飞行模式切到“Offboard”或者如果不想再额外写模式切换代码也可以用ros2 topic pub发一条MAVLink指令但这比较绕。我更推荐先在C节点里直接发MAVLink指令做自动模式切换或者跑起来以后在QGC手动切。手动切最省事适合第一次验证。切换后看页面上的飞控状态确认当前模式是Offboard而不是Position。第三用ros2 topic echo观察实际位置ros2 topic echo /fmu/out/vehicle_local_position这里面有几个字段值得关注x、y、z是当地NED坐标系下的位置估计vx、vy、vz是速度估计。如果它输出的数值和我们的圆参数一致说明整个链路已经通了。更直观的方法是装rqt_plot把x和y画到一个二维坐标里就能看到圆心位置和轨迹形状。第四看Gazebo画面。如果无人机在画面里不断画圈并且圆半径看着稳定这事就算大功告成。轨迹是椭圆的话多半是半径参数或速度前馈没对下面一节会细说。4.3 如何让圆“飞得更圆”你可能觉得能飞起来就算圆了。但其实第一版代码跑出来的轨迹大概率不圆甚至有点“方”。我的经验是从三个方向调一是提高发布频率到50Hz以上。Offboard模式对丢包和延迟非常敏感如果发布频率只有5HzPX4会频繁触发Offboard超时甚至自动退出Offboard。频率高一点之后控制环的跟踪误差会明显下降。二是用好速度前馈。前面代码里已经填了velocity字段前提是OffboardControlMode里positiontrue的同时别把velocity也置true否则PX4会尝试做速度控制反而丢掉了位置环。实际上PX4在位置控制下会把TrajectorySetpoint.velocity当作前馈速度。如果你发现“画出来的圆有滞后”可以先检查这个字段是否有值。三是调PX4位置控制器的增益。所有PX4的Offboard外部控制最终都会落到内部位置环。位置环PID增益在QGC的MC_POSCTRL参数组里。给一个实用经验如果无人机飞圆时有明显“追赶”感可适当增大MC_POSCTRL_POS_P比如从默认的1.0提到1.2。但别一次调太多否则会有高频抖动。修改后直接改参数无人机会在线响应不用重启仿真。5. 常见问题与排查实录5.1 高频故障速查表我把自己和身边人实测中踩过的坑整理成了表格遇到问题先对号入座症状可能原因排查思路消息发了但无人机不动没有进入Offboard模式用QGC或MAVLink确认当前模式进入Offboard后立即退飞/降落设定值不连续或频率不足保证循环发布50Hz预置点设在当前位置附近无人机起飞就翻转坐标系反了/IMU标定错误检查NED vs ENU重新校准加速度计Gazebo画面狂闪显卡渲染驱动问题尝试LIBGL_ALWAYS_SOFTWARE1或升级显卡驱动ros2 topic list里没有/fmu开头的消息Agent未启动或端口不一致运行Agent确认8888端口看verbose日志圆飞成椭圆速度前馈没填/增益不合适检查速度字段提升发布频率微调MC_POSCTRL_POS_P高度一直往下掉NED的Z方向填反了NED的期望高度应为负数如-3无人机频繁退Offboard控制指令超时查看PX4终端是否打印“Offboard control lost”px4_msgs编译不过版本与PX4不匹配检查px4_msgs的tag最好和固件版本对齐5.2 几个被忽略的细节除了表格里的问题还有几个细节严重影响成功率但常规文档很少提。第一个是预置点策略。如果无人机在地面上代码运行后第一帧就发送3米高的圆心位置PX4会认为你期望它瞬间到3米。这时候电机会猛加速飞控可能直接进入保护模式。不要一上来就发目标点应该先发“当前位置保持”的设定值等无人机起飞并稳定在目标高度后再开始圆的轨迹。我的做法是节点启动后先订阅vehicle_local_position拿当前位置把它作为前1秒的期望输出等时间超过1秒后再切换到圆轨迹。第二个是模式切换的“参数豁免”。如果你没有RC遥控器SITL在启动时会默认检测到RC信号丢失一旦无人机进入Offboard后有哪怕几百毫秒的异常就可能导致飞行终止或落回Position模式。建议在QGC里把COM_RCL_EXCEPT设置为1表示Offboard模式可以工作于RC丢失状态。这个参数我在多次实物测试里也被坑过仿真里不设置也能飞但真机上不设置的话很容易一上天就触发保护。第三个是不要在主循环里做耗时操作。比如把打印日志、打开文件操作放在50Hz的publish循环里偶尔卡顿一帧可能问题不大但当频率达到20Hz以上时几百毫秒的卡顿可能会让PX4认为Offboard超时。我一般是发布逻辑只做数学计算和消息发送必须做的日志用单独的线程或降频输出。第四个值得单独说的是**“时间戳差”的问题**。PX4 v1.14开始OffboardControlMode消息里的timestamp必须填写否则飞控无法正确判断消息的新鲜度。有的代码里随便填一个固定值无人机能跑但会有偶发抖动。正确做法是每次消息都取当前时刻并且单位要统一。PX4内部的timestamp通常是微秒但有些消息是纳秒比如vehicle_local_position输出是微秒时间戳。我习惯统一用微秒避免出现消息被当作过期数据丢掉。最后的经验分享这个demo做完之后最大的收获不是“我会发圆轨迹了”而是终于把ROS2到PX4之间的这层通信彻底搞明白了。后面你再去做航线规划、视觉避障、编队飞行核心思路都是一样的ROS2负责算期望值PX4负责执行Agent负责翻译。区别只是期望值从“圆”变成“贝塞尔曲线”还是“多机协同轨迹”而已。再分享一个小技巧调通圆轨迹之后可以把代码里的半径、角速度、高度改成从ROS2参数读取这样就不用每次改代码重新编译。如果你后续打算做路径规划把“生成轨迹”的部分抽象成接口圆只是其中一种轨迹实现后面接Bezier曲线或B样条就顺理成章了。ROS2的可复用性真正发挥出来是从“让无人机飞起来”变成“让无人机按我的算法去飞”的时候。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询