FPGA 硬件电流环 基于FPGA的永磁同步伺服控制系统的设计,在FPGA实现了伺服电机的矢量控制。 有坐标变换,电流环,速度环,ad7606采样,电机正交编码器反馈接口,SVPWM,pi运算等等模块。 Verilog语言。
最近在搞一个硬核项目——用FPGA实现永磁同步电机的矢量控制。这玩意儿可不是单片机能搞定的,电流环的快速响应得靠FPGA的并行处理能力。咱们直接上干货,聊聊实现过程中的关键模块和代码设计。
坐标变换是矢量控制的灵魂。Clark变换把三相电流转成两相静止坐标系时,我用了查表法替代实时计算。Verilog里直接嵌入预计算的正弦值表,比实时运算省了20个时钟周期:
// Clark变换查表模块 module clark_transform ( input [15:0] ia, ib, ic, output reg [15:0] i_alpha, i_beta ); // 1/√3的定点数表示 Q1.15格式 localparam INV_SQRT3 = 16'h4D93; always @(*) begin i_alpha = ia; // 直接传递 // β轴分量计算 i_beta = (ib * INV_SQRT3) >>> 15; i_beta = i_beta + (ic * INV_SQRT3) >>> 15; end endmodule电流环的PI调节器用了抗饱和结构。这里有个坑:当误差过大时直接给输出限幅会导致积分项溢出。我的处理方法是增加积分分离条件:
// 电流环PI核心代码片段 always @(posedge clk) begin if (abs(error) < 0.2) // 误差小时启用积分 integral <= integral + error * Ki; else integral <= integral; // 冻结积分 output_p = error * Kp; output_total = output_p + integral; // 输出限幅 if (output_total > MAX_OUTPUT) output_total = MAX_OUTPUT; else if (output_total < -MAX_OUTPUT) output_total = -MAX_OUTPUT; endAD7606采样模块要特别注意时序。SPI接口的时钟相位设置错了会导致采样值漂移。实测发现CS信号下降沿后必须等待3个时钟周期才能开始读取数据:
// AD7606状态机片段 parameter IDLE = 3'd0; parameter CONVST = 3'd1; parameter WAIT = 3'd2; parameter READ = 3'd3; always @(posedge clk) begin case(state) CONVST: begin convst_n <= 1'b0; if (cnt == 50) begin // 保持低电平50个时钟 state <= WAIT; cnt <= 0; end end WAIT: begin if (busy) begin state <= READ; sck <= 1'b0; end end READ: begin if (bit_cnt == 16) begin state <= IDLE; end else begin sck <= ~sck; // 生成SPI时钟 if (sck) data_reg <= {data_reg[14:0], sdo}; end end endcase endSVPWM生成模块的开关时序是关键。为了避免上下桥臂直通,每个PWM周期都要插入死区时间。这里用计数器实现相位调制:
// SVPWM死区生成 reg [9:0] counter; always @(posedge clk) begin if (counter >= PERIOD) counter <= 0; else counter <= counter + 1; end // 比较点计算 wire [9:0] cmp_a = duty_a * PERIOD; wire [9:0] cmp_b = duty_b * PERIOD; // 死区插入 assign pwm_a = (counter < cmp_a) ? 1'b1 : 1'b0; assign pwm_a_n = (counter < (cmp_a - DEAD_TIME)) ? 1'b1 : 1'b0;正交编码器解码用了四倍频技术,通过检测AB相信号跳变沿实现。注意用同步器消除亚稳态:
// 编码器四倍频核心逻辑 reg [1:0] encoder_sync; always @(posedge clk) begin encoder_sync <= {enc_b, enc_a}; // 同步输入 end wire [1:0] prev_state = encoder_sync[1:0]; always @(posedge clk) begin case({prev_state, encoder_sync}) 4'b0010,4'b0001,4'b1110,4'b1101: position <= position + 1; 4'b0011,4'b0111,4'b1100,4'b1000: position <= position - 1; endcase end整个系统跑在200MHz时钟下,电流环周期能做到50us。调试时用SignalTap抓波形发现,PI输出偶尔会有毛刺,最后发现是跨时钟域没处理好。加了个双寄存器同步后问题解决。
FPGA做电机控制的优势在于能并行处理多个控制环路。比如电流环和速度环可以独立运行在不同时钟域,这个在传统DSP上根本实现不了。不过资源消耗也得注意,整个设计用了Cyclone IV EP4CE115的60%逻辑单元,特别是CORDIC核做Park变换时吃了不少DSP块。
最后上电测试时,电机一转起来就有高频啸叫。用示波器看SVPWM波形发现载波频率设成了16kHz,改成20kHz后噪音明显降低。现在电机转速能稳定在±0.1rpm误差范围内,算是达到设计指标了。