这个想法来源于我自己突然想做个吊舱或者类似的云台,尝试自己控制方位、俯仰,然后输出框架角,将来配合我的 IMU 做一个姿态测试。于是我就在京东狠狠心买了一个云台。一开始对这个也不是很了解,就想着买个资料、东西多的,回来至少不会因为缺东西跑不起来、消磨积极性。所以,就下单了。
一、硬件装配与调试

大概两天就到了。正好第二天是个周六,我赶紧组装调试起来。一开始我还以为非常简单,就是串口发个指令,然后这个云台就会按照指令动起来。后来发现,装配没那么简单、调试也是一波三折。
头次装配的时候,我看着各种螺钉、铁片、轴承、舵机、控制板。我想,这有啥难的,就没有看教程,自己动手组装,参考店家的图片。反复试验了几次,琢磨出安装方法。用了两个小时,安装上了。

第一次安装的时候,我把两个舵机的控制线安装到舵机控制板上,我想,这个"舵机控制板"顾名思义就是控制舵机的。还有一块锂电池及其充电器,我都插上了。

哦,还有一个手柄。我买的时候还奇怪,给手柄做什么。

都组装上后,第一次试验开始。

第一次试验是成功的。起码俯仰舵机和方位舵机是动的。就是我很不理解,为什么我遥控按一个按键,俯仰和方位都在动。
此时最快的方法莫过于看店家的资料了。店家的资料给的相对比较全吧,从装配到调试都有。
首先是装配,我得确认自己是否安装对了。我看了视频,嗯,怎么说呢,安装的方式不太一样,我是先固定中间轴承,然后固定舵机的多功能支架,再固定方位舵机、俯仰舵机,最后固定长 U 支架。虽然安装不太一样,但我认为目前应该是可以运行的。没多想,继续安装。
改 ID 之路:从乱码到仿真器
下一步就是看店家的视频,看看如何调试。店家提供的视频,使用一个软件叫"Yeahbot+V2.0.6.exe",该软件可以设置舵机的 ID。然后我又问 WorkBuddy,得到了一个相同的结论。是的,要调 ID,这是为什么遥控一个按钮、控制两个轴的原因。我运行这个上位机,上位机消失了——迈克菲报告该软件有毒。怎么会有病毒呢,我让 WorkBuddy 分析了一下,说大概率是"大概率误报",但是有明显的病毒特征;VirusTotal 这结果推翻了这个判断,11/71 家报毒。我估计还是应该没有签名导致的。我自己的主力电脑杀软没关过吧,我不记得了,现在我觉得没必要关。办法总比困难多,我就在虚拟机调试,把这个软件在虚拟机运行,然后把串口给虚拟机。
虚拟机出现大量乱码。又一个滑稽的事情,怎么会出乱码。我没办法配置 ID 了。

我开始考虑,不用这个上位机进行 ID 配置,没必要依靠这个上位机。而且,乱码的来源还搞不懂。我尝试用 SSCOM 直接发一些指令,这个舵机控制板也是出乱码。奇怪,没道理,店家不应该发我有问题的产品。
我让 WorkBuddy 分析店家给的资料,发现有自己写上位机的可能。然后我让 WorkBuddy 写上位机,C# + WPF。我提需求,WorkBuddy 写,我来测试,还是老套路。我都觉得有点无聊。
上位机写出来了,尝试用上位机连接,同样的乱码。奇怪了。难道是该控制板出厂没刷程序,还是波特率不对,我尝试了不同的波特率,全都是乱码。好吧,看来要重新刷程序了。我仔细观察这个舵机控制板店家自带的原理图,没有直接留出仿真器接口,串口 1 通过 USB 接出来了。理论上,我通过串口烧录就行了。
我在出厂程序中找到"FlyMcu.exe"软件,看样子是烧录软件。我尝试启动,可以。选 HEX,烧录。额,报错了。

好吧,我让 WorkBuddy 分析,它说:"这是 mcuisp 自己崩溃了,跟板子、hex 文件、串口都没关系,是软件本身报非法内存访问(访问空指针 00002FFC)。FlyMcu 是 2009 年的老古董,对 Win10/11 兼容性差。"这事情越来越离谱。
梳理一下当时的死循环:我想改 ID → 发现无论上位机还是 SSCOM 都有乱码 → 怀疑舵机控制板有问题 → 烧程序 → FlyMcu 报错 → 尝试接仿真器,重新烧。
是的,既然串口烧录走不通,那么我就用仿真器烧,平时学习调试都是用仿真器。我让 WorkBuddy 查了这个主控的仿真器接口,PA13、PA14 原来被手柄占用了。理论上来说,目前用不到手柄。那么我就可以把手柄的线拆掉,然后剩下的杜邦线可以接仿真器。

看起来非常和谐,彷佛这个手柄就是这样设计的,哈哈。还有个问题,就是原 C8T6 芯片的仿真被屏蔽了,直接连 Keil 会有问题。那么,我想起我刚学 CubeMX 的时候,会忘了开 debug,然后导致烧录一次就失败的经历。我记得当时就是仿真器接 PWLink 软件,上电的时候怎么操作两下,然后写点什么东西,这个毛病就搞好了。这个没总结成固定的经验,这次也是碰运气。是的,试了大概十几次,就成功了。

OK,现在获取芯片完全主导权,看看为啥乱码,重新烧程序。稍微改下出厂程序,解除屏蔽仿真。

真相:舵机控制板只是个"中间商"
当时已经搞定了,后来发现还是有问题。我开始尝试问 WorkBuddy,为什么还有乱码。它说,其实这个舵机控制板只是透传,啥事也没干……我当时看的有点懵,那么,我要舵机控制板干什么,多一个中间商,多一个问题?也就是舵机本身支持串口数据发送。
我开始查看带的东西,发现有个总线调试板。

这个总线调试板,我越看越觉得,就这个板就够了。还要什么舵机控制板啊,又交学费了!!!然后通过网络查询到舵机通信协议,通过串口改了 ID。这次不会两个都联动了。
机械干涉:俯仰超调与长 U 支架
然而,又有新的问题。

总线调试板安装后,开始调试。发现俯仰角容易超调,长 U 支架与小圆盘非常容易干涉。舵机控制还经常控制过头。我想了一下,安装的时候,两个舵机是不分的。我记得买的时候,舵机的旋转范围是 270°,所以俯仰每次必过。随即,我把俯仰角调成了 0~180°。这时候再控制俯仰会好些,但是还是会超。
是的,此时怀疑安装的问题了。因为一开始安装的时候没顾及舵机的零位,所以只能拆掉与舵机相关的部分,调整好舵机位置,重新安装。

重新安装后,还是会卡在小圆盘的螺钉上。那就加高长 U 支架。

垫螺母后就好多了。再查阅舵机通信协议的时候,有一个"矫正 1500 初值"的命令。在目视水平差不多到达 90° 位置时,发一条命令,该俯仰框架就比较"正"了。至于方位轴,只需要在上位机控制时,注意零位就好了。
交给 AI:从 Excel 构想到三个版本
自此,硬件的部分,暂时告一段落,下一步就是想办法控制好。就是俯仰动 30° 就是 30°,不是别的角度;方位动 60° 就是 60°。而上位机的编写,目前我主要靠 AI,使用不同的 agent 来辅助编写。我自己脑海中有个大概模样,无法描述清楚的,我就写个 Excel 表,让他们自己解析。当然,不是每个 AI 都能准确理解我的意思。我折腾了快一天,也很累,但是的确,这个搞不好,一切白搭,这仅仅是开始而已。所以,就持续输出让 AI 搞。

一开始是 WorkBuddy。可能 WorkBuddy 积累了太多的调试对话,这个界面没搞好,反而越搞越差。我决定先让 WorkBuddy 休息下,换 QoderWork、QClaw 上场。之后就是我不停的提需求、测试、让他们改 BUG,一直折腾到 22 点。终于,三个 agent 搞了三个版本,最后我拍板使用 QoderWork 的版本。已经上传 Gitee 平台:
https://gitee.com/gaokun3540/2DpanTilt_qoder.git
至于调试过程中,又有什么问题,让 AI 自己说吧。

二、上位机调试踩坑记录(AI 视角)
以下由 QoderWork 记录,是三个 agent 协作开发上位机过程中遇到的主要问题及解决方式。
1. 串口收不到数据(RX 永远为 0)
这是最棘手的问题。上位机发送指令正常(TX 计数在涨),但接收计数始终为 0,舵机明明有回复,软件就是读不到。
排查过程相当曲折。先用 pyserial 写了个 Python 脚本直连串口——能收到,说明硬件没问题。再用 .NET 写了个控制台程序测试——单线程发完等 120ms 再读,也能收到。最后定位到问题出在 WPF 程序里:SerialPortService 开了一个独立的接收线程(RxLoop),和发送线程并发运行。在 CH340 这个 USB-TTL 芯片上,.NET 的 SerialPort 一旦收发线程同时操作,接收通路就会死锁——不报错、不异常、就是永远读不到数据。
最终方案已解决:彻底删掉接收线程,改成单线程顺序收发。轮询线程里按"发送 → Sleep(120ms) → ReadExisting()"的节奏走,一条一条来,不并发。丑是丑了点,但稳。
2. 角度显示不对(负数、死区)
串口通了之后,角度读数一塌糊涂:航向出现 -477 度这种离谱数字,俯仰在 0~47 度之间完全没输出。
原因是角度换算模型不对。最初用的是"零点 + 量程"的简单偏移,但实际舵机的 PWM 行程并不是从 500 到 2500 整整齐齐的。实测航向 0 度对应 P505,270 度对应 P2496;俯仰 0 度对应 P514,180 度对应 P2492。
最终方案已解决:改成两点线性标定 angle = (P - PMin) / (PMax - PMin) * Range。用实测的两个端点做线性插值,角度就准了。PMin 和 PMax 存在 config.json 里,重启不丢。
3. 低角度突然不更新了
标定完之后,航向从 270 度往 0 度转,转到 73 度就不更新了;俯仰转到 43 度也停了。
查日志发现,舵机在 P 值小于 1000 时回复的帧是三位数,比如 #000P505!,而正则写的是 P(\d{4})——必须恰好四位。三位数的帧直接被丢弃了。
最终方案已解决:改成 P(\d{3,4}) 就通了。这种变长响应在协议文档里没写清楚,属于实测才能发现的坑。
4. 滑块不跟手
TAB2 的 PWM 控制滑块,用户拖到某个位置发指令,但轮询回读又把滑块拉回实际位置,导致"刚拖过去就弹回来"。
最初的做法是加一个 _updatingSlider 标志位,回读更新滑块时置 true,ValueChanged 事件里检测到就跳过发送。能用,但体验不好——控制滑块和实际位置混在一起,分不清谁是谁。
最终方案已解决:改成双滑块方案——控制滑块(用户拖动,发指令)和实际位置滑块(橙色半透明,只读,跟随回读)叠在同一行。连接串口时做一次首次同步,把控制滑块拉到实际位置,之后各走各的。视觉上有个阴影重叠,但一目了然。
5. 暗色主题"白块"问题
界面主体是 VS Code 风格的暗色主题,但 WPF 的 ComboBox 下拉框、CheckBox、Menu 菜单、TabItem 标签头、MessageBox 对话框,全都保持着系统默认的白色。设个 Background 属性根本不管用——WPF 默认模板里写死了系统色。
最终方案已解决:给每个控件写完整的 ControlTemplate。ComboBox 要覆盖 ToggleButton + Popup + ComboBoxItem 三层模板;Menu 要覆盖 MenuItem 的弹出面板和分隔线;MessageBox 没法改,直接用自定义 Window 替代。前前后后改了五六个控件模板,才把白色全部消灭。
6. 联动区域的冗余输入
TAB1 底部原来有个"联动"区域,带独立的方位、俯仰、速度三个输入框。但左右两个面板已经各有一套输入了,联动不过是"把两边填好的值一起发出去"。重复输入纯属多余。
最终方案已解决:砍掉所有输入框,只留一个"联动执行"按钮。点击时直接读左面板的方位目标/速度和右面板的俯仰目标/速度,取两轴中运动时间较长的那个作为同步时间,一帧多发。
7. 三个 agent 的分工
这个项目同时用了三个 AI agent:QoderWork(Qwen3.8-Max-Preview)负责串口底层调试和最终定版;WorkBuddy(Deepseek-V4-Flash)负责最初的上位机框架搭建和协议分析;QClaw(Auto)把 Excel 构想图落地成了 UI 布局,是三个版本里最接近我原始构想的一个。三个 agent 各搞了一个版本,最终我拍板选了 QoderWork 的暗色版本作为主线,其余两个冻结。
代码已开源(MIT):gitee.com/gaokun3540/2DpanTilt_qoder
三、三个 Agent 的补充与勘误
定版之后,三个 agent 又分别对这份笔记做了补充。下面原样保留,算是这次协作的一个有趣注脚。
WorkBuddy 记录:建议补充两点
① 协议从哪来的(我们这边的工作)
当时商家源码只是"固件透传",真正的协议是上线搜索后从公开渠道(cnblogs、CSDN)找到的 Yeahbot 总线舵机指令表。我们整理出 20 条命令(PVER/PID/PMOD/PRAD/PRTV/PULK/PULR…),后来实测验证了 PCSD 才是有效写开机位置命令(文档里写 PSCD 有误)。这部分工作在笔记里没提——协议不是从商家源码逆出来的,是从网络上搜到的公开资料。
② SWD 解锁("芯片夺舍")
原厂固件禁用了 JTAG+SWD(GPIO_Remap_SWJ_Disable),板子插 ST-Link 连不上。我把 PS2 手柄接收器拆掉(那 4 根信号线 PA12-15 正好是手柄信号),把 PA13/PA14 改成 SWD 飞线。但问题来了:固件还在跑,SWD 被锁死——最后用 PWLink"复位时强连"抢到 SWD 时序(也就是芯片复位瞬间调试口还没被释放),才连上。之后改了固件一行代码(SWJ_JTAGDisable),保留下 SWD 调试口。这块是 WorkBuddy 和我一起完成的,QoderWork 的踩坑记录没提。
QClaw 补充与勘误(2026-08-02 增补)
一、勘误(必改)
协议表第 14 行:当前写为 #000PSCD!(设置初始值),应为 #000PCSD!(设置开机位置 / 断电回位)。依据:①正文第①节已确认 PCSD 才有效、PSCD 有误;②开源代码 ServoProtocol.cs 中 SetStartPos 用 #…PCSD!。后续以 PCSD 为准。
二、补充要点
协议通信要点小结:帧格式 #ID(3位)P参数!;波特率默认 115200,支持 9600~460800;半双工总线,指令间隔 ≥100ms;P 值标称 500~2500,实测行程 Yaw 505~2496 / Pitch 514~2492;响应中 P<1000 时为 3 位(如 #000P505!),解析正则须用 \d{3,4}(即踩坑三)。
config.json 标定参数:字段 YawPMin / YawPMax / PitchPMin / PitchPMax / YawRange / PitchRange;公式 angle = (P - PMin) / (PMax - PMin) * Range;存入 config.json,重启不丢。
源码 UTF-8 乱码提醒:读 2DpanTilt_qoder 的 MainWindow.xaml / .cs 时中文为乱码(如"浜戝彴")。代码能编译是因为编译器按 UTF-8 读取,但换机器/编辑器打开易乱码。提交前请统一以 UTF-8 重新保存这些文件。
三、可选修正
① 投票过程:正文/踩坑七写"三票一致选 qoder"。实际过程:先三票投 V2 → WorkBuddy 乌龙(误以为自己是 V2)→ 投票作废 → 改保留 Qoder。结局一致,精确记录可补一句。② 协议表第 3 行(写 ID 响应 #001P!):响应格式无法从代码确认,建议标"响应格式待实测核对"。③ 后续计划:开头提到"将来配合 IMU 做姿态测试",可加"后续计划"小节收尾;下一步准备做网页版上位机。
四、后续计划
硬件部分暂时告一段落,但"俯仰动 30° 就是 30°"这个目标还远没到终点。接下来想做的事情有几件:把上位机的发送节流、连接 FlushInput、PMax 标定按钮、多帧按 ! 拆分解析这些细节补齐;按 QClaw 的建议把源码统一存成 UTF-8、修正协议表;再往后,准备试试网页版上位机,以及最初设想的那件事——配合我的 IMU 做姿态测试,让云台输出框架角真正派上用场。
折腾了快一天,确实累,但看着三个 AI agent 各写一版、最后被我拍板定版的过程,也挺有意思。这仅仅是开始而已。
五、总线舵机实测:抖动诊断与升级方向(2026-08-03 增补)
上位机定版之后,剩下一个一直没解决的心结:云台转动的时候总是抖。一开始我以为是舵机"速度稳不住",甚至动过换更高精度电机的念头。干这行不能拍脑袋——我拿测试工程师的老本行来量化:把 SBG 惯导绑在云台上,用 IMU(77Hz 采样)记录转动过程中的角速度,让数据说话。
| 状态 | 陀螺仪波动 (std) | 特征 |
|---|---|---|
| 静止 | 0.01 ~ 0.02 °/s | 稳如磐石 |
| 旋转(绕 X · Pitch) | 5.35 °/s | 7.6 Hz 振荡 |
| 旋转(绕 Z · Yaw) | 3.6 ~ 7.2 °/s | 6.5 ~ 8.1 Hz 振荡 |
结论很明确:静止完全不抖,一转就抖,而且所有旋转段的主频都死死压在 7~8Hz——这是典型的位置闭环极限环振荡(舵机内部 PID 反复"过冲→回调→再过冲"),再叠加齿轮回差,跟"电机精度"没有关系。

接着按嫌疑顺序排查供电:示波器实测舵机运动时电压非常稳定,供电排除。真正放大抖动的是指令方式——原版上位机不停轮询重发位置,舵机被反复唤醒追位,抖动明显更大;改成串口助手一次发一条指令、等舵机跑完再发下一条,立刻好很多。


三条结论
① 控制方式硬约束:发一次 #xxxPxxxTxxx!,等 busy 清除后再发下一条,绝对禁止高频重发——这是后续任何固件/上位机移植都要遵守的红线。
② 直发后残余 2~4 °/s(7~8Hz)是这套舵机内部闭环 + 齿轮回差的固有水平,基本到极限了。
③ 想再稳只有换硬件:磁编码 + 金属齿轮的高端总线舵机。换无刷电机是错误方向——裸无刷连位置环都没有,只会更失控。
原舵机型号
机身标签只有 30kg.cm DC5~8.4V Yeahbot digital servo——即松甲 Yeahbot 总线舵机 30kg 款(松甲官方商城按 15/20/30kg 三档在售,无更细型号名),协议就是附录的 ASCII 指令表(#xxxPxxxTxxx!),供电 5~8.4V 宽压。
升级舵机推荐(待购买)
| 方案 | 推荐 | 要点 |
|---|---|---|
| 0 成本 | 不换,用直发方式控制 | 已到舵机极限,最省事 |
| 物理升级 | 飞特(Feetech) 30kg 级串行总线舵机(淘宝搜"飞特 32kg 总线舵机" / STSCS3206 系列) | 12 位磁编码(0.088°)、金属齿轮、TTL 半双工、6~7.4V 与现供电兼容,约 100~160 元/个 |
| 终极方案 | 无刷总线舵机(如达妙等) | 几百上千元,非必要 |
换 Feetech 前必须确认三件事:① 协议不兼容——Feetech 是二进制协议,qoder 上位机需要适配(改 ServoProtocol.cs 即可,工程量可控);② 安装孔位实测——云台是松甲定制孔位,标准舵机孔距大概率不同,先量孔距和输出花键(常见 25T)再下单;③ 换舵机后标定重做——位置单位是 0~4095 而非 500~2500,PMin/PMax 要重新标。
附录:舵机协议(部分)
Yeahbot 总线舵机指令表,整理自公开资料并经实测验证。自身 ID 以 ID=000 为例。
| 序号 | 指令格式 | 释义 | 响应示例 |
|---|---|---|---|
| 1 | #000PVER! | 读取版本 | #000PSERVO.C(4.3) |
| 2 | #000PID! | 读取 ID | #000P! |
| 3 | #000PID001! | 设置 / 修改 ID | #001P!(待实测核对) |
| 4 | #000PULK! | 释放扭力 | #OK! |
| 5 | #000PULR! | 恢复扭力 | #OK! |
| 6 | #000PMOD! | 读取工作模式 | #000PMOD1! |
| 7 | #000PMOD1! | 设置工作模式 | #000PMOD1! |
| 8 | #000PRAD! | 读取舵机位置 | #000P1500! |
| 9 | #000PDPT! | 暂停 | #OK! |
| 10 | #000PDCT! | 继续 | #OK! |
| 11 | #000PDST! | 舵机停止当前位置 | #OK! |
| 12 | #000PBD5! | 设置通信波特率 | #OK! |
| 13 | #000PSCK! | 矫正 1500 初值 | #OK! |
| 14 | #000PCSD! | 设置开机位置 / 断电回位 | #OK! |
| 15 | #000PCSM! | 开机释力 | #OK! |
| 16 | #000PCSR! | 开机恢复扭力 | #OK! |
| 17 | #000PSMI! | 设置最小值 | #OK! |
| 18 | #000PSMX! | 设置最大值 | #OK! |
| 19 | #000PCLE! | 除 ID 外全部恢复出厂 | #OK! |
| 20 | #000PRTV! | 读取温度电压 | #000PDA1563P1220T34V5.1! |
勘误说明:第 14 行原稿误作
PSCD,实测有效命令为PCSD,本表已修正。通信要点:半双工总线、指令间隔 ≥100ms、默认波特率 115200。