基于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])将采样数据分配到adcdataCH1adcdataCH2,核心代码如下:

// 启动状态机:控制通道切换与数据分配
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个状态)实现信号处理的有序执行,核心流程与代码如下:

  1. 信号采集(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位有效数据);
  • 死区处理:通过比较inputdeadzoneDeadZoneAdCmd,抑制小信号波动导致的控制抖动,输出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  

代码逻辑解读

  • 有符号运算AB定义为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 代码设计思想

  1. 模块化与参数化:每个功能模块独立封装(如adc_7922/MIXJXK/CordicRotate),通过参数化设计支持灵活配置,适配不同硬件与应用场景;
  2. 硬件化算法实现:将复杂算法(如CORDIC旋转、PI调节)通过硬件逻辑实现,利用FPGA并行处理特性,降低运算延迟,满足伺服控制的实时性要求;
  3. 资源优化:采用SRL16移位寄存器、移位乘法等低资源消耗方案,平衡性能与资源占用;
  4. 调试友好:每个模块预留调试接口(如state1debug/cntspidata_debug),便于硬件调试与时序验证。

6.2 工程价值

  1. 实时性:电流环控制周期可达微秒级,速度环与位置环周期可配置,满足高速伺服驱动需求;
  2. 可靠性:集成过流、过压、死区处理等保护机制,确保系统稳定运行;
  3. 可移植性:支持多种FPGA芯片与传感器接口(正交编码器、BISS编码器),适配不同伺服电机;
  4. 可扩展性:预留通信接口(RS232/SPI)与诊断模块,便于系统集成与故障排查。

该代码是FPGA-based伺服控制系统的典型实现,涵盖从模拟信号采集到功率驱动的全链路逻辑,可作为高精度伺服驱动开发的参考模板,通过参数调整与模块扩展,适配工业机器人、数控机床、精密定位平台等多种应用场景。

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

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐