4、脉冲输出模块设计(二):多通道脉冲输出、相位差控制、死区时间插入

上一节我们把单通道脉冲输出的基本功打扎实了。这一节,咱们来点真格的——多通道协同工作。

说实话,单通道脉冲输出,你只要把计数器玩明白,基本就搞定了。但一旦涉及到多通道,事情就变得有意思了。相位差、死区时间、通道间干扰……这些坑,我当年一个一个踩过来的。

4.1 多通道脉冲输出的架构设计

多通道脉冲输出,说白了就是在一个模块里同时管理N路脉冲信号。每路都有自己的频率、占空比、起始相位。

我习惯的做法是这样的:用一个全局计数器作为时间基准,每个通道独立配置自己的比较寄存器。这样所有通道都同步在同一个时间轴上,相位关系天然可控。

核心思路: 全局时基 + 通道独立配置 = 精确的通道间时序关系

来看看我常用的模块结构:

module multi_ch_pulse #(
    parameter CH_NUM = 4,          // 通道数
    parameter CNT_WIDTH = 32       // 计数器位宽
)(
    input  wire                clk,
    input  wire                rst_n,
    // 配置接口
    input  wire [CNT_WIDTH-1:0] period    [CH_NUM-1:0],  // 周期
    input  wire [CNT_WIDTH-1:0] duty      [CH_NUM-1:0],  // 占空比
    input  wire [CNT_WIDTH-1:0] phase     [CH_NUM-1:0],  // 相位偏移
    input  wire [7:0]           dead_time [CH_NUM-1:0],  // 死区时间
    // 输出
    output reg  [CH_NUM-1:0]    pulse_out
);

    reg [CNT_WIDTH-1:0] cnt;
    reg [CNT_WIDTH-1:0] cnt_phase [CH_NUM-1:0];  // 各通道相位调整后的计数值

    // 全局计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt <= 0;
        else if (cnt >= period[0])  // 以通道0的周期为基准
            cnt <= 0;
        else
            cnt <= cnt + 1;
    end

    // 各通道相位调整
    generate
        genvar i;
        for (i = 0; i < CH_NUM; i = i + 1) begin : gen_phase
            always @(*) begin
                if (cnt >= phase[i])
                    cnt_phase[i] = cnt - phase[i];
                else
                    cnt_phase[i] = cnt + period[0] - phase[i];
            end
        end
    endgenerate

    // 脉冲生成(含死区)
    generate
        for (i = 0; i < CH_NUM; i = i + 1) begin : gen_pulse
            always @(posedge clk or negedge rst_n) begin
                if (!rst_n)
                    pulse_out[i] <= 0;
                else begin
                    // 这里只是示意,实际死区逻辑要更复杂
                    if (cnt_phase[i] < duty[i])
                        pulse_out[i] <= 1;
                    else
                        pulse_out[i] <= 0;
                end
            end
        end
    endgenerate

endmodule

我的经验: 全局计数器不要用所有通道中最大的周期,而是用通道0的周期。这样其他通道的相位偏移计算就简单了。当然,前提是通道0的周期是所有通道的公约数。

4.2 相位差控制——让脉冲们排好队

相位差控制,是电机控制、多相电源、步进驱动等场景的核心需求。你想想看,如果两路PWM同时跳变,那电流尖峰得多大?

相位差说白了就是:各路脉冲的起始点错开一定的时间。比如通道0在0°开始,通道1在90°开始,通道2在180°开始……

实现方式其实很简单:

  • 方法一: 每个通道独立计数器,各自从不同的初始值开始计数。这是最直观的做法。
  • 方法二: 共享一个全局计数器,每个通道配置一个相位偏移寄存器。我推荐这种做法,因为所有通道天然同步。

方法二的相位计算逻辑:

// 相位调整后的计数值
// 假设全局计数器 cnt 从 0 到 PERIOD-1
// 通道 i 的相位偏移为 phase[i]
// 则通道 i 的有效计数值为:
//   cnt_eff = (cnt >= phase[i]) ? (cnt - phase[i]) : (cnt + PERIOD - phase[i])

嗯,这里要注意:相位偏移的单位是时钟周期。如果你需要更精细的相位控制,比如0.1°的精度,那就得用小数分频或者DDS技术了。这个我们后面章节会讲。

我曾经踩过的坑: 相位偏移值不能大于周期值。否则会出现相位绕回,导致脉冲时序完全乱掉。一定要在配置接口做合法性检查。

4.3 死区时间插入——保护你的功率管

死区时间,这是做电机驱动、逆变器、开关电源的朋友最熟悉的概念了。

为什么要插死区?因为功率管(MOSFET、IGBT)的开关速度不是无限快的。关断需要时间,开通也需要时间。如果上下桥臂同时导通,那就是直通短路,瞬间烧管子。

死区时间,就是在上管关断后、下管开通前,插入一段两者都关断的时间。反过来也一样。

我见过很多新手犯的错误:死区时间设得太短,觉得"我的管子开关速度很快,1ns就够了"。结果一上电,管子冒烟了。为什么?因为PCB走线寄生电感、驱动芯片的传播延迟、管子本身的米勒效应……这些都会让实际开关时间变长。

死区插入的典型实现:

// 死区插入逻辑(以一对互补PWM为例)
// pwm_high 和 pwm_low 是原始的互补信号
// dead_time 是死区时间(时钟周期数)

reg [7:0] dead_cnt_high, dead_cnt_low;
reg pwm_high_out, pwm_low_out;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        pwm_high_out <= 0;
        pwm_low_out  <= 0;
        dead_cnt_high <= 0;
        dead_cnt_low  <= 0;
    end else begin
        // 高边通道
        if (pwm_high) begin
            // 等待低边完全关断
            if (dead_cnt_low == 0 && !pwm_low_out)
                pwm_high_out <= 1;
        end else begin
            pwm_high_out <= 0;
            dead_cnt_high <= dead_time;  // 开始计时
        end

        // 低边通道
        if (pwm_low) begin
            if (dead_cnt_high == 0 && !pwm_high_out)
                pwm_low_out <= 1;
        end else begin
            pwm_low_out <= 0;
            dead_cnt_low <= dead_time;
        end

        // 死区计数器递减
        if (dead_cnt_high > 0) dead_cnt_high <= dead_cnt_high - 1;
        if (dead_cnt_low  > 0) dead_cnt_low  <= dead_cnt_low  - 1;
    end
end

关键点: 死区时间不是越长越好。太长的死区会导致输出波形失真,电机电流谐波增大,效率下降。一般建议在满足安全的前提下,尽量短。我常用的经验值是:IGBT用2-5μs,MOSFET用100-500ns。

4.4 多通道脉冲输出的时序图

光说理论不够直观,我画了一张时序图,帮你理解多通道相位差和死区的关系。

多通道脉冲输出时序图(4通道,90°相位差) t CH0 CH1 90° CH2 180° CH3 270° 死区 一个完整周期(360°) CH0 CH1 CH2 CH3

从这张图你可以清楚地看到:每个通道的脉冲波形完全一样,只是起始时间依次错开了90°。这就是四相步进电机驱动的基础波形。

4.5 实际项目中的避坑指南

做多通道脉冲输出,有几个坑我不得不提:

  • 通道间串扰: 多个通道同时翻转时,电源和地平面会产生很大的瞬态电流。我建议在FPGA内部把各通道的输出寄存器分散放置,不要挤在一起。
  • 死区时间的一致性: 不同通道的死区时间可能会有微小差异,这会导致桥臂不平衡。我习惯用一个统一的死区计数器,所有通道共享。
  • 相位精度: 如果全局计数器位宽不够,相位偏移的精度就会受限。比如32位计数器,时钟100MHz,相位精度是10ns。如果需要更高精度,就得用DDS或者小数分频。
  • 配置更新时机: 在运行中修改相位或死区参数时,一定要等当前周期结束再更新。否则会出现半个周期用新参数、半个周期用旧参数的混乱情况。

我的小技巧: 在调试多通道脉冲时,先用示波器看两两通道之间的相位关系。如果发现相位不对,先检查全局计数器是否溢出,再检查相位偏移寄存器是否被意外修改。这两个问题我各遇到过两次,每次排查都花了不少时间。

4.6 总结一下

多通道脉冲输出,核心就三件事:

  1. 全局时基: 所有通道共享一个计数器,保证同步性。
  2. 相位偏移: 每个通道独立配置起始相位,实现精确的通道间时序。
  3. 死区插入: 在互补信号之间插入安全间隔,保护功率器件。

这三件事做好了,多通道脉冲输出模块就稳了。下一节我们会聊到脉冲输出的高级话题——如何用DDS技术实现超高精度的频率和相位控制。嗯,那个更有意思。


公众号:蓝海资料掘金营,微信deep3321