Arduino控制步进电机转速和方向

本文尝试用Arduino开发版控制42步进电机,搭配通用的步进电机驱动器,实现对步进电机的转速控制和方向控制。


原材料:

  1. Arduino开发板及附件
  2. 42步进电机和配套驱动器
  3. 电源
  4. 接线方式:共阴

总览

2、42步进电机

可以看到这个是四线步进电机,内部两两短接,可以通过万用表测出,相同相的线随意接入驱动器的A+,A-和B+,B-即可。

3、驱动器

驱动器侧面有一排按钮,往上拨为OFF,往下为ON,其中看驱动器界面标识可见,        SW1-SW3:为步进电机驱动器电流控制按钮,电流越大步进电机就越有劲,不容易丢步。

SW4:是控制限时电流按钮,即电机不转时是否给电机电流按钮,据说开启OFF可以更好的保护电机,但是为了更好地控制步进电机建议打开。

SW5-SW8:是控制电机精度的,Pulsw/rev表示的是多少个脉冲为一圈,数字越小即转的越快,数字越高越精确转的越慢。

PUL+,PUL-:步数控制,输入脉冲信号,一个脉冲(一高一低)走一步。

DIR+,DIR-:方向控制,接入高电平即正转,接入低电平则反转。

ENA+,ENA-:使能控制,一般情况悬空(都不接)即可,接入高电平步数控制失效,可以手动转动,当接入低电平恢复原有的步数控制,此时无法手动转动。

4、程序代码

保证线路连接正确的情况下,以下程序就能简单控制步进电机的运动,如果要精确控制等更精确的控制,或者用库控制的话需要更深入的研究。

代码如下:

#define STEPPIN 9
#define DIRPIN 8
//方向位为8,脉冲位为9
 
void setup() {
  pinMode(STEPPIN, OUTPUT);
  pinMode(DIRPIN, OUTPUT);
  Serial.begin(9600);
}
 
void loop() {
  // Enables the motor to move in a particular direction
  Serial.println("Forward Begins");
  digitalWrite(DIRPIN, HIGH);
  // 正向转1圈(200脉冲)
  for (int x = 0; x < 200; x ++) {
    digitalWrite(STEPPIN, HIGH);
    delayMicroseconds(500);
    digitalWrite(STEPPIN, LOW);
    delayMicroseconds(500);
  }
  Serial.println("Forward Ends");
  delay(1000); // Delay for one second
 
  // Changes the rotation direction or rotates in opposite direction
  Serial.println("Backward Begins");
  digitalWrite(DIRPIN, LOW);
  // 反向转3圈(600脉冲)
  for (int x = 0; x < 600; x ++) {
    digitalWrite(STEPPIN, HIGH);
    delayMicroseconds(500);
    digitalWrite(STEPPIN, LOW);
    delayMicroseconds(500);
  }
  Serial.println("Backward Ends");
  delay(2000); //Delay for two seconds
}

PID调速代码:


// 导入PWM库,此库可自定义PWM的频率和占空比(除了timer0相关的端口,因为timer0是负责dealay等函数的,因此不能去改动timer0)
#include <PWM.h>
 
// 导入 PID 库的头文件
#include <PID_v1.h>
 
// 导入 定时中断 库的头文件,这个库用的是timer2,因此这个程序的其他地方不能对timer2进行修改,否则会影响这个库的功能
#include <MsTimer2.h>
 
// ↓↓↓↓↓↓↓↓↓  变量定义与初始化:PID 控制部分 ↓↓↓↓↓↓↓↓↓// 
// 定义三个变量,分别代表 PID 控制器的期望输入值、实际输入值、控制量
double Setpoint = 6;   // 希望电机的转速为 6rad/s
double Input; 
double Output;
// 初始化 PID 的三个参数 
double kp = 2, ki =15, kd = 1;
//double kp = 11.46, ki =98.22, kd = 0.33;
// 创建一个 PID 控制器的实例
PID myPID(&Input, &Output, &Setpoint, kp, ki, kd, DIRECT);
// ↑↑↑↑↑↑↑↑↑  变量定义与初始化:PID 控制部分  ↑↑↑↑↑↑↑↑↑//
 
 
// ↓↓↓↓↓↓↓↓↓  变量定义与初始化:PID 自整定部分 ↓↓↓↓↓↓↓↓↓// 
double OutputHigh,OutputLow;
double outputStep = 30 ;  //必须是正值
double OutputUsedinPIDAutotune = 0;
double inputHistory[20];
int caculateFlag = 1;
int inputHighAll = 0;
int inputHighNum = 0;
int inputLowAll = 0;
int inputLowNum = 0;
int atemp, btemp;
int if_inputHistory_or_not = 0; // 决定是否开始PID自整定过程中的对Input的历史记录
// ↑↑↑↑↑↑↑↑↑  变量定义与初始化:PID 自整定部分  ↑↑↑↑↑↑↑↑↑//
 
 
 
// ↓↓↓↓↓↓↓↓↓  变量定义与初始化:电机测速部分   ↓↓↓↓↓↓↓↓↓// 
int count = 0;  // 记录在指定时间段内,编码器码盘脉冲值
double omega = 0;  //电机转速 rad/s
unsigned long  old_time = 0; // 时间标记
double measure_sample_time = 100; // 测速周期,单位:毫秒
// ↑↑↑↑↑↑↑↑↑  变量定义与初始化:电机测速部分   ↑↑↑↑↑↑↑↑↑// 
 
 
// ↓↓↓↓↓↓↓↓↓  变量定义与初始化:MATLAB绘图部分   ↓↓↓↓↓↓↓↓↓//
//  当这个变量为0的时候,串口会显示最详细的数据,此时不宜用MATLAB绘图
//  当这个变量不为0的时候,串口会只显示一些关键的数据,共1列,每4个数据为1组,第一个是当前的实际转速(rad/s),第二个是要求的转速(rad/s),第三个是当前实际的占空比,第四个是当前的时间(毫秒),此时宜用MATLAB绘图
int Use_MATLAB_to_Draw_or_not = 0;
// ↑↑↑↑↑↑↑↑↑  变量定义与初始化:MATLAB绘图部分   ↑↑↑↑↑↑↑↑↑// 
 
 
// ↓↓↓↓↓↓↓↓↓  变量定义与初始化:XXX部分   ↓↓↓↓↓↓↓↓↓//
 
// ↑↑↑↑↑↑↑↑↑  变量定义与初始化:XXX部分   ↑↑↑↑↑↑↑↑↑// 
 
// 自定义函数:外部中断起作用时的中断处理函数,(当端口收到测速信号线(FG线)的下降沿信号时,会有一个外部中断,而中断处理函数就在此定义) ↓↓↓↓↓↓↓↓↓
void Code()
{  
  //每中断一次,记录一次  
  count += 1; // 编码器码盘计数加一  
}
 
 
// 自定义函数:定时中断起作用时的中断处理函数,用于计算电机转速 ↓↓↓↓↓↓↓↓↓
void omega_measure()
{                       
    detachInterrupt(0); // 关闭外部中断0       
    
//    Serial.print("time: ");Serial.print(millis());Serial.println("毫秒");
//    Serial.print("old_time: ");Serial.print(old_time);Serial.println("毫秒");
 
     //把measure_sample_time毫秒内编码器码盘计得的脉冲数,换算为当前转速值
     //转速单位 rad/s。这个编码器码盘为2044个空洞。 (2044这个数字是标定过的  我自己标定的)
     //电机转速,其中 count/2044 计算的是在measure_sample_time毫秒之内,转了多少圈,“2*3.141592654”是为了转换为rad制
      omega =(double)count/2044*2*3.141592654*1000/measure_sample_time;
     
  
     // 记录被调量Input(即 omega)的历史值,20个
     if (if_inputHistory_or_not == 1)
     {
        for (int i = 1; i < 20; i++) 
          {
          inputHistory[20 - i] = inputHistory[20 - i - 1];
          }                                                   
        inputHistory[0] = omega;
     }
  
     //在串口监视器中输出一些结果
     if (Use_MATLAB_to_Draw_or_not == 0)
     {
      // 输出详细数据
       Serial.println(" ");
       Serial.print("期望转速: ");Serial.print(Setpoint);Serial.println("rad/s");
       Serial.print("实际转速: ");Serial.print(omega);Serial.println("rad/s");
       
       Serial.print("PID自整定震荡前或者后的占空比值: ");Serial.println(Output);
       Serial.print("PID自整定震荡中的占空比值: ");Serial.println(OutputUsedinPIDAutotune);
       Serial.print("当前系统时间:");Serial.print(millis());Serial.println("毫秒");
       Serial.println(" ");
     }
     else
     {
      // 输出简单数据
      Serial.println(omega);
      Serial.println(Setpoint);
      Serial.println(Output);
      Serial.println(millis());
      
      }
  
    //恢复到编码器测速的初始状态
    count = 0;   //把脉冲计数值清零,以便计算下一个measure_sample_time毫秒的脉冲计数        
    old_time=  millis();     // 记录每次测速时的时间节点   
    attachInterrupt(0, Code,FALLING); // 重新开放外部中断0
}
 
 
 
 
 
void setup() {
  // put your setup code here, to run once:
  
   
   // 2口接FG信号测速线,另外,2口是外部中断口,中断接口是in.0(Ardunio mega2560 自带的特性)
   pinMode(2, INPUT); 
   
   //电机的编码器脉冲中断函数, 当编码器码盘的脉冲信号下跳沿的时候,中断一次(FALLING),中断函数是Code,中断接口是in.0,对应pin2口
   attachInterrupt(0, Code, FALLING);
 
   // 对定时中断进行设置,每 measure_sample_time毫秒 进入一次定时中断,并调用测速函数(即:每measure_sample_time毫秒测一次速)
   MsTimer2::set(measure_sample_time, omega_measure);
 
  //  开启定时中断
   MsTimer2::start(); 
 
 
 
  // ↓↓↓↓↓↓↓↓↓设置PWM的频率,与pin口 ↓↓↓↓↓↓↓↓↓
   InitTimersSafe(); 
   // 在 mega2560 上,12口属于timer1,不会与定时中断库“MsTimer2”冲突
   bool success = SetPinFrequencySafe(12, 10000);
 
  if(success) 
  {
    // PWM 信号是从 12 口输出的
    pinMode(12, OUTPUT);   
  }
 
  // ↑↑↑↑↑↑↑↑↑设置PWM的频率,与pin口 ↑↑↑↑↑↑↑↑↑
 
 
   
 
  // 打开串口监视器
   Serial.begin(9600); //串口波特率为9600
   
 
   //打开 PID 控制器
  myPID.SetMode(AUTOMATIC);
 
  // 设置控制量的范围,在此应用中,控制量是占空比,范围是0~255
  myPID.SetOutputLimits(0,255);
 
  // 设置计算采样周期
  myPID.SetSampleTime(1);
 
  // 初始化 inputHistory 数组
  for (int i = 0; i < 20; i++)
  {
    inputHistory = 0;
  }
 
}
 
void loop() 
{
  // 主程序,将重复运行:
  // loop()每执行一次就将当前速度传给Input
  Input = omega;
 
 
  
  if (Use_MATLAB_to_Draw_or_not == 0)
  {               // 做详细计算、PID自整定等
  
                  if (millis() < 25000)
                  {
                    // 当运行时间小于25秒,则进行普通的 PID 控制,并且 以PID控制器计算得到的占空比运转 (稳定为先)
                    myPID.Compute();
                    pwmWrite(12, Output);
                  }
                
                  // 当运行时间大于25秒,且小于30秒,则 PID 进行自整定  (强行震荡)
                  else if(millis() < 30000)
                  {       if_inputHistory_or_not = 1; // 打开“记录被调量Input(即 omega)的历史值”
                
                          // Output包含的是稳定值
                          OutputLow = Output - outputStep;
                          OutputHigh = Output + outputStep;
                          
                          if (Input < Setpoint) 
                          { 
                              OutputUsedinPIDAutotune = OutputHigh;
                              if (int(OutputUsedinPIDAutotune) > 255) {OutputUsedinPIDAutotune = 255;}
                          } 
                          else if (Input > Setpoint) 
                          {
                              OutputUsedinPIDAutotune = OutputLow;         
                              if (int(OutputUsedinPIDAutotune) < 0) {OutputUsedinPIDAutotune = 0;}
                          }
                          pwmWrite(12, OutputUsedinPIDAutotune);
                  }
                  
                  // 震荡结束后,继续 PID 自整定  (分析波形 (即数组inputHistory[]的波形) 计算自整定后的PID参数)
                  else if(millis() < 35000)
                  
                  {     // 先关闭电机
                        pwmWrite(12, 0);
                        // 关闭“记录被调量Input(即 omega)的历史值”
                        if_inputHistory_or_not = 0; 
                        
                        while (caculateFlag == 1) 
                        {
                            for (int i = 1; i < 19; i++) 
                            {
                                //如果是波峰
                                if (inputHistory > inputHistory[i - 1] && inputHistory > inputHistory[i + 1]) 
                                {       
                                    inputHighAll += inputHistory;  //波峰值总数增加
                                    inputHighNum++;  //波峰个数计数增加
                                    if (inputHighNum == 1) atemp = i;  //当产生第一个波峰的时候,atemp记录下此时是第几个数据(每个数据相隔100ms)
                                    btemp = i;  //当对20个历史数据分析完,btemp则可记录下最后一个波峰对应的是第几个数据
                                } 
                
                                //如果是波谷,类似上面
                                else if (inputHistory < inputHistory[i - 1] && inputHistory < inputHistory[i + 1]) 
                                {
                                    inputLowAll += inputHistory;
                                    inputLowNum++;
                                }
                            }
                            double autoTuneA = (inputHighAll / inputHighNum) - (inputLowAll / inputLowNum);                  //峰峰值(即波峰值 - 波谷值)的平均数A
                            double autoTunePu = (btemp - atemp) * measure_sample_time/1000 / (inputHighNum - 1);                                  //两个波峰之间的时间间隔Pu(s)
                            double Ku = 4 * outputStep / (autoTuneA * 3.14159);
                            double Kp_self = 0.6 * Ku;
                            double Ki_self = 1.2 * Ku / autoTunePu;
                            double Kd_self = 0.075*Ku*autoTunePu;
                
                            //  暂时关闭定时中断
                            MsTimer2::stop();
                
                            // 在串口监视器输出自整定的最终结果
                            Serial.println(" ");
                            Serial.println("******************************");
                            Serial.println("PID 自整定过程完成!");
                            Serial.println("自整定的PID参数为: ");
                            Serial.print("Kp_self: ");Serial.println(Kp_self);
                            Serial.print("Ki_self: ");Serial.println(Ki_self);
                            Serial.print("Kd_self: ");Serial.println(Kd_self);
                            Serial.println(" ");
                            Serial.print("震荡过程中的峰峰值 A: ");Serial.print(autoTuneA);Serial.println("rad/s");
                            Serial.print("两个波峰之间的时间间隔Pu: ");Serial.print(autoTunePu);Serial.println("秒");
                            Serial.println("******************************");
                            Serial.println(" ");
                            
                            
                            // 上述消息显示5秒
                            delay(5000);
                
                            // 把自整定的PID参数导入PID控制器中
                            myPID.SetTunings(Kp_self, Ki_self, Kd_self);
                
                            //
                
                            Serial.println("已经将自整定的PID参数导入PID控制器中");
                            Serial.println("即将以自整定的PID参数进行全新的控制,请等待10秒......");
                
                            // 在延时10秒
                            delay(10000);
                
                            caculateFlag = 0;
                
                            //  重新开启定时中断
                            MsTimer2::start(); 
                
                            
                        }
                             
                   }
                
                   // 从速度0开始,以自整定的PID参数进行全新的控制
                  else
                  {
                          if (caculateFlag = 0) 
                          {
                            Output = 255;
                            Input = 0;
                            myPID.Compute();
                            pwmWrite(12, Output);
                            omega = 0;
                            caculateFlag = 2;
                            old_time=  millis();      
                          }
                          else
                          {
                            myPID.Compute();
                            pwmWrite(12, Output);
                          }
                    
                  } 
  }
 
 
 
 
 
 
  else
  {
                  // 只在一组PID参数下运算
                  myPID.Compute();
                  pwmWrite(12, Output);
    
  }
 
 
  
 
}
 
//转角θ=-ANcos(wt),转速V=ANwsin(wt)  
float w=3;  
int N=100; //N是半个周期的脉冲数,正比于正弦函数的振幅  
           //如果乘积Nw太大,步进电机就会丢步  
float dt[400]={0}; //脉冲的时间间隔  
int k;  
const byte pinSPEED=5;
const byte pinDIREC=6;
  
void setup() {  
pinMode(pinSPEED,OUTPUT);  // 5号引脚发送PULSE(控制速度)  
pinMode(pinDIREC,OUTPUT);  // 6号引脚指定SIGN (控制方向)   
int dtMAX=30;  
float t=0;  
  for(k=1;k<=N;k++)  
  {dt[k]=(1.0F/sin(k*PI/(N+1))> dtMAX ? dtMAX : 1.0F/sin(k*PI/(N+1)));  
//如果两个脉冲的时间间隔超过预设的dtMAX,就认为它是dtMAX  
//dtMAX的值可以根据需要自行修改  
  t=t+dt[k];}  
//for循环结束后,t代表数组dt的前N项的和    
  for(k=1;k<=N;k++)  
  {dt[k]=PI*dt[k]/(w*t);}  
}  
  
void loop() {  
digitalWrite(pinDIREC,HIGH); //一个方向运动  
for(k=1;k<=N;k++)  
{digitalWrite(pinSPEED,HIGH);   
 digitalWrite(pinSPEED,LOW);  
 delayMicroseconds(1E6*dt[k]);}  
  
digitalWrite(pinDIREC,LOW);  //反方向运动  
for(k=1;k<=N;k++)  
{digitalWrite(pinSPEED,HIGH);  
 digitalWrite(pinSPEED,LOW);  
 delayMicroseconds(1E6*dt[k]);}  
}  

猜你喜欢

转载自blog.csdn.net/hhaowang/article/details/86359014