Arduino电机测速与PID原理及代码实现

闭环控制

霍尔传感器编码器测速原理

来源:长话短说 霍尔传感器编码器测速原理分析_哔哩哔哩_bilibili

 

 

 编码器不仅可检测脉冲,还可以检测边沿

通过两个霍尔编码器可判断转向→输出有相位差的两组方波信号

eg. 在A的上升沿计数,B是高电平就正转、B是低电平就反转

电机测速代码1

来源:06_Arduino_PID教程_哔哩哔哩_bilibili

①定时器中断:在一定时间内计数

//定时器中断方法1:调库
#include<Timerone.h>  // <MsTimer2.h>
void setup()

{
  Timer1.initialize(50000); // 每隔50000微秒调用一次中断函数
  Timer1.attachInterrupt(timerIsr); // 中断函数为timerIsr

}
void timerIsr()

{
  rpm = count / period * 60 / edges_per_cycle / reduction_ratio;
  //rpm: 转/分,电机前端真实转速
  //count: 50ms内的信号(上升)边沿数
  //period:调用中断函数的时间间隔,此处是0.05s
  //edges_per_cycle:电机后端每圈霍尔脉冲数,理论值为13
  //reduction ratio: 电机减速比,理论值为20
  count = 0;

}

【能调用库还是推荐调用库,前提是知道该库的功能】

//定时器中断方法2:手写(不推荐)

unsigned long curtime,oldtime;
void loop()
{
  curtime = millis(); // 返回当前时间,单位为ms
  int dt = abs(curtime - oldtime);
  if(dt > 50)

  {
    rpm = count / period * 60 / edges_per_cycle /reduction_ratio;
    count =0;
    oldtime = millis();
  }

}

②外部中断:计数的方法

如图:

假设希望计算A在某一段时间内的脉冲,需要知道何时产生脉冲(脉冲的上升沿在哪里) 

因此检测边沿可用外部中断→出现一次下降沿就调用一次中断函数

//外部中断
int ENC A=2;//电机的编码器A端
int ENC_B=3;//电机的编码器B端
int count =0;//上升沿(脉冲)数量
void Code()
{
  if(digitalRead(ENCB) == Low) //正转
  {
    count +1;
  }
  if(digitalRead(ENC_B) == HIGH) //反转

  {
    count -1;
  }
void setup()
{
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);
  attachInterrupt(0, Code, FALLING);
  //UN0管脚2对应参数为0,3对应参数为1
  //在ENCA下降沿处执行Code
}

【更高级的方式:四倍频技术,计上下降沿,提高四倍精度→同时检测A、B的外部中断】

void setup()

{
  pinMode(ENC_A,INPUT);
  pinMode(ENC_B,INPUT);
  attachInterrupt(0,Code0,CHANGE);
  attachInterrupt(1,Code1,CHANGE);

}

void Code0()
{
  if(ENCA=LOW) // 判断A为下降沿
  {

    if(digitalRead(ENC_B) == LOW) // 正转

    {
      count += 1;

    }
    if(digitalRead(ENC_B) == HIGH) // 反转

    {
      count -= 1;

    }

  }
  else // 未完成

}
void Code1(){}
//未完成

电机测速代码2

来源:用示波器理解 Arduino 小车的测速方法_哔哩哔哩_bilibili

//Include the TimerOne Library from Paul Stoffregen
#include "TimerOne.h"

//Constants for Interrupt Pins
//Change values if not using Arduino Uno

const byte MOTOR1 = 2;  //Motor 1 Interrupt Pin - INT 0
const byte MOTOR2 = 3;  //Motor 2 Interrupt Pin - INT 1

//Integers for pulse counters
unsigned int counter1 = 0;
unsigned int counter2 = 0;
//Float for number of slots in encoder disk
float diskslots 30; //Change to match value of encoder disk

//Interrupt Service Routines

//Motor 1 pulse count ISR
void ISR_count1()
{
    counter1++;    // increment Motor 1 counter value
}
//Motor 2 pulse count ISR
void ISR_count2()
{ 
   counter2++;    // increment Motor 2 counter value
}

//TimerOne ISR
void ISR timerone (
{
    Timerl.detachInterrupt ();    //    Stop the timer
    Serial.print ("Motor Speed 1:");
    float rotationl = (counterl / diskslots) * 60.00;    //calculate RPM for Motor 1
    Serial.print (rotationl);
    Serial.print (" RPM - ");
    counterl = 0; // reset counter to zero
    Serial.print ("Motor Speed 2:");
    float rotation2 = (counter2 / diskslots) * 60.00; //calculate RPM for Motor 2
    Serial.print (rotation2);
    Serial.println (" RPM");
    counter2 = 0; // reset counter to zero
    Timerl.attachInterrupt ( ISR_timerone ); // Enable the timer
}

void setup()
{
    Serial.begin(9600);

    Timerl.initialize(1000000); // set timer for lsec
    attachInterrupt (digitalPinToInterrupt (MOTOR1), ISR_count1, RISING); // Increase counter 1 when speed sensor pin goes High
    attachInterrupt (digitalPinToInterrupt (MOTOR2), ISR_count2, RISING); // Increase counter 2 when speed sensor pin goes High
    Timer1.attachInterrupt( ISR_timerone ); // Enable the timer
}

void loop()
{
    //Nothing in the loop!
    //You can place code here
}

PID算法

根据反馈进行控制

1.误差比例控制 : $K_p$增大,加快系统响应

$err(t) = goal - rpm(t);$

$pwm(t) = K_p × err(t);$

2.微分阻尼项 : $K_d = K_p × T_d$

$pwm(t) = K_p × (err(t) + T_d × \frac{d(err(t))}{dt}$

3.积分消除静态误差:$K_i = K_p / T_i$

$pwm(t) = K_p × (err(t) + \frac{1}{T_i}\int ^t_0err(\tau)d\tau + T_d × \frac{d(err(t))}{dt})$

位移式PID

【连续时间】$pwm(t) = K_p × err(t) + K_i\int ^t_0err(\tau)d\tau + K_d × \frac{d(err(t))}{dt}$

 【离散时间】$pwm(k) = K_p × err(k) + {K_i} × \sum ^k_0err(i) + K_d × (err(k) - err(k-1)$ 

适用:执行机构不带积分部件的对象

缺点:误差积累大

增量式PID

【连续时间】$dpwm(t) = K_p × \frac{d(err(t))}{dt}+ K_i × err(t) + K_d × \frac{d^2(err(t))}{dt^2}$

 【离散时间】$dpwm(k) = K_p × (err(k)-err(k-1)) + K_i × err(k) + K_d × [(err(k) - err(k-1))- (err(k-1)-err(k-2)]$

适用:执行机构带积分部件的对象 eg.步进电机

缺点:积分截断大、有静态误差  

接线

电池正极连接L298N12V供电
电池负极连接L298N供电GND
电机M+连接L298NOUT1
电机M-连接L298NOUT2
L298NIN1连接UNO10
L298NIN2连接UNO11
L298NENA连接UNO6
L298N5V连接UNO5V
L298N供电GND连接UNO GND
电机VCC接UNO 5V
电机GND接JNO GND
电机编码器A端接UNO端口2
电机编码器B端接UNO端口3

float err = 0,derr = 0,dderr = 0;
float Kp,Ki,Kd;
unsigned Long curtime,oldtime;
void loop()
{
  curtime=millis();/返回当前时间,单位为ms
  int dt abs(curtime = oldtime);
  if(dt > 50)
  {
    oldtime = millis();
    rpm = count / period * 60 / edges_per_cycle / reduction ratio;
    count = 0;
    Serial.print("  rpm:");/打印
    Serial.println(rpm);
    pwm += PID(50, rpm);


    if(abs(pwm)>255)/判断PWM是否超过最大值
    if(pwm<0)//控制电机正反转…
    else
    analogwrite(PWM,pwm);

  }

}

 

int PID(fLoat goal,fLoat now)

{

  dderr = goal-now - err-derr;
  derr = goal - now-err;
  err = goal - now;
  float dPWM = Ki * (err) + Kp * (derr) + Kd * (dderr);
  return dPWM;

}

PID调参

先是比例后积分,最后再把微分加,
曲线振荡很频繁,比例度盘要放大,
曲线漂浮绕大弯,比例度盘往小扳,
曲线偏离回复慢,积分时间往下降,
曲线波动周期长,积分时间再加长,

【06_Arduino_PID教程】 【精准空降到 38:09】 

作业

 

posted @ 2023-07-30 12:50  asandstar  阅读(2634)  评论(0编辑  收藏  举报