【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