【FPGA功能模块】电机控制_方向步进@mtr_ctrl_dir_step

1、步进电机介绍

1.1 简介

步进电机(Stepper Motor)是一种将电脉冲信号转换为角位移的电机。它能够将输入的电脉冲转换为对应的角度位移,每接收到一个脉冲,电机转动一个固定的角度(称为步距角),因此步进电机具有良好的定位控制能力。

1.2 工作原理

步进电机内部有多个磁极,当驱动线圈通以某一相的脉冲电流时,定子产生一个磁场,转子按照磁场方向转动一个步进角,当输入下一个脉冲时,磁场切换,转子再次转动一个步进角。
参考资料:步进电机基本原理及驱动方式详解
在这里插入图片描述

1.3 常见参数

(1)步距角:电机每接收一个脉冲所转动的角度,典型值为1.8°(即200步/转)。
(2)保持力矩:电机静止时保持转子位置的力矩。
(3)电机电阻和电感:影响电机响应速度和驱动电流。
(4)最大转速:电机能够稳定运行的最高速度。

2、步进电机DIR方向信号控制

2.1 电机运行指令

根据接收到的电机运行指令,确定电机运行方向。
IDLE= 2’d0 ;//空闲
INT = 2’d1 ;//中断
CW = 2’d2 ;//顺时针
CCW = 2’d3 ;//逆时针

2.2 DIR to STEP setup time

为了保证电机驱动芯片稳定工作,在STEP处于空闲状态时改变DIR,并在下一个STEP的有效边沿前保持DIR一段时间,一般1us-5us左右。将setup time所需时钟周期设置为200,则可以覆盖50MHz-200MHz的FPGA芯片。

3、步进电机STEP步进信号控制

3.1 相关参数

以普通步进电机为例
(1)步距角1.8°,即360°÷1.8°=200步/圈
(2)转速v,单位为RPM(转/分)
(3)细分数M,则步进电机M×200步/圈
(4)步进频率f=M×200×v÷60
(5)步进频率对应的系统时钟周期mtr_data=SYS_FREQ÷f

3.2 加减速S型曲线

(1)基本表达式
v(t)=V0+(Vmax-V0)(3t²-2t³),t∈[0,1],其中V0为启动转速,Vmax为目标转速
当V0=0,Vmax=1时,
速度函数为v(t)=3t²-2t³,如下图红色曲线所示
加速度函数为a(t)=6t-6t²,如下图蓝色曲线所示
在这里插入图片描述
(2)将当前执行的步数n归一化到总加速步数N
v(n)=V0+(Vmax-V0)[3(n/(N-1))²-2(n/(N-1))³],n∈[0,N-1]
(3)频率与转速成正比例关系,则频率-步数表达式为
f(n)=F0+(Fmax-F0)[3(n/(N-1))²-2(n/(N-1))³],n∈[0,N-1],其中F0为启动频率,Fmax为目标频率

3.3 电机速度参数ROM的说明

尽量避免在Verilog代码中进行复杂乘除法运算和浮点数运算,因为这将占用FPGA的资源,如果将电机速度参数存储在ROM中,则极大程度上减少了FPGA资源的消耗。
(1)根据公式、加速步数、启动频率和目标频率计算系统时钟周期数据
(2)将系统时钟周期数据存入外部的速度参数ROM中
(3)速度参数ROM的Width为系统时钟周期数据的位数,Depth为加速步数
(4)速度参数ROM命名
①要求:rom_加速步数_启动频率Hz_目标频率Hz_系统时钟MHz_mtr
②示例:rom_32_100_4000_50_mtr
(5)设置ROM参数时,使能方式设置为“Always Enabled”,另外,不要勾选“Primitives Output Register”,否则ROM输出的数据会多一个时钟周期的延时。
(6)rom_32_100_4000_50_mtr相应的coe文件内容如下,关注微信公众号之后可提供Excel计算表格。

MEMORY_INITIALIZATION_RADIX=10;        //表示ROM内容的数据格式是10进制
MEMORY_INITIALIZATION_VECTOR= 
500000	,
446776	,
341054	,
246908	,
179830	,
134538	,
103790	,
82404	,
67102	,
55848	,
47364	,
40828	,
35696	,
31600	,
28284	,
25566	,
23318	,
21440	,
19860	,
18524	,
17390	,
16424	,
15602	,
14904	,
14314	,
13818	,
13410	,
13082	,
12828	,
12648	,
12538	,
12500	;

在这里插入图片描述

4、Verilog代码

4.1 功能描述

通过方向信号和步进信号控制电机运行的功能模块

4.2 时序图

在这里插入图片描述

4.3 DIR to STEP setup time时钟周期计数器

在接收到电机运行的指令后,先将方向信号设置为期望的方向,然后延迟SETUP_TIME个时钟周期。待延时完成后,在第(SETUP_TIME-1)个时钟周期生成矩形波START控制指令。

//---------------------------------------------------------------------------------------
//	DIR to STEP建立时间计数器cnt_setup_time
//---------------------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
	if(rst_n == 1'b0)
		cnt_setup_time <= 8'd0 ;
	else if(mtr_move_end == 1'b1)//电机运行结束
		cnt_setup_time <= 8'd0 ;
	else if(mtr_busy == 1'b1) begin//电机运行状态下
		if(cnt_setup_time < SETUP_TIME)//没有满足Setup Time
			cnt_setup_time <= cnt_setup_time + 1 ;
		else
			cnt_setup_time <= cnt_setup_time ;
	end
	else//电机空闲状态下
		cnt_setup_time <= 8'd0 ;
end
//---------------------------------------------------------------------------------------
//矩形波控制指令wave_cmd(仅持续1个时钟周期)(2bit)
//---------------------------------------------------------------------------------------
assign	wave_cmd =
		(mtr_move_end == 1'b1)				? INT	:
		(cnt_setup_time == SETUP_TIME - 1)	? START : IDLE ;

4.4 当前STEP在速度参数ROM中的地址

//---------------------------------------------------------------------------------------
//	当前STEP在速度参数ROM中的地址mtr_rom_addr(rom_data相对rom_addr固定延迟一个时钟周期)
//	如果定义为reg型,则会额外增加一个时钟周期的延迟
//	当cnt_steps=N时,第N个STEP在下一个时钟周期会结束,此时需要更新下一个STEP的rom_addr
//---------------------------------------------------------------------------------------
assign mtr_rom_addr =
	//参数不合理			
	((mtr_move_steps < (MTR_ACC_STEPS << 1)) || (mtr_move_steps < 2)) ? 10'd0 :
	//加速阶段
	(cnt_steps <= MTR_ACC_STEPS - 1) ? cnt_steps :
	//匀速阶段
	(cnt_steps <= mtr_move_steps - MTR_ACC_STEPS - 1) ? MTR_ACC_STEPS-1 :
	//减速阶段
	(cnt_steps <= mtr_move_steps - 1) ? mtr_move_steps - cnt_steps - 1 : 10'd0 ;

4.5 源代码

//=======================================================================================
//	作者:DingXY的硬件笔记本
//	邮箱:1324830818@qq.com
//	日期:2025/08/28
//	功能描述
//		1.通过方向信号和步进信号控制电机运行的功能模块
//		2.电机运行相关参数的关系式
//			2.1步距角1.8°,即360°/1.8°=200步/圈
//			2.2转速v,单位为RPM(转/分)
//			2.3细分数M,则步进电机M*200步/圈
//			2.4步进频率f=M*200*v/60
//			2.5步进频率对应的系统时钟周期mtr_data=SYS_FREQ/f
//		3.电机加减速S型曲线的函数表达式
//			3.1基本表达式
//				v(t)=V0+(Vmax-V0)(3t2-2t3),t∈[0,1]
//				其中V0为启动转速,Vmax为目标转速
//			3.2将当前执行的步数n归一化到总加速步数N
//				v(n)=V0+(Vmax-V0)*[3*(n/(N-1))2-2*(n/(N-1))3],n∈[0,N-1]
//			3.3频率与转速成正比例关系,则频率-步数表达式为
//				f(n)=F0+(Fmax-F0)*[3*(n/(N-1))2-2*(n/(N-1))3],n∈[0,N-1]
//				其中F0为启动频率,Fmax为目标频率
//		4.电机速度参数ROM的说明
//			4.1根据公式、加速步数、启动频率和目标频率计算系统时钟周期数据
//			4.2将系统时钟周期数据存入外部的速度参数ROM中
//			4.3速度参数ROM的Width为系统时钟周期数据的位数,Depth为加速步数
//			4.4速度参数ROM命名
//				要求:rom_加速步数_启动频率Hz_目标频率Hz_系统时钟MHz_mtr
//				示例:rom_32_100_4000_50_mtr
//		5.控制指令
//			IDLE= 2'd0 ;//空闲
//			INT	= 2'd1 ;//中断
//			CW	= 2'd2 ;//顺时针运行
//			CCW	= 2'd3 ;//逆时针运行
//		6.电机驱动芯片SETUP_TIME
//			为了保证电机驱动芯片稳定工作,在STEP处于空闲状态时改变DIR,
//			并在下一个STEP的有效边沿前保持DIR一段时间,一般1us-5us左右。
//=======================================================================================

module mtr_ctrl_dir_step
#(
parameter	SETUP_TIME		=  8'd20 ,//DIR to STEP setup time÷系统时钟周期(8bit)
parameter	MTR_ACC_STEPS	= 10'd32 ,//电机加速步数(10bit)
parameter	WIDTH_STEP_DATA	=  5'd19 ,//mtr_rom_data的位宽
parameter	WIDTH_STEPS_NUM	=  3'd7   //mtr_move_steps的位宽
)(
input						clk				,//系统时钟
input						rst_n			,//复位信号(低电平有效)
input				 [1:0]	mtr_move_cmd	,//电机运行指令(仅持续1个时钟周期)(2bit)
input[WIDTH_STEPS_NUM-1:0]	mtr_move_steps	,//电机运行总步数(位宽见参数)
input[WIDTH_STEP_DATA-1:0]	mtr_rom_data	,//当前STEP在速度参数ROM中的数据(位宽见参数)

output wire			 [9:0]	mtr_rom_addr	,//当前STEP在速度参数ROM中的地址(10bit)
output wire					mtr_move_end	,//电机运行结束信号(1个时钟周期的高电平)
output reg					mtr_dir			,//电机方向信号(1:顺时针/0:逆时针)
output reg					mtr_busy		,//电机繁忙信号(1:繁忙/0:空闲)
output wire					mtr_step		 //电机步进信号
);

//---------------------------------------------------------------------------------------
//	信号定义
//---------------------------------------------------------------------------------------
localparam	IDLE	= 2'd0 ;//空闲
localparam	INT		= 2'd1 ;//中断
localparam	CW		= 2'd2 ;//顺时针运行
localparam	CCW		= 2'd3 ;//逆时针运行
localparam	START	= 2'd2 ;//开始

reg	[7:0]	cnt_setup_time ;//DIR to STEP setup time时钟周期计数器(8bit)

wire[1:0]					wave_cmd	;//矩形波控制指令(仅持续1个时钟周期)(2bit)
wire[WIDTH_STEPS_NUM-1:0]	cnt_steps	;//电机运行总步数计数器

//---------------------------------------------------------------------------------------
//	例化rectangular_wave模块
//---------------------------------------------------------------------------------------
rectangular_wave
#(
.WIDTH_PERIOD_DATA(WIDTH_STEP_DATA	),//矩形波period_data的位宽
.WIDTH_PERIOD_NUM (WIDTH_STEPS_NUM	) //矩形波period_num的位宽
)
rectangular_wave_inst0
(
.clk		(clk			),//系统时钟
.rst_n		(rst_n			),//系统复位信号(低电平有效)
.wave_cmd	(wave_cmd		),//矩形波控制指令(仅持续1个时钟周期)(2bit)
.period_data(mtr_rom_data	),//矩形波PERIOD周期÷系统时钟周期(位宽见参数)
.duty_data	(mtr_rom_data>>1),//矩形波PERIOD周期×占空比÷系统时钟周期(位宽见参数)

.wave_busy	(				),//矩形波繁忙信号(1:繁忙/0:空闲)
.period_num	(cnt_steps		),//矩形波PERIOD数量(位宽见参数)
.wave_output(mtr_step		) //矩形波输出信号
);

//---------------------------------------------------------------------------------------
//	电机运行结束信号mtr_move_end(1个时钟周期的高电平)
//---------------------------------------------------------------------------------------
assign	mtr_move_end =
		((mtr_busy == 1'b1) && (mtr_move_cmd == INT)) ? 1'b1 ://接收到中断指令
		(cnt_steps == mtr_move_steps + 1) ? 1'b1 : 1'b0 ;//电机运行结束

//---------------------------------------------------------------------------------------
//	电机方向信号mtr_dir(1:顺时针/0:逆时针)
//	电机繁忙信号mtr_busy(1:繁忙/0:空闲)
//---------------------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
	if(rst_n == 1'b0) begin
		mtr_dir <= 1'b1 ;//默认顺时针
		mtr_busy <= 1'b0 ;
	end
	else if(mtr_move_end == 1'b1) begin//电机运行结束
		mtr_dir <= 1'b1 ;//默认顺时针
		mtr_busy <= 1'b0 ;
	end
	else if((mtr_busy == 1'b0) && (mtr_move_cmd ==  CW)) begin//顺时针运行指令
		mtr_dir <= 1'b1 ;
		mtr_busy <= 1'b1 ;
	end
	else if((mtr_busy == 1'b0) && (mtr_move_cmd == CCW)) begin//逆时针运行指令
		mtr_dir <= 1'b0 ;
		mtr_busy <= 1'b1 ;
	end
	else begin
		mtr_dir <= mtr_dir ;
		mtr_busy <= mtr_busy ;
	end
end

//---------------------------------------------------------------------------------------
//	DIR to STEP建立时间计数器cnt_setup_time
//---------------------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
	if(rst_n == 1'b0)
		cnt_setup_time <= 8'd0 ;
	else if(mtr_move_end == 1'b1)//电机运行结束
		cnt_setup_time <= 8'd0 ;
	else if(mtr_busy == 1'b1) begin//电机运行状态下
		if(cnt_setup_time < SETUP_TIME)//没有满足Setup Time
			cnt_setup_time <= cnt_setup_time + 1 ;
		else
			cnt_setup_time <= cnt_setup_time ;
	end
	else//电机空闲状态下
		cnt_setup_time <= 8'd0 ;
end

//---------------------------------------------------------------------------------------
//矩形波控制指令wave_cmd(仅持续1个时钟周期)(2bit)
//---------------------------------------------------------------------------------------
assign	wave_cmd =
		(mtr_move_end == 1'b1)				? INT	:
		(cnt_setup_time == SETUP_TIME - 1)	? START : IDLE ;

//---------------------------------------------------------------------------------------
//	当前STEP在速度参数ROM中的地址mtr_rom_addr(rom_data相对rom_addr固定延迟一个时钟周期)
//	如果定义为reg型,则会额外增加一个时钟周期的延迟
//	当cnt_steps=N时,第N个STEP在下一个时钟周期会结束,此时需要更新下一个STEP的rom_addr
//---------------------------------------------------------------------------------------
assign mtr_rom_addr =
	//参数不合理			
	((mtr_move_steps < (MTR_ACC_STEPS << 1)) || (mtr_move_steps < 2)) ? 10'd0 :
	//加速阶段
	(cnt_steps <= MTR_ACC_STEPS - 1) ? cnt_steps :
	//匀速阶段
	(cnt_steps <= mtr_move_steps - MTR_ACC_STEPS - 1) ? MTR_ACC_STEPS-1 :
	//减速阶段
	(cnt_steps <= mtr_move_steps - 1) ? mtr_move_steps - cnt_steps - 1 : 10'd0 ;

endmodule

4.6 TestBench仿真代码

//定义时间刻度
`timescale 1ns/1ns

module mtr_ctrl_dir_step_tb();
//----------------------------------------------------------------------------
//	信号定义
//----------------------------------------------------------------------------
reg			clk				;//系统时钟
reg			rst_n			;//复位信号(低电平有效)
reg	[ 1:0]	mtr_move_cmd	;//电机运行指令(仅持续1个时钟周期)(2bit)

reg	[ 3:0]	mtr01_move_steps;//电机运行总步数(位宽见参数)
reg	[ 3:0]	mtr01_rom_data	;//当前STEP在速度参数ROM中的数据(位宽见参数)
wire[ 9:0]	mtr01_rom_addr	;//当前STEP在速度参数ROM中的地址(10bit)
wire		mtr01_move_end	;//电机运行结束信号(1个时钟周期的高电平)
wire		mtr01_dir		;//电机方向信号(1:顺时针/0:逆时针)
wire		mtr01_busy		;//电机繁忙信号(1:繁忙/0:空闲)
wire		mtr01_step		;//电机步进信号

reg	[ 6:0]	mtr02_move_steps;//电机运行总步数(位宽见参数)
wire[18:0]	mtr02_rom_data	;//当前STEP在速度参数ROM中的数据(位宽见参数)
wire[ 9:0]	mtr02_rom_addr	;//当前STEP在速度参数ROM中的地址(10bit)
wire		mtr02_move_end	;//电机运行结束信号(1个时钟周期的高电平)
wire		mtr02_dir		;//电机方向信号(1:顺时针/0:逆时针)
wire		mtr02_busy		;//电机繁忙信号(1:繁忙/0:空闲)
wire		mtr02_step		;//电机步进信号

//----------------------------------------------------------------------------
//	定义主时钟,周期20ns,频率50MHz
//----------------------------------------------------------------------------
always #10 clk = ~clk ;

//----------------------------------------------------------------------------
//	设计STEP数据(mtr01)
//----------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
	if(rst_n == 1'b0)
		mtr01_rom_data <= 4'd10 ;
	else begin
		case(mtr01_rom_addr)
			4'd0:	mtr01_rom_data <= 4'd10 ;
			4'd1:	mtr01_rom_data <= 4'd8 ;
			4'd2:	mtr01_rom_data <= 4'd6 ;
			4'd3:	mtr01_rom_data <= 4'd4 ;
			default:mtr01_rom_data <= 4'd10 ;
		endcase
	end
end

//----------------------------------------------------------------------------
//	设计仿真时序
//----------------------------------------------------------------------------
initial begin
	clk <= 1'b0 ;
	rst_n <= 1'b0 ;
	mtr_move_cmd <= 2'd0 ;
	mtr01_move_steps <= 4'd10 ;
	mtr02_move_steps <= 7'd100;
//系统开始工作
	#10	rst_n <= 1'b1 ;
//发送开始信号
	#20	mtr_move_cmd <= 2'd2 ;
	#20	mtr_move_cmd <= 2'd0 ;
//结束仿真
	#120_000_000	$finish;
end

//----------------------------------------------------------------------------
//	例化mtr_ctrl_dir_step模块(mtr01)
//----------------------------------------------------------------------------
mtr_ctrl_dir_step
#(
.SETUP_TIME		( 8'd5	),//DIR to STEP setup time÷系统时钟周期(8bit)
.MTR_ACC_STEPS	(10'd4	),//电机加速步数(10bit)
.WIDTH_STEP_DATA( 4'd4	),//mtr_rom_data的位宽
.WIDTH_STEPS_NUM( 4'd4	) //mtr_move_steps的位宽
)
mtr01_ctrl_dir_step
(
.clk			(clk			 ),//系统时钟
.rst_n			(rst_n			 ),//复位信号(低电平有效)
.mtr_move_cmd	(mtr_move_cmd	 ),//电机运行指令(仅持续1个时钟周期)(2bit)
.mtr_move_steps	(mtr01_move_steps),//电机运行总步数(位宽见参数)
.mtr_rom_data	(mtr01_rom_data	 ),//当前STEP在速度参数ROM中的数据(位宽见参数)

.mtr_rom_addr	(mtr01_rom_addr	 ),//当前STEP在速度参数ROM中的地址(10bit)
.mtr_move_end	(mtr01_move_end	 ),//电机运行结束信号(1个时钟周期的高电平)
.mtr_dir		(mtr01_dir		 ),//电机方向信号(1:顺时针/0:逆时针)
.mtr_busy		(mtr01_busy		 ),//电机繁忙信号(1:繁忙/0:空闲)
.mtr_step		(mtr01_step		 ) //电机步进信号
);

//----------------------------------------------------------------------------
//	例化mtr_ctrl_dir_step模块(mtr02)
//----------------------------------------------------------------------------
mtr_ctrl_dir_step
#(
.SETUP_TIME		( 8'd5 	),//DIR to STEP setup time÷系统时钟周期(8bit)
.MTR_ACC_STEPS	(10'd32	),//电机加速步数(10bit)
.WIDTH_STEP_DATA( 5'd19	),//mtr_rom_data的位宽
.WIDTH_STEPS_NUM( 3'd7	) //mtr_move_steps的位宽
)
mtr02_ctrl_dir_step
(
.clk			(clk			 ),//系统时钟
.rst_n			(rst_n			 ),//复位信号(低电平有效)
.mtr_move_cmd	(mtr_move_cmd	 ),//电机运行指令(仅持续1个时钟周期)(2bit)
.mtr_move_steps	(mtr02_move_steps),//电机运行总步数(位宽见参数)
.mtr_rom_data	(mtr02_rom_data	 ),//当前STEP在速度参数ROM中的数据(位宽见参数)

.mtr_rom_addr	(mtr02_rom_addr	 ),//当前STEP在速度参数ROM中的地址(10bit)
.mtr_move_end	(mtr02_move_end	 ),//电机运行结束信号(1个时钟周期的高电平)
.mtr_dir		(mtr02_dir		 ),//电机方向信号(1:顺时针/0:逆时针)
.mtr_busy		(mtr02_busy		 ),//电机繁忙信号(1:繁忙/0:空闲)
.mtr_step		(mtr02_step		 ) //电机步进信号
);
//	例化rom_32_100_4000_50_mtr模块
rom_32_100_4000_50_mtr
rom_32_100_4000_50_mtr02
(
.clka	(clk			),// input wire clka
.addra	(mtr02_rom_addr	),// input wire [4 : 0] addra
.douta	(mtr02_rom_data	) // output wire [18 : 0] douta
);

endmodule

4.7 仿真结果(mtr01)

在这里插入图片描述

4.8 仿真结果(mtr02)

在这里插入图片描述

5、更多内容请关注微信公众号:DingXY的硬件笔记本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值