基于FPGA的永磁同步伺服控制系统的设计,在FPGA实现了伺服电机的矢量控制, 坐标变换,电流...
基于FPGA的永磁同步伺服控制系统的设计,在FPGA实现了伺服电机的矢量控制, 坐标变换,电流环,速度环,位置环,电机反馈接口,SVPWM。 。 。 都是通过Verilog 语言来实现的,具有很高的研究价值。
一、代码工程整体架构与核心文件定位
本次分析的代码工程聚焦于FPGA-based永磁同步伺服控制系统,通过Verilog与VHDL混合编程实现伺服电机全闭环控制,工程包含694个文件,核心代码集中在HDL目录下,涵盖模拟信号采集、数字信号处理、控制算法、功率驱动、反馈接口五大核心模块。工程入口为BPFPGA.v顶层模块,通过BpfpgaConfig.inc配置文件实现功能模块化裁剪,支持Spartan3E、SpartanIIE等FPGA芯片,代码遵循“配置化+模块化”设计思想,每个模块独立封装且通过标准化接口交互,便于调试与移植。
二、配置文件代码解析:系统功能的“开关与参数定义”
2.1 核心配置文件`BpfpgaConfig.inc`代码逻辑
该文件通过define宏定义实现系统功能的启用/禁用、芯片型号选择与测试模式配置,是整个系统的“功能入口”,代码结构与关键逻辑如下:
2.1.1 可选配置选项:功能模块的“开关矩阵”
代码中以//注释的形式列出所有可选功能模块,用户通过取消注释启用对应功能,核心配置项及代码解析如下:
//*************************************************************************
// AVAILABLE CONFIGURATION OPTIONS
//*************************************************************************
//`define VirtexII // 启用VirtexII系列FPGA支持
//`define SpartanIIE // 启用SpartanIIE系列FPGA支持
`define Spartan3E // 启用Spartan3E系列FPGA(当前配置)
//`define DataExtract // 启用数据提取模块(用于调试)
//`define IfbFilter // 启用电流反馈滤波器
`define DiagnosticRegs // 启用诊断寄存器(状态监控)
`define SysConfig // 启用系统配置模块(动态参数调整)
`define MOverTSpeed // 启用M/T法速度计算
`define ADS7818_1 // 启用ADS7818 ADC芯片(通道1)
//`define ADS7818_2 // 启用ADS7818 ADC芯片(通道2)
`define SvPwm // 启用SVPWM生成模块
//`define SvPwmOverMod // 启用SVPWM过调制功能
`define DcBusMonitor // 启用直流母线电压监控
//`define HeartbeatLED // 启用心跳LED(系统运行指示)
//`define DiagnosticDac // 启用诊断DAC(模拟信号输出)
//`define Spi // 启用SPI通信接口
//`define RS232 // 启用RS232通信接口
//`define HostParallelPort // 启用并行主机接口
//`define TraceBuffer // 启用轨迹缓冲区(调试用)
`define SpeedLoop // 启用速度环控制
`define QuadDecoderPos // 启用16位正交编码器解码
`define QuadDecoderPos32 // 启用32位正交编码器解码
//`define ResolverPos // 启用旋转变压器位置解码
`define Shift_Mult // 启用移位乘法器(低资源消耗)
//`define EEPROM // 启用EEPROM(参数存储)
//`define EpsonVcd // 启用Epson VCD文件生成(仿真用)
//`define XilinxSdf // 启用Xilinx SDF时序约束
//`define MuxImplementation // 启用多路选择器优化实现
`define NewPiController // 启用新型PI控制器
`define ZY_4Floor // 启用ZY_4Floor项目专用配置
//`define GD_3Floor // 启用GD_3Floor项目专用配置
代码逻辑解读:
- 宏定义采用“功能模块+硬件接口”分类,如
SvPwm对应功率驱动模块,QuadDecoderPos32对应位置反馈模块; - 项目专用配置(如
ZY_4Floor)用于适配特定硬件环境,通过条件编译加载对应参数; - 未启用的功能(如
RS232)通过//注释屏蔽,减少FPGA资源占用。
2.1.2 测试配置:仿真与验证的“模式切换”
代码中提供4种测试模式,用于FPGA调试、ASIC仿真与网表生成,核心代码如下:
//*************************************************************************
// TEST CONFIGURATION
//*************************************************************************
//`define FpgaTest // FPGA测试模式:不包含ASIC专用信号(CLK2X/CLK4X/NRESET)
//`define AsicTest // ASIC测试模式:用于VCD文件生成与功能验证
`define AsicNetlist // ASIC网表模式:生成Epson专用网表(当前配置)
//`define AsicFpgaTest1 // 简化ASIC模式1:用于FPGA兼容性测试
//`define AsicFpgaTest2 // 简化ASIC模式2:用于FPGA兼容性测试
代码逻辑解读:
AsicNetlist模式为当前启用配置,代码会自动适配ASIC设计规则,如移除FPGA专用原语(如BUFG);FpgaTest模式用于FPGA硬件调试,保留所有测试信号(如TPout1/TPout2),便于示波器观测。
三、模拟信号采集模块代码解析:从ADC驱动到信号预处理
3.1 AD7922 ADC驱动模块(`AD7922.v`):硬件接口的“时序精准控制”
该模块实现AD7922 16位双通道ADC的驱动逻辑,核心功能是将模拟电流/电压信号转换为数字信号,代码总长度198行,包含接口定义、状态机控制、时钟分频、数据处理四大核心部分。
3.1.1 接口定义:信号交互的“标准化协议”
代码开头通过module定义模块接口,明确输入输出信号的方向与位宽,核心代码如下:
module adc_7922 #(parameter DWIDTH = 16)
(
clk, reset_n, startpulse, donepulse, adc_cs_n, adc_clk, adc_dout, adc_din,
adc_data_CH1, adc_data_CH2, state1_debug, state2_debug, cnt_spidata_debug
);
input clk; // 系统时钟(20MHz)
input reset_n; // 异步复位(低有效)
// 用户控制输入
input startpulse; // 采样启动脉冲(高有效)
input adc_dout; // ADC输出数据(串行)
// ADC硬件接口
output adc_cs_n; // ADC片选(低有效)
output adc_clk; // ADC采样时钟
output adc_din; // ADC控制指令(串行)
output reg [15:0] adc_data_CH1; // 通道1采样结果(16位)
output reg [15:0] adc_data_CH2; // 通道2采样结果(16位)
// 调试接口
output [3:0] state1_debug; // 主状态机状态
output [3:0] state2_debug; // 启动状态机状态
output [4:0] cnt_spidata_debug; // SPI数据计数器
output donepulse; // 采样完成脉冲
代码逻辑解读:
- 采用参数化设计(
DWIDTH = 16),支持AD7922的16位采样精度; - 调试接口(如
state1_debug)直接输出状态机状态,便于硬件调试时观测时序; adccsn/adcclk/adcdin为ADC硬件接口,严格遵循AD7922的SPI通信协议。
3.1.2 时钟分频:采样时序的“稳定保障”
AD7922要求采样时钟频率低于5MHz,代码通过cnt_clkdiv计数器实现20MHz到0.625MHz(20MHz/32)的分频,核心代码如下:
`define CLKDIV 4 // 分频系数配置
// 时钟分频:输入20MHz,输出20MHz/(2^(CLKDIV+1)) = 0.625MHz
always@(posedge clk or negedge reset_n)
begin
if (!reset_n) begin
cnt_clkdiv <= {`CLKDIV{1'h0}};
adc_clk_reg <= {`CLKDIV{1'h0}};
end
else if(cnt_clkdiv == {`CLKDIV{1'h1}}) begin // 计数到最大值(15)
adc_clk_reg <= ~adc_clk_reg; // 时钟翻转
cnt_clkdiv <= cnt_clkdiv + 1;
end
else begin
cnt_clkdiv <= cnt_clkdiv + 1;
end
end
assign adc_clk = adc_clk_reg; // 分频后时钟输出
代码逻辑解读:
CLKDIV为分频系数参数,当前配置为4,即分频比为32(2^(4+1));- 通过
adcclkreg寄存器实现时钟翻转,保证时钟占空比接近50%; - 分频逻辑采用异步复位,确保系统复位时时钟输出稳定为低电平。
3.1.3 状态机控制:采样流程的“有序调度”
代码采用双状态机(主状态机+启动状态机)实现采样流程控制,主状态机负责SPI传输,启动状态机负责通道切换,核心代码如下:
// 主状态机:控制SPI传输流程
reg [3:0] state;
parameter IDLE = 4'b0000, // 空闲状态
SPI_TRANS = 4'b0001, // SPI传输状态
FINISH = 4'b0010; // 采样完成状态
always@(posedge clk or negedge reset_n)
begin
if (!reset_n) begin
state <= IDLE;
cnt_spidata <= {`LOG2N{1'h0}}; // 数据计数器清零
adc_cs_reg_n <= 1'h1; // 片选默认高(未选中)
adc_din_reg <= 1'h0; // 数据默认低
adc_data_reg <= {DWIDTH{1'h0}}; // 采样数据清零
donepulse_reg <= 1'b0; // 完成脉冲清零
end
else if((cnt_clkdiv == {`CLKDIV{1'h0}}) && (adc_clk_reg == 1'h1)) begin // ADC时钟上升沿
case(state)
IDLE: begin // 空闲状态:等待启动脉冲
adc_cs_reg_n <= 1'b1;
adc_din_reg <= 1'b0;
if(startpulse_reg) begin // 启动脉冲有效
state <= SPI_TRANS;
end
end
SPI_TRANS: begin // SPI传输状态:发送指令+接收数据
cnt_spidata <= cnt_spidata + 1;
adc_cs_reg_n <= 1'b0; // 选中ADC
// 通道选择:第2-3个SPI时钟发送通道标识
if((cnt_spidata == 5'h2) || (cnt_spidata == 5'h3)) begin
adc_din_reg <= adc_ch_reg; // adc_ch_reg:通道选择(0=CH1,1=CH2)
end
else begin
adc_din_reg <= 1'b0;
end
// 接收数据:将串行数据拼接为并行数据
adc_data_reg <= {adc_data_reg[DWIDTH-2:0], adc_dout};
// 传输完成:16位数据传输结束
if(cnt_spidata == DWIDTH) begin
state <= FINISH;
adc_cs_reg_n <= 1'b1; // 取消片选
donepulse_reg <= 1'b1; // 置位完成脉冲
cnt_spidata <= {`LOG2N{1'h0}}; // 计数器清零
adc_ch_reg <= ~adc_ch_reg; // 切换通道(CH1→CH2或CH2→CH1)
end
end
FINISH: begin // 完成状态:复位完成脉冲
donepulse_reg <= 1'b0;
state <= IDLE;
end
default: state <= IDLE;
endcase
end
end
代码逻辑解读:
- 主状态机在
ADC时钟上升沿执行状态切换,确保与ADC采样时序同步; SPITRANS状态中,通过cntspidata计数器控制16位数据传输,第2-3个时钟周期发送通道标识(adcchreg),实现双通道切换;- 数据接收采用
{adcdatareg[DWIDTH-2:0], adc_dout}串行拼接,将16位串行数据转换为并行数据; donepulse_reg为采样完成标志,在FINISH状态复位,确保为单周期脉冲。
3.1.4 通道数据分配:采样结果的“正确归属”
启动状态机负责根据AD7922的通道标识位(adcdatareg[14])将采样数据分配到adcdataCH1或adcdataCH2,核心代码如下:
// 启动状态机:控制通道切换与数据分配
reg [3:0] state_start;
parameter START_IDLE = 4'b0000, // 空闲状态
START_CH2 = 4'b0001, // 通道2采样
DONE = 4'b0010; // 完成状态
always@(posedge clk or negedge reset_n)
begin
if (!reset_n) begin
state_start <= 0;
startpulse_reg <= 0;
donepulse_reg_image <= 0;
adc_data_CH1 <= 0;
adc_data_CH2 <= 0;
end
else begin
case(state_start)
START_IDLE: begin
if(startpulse) startpulse_reg <=1; // 捕获启动脉冲
else if(startpulse_reg == 1'b1) begin
if(cnt_start == {(`CLKDIV+1){1'h1}}) begin
startpulse_reg <=0;
state_start <= START_CH2; // 进入通道2采样
end
cnt_start <= cnt_start + 1;
end
end
START_CH2: begin
if(donepulse_reg) begin // 主状态机采样完成
startpulse_reg <=1;
// 根据通道标识位分配数据:bit14=0→CH1,bit14=1→CH2
if(!adc_data_reg[14]) adc_data_CH1 <= adc_data_reg[12:1];
else adc_data_CH2 <= adc_data_reg[12:1];
end
else if(startpulse_reg == 1'b1) begin
if(cnt_start == {(`CLKDIV+1){1'h1}}) begin
startpulse_reg <=0;
state_start <= DONE; // 进入完成状态
end
cnt_start <= cnt_start + 1;
end
end
DONE: begin
donepulse_reg_image <= donepulse_reg;
// 完成脉冲边沿检测:复位状态机
if((donepulse_reg_image==0) && (donepulse_reg==1)) begin
state_start <= START_IDLE;
// 再次确认数据分配(避免漏采)
if(!adc_data_reg[14]) adc_data_CH1 <= adc_data_reg[12:1];
else adc_data_CH2 <= adc_data_reg[12:1];
end
end
default: state_start <= START_IDLE;
endcase
end
end
代码逻辑解读:
- AD7922的采样数据
adcdatareg[14]为通道标识位,0表示CH1,1表示CH2; - 数据分配时取
adcdatareg[12:1](12位有效数据),丢弃高位符号位与低位噪声位; - 通过
donepulseregimage实现完成脉冲的边沿检测,确保状态机只在脉冲上升沿复位,避免重复触发。
3.2 模拟命令处理模块(`AdCmd.v`):信号预处理的“核心中枢”
该模块对AD7922采集的模拟信号进行滤波、增益调整、死区处理,生成扭矩指令(TorqCmdAdCmd)和速度指令(SpdCmdAdCmd),代码总长度342行,核心依赖adc7922(ADC驱动)、MIXJXKIC(乘法器)、SlideaveB(滑动平均滤波器)三个子模块。
3.2.1 模块实例化:功能复用的“模块化思想”
代码通过实例化子模块实现功能复用,核心实例化代码如下:
// 实例化乘法器:用于增益调整(15位输入×16位增益=31位输出)
MIXJXK_IC #(15,16,31) Gain_ADCmd(
.CLK(CLK),
.nRESET(nRESET),
.StartPulse(Mult_Start),
.DonePulse(Mult_Done),
.A(Mul_In1),
.B(Mul_In2),
.P(Mul_Out_1)
);
// 实例化AD7922驱动模块:采集双通道模拟信号
adc_7922 adc7922_inst (
.clk(CLK),
.reset_n(nRESET),
.startpulse(Startpulse),
.donepulse(ad7922_donepulse),
.adc_cs_n(adc7922_cs_n),
.adc_clk(adc7922_clk),
.adc_dout(adc7922_dout),
.adc_din(adc7922_din),
.adc_data_CH1(adc_data_CH1),
.adc_data_CH2(adc_data_CH2)
);
// 实例化滑动平均滤波器:抑制采样噪声(窗口大小=7)
Slide_ave_B SLPF_Inst (
.RST(!nRESET),
.CLK(CLK),
.CAL_EN(startpulse_SLPF),
.WINDOW(4'h7),
.D_IN({{4{adc_data_sign_reg[11]}},adc_data_sign_reg[11:0]}),
.D_OUT(output_SLPF[19:0]),
.DONEPULSE(donepulse_SLPF)
);
代码逻辑解读:
MIXJXKIC为参数化乘法器,当前配置为15位输入(滤波后信号)×16位增益(GainAdCmd)=31位输出,用于信号增益调整;adc7922inst为AD7922驱动实例,直接复用AD7922.v模块的功能;SLPFInst为滑动平均滤波器,窗口大小配置为7(WINDOW=4'h7),输入信号通过符号位扩展为20位({{4{adcdatasignreg[11]}},adcdatasign_reg[11:0]}),避免滤波过程中溢出。
3.2.2 信号处理流程:从采集到指令的“全链路逻辑”
代码通过状态机(8个状态)实现信号处理的有序执行,核心流程与代码如下:
- 信号采集(STATE0→STATE1):
reg [7:0] state;
parameter STATE0 = 8'b00000000, // 空闲状态
STATE1 = 8'b00000001, // 等待ADC采样完成
// ... 其他状态省略 ...
always @(posedge CLK or negedge nRESET)
begin
if (!nRESET) begin
// 复位初始化:所有寄存器清零
TorqCmd_AdCmd <= 0;
SpdCmd_AdCmd <= 0;
DonePulse <= 0;
state <= 0;
Mult_Start <= 0;
Mul_In1 <= 0;
Mul_In2 <= 0;
startpulse_SLPF <= 0;
end
else begin
// 通道选择:TorqSpdSel_AdCmd=0→CH1(扭矩),=1→CH2(速度)
if(!TorqSpdSel_AdCmd) begin
adc_data_reg <= adc_data_CH1 + {Offset_AdCmd[7],Offset_AdCmd[7:0],3'b000}; // CH1+偏移
TorqCmd_AdCmd[14:0] <= output_deadzone[14:0]; // 输出扭矩指令
SpdCmd_AdCmd[14:0] <= 0;
end
else begin
adc_data_reg <= adc_data_CH2 + {Offset_AdCmd[7],Offset_AdCmd[7:0],3'b000}; // CH2+偏移
TorqCmd_AdCmd[14:0] <= 0;
SpdCmd_AdCmd[14:0] <= output_deadzone[14:0]; // 输出速度指令
end
Mul_In2 <= Gain_AdCmd; // 加载增益系数
case(state)
STATE0: begin // 空闲状态:等待启动脉冲
if(Startpulse) state <= STATE1;
end
STATE1: begin // 等待ADC采样完成
if(ad7922_donepulse) begin
// 符号位调整:SignalControl_AdCmd=0→取反,=1→不变
if(!SignalControl_AdCmd) adc_data_sign_reg[11] <= !adc_data_reg[11];
else adc_data_sign_reg[11] <= adc_data_reg[11];
adc_data_sign_reg[10:0] <= adc_data_reg[10:0];
startpulse_SLPF <= 1'b1; // 启动滑动平均滤波
state <= STATE2;
end
end
STATE2: begin // 等待滤波完成
startpulse_SLPF <= 1'b0;
if(donepulse_SLPF) begin
Mul_In1[14:0] <= output_SLPF[14:0]; // 加载滤波后数据
Mult_Start <= 1'b1; // 启动乘法器(增益调整)
state <= STATE3;
end
end
STATE3: begin // 等待增益调整完成
Mult_Start <= 1'b0;
if(Mult_Done) begin
input_deadzone <= Mul_Out_1[29:15]; // 加载增益调整后数据
state <= STATE4;
end
end
STATE4: begin // 死区处理
// 死区判断:输入信号绝对值 < 死区→输出0,否则输出原始信号
if( (input_deadzone < DeadZone_AdCmd) || (((15'h0-input_deadzone) < DeadZone_AdCmd)))
output_deadzone <= 0;
else
output_deadzone <= input_deadzone;
DonePulse <= 1'b1; // 置位命令完成脉冲
state <= STATE5;
end
STATE5: begin // 复位完成脉冲
DonePulse <= 1'b0;
state <= STATE0;
end
default: state <= STATE0;
endcase
end
end
代码逻辑解读:
- 通道选择:通过
TorqSpdSelAdCmd信号选择CH1(扭矩)或CH2(速度),并叠加偏移量(OffsetAdCmd),偏移量通过{OffsetAdCmd[7],OffsetAdCmd[7:0],3'b000}扩展为12位,与ADC采样数据(12位)对齐; - 符号位调整:
SignalControl_AdCmd信号控制符号位是否取反,适配不同传感器的信号极性; - 滑动平均滤波:
startpulseSLPF为滤波启动信号,outputSLPF为滤波后输出,15位有效数据; - 增益调整:
MulIn1为滤波后数据,MulIn2为增益系数(GainAdCmd),乘法器输出MulOut_1[29:15](15位有效数据); - 死区处理:通过比较
inputdeadzone与DeadZoneAdCmd,抑制小信号波动导致的控制抖动,输出output_deadzone作为最终指令。
四、数字信号处理模块代码解析:从乘法器到滤波器的“算法实现”
4.1 乘法器模块(`Multiply.v`):硬件运算的“基础组件”
该模块提供移位乘法(ShiftMult)和组合乘法(CombMult)两种实现,支持参数化位宽配置,是系统中电流环、速度环PI调节的核心运算组件,代码总长度468行。
4.1.1 移位乘法(`MIXJXK`):低资源消耗的“高效实现”
移位乘法采用“移位累加”算法,适用于FPGA资源紧张的场景,核心代码如下:
`ifdef Shift_Mult
module MIXJXK
#(parameter I = 16, J = 16, K = 32) // I:A位宽,J:B位宽,K:P位宽
(CLK, nRESET, StartPulse, DonePulse, A, B, P);
input CLK; // 系统时钟
input nRESET; // 异步复位
input StartPulse; // 运算启动脉冲
output DonePulse; // 运算完成脉冲
reg DonePulse;
input [I-1:0] A; // 乘数A
input [J-1:0] B; // 乘数B
output [K-1:0] P; // 乘积P
reg [K-1:0] P;
// 本地参数定义
parameter CNT_MSB = 4; // 计数器最高位(支持最大31位乘数)
parameter PS_LSB = K - J; // 乘积累加器中求和项的最低位
reg [CNT_MSB:0] BitCount; // A的移位计数器
reg [(I-1):0] Ashift; // A的移位寄存器
wire [J:0] Sum; // 求和项
wire [J:0] Diff; // 求差项
// 生成求和项与求差项:符号扩展避免溢出
assign Sum = Ashift[0] ? {P[K-1],P[K-1:PS_LSB]} + {B[J-1],B} : {P[K-1],P[K-1:PS_LSB]};
assign Diff = Ashift[0] ? {P[K-1],P[K-1:PS_LSB]} - {B[J-1],B} : {P[K-1],P[K-1:PS_LSB]};
always @(posedge CLK or negedge nRESET)
begin
if (!nRESET) begin
DonePulse <= 0;
P <= 0;
Ashift <= 0;
BitCount <= 0;
end
else begin
if (StartPulse) begin
BitCount <= I; // 加载计数器(A的位宽)
P <= 0; // 乘积清零
Ashift <= A; // 加载乘数A
end
else
Ashift[I-2:0] <= Ashift[I-1:1]; // A右移1位(处理下一位)
if (BitCount > 0) begin
BitCount <= BitCount - 1; // 计数器减1
if (BitCount == 1) begin // 最后一位:用Diff(避免累积误差)
P[K-1:PS_LSB-1] <= Diff;
DonePulse <= 1; // 置位完成脉冲
end
else // 非最后一位:用Sum
P[K-1:PS_LSB-1] <= Sum;
P[PS_LSB-2:0] <= P[PS_LSB-1:1]; // 乘积右移1位
end
if (DonePulse)
DonePulse <= 0; // 复位完成脉冲
end
end
endmodule
`endif
代码逻辑解读:
- 参数化设计:
I/J/K分别为乘数A、乘数B、乘积P的位宽,支持灵活配置; - 移位累加原理:将乘法分解为“移位+累加”,如
A×B = A[0]×B×2^0 + A[1]×B×2^1 + ... + A[I-1]×B×2^(I-1),通过Ashift寄存器右移实现2^n加权; - 符号扩展:
{P[K-1],P[K-1:PS_LSB]}和{B[J-1],B}实现符号扩展,避免有符号数运算溢出; - 最后一位处理:最后一位采用
Diff而非Sum,减少累积误差,提高运算精度。
4.1.2 组合乘法(`MIXJXK_IC`):高速运算的“硬件加速”
组合乘法直接调用FPGA硬件乘法器资源,适用于对运算速度要求高的场景(如电流环),核心代码如下:
module MIXJXK_IC
#(parameter I = 16, J = 16, K = 32)
(CLK, nRESET, StartPulse, DonePulse, A, B, P);
input CLK; // 系统时钟
input nRESET; // 异步复位
input StartPulse; // 运算启动脉冲
output DonePulse; // 运算完成脉冲
reg DonePulse;
input signed [I-1:0] A; // 有符号乘数A
input signed [J-1:0] B; // 有符号乘数B
output [K-1:0] P; // 乘积P
reg [K-1:0] P;
// 组合乘法:直接调用FPGA硬件乘法器
wire signed [I+J-1:0] Product;
assign Product = A * B; // 硬件乘法器运算
always @(posedge CLK or negedge nRESET)
begin
if (!nRESET) begin
P <= 0;
DonePulse <= 0;
end
else begin
if (StartPulse)
P <= Product[I+J-1:I+J-K]; // 取乘积高位K位(自动缩放)
DonePulse <= StartPulse; // 启动脉冲后1拍输出完成脉冲
end
end
endmodule
代码逻辑解读:
- 有符号运算:
A和B定义为signed类型,支持有符号数乘法; - 硬件加速:
A * B直接调用FPGA内置乘法器(如Xilinx的MULT18X18),运算延迟仅1-2个时钟周期; - 乘积缩放:
Product[I+J-1:I+J-K]取乘积的高位K位,实现自动缩放,避免溢出,如16×16=32位乘积,取高位16位则相当于除以2^16。
4.2 滑动平均滤波器(`Slide_ave_B.vhd`):噪声抑制的“硬件实现”
该模块采用VHDL编写,基于SRL16(16位移位寄存器)实现滑动平均滤波,适用于电流、速度信号的噪声抑制,代码总长度182行。
4.2.1 模块接口与参数定义
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
library UNISIM;
use UNISIM.VComponents.all;
entity Slide_ave_B is
generic(
SIZE : integer := 16 -- 输入数据位宽(默认16位)
);
PORT(
RST : IN std_logic; -- 复位(高有效)
CLK : IN std_logic; -- 系统时钟
CAL_EN : IN std_logic; -- 计算使能(单周期脉冲)
WINDOW : in std_logic_vector(3 downto 0); -- 窗口大小(0-15)
D_IN : IN std_logic_vector(SIZE -1 downto 0); -- 输入数据
D_OUT : OUT std_logic_vector(SIZE +3 downto 0); -- 输出数据(位宽+4)
DONEPULSE : OUT std_logic -- 完成脉冲
);
end Slide_ave_B;
代码逻辑解读:
- 参数化位宽:
SIZE为输入数据位宽,默认16位,输出位宽为SIZE+3,预留4位用于累加,避免溢出; - 窗口大小:
WINDOW为4位,支持1-16点滑动窗口,窗口大小=2^WINDOW(如WINDOW=3表示8点窗口); - SRL16依赖:调用Xilinx的
SRL16E原语,实现16位移位寄存器,减少FPGA资源占用。
4.2.2 SRL16移位寄存器实例化:滑动窗口的“硬件基础”
代码通过generate语句生成SIZE个SRL16E移位寄存器,实现输入数据的延迟存储,核心代码如下:
architecture Behavioral of Slide_ave_B is
component SRL16E
generic (INIT: bit_vector := X"0000"); -- 初始化值(全0)
port (Q : out STD_ULOGIC;
A0 : in STD_ULOGIC; -- 地址0
A1 : in STD_ULOGIC; -- 地址1
A2 : in STD_ULOGIC; -- 地址2
A3 : in STD_ULOGIC; -- 地址3
CE : in STD_ULOGIC; -- 时钟使能
CLK : in STD_ULOGIC; -- 时钟
D : in STD_ULOGIC); -- 数据输入
end component;
signal reg_OldData : std_logic_vector(SIZE-1 downto 0); -- 最旧数据
signal reg_sum : std_logic_vector(SIZE+3 downto 0); -- 累加和
signal donepulse_reg : std_logic; -- 完成脉冲寄存器
signal donepulse_regdelay : std_logic; -- 完成脉冲延迟
begin
-- 生成SIZE个SRL16E移位寄存器:每个bit对应一个SRL16E
bus_width_loop: for i in 0 to SIZE-1 generate
begin
U_X : SRL16E
generic map (INIT => X"0000")
port map (Q => reg_OldData(i), -- 输出:最旧数据(延迟WINDOW拍)
A0 => WINDOW(0), -- 地址0:窗口大小低位
A1 => WINDOW(1), -- 地址1
A2 => WINDOW(2), -- 地址2
A3 => WINDOW(3), -- 地址3:窗口大小高位
CE => CAL_EN, -- 使能:与计算使能同步
CLK => CLK, -- 系统时钟
D => D_IN(i)); -- 输入:当前数据
end generate bus_width_loop;
代码逻辑解读:
- 移位寄存器阵列:
buswidthloop生成SIZE个SRL16E,每个寄存器存储输入数据的1位,实现“并行移位”; - 地址选择:
WINDOW[3:0]作为SRL16E的地址输入,选择延迟2^WINDOW拍的数据作为reg_OldData(最旧数据); - 同步使能:
CE=CALEN确保移位寄存器仅在CALEN有效时更新数据,与系统控制周期同步。
4.2.3 滑动平均计算:累加与更新的“核心逻辑”
代码通过累加器reg_sum实现“新数据加入+旧数据移除”的滑动平均计算,核心代码如下:
-- 累加器:新数据加入,旧数据移除
process(RST,CLK)
begin
if rst = '1' then
reg_sum <= (others => '0');
donepulse_reg <= '0';
elsif rising_edge(clk) then
if(CAL_EN = '1') then
-- 累加:新数据(符号扩展4位) - 旧数据(符号扩展4位)
reg_sum <= signed(reg_sum) + signed(D_IN(SIZE-1) & D_IN(SIZE-1) & D_IN(SIZE-1) & D_IN(SIZE-1) & D_IN)
- signed(reg_OldData(SIZE-1) & reg_OldData(SIZE-1) & reg_OldData(SIZE-1) & reg_OldData(SIZE-1) & reg_OldData);
donepulse_reg <= '1'; -- 置位完成脉冲
else
donepulse_reg <= '0';
end if;
end if;
end process;
-- 完成脉冲延迟:与累加结果同步
process(RST,CLK)
begin
if rst = '1' then
donepulse_regdelay <= '0';
elsif rising_edge(clk) then
donepulse_regdelay <= donepulse_reg;
end if;
end process;
D_OUT <= reg_sum ; -- 输出累加和(滑动平均结果)
DONEPULSE <= donepulse_regdelay; -- 输出完成脉冲
end Behavioral;
代码逻辑解读:
- 符号扩展:输入数据
DIN和旧数据regOldData通过{DIN(SIZE-1) & ... & DIN}扩展为SIZE+4位,确保有符号数运算正确; - 滑动平均原理:
regsum = regsum + 新数据 - 旧数据,避免重复累加所有数据,提高运算效率; - 完成脉冲同步:
donepulseregdelay延迟1拍输出完成脉冲,确保与regsum的更新同步,避免数据未准备好就被读取。
五、核心控制算法模块代码解析:从坐标变换到PI调节
5.1 CORDIC旋转模块(`CordicRotate.v`):矢量控制的“数学核心”
该模块实现CORDIC(坐标旋转数字计算机)算法,用于Park变换(α-β→d-q)和逆Park变换(d-q→α-β),是矢量控制的关键模块,代码总长度368行。
5.1.1 模块接口与参数定义
`define XY_MSB 17 // X/Y累加器最高位(18位)
`define XYOUT_MSB 13 // X/Y输出最高位(14位)
`define XYIN_MSB 11 // X/Y输入最高位(12位,不可修改)
`define ANGLE_MSB 15 // 角度累加器最高位(16位)
`define ANGLEIN_MSB 13 // 角度输入最高位(14位)
module CordicRotate(
nReset, Clk, StartPulse, DonePulse, Angle, Xin, Yin, Xout, Yout
);
input nReset; // 异步复位(低有效)
input Clk; // 系统时钟
input StartPulse; // 启动脉冲
output DonePulse; // 完成脉冲
reg DonePulse;
input [`ANGLEIN_MSB:0] Angle; // 旋转角度(14位:0-359.978°)
input [`XYIN_MSB:0] Xin; // X输入(12位)
input [`XYIN_MSB:0] Yin; // Y输入(12位)
output [`XYOUT_MSB:0] Xout; // X输出(14位)
output [`XYOUT_MSB:0] Yout; // Y输出(14位)
代码逻辑解读:
- 固定位宽设计:
XYINMSB=11(12位输入),XYOUTMSB=13(14位输出),ANGLEIN_MSB=13(14位角度输入),确保旋转精度; - 角度范围:14位角度输入对应0-360°,精度为360°/2^14≈0.022°,满足伺服控制的角度精度要求。
5.1.2 CORDIC旋转算法实现:迭代逼近的“数学逻辑”
CORDIC算法通过迭代实现矢量旋转,核心思想是将旋转角度分解为一系列预设小角度的叠加,每次迭代调整X/Y坐标,核心代码如下:
parameter MAX_SHIFT_COUNT = 13; // 迭代次数(14次)
reg [3:0] ShiftCount; // 迭代计数器
reg [`XY_MSB:0] prevX; // 上一次X坐标
reg [`XY_MSB:0] prevY; // 上一次Y坐标
wire [`XY_MSB:0] nextX; // 下一次X坐标
wire [`XY_MSB:0] nextY; // 下一次Y坐标
reg [`ANGLE_MSB:0] AngleAccum; // 角度累加器
wire [`ANGLE_MSB:0] AngleDelta; // 角度差值
wire [`ANGLE_MSB:0] nextAngle; // 下一次角度
// 角度差值:目标角度 - 当前角度累加值
assign AngleDelta = {Angle,2'b0} - AngleAccum;
// 下一次X/Y坐标:根据角度差值符号决定旋转方向
assign nextX = !AngleDelta[`ANGLE_MSB] ? prevX - YTanPhi : prevX + YTanPhi;
assign nextY = !AngleDelta[`ANGLE_MSB] ? prevY + XTanPhi : prevY - XTanPhi;
// 下一次角度累加值:根据角度差值符号决定加/减预设角度
assign nextAngle = !AngleDelta[`ANGLE_MSB] ?
AngleAccum + getAngle(ShiftCount) : AngleAccum - getAngle(ShiftCount);
// 预设角度函数:返回第ShiftCount次迭代的预设角度(16位)
function [`ANGLE_MSB:0] getAngle;
input [3:0] ShiftCount;
begin
case (ShiftCount) /* synthesis full_case */
0: getAngle = 16384; // 90.000°(2^14/2)
1: getAngle = 8192; // 45.000°
2: getAngle = 4836; // 26.565°
3: getAngle = 2555; // 14.036°
4: getAngle = 1297; // 7.125°
5: getAngle = 651; // 3.576°
6: getAngle = 326; // 1.789°
7: getAngle = 163; // 0.895°
8: getAngle = 81; // 0.448°
9: getAngle = 41; // 0.224°
10: getAngle = 20; // 0.112°
11: getAngle = 10; // 0.056°
12: getAngle = 5; // 0.028°
endcase
end
endfunction
// 移位函数:实现X/Y坐标的右移(除以2^ShiftCount)
function [`XY_MSB:0] RShift;
input [3:0] ShiftCount;
input [`XY_MSB:0] D;
begin
case (ShiftCount) /* synthesis full_case */
0: RShift = D; // 不移位
1: RShift = {{2{D[`SB]}},D[`SB-1:1]}; // 右移1位
2: RShift = {{3{D[`SB]}},D[`SB-1:2]}; // 右移2位
// ... 其他移位次数省略 ...
12: RShift = {{13{D[`SB]}},D[`SB-1:12]}; // 右移12位
endcase
end
endfunction
// 主迭代过程
always @(posedge Clk or negedge nReset)
begin
if (!nReset) begin
prevX <= 0;
prevY <= 0;
AngleAccum <= 0;
DonePulse <= 0;
XTanPhi <= 0;
YTanPhi <= 0;
ShiftCount <= MAX_SHIFT_COUNT + 1;
end
else begin
if (StartPulse) begin
AngleAccum <= 0;
prevX <= 0;
prevY <= 0;
// 加载输入数据(符号扩展到18位)
YTanPhi[`XY_MSB:`XY_MSB-`XYIN_MSB-2] <= {{2{Yin[`XYIN_MSB]}},Yin};
YTanPhi[`XY_MSB-`XYIN_MSB-3:0] <= 0;
XTanPhi[`XY_MSB:`XY_MSB-`XYIN_MSB-2] <= {{2{Xin[`XYIN_MSB]}},Xin};
XTanPhi[`XY_MSB-`XYIN_MSB-3:0] <= 0;
ShiftCount <= 0;
end
if (ShiftCount < MAX_SHIFT_COUNT) begin
prevX <= nextX;
prevY <= nextY;
AngleAccum <= nextAngle;
// 计算下一次迭代的XTanPhi/YTanPhi(右移)
YTanPhi <= RShift(ShiftCount,nextY);
XTanPhi <= RShift(ShiftCount,nextX);
ShiftCount <= ShiftCount + 1;
end
if (ShiftCount == MAX_SHIFT_COUNT) begin
ShiftCount <= ShiftCount + 1;
DonePulse <= 1; // 迭代完成,置位完成脉冲
end
else
DonePulse <= 0;
end
end
// 输出结果:取累加器的高位14位
assign Xout = nextX[`XY_MSB:`XY_MSB - `XYOUT_MSB];
assign Yout = nextY[`XY_MSB:`XY_MSB - `XYOUT_MSB];
代码逻辑解读:
- 迭代次数:
MAXSHIFTCOUNT=13,即14次迭代,旋转精度可达0.022°,满足伺服控制要求; - 预设角度:
getAngle函数返回14个预设角度,覆盖0.028°-90°,确保任意角度可分解; - 移位运算:
RShift函数实现X/Y坐标的右移,对应CORDIC算法中的tan(θ_i)=1/2^i,避免三角函数运算; - 符号判断:通过
AngleDelta[ANGLE_MSB]`(角度差值的符号位)决定旋转方向,实现自适应迭代; - 结果输出:
nextX/nextY为18位累加结果,取高位14位作为输出,自动去除CORDIC算法的增益(约1.64676)影响。
六、总结:代码设计思想与工程价值
6.1 代码设计思想
- 模块化与参数化:每个功能模块独立封装(如
adc_7922/MIXJXK/CordicRotate),通过参数化设计支持灵活配置,适配不同硬件与应用场景; - 硬件化算法实现:将复杂算法(如CORDIC旋转、PI调节)通过硬件逻辑实现,利用FPGA并行处理特性,降低运算延迟,满足伺服控制的实时性要求;
- 资源优化:采用SRL16移位寄存器、移位乘法等低资源消耗方案,平衡性能与资源占用;
- 调试友好:每个模块预留调试接口(如
state1debug/cntspidata_debug),便于硬件调试与时序验证。
6.2 工程价值
- 实时性:电流环控制周期可达微秒级,速度环与位置环周期可配置,满足高速伺服驱动需求;
- 可靠性:集成过流、过压、死区处理等保护机制,确保系统稳定运行;
- 可移植性:支持多种FPGA芯片与传感器接口(正交编码器、BISS编码器),适配不同伺服电机;
- 可扩展性:预留通信接口(RS232/SPI)与诊断模块,便于系统集成与故障排查。
该代码是FPGA-based伺服控制系统的典型实现,涵盖从模拟信号采集到功率驱动的全链路逻辑,可作为高精度伺服驱动开发的参考模板,通过参数调整与模块扩展,适配工业机器人、数控机床、精密定位平台等多种应用场景。

基于FPGA的永磁同步伺服控制系统的设计,在FPGA实现了伺服电机的矢量控制, 坐标变换,电流环,速度环,位置环,电机反馈接口,SVPWM。 。 。 都是通过Verilog 语言来实现的,具有很高的研究价值。

更多推荐
所有评论(0)