串口UART分频:整数分频器为何失败,分数分频器如何解决?
原创串口UART分频:整数分频器为何失败,分数分频器如何解决?
非整数时钟分频:通信不稳的根源
做Tang Console的独立软件栈时,我撞上一个不大不小的麻烦。FPGA核和MCU要串口通信,约定波特率1Mbps。SPI那边主设备说了算,UART这边得两边预先商量好速度,谁也不能临时改。我的系统时钟是21MHz,目标是2Mbps,整数分频只能设成11个周期。算下来每个bit实际0.524us,不是0.5us。10位累积误差0.24us,快摸到半位0.25us的阈值了,实测通信确实不稳。这个误差从第一bit开始就存在,10个bit过去,已经占了大半个bit窗口,接收端采样点几乎站不住脚 来源:原文。
100MHz系统时钟对准115,200 baud时,理想分频值约868.056。整数分频只能取868或869,误差很小,多数情况可以容忍。作者指出,标准UART设计通常把系统时钟除以整数产生波特率时钟,这种做法在非整数倍率下精度不足。868.056这个数,取整后偏0.056个周期,换算成时间误差大约0.06us,对115,200 baud来说每个bit窗口8.68us,这点误差可以忽略 来源:原文。
问题不是个例。只要波特率接近系统时钟,整数分频的误差就会被放大。21MHz除以11是1.909MHz,离2Mbps差4.8%。而100MHz除以868是115,207 baud,离115,200差0.006%。波特率越接近系统时钟,分频比的整数部分越小,舍入误差占比就越大。UART不像SPI那样由主设备决定时钟,双方必须遵守预同意波特率,分频精度直接决定通信稳定性 来源:原文。
分数分频器:Bresenham算法的巧妙应用
解决的办法是累加器跟踪分数间隔,纯整数运算实现Bresenham算法。每步加分数分母DIV_DEN,检查溢出分子DIV_NUM,溢出时余数进位。这样近似出:baud_clock_period = system_clock_period * (DIV_NUM / DIV_DEN)。这个公式看起来很抽象,但实现起来就是几个加法器和比较器的事。累加器不断累加分母,一旦超过分子,就输出一个脉冲并保留余数,继续下一个周期。不同周期间隔因此有了微小差别,但平均起来就是精确的分数比 来源:原文。
这个方案好在完全避免浮点运算和乘法。FPGA设计里浮点和乘法资源消耗大,还可能带来时序头疼。只用整数加法和比较,效率很高。生成时钟有少量抖动,抖动幅度多大,我不确定,但平均频率精确。接收端采样点通常在bit窗口中间,这点抖动应该没问题。作者在Tang Console项目中最终选用这个分数分频方案 来源:原文。
以21MHz/2Mbps为例,分数分频比是10.5。累加器在不同周期间切换,有的周期输出10个系统时钟,有的输出11个,平均下来每bit正好10.5个周期。21MHz除以10.5等于2Mbps,精确匹配。10位累积误差被消除,每个bit的起始点都被重新校准 来源:原文。
对比与验证:整数 vs 分数
传统整数分频对115,200 baud用868或869,误差在多数情况可容忍。波特率接近系统时钟时,整数分频误差不可接受。分数分频通过溢出进位控制输出脉冲间隔,平均后得到精确波特率,消除累积误差。作者实际测试了整数分频方案在21MHz/2Mbps下的表现,通信不稳定,因此开发分数分频方案 来源:原文。
这个问题在非整数时钟倍率下普遍存在。Bresenham算法提供了简洁的整数运算解决方案,不占用乘法器和浮点单元,在FPGA上实现非常轻量。作者强调,标准UART设计通常将系统时钟除以整数产生波特率时钟,但这种做法在非整数倍率下精度不足,分数分频正好填补这个缺口 来源:原文。
我倾向于认为,凡是遇到系统时钟和波特率不成整数倍的情况,直接上分数分频器。整数分频在边缘场景下的误差,实测已经证明会翻车,没必要赌运气。Bresenham累加器又不复杂,几行Verilog的事。与其花时间调整数分频的边界参数,不如一次到位用分数分频 来源:原文。
原文:
UART in Verilog with Fractional Clock Dividers · 来源:Hacker News
作者:itfan123 值班编辑 · 发布于 2026年11月6日
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除
itfan123






