队伍选了电赛控制题,用高云GW2A驱动六路舵机,PWM模块用50MHz分频到50Hz。测试时发现同时更新占空比会有几路输出瞬间抖动,舵机嗡嗡响。我怀疑是占空比寄存器跨时钟域没同步,但队友说分频计数器本身就有毛刺。想请教做过电赛舵机控制的同学,这种多路PWM同步更新的正确写法是什么?要不要加输出寄存器打拍?
2026年电赛FPGA控制题用高云GW2A做多路舵机同步,PWM占空比更新时总有毛刺,是寄存器打拍不够还是时钟域没统一?
5 个回答

先对齐一下场景:电赛控制题、六路舵机、50MHz 分频到 50Hz,大概率是拿一路计数器当公共时基,六路各自比一个占空比阈值。这个结构本身没毛病,问题多半出在『比较值什么时候换』上。
你队友说的『分频计数器有毛刺』,其实要拆开看。计数器是时序逻辑,输出本身不会有组合毛刺,真正容易出问题的是比较器和后续的组合逻辑:如果占空比寄存器直接参与比较、比较结果又直接出引脚,那在占空比跳变的瞬间,比较器输出可能出现窄脉冲,被舵机当成一次异常脉宽。
所以正确写法通常是两层:一是占空比更新值先写进一个『影子寄存器』,等 PWM 周期结束(计数器归零或到周期点)再统一搬到工作寄存器,这样六路在同一拍换值,相位天然一致;二是比较结果后面再打一拍寄存器输出,把组合路径上的毛刺滤掉。注意这两拍解决的是不同问题,不能互相替代——前者管『什么时候换』,后者管『输出干净』。
还有一个容易被忽略的点:六路共用一个计数器,才能保证相位一致;如果每路各分频,哪怕参数一样,复位释放时刻不同也会错相。
定位手段上,建议用 ILA 抓更新瞬间,把计数器、占空比工作寄存器、比较结果、输出引脚一起看,毛刺出现在哪一级就清楚了。
你现在的占空比是直接写工作寄存器,还是已经过了一级影子寄存器?这个决定了改法。

这个问题我倾向于从因果链上捋一遍,而不是直接告诉你加几拍。
舵机嗡嗡响、几路瞬间抖动,本质是某几个周期的有效脉宽偏离了目标值。偏离可能来自三个地方:比较值在周期中间被改了、比较器输出有组合毛刺、或者多路相位本身就不一致。你说的『跨时钟域没同步』,在纯 FPGA 内部、如果占空比来源也是 50MHz 同源时钟,其实不算真正的跨时钟域,更像是『更新时刻没对齐』。真正跨时钟域的是你从别处(比如串口、软核、状态机另一个时钟)把占空比送进来那一段,那里才需要同步器或握手。
所以先分清两件事:数据来源跨不跨时钟域,和更新动作在不在周期边界。前者用两级同步或异步 FIFO 解决,后者用影子寄存器加周期末搬运解决。很多人一上来就狂打拍,结果数据是同步了,但六路在不同时刻搬运,相位还是乱的。
工程上的取舍是这样:如果六路共用一个计数器、共用一个周期点做 load 使能,那搬运是同一拍发生的,相位一致基本免费拿到;输出级再加一级寄存器,成本就一个 LUT 加一个 FF,几乎不占资源,但能挡掉比较器组合路径上的毛刺,性价比很高,建议加。
学习路径上,我建议你把这段逻辑单独写个 testbench,用仿真看更新瞬间几路输出的边沿,比直接上板调快得多。仿真里能复现,说明是逻辑问题;仿真干净但上板抖,再怀疑引脚约束、电源或舵机本身。
顺便问一句,你的占空比是从按键或串口这类异步源来的,还是内部状态机直接给的?这决定了要不要真同步器。

先别急着纠结时钟域,你描述的现象更像是更新时刻不统一。50MHz 分频到 50Hz,六路共用一个计数器的话,本身就是在同一个时钟域里跑,占空比寄存器如果是同一个 50MHz 时钟写进去的,那就不存在真正的跨时钟域问题,顶多是写进去的时刻恰好在周期中间。真正的毛病通常出在比较值直接参与比较、比较结果又直接出引脚:占空比一改,比较器组合路径上可能冒出窄脉冲,舵机看到异常脉宽就嗡嗡响。
正确的做法分两层,别混在一起。第一层,占空比更新值先写进影子寄存器,等 PWM 周期结束(计数器归零那一拍)再统一搬到工作寄存器,这样六路在同一拍换值,相位天然一致。第二层,比较结果后面再打一拍寄存器输出,把组合路径的毛刺滤掉。这两拍解决的是不同问题,前者管更新对齐,后者管输出干净,缺一个都不行。
还有个容易忽略的点:如果你是从串口或软核那边把占空比送进来的,那一段才是真跨时钟域,得用两级同步或者握手。但那是数据来源的问题,跟你现在这个『同时更新抖动』不是一回事。定位的话,建议用高云的在线逻辑分析仪抓一下更新瞬间的几路输出波形,看毛刺是出现在周期中间还是周期边界,基本一眼就能判断是更新对齐问题还是输出级问题。
你现在的占空比是从哪里写进来的,状态机还是外部接口?这个决定了要不要额外加同步。

这个问题我想沿着一条线往下说,就是『毛刺到底从哪来』。很多人第一反应是打拍不够,于是疯狂加寄存器,结果发现抖动还在,因为方向错了。
先把结构画清楚:一个 50MHz 时钟,一个分频计数器产生 50Hz 周期,六路各自拿一个占空比阈值去跟计数器比较。计数器是时序逻辑,它的输出是寄存器出来的,不会有组合毛刺,所以你队友说的『分频计数器有毛刺』这个说法本身就不太站得住——真正有毛刺风险的是比较器和它后面的组合逻辑。
那抖动为什么出现在『同时更新占空比』的时候?因为你的占空比寄存器是在某个任意时刻被写进去的,可能这一路在周期第 30 个计数时换了值,那一路在第 70 个计数时换了值,六路的相位就错开了。舵机对脉宽的容忍度没那么高,几路相位一乱,表现就是嗡嗡响加抖动。
所以核心矛盾不是『跨时钟域』,而是『更新动作没有对齐到周期边界』。解法就是影子寄存器加周期末搬运:占空比先写进影子寄存器,等计数器归零那一拍,六路一起搬到工作寄存器。这样六路永远在同一拍换值,相位一致。
再往深一层,输出级要不要打拍?要,但它解决的是另一个问题。比较器输出是组合逻辑,占空比刚换的那一拍,比较结果可能出现窄脉冲,这时候在比较结果后面加一级寄存器,用 50MHz 打一拍再出引脚,就能把这个窄脉冲滤掉。注意这一拍会引入一个时钟周期的延迟,对 50Hz 舵机来说完全可以忽略。
这里有个取舍:如果你为了省资源,六路共用一个计数器但各自独立比较,那更新对齐必须靠影子寄存器统一搬运;如果你图省事每路一个计数器,那相位本来就难保证,反而更麻烦。工程上一般推荐前者。
定位手段还是那句,用 ILA 抓更新瞬间的输出,看毛刺在周期中间还是边界,基本就能定性。至于要不要再往上游查数据来源的同步,那得看你的占空比是从哪来的,如果是片内状态机产生的,就不用折腾了。

先把问题定性:你这大概率不是跨时钟域,而是更新时刻不统一加上输出级组合毛刺,两件事叠在一起。顺着一条线往下捋会清楚很多。
50MHz 分频出 50Hz,六路共用同一个计数器,占空比寄存器又都是这 50MHz 时钟写进去的,那它们本来就在一个时钟域里,谈不上同步不同步。你队友说分频计数器有毛刺,这话要拆开看:计数器输出是寄存器出来的,本身干净,真正有毛刺风险的是比较器那条组合路径。占空比在周期中间被改,比较结果立刻跟着跳,就可能冒出一个窄脉冲,舵机把它当成一次异常脉宽,表现就是嗡嗡响、某几路瞬间抖。
所以判断维度就两个,优先级别搞反。第一优先是更新对齐:占空比新值先写进影子寄存器,等计数器归零那一拍统一搬到工作寄存器,六路同一拍换值,相位天然一致。这一步解决的是几路不同步。第二优先才是输出打拍:比较结果后面再挂一级寄存器出引脚,把组合路径上的窄脉冲滤掉。这一步解决的是单路自己冒毛刺。两拍管的是两件事,缺一个都不行,别指望加输出寄存器就能把相位问题也一起治好。
落到 GW2A 上,写法上有一点值得注意:高云的时序原语和复位处理跟 Xilinx 不太一样,影子寄存器的搬运条件最好就用计数器归零那个使能,别用异步复位去凑,否则容易在综合后多出意想不到的路径。六路共用一个计数器这点你已经做对了,保住它,相位一致的前提就在这。
定位手段上,别光靠示波器看引脚。用高云自带的在线逻辑分析仪抓更新瞬间那几拍,把计数器值、影子寄存器、工作寄存器、比较结果一起放进波形,哪一拍跳、跳几拍,一眼就看得出来。这比反复加寄存器猜要快得多。
你现在是影子寄存器还没加,还是加了但搬运时刻没跟计数器归零对齐?这个决定了下一步先动哪。
发表回答
登录后可在本页底部提交回答