此代码是在做显微镜高速聚焦系统中自己写的步进电机电机驱动源码,为了达到最快的驱动速度,因此选用脉冲触发方式进行驱动。在电机驱动的过程中往往需要对脉冲进行使能,启动,配置好输出N个脉冲,设置电机转动的方向,发送脉冲的过程中发送急停信号,停止当前的脉冲输出以及脉冲输出完后反馈回来中断触发信号。经过实测代码能够满足步进电机的驱动需求,且能够在驱动完毕后反馈中断信号提示脉冲信号已经输出完毕。
此代码适用的地方主要在需要脉冲触发的应用场景,最终输出两个信号出去(输出脉冲和电机方向电平),若有需要可以把脉冲触发跟运动坐标系建立起来,内部加一个计数器做大致位置的驱动,但为了避免步进电机丢步带来的影响,建议还是加上闭环的编码器进行坐标系的建立,我采用的是张大头步进电机的闭环驱动,实测效果OK,120KHz全功率运行没问题,噪声较小。
拟设定五路输入信号,三路输出信号以及两个全局信号加时钟复位信号
sys_clk:时钟信号(通常情况下100M左右)
sys_rst:复位信号(低电平复位)
motor_enable:此路脉冲输出使能信号(低电平失能,高电平使能)
motor_start:脉冲开始输出信号(默认低电平,拉高后脉冲开始输出)
motor_pulse:脉冲个数信号(根据需求赋值)
motor_stop:脉冲急停信号(默认低电平,拉高后脉冲停止输出)
motor_direct:脉冲方向信号(低电平代表一个方向,高电平代表另外一个方向)
motor_exti:完成脉冲输出后中断信号(持续时间可定)
motor_step:外接至脉冲输出引脚
motor_dir:外接至步进电机驱动器方向引脚
主要代码通过状态机实现,状态机采用一段式状态机。状态机分为三个状态,当motor_enable信号拉高了高电平时,代表这路脉冲驱动信号有效,可以进行脉冲的输出。
通过case语句判断当前的状态的阶段,若处于第一个状态(idle)就对启动信号的上升沿进行判断,我们会在motor_start信号输入进行后打一个拍,然后判断其上升沿,当上升沿信号拉高后,代表脉冲即将输出,满足这个条件后状态机状态跳往第二个状态。若满足不了这个状态则持续为先前的状态。
进入到one第二个状态后,外部调用一个always语句,判断当前的状态是否跟上一个状态不同,且当前的状态属于第二个状态。如果判断成功就拉高pulse_status信号,这个信号处于高电平时,脉冲的计数器cnt_pulse_us就开始计数,当计数到指定脉宽的计数值后,翻转输出电平。脉宽计数值通过全局变量PULSE_WIDTH设定,比如基频为100MHz,当PULSE_WIDTH为50时,脉冲的工作频率为100M/50/2为1MHz。每计数到PULSE_WIDTH-1后,now_pulse_num就会自加到我们设定的脉冲个数*2(因为涉及到电平的翻转),当到达这个设定脉冲数后,pulse_status就会拉低,cnt_pulse_us就不会再计数,同时now_pulse_num归零,motor_step引脚输出低电平。从第二个状态跳转到第三个状态的条件也是这个。
第三个状态只持续一个时钟周期,在这个时钟周期内将rst_status拉高,同时state的状态转换成idle状态。等待下一个启动信号的来临。rst_status是整体的复位信号,也是输出中断信号的触发信号。当复位信号为1的时候,motor_step信号拉低,中断信号motor_exti信号拉高,pulse_status信号拉低。当motor_exti信号被拉高后,cnt_exti_us信号就会不断计数,当计数到EXTI_WIDTH-1时,拉低motor_exti信号。相当于将motor_exti信号拉高了一段时间然后再拉低。在外部我们可以设置PULSE_WIDTH和EXTI_DIDTH信号,以求获得较好我们可控的脉冲频率和中断信号时长。
以上都是无干扰情况下的正常运作流程,如果motor_stop信号在脉冲输出的时候被拉高了,检测到上升沿时,我们将下一个状态设置为下一个状态,且将pulse_status、cnt_exti_us、motor_step、now_pulse_num拉低,motor_exti信号拉高,即可实现急停停止输出脉冲的功能。
代码如下
`timescale 1ns / 1ps
/************************************
小董出品,必属精品
2023/8/18
************************************/
module motor_control#(
parameter EXTI_WIDTH = 50 ,
parameter PULSE_WIDTH = 5000
)
(
input wire sys_clk ,//时钟信号
input wire sys_rst ,//复位信号
input wire motor_stop ,//电机急停信号,停止脉冲输出
input wire motor_start ,//触发输出脉冲信号,比如摄像头数据采集完毕
input wire motor_enable,//电机使能信号,只有使能的时候才能运行
input wire [15:0] motor_pulse ,//单次行程总脉冲数
input wire motor_direct,//单次行程方向
output reg motor_exti ,//电机完成单次行程后中断信号
output reg motor_step ,//实际输出脉冲引脚
output wire motor_dir //实际输出方向引脚
);
localparam idle = 3'b001 ;
localparam one = 3'b010 ;
localparam two = 3'b100 ;
wire start_pose ;//上升沿触发信号
wire stop_pose ;//上升沿触发信号
reg motor_start_r;//motor_start信号打了一拍
reg motor_stop_r ;//motor_stop信号打了一拍
reg motor_enable_r;//motor_enable信号打了一拍
reg rst_status ;//复位标志位
reg [2:0] state ;//当前状态
reg [2:0] state_r ;//上一次状态
reg [15:0] now_pulse_num;//当前脉冲数
reg pulse_status ;//脉冲输出开始标志位
reg [15:0] cnt_pulse_us ;//脉冲一半周期定时计数,根据基频来
reg [15:0] cnt_exti_us ;//中断信号定时计数,根据基频来
//motor_start打拍
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
motor_start_r <= 1'b0;
else
motor_start_r <= motor_start;
//motor_enable打拍
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
motor_enable_r <= 1'b0;
else
motor_enable_r <= motor_enable;
//motor_stop打拍
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
motor_stop_r <= 1'b0;
else
motor_stop_r <= motor_stop;
//motor_start信号上升沿判断
assign start_pose = ~motor_start_r&&motor_start;
assign stop_pose = ~motor_stop_r&&motor_stop;
//state打拍
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
state_r <= 'd0;
else
state_r <= state;
//状态机逻辑
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
begin
state <= idle;
rst_status <= 1'b0;
end
else if(stop_pose == 1'b1)
begin
state <= idle;
rst_status <= 1'b0;
end
else if(motor_enable_r==1'b1)
case(state)
idle:
if(start_pose)
begin
state <= one;
end
else
begin
rst_status<= 1'b0;
state <= state;
end
one :
if(now_pulse_num == 2*motor_pulse)
begin
state <= two;
end
else
begin
state <= state;
end
two :
begin
rst_status<= 1'b1;
state <= idle;
end
default:;
endcase
else
begin
state <= idle;
end
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
pulse_status <= 1'b0;
else if(stop_pose==1'b1)
pulse_status <= 1'b0;
else if(state_r!=one&&state==one)
pulse_status <= 1'b1;
else if(now_pulse_num == 2*motor_pulse)
pulse_status <= 1'b0;
else if(rst_status==1'b1)
pulse_status <= 1'b0;
else
pulse_status <= pulse_status;
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
motor_exti <= 1'b0;
else if(stop_pose==1'b1)
motor_exti <= 1'b1;
else if(rst_status==1'b1)
motor_exti <= 1'b1;
else if(cnt_exti_us == EXTI_WIDTH-1'b1)
motor_exti <= 1'b0;
else
motor_exti <= motor_exti;
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
cnt_exti_us <= 'd0;
else if(stop_pose==1'b1)
cnt_exti_us <= 1'b0;
else if(cnt_exti_us== EXTI_WIDTH-1'b1)
cnt_exti_us <= 'd0;
else if(motor_exti==1'b1)
cnt_exti_us <= cnt_exti_us + 1'b1;
else
cnt_exti_us <= cnt_exti_us;
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
cnt_pulse_us <= 1'b0;
else if(stop_pose==1'b1)
cnt_pulse_us <= 1'b0;
else if(cnt_pulse_us == PULSE_WIDTH-1'b1)
cnt_pulse_us <= 1'b0;
else if(pulse_status == 1'b1)
cnt_pulse_us <= cnt_pulse_us + 1'b1;
else
cnt_pulse_us <= 1'b0;
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
motor_step <= 1'b0;
else if(stop_pose==1'b1)
motor_step <= 1'b0;
else if(cnt_pulse_us == PULSE_WIDTH-1'b1)
motor_step <= ~ motor_step;
else if(now_pulse_num == 2*motor_pulse )
motor_step <= 1'b0;
else if(rst_status==1'b1)
motor_step <= 1'b0;
else
motor_step <= motor_step;
always@(posedge sys_clk or negedge sys_rst)
if(!sys_rst)
now_pulse_num <= 1'b0;
else if(stop_pose==1'b1)
now_pulse_num <= 1'b0;
else if(cnt_pulse_us == PULSE_WIDTH-1'b1)
now_pulse_num <= now_pulse_num + 1'b1;
else if(now_pulse_num == 2*motor_pulse)
now_pulse_num <= 1'b0;
else if(rst_status==1'b1)
now_pulse_num <= 1'b0;
else
now_pulse_num <= now_pulse_num;
//电机运动方向与输入设定方向相同
assign motor_dir = motor_direct;
endmodule
可见脉冲脉宽为100个时钟周期,中断周期为50个时钟周期。在原先的参数设定中,PULSE_WIDTH为50,EXTI_WIDTH为50,符合设计要求。且发出脉冲为3个脉冲。
今天吃了手撕鸡+豆腐,味道也很不错,手撕鸡挺柴的,晚上吃的鱼香肉丝拌面,味道确实不错,下次可以继续尝尝。