概述

这是坦克智能小车的最后一课,在前面已经提到过,由于电机之间存在一定误差,即使给两个电机相同的电压,其转速一般是不一样的,这样坦克小车就无法行走直线。要想电机走直线就需要用编码器调节两个电机速度,使其速度一样。

编码器原理

编码器分为光电式编码器和霍尔编码器,我们使用的是霍尔式编码器。霍尔编码器是一种通过磁电转换将输出轴上机械几何位移转换成脉冲或数字量的传感器。霍尔编码器由霍尔码盘和霍尔元件组成。霍尔码盘是在一定直径的直板上等分地布置有不同的磁极,霍尔码盘与电机同轴,电机旋转时,霍尔元件检测输出若干脉冲信号,为判定转向,一般输出两组存在一定相位差的脉冲信号。

编码器接线说明

电机一共有6pin,其中VM和GM是控制电机的,接到L298N上,这在之前已经讲过,剩下的4pin分别是

V:霍尔编码器电源,一般接5V

G:霍尔编码器地,接GND

S1:编码器的A相输出

S2:编码器B相输出

motor

左右两个电机与arduino连接关系如下表所示:(翻译的时候把表变成连线图)

左边电机
编码器 esp8266 uart wifi shield
V 5V
G GND
S1 D2
S2 D4
右边电机
编码器 esp8266 uart wifi shield
V 5V
G GND
S1 D3
S2 D7

采集脉冲

因为编码器输出的是方波信号,可以用arduino直接读取,在上面的接线图中我们分别将编码器的A相输出接到arduino的外部中断引脚,用中断的方式采集脉冲;通过B相的电平判断正反转。通过arduino定时器把采样周期设定为10ms,采集完两个电机的脉冲后,需要用PID算法来调节电机速度。所谓PID就是对偏差进行比例、积分、微分,PID由3个单元组成,分别是比例(P)单元、积分(I)单元和微分(D)单元。在工程实践中,比例单元是必须的,所以衍生出许多组合的PID控制器,如PD、PI、PID,更多关于PID算法详细介绍请看这里。在本课中我才用增量式PI算法。在这个PI算法中把左边电机的速度作为目标速度,通过PI算法调节右边电机速度使其与左边电机速度一致。

软件

下载https://osoyoo.com/driver/tank_robot_lesson7.zip并解压文件,用arduino IDE打开tank_robot_lesson7.ino文件,下面对部分代码做简要说明。

const byte encoder0pinA = 2;//A pin -> the interrupt pin 0
const byte encoder0pinB = 4;//B pin -> the digital pin 4
const byte rencodPinA = 3;  //rencodPinA -> the interrupt pin 1
const byte rencodPinB = 7;  //rencodPinB -> the digital pin 11

上面四行代码分别定义了左右两个电机A、B接口

 MsTimer2::set(10, control);  //use timer2 to set the 10ms timer interrupt
 MsTimer2::start();          //enable interrupt

设定采样周期为10ms,10ms到达后,进入定时器中断服务函数control中

  l_direction = true;//default -> Forward  
  r_direction = true;//default -> Forward  
  pinMode(encoder0pinB,INPUT);  
  pinMode(rencodPinB,INPUT);  
  attachInterrupt(0, lwheelSpeed, CHANGE);
  attachInterrupt(1, rwheelSpeed, CHANGE);

编码器引脚初始化,左右电机编码的A相输出分别使用arduino外部中断0和外部中断1,使用跳变沿(上升沿和下降沿)

void lwheelSpeed()
{
  int Lstate = digitalRead(encoder0pinA);
  if((encoder0PinALast == LOW) && Lstate==HIGH)
  {
    int val = digitalRead(encoder0pinB);
    if(val == LOW && l_direction)
    {
      l_direction = false; //Reverse
    }
    else if(val == HIGH && !l_direction)
    {
      l_direction = true;  //Forward
    }
  }
  encoder0PinALast = Lstate;
 
  if(!l_direction)  duration++;
  else  duration--;
}

void rwheelSpeed()
{
  int Lstate = digitalRead(rencodPinA);
  if((encoder0PinALast1 == LOW) && Lstate==HIGH)
  {
    int val = digitalRead(rencodPinB);
    if(val == LOW && r_direction)
    {
      r_direction = false; //Reverse
    }
    else if(val == HIGH && !r_direction)
    {
      r_direction = true;  //Forward
    }
  }
  encoder0PinALast1 = Lstate;
 
  if(!r_direction)  duration1++;
  else  duration1--;
}

编码器中断服务函数。当有跳变沿到来时候,会执行中断服务函数,对脉冲加1或减1

int PID_controller(int master,int slave)
{ 
  static float power,error,integralerror,lasterror;
  if(master < 0) master = -master;
  if(slave < 0) slave = -slave;
  error = master - slave;
  integralerror += error;
  power += Kp *(error-lasterror) + Ki * error;
  lasterror = error;
  return power;
}

上面的函数就是增量式PI算法的C语言实现,左右电机脉冲相减作为误差,误差的累积作为积分项。

void control()
{
   sei();//enable global interrupts
   if(++i >=4)//20ms
   {
   master_pulse = duration ,duration = 0;
   slave_pulse = duration1, duration1 = 0;
   pwm = PID_controller(master_pulse,slave_pulse);
   i = 0;
   }
   int newpower1 = motorspeed+pwm;
   constrain(newpower1,0,255);
   analogWrite(ENB,newpower1),analogWrite(ENA,motorspeed);
   cli();//disenable global interrupts
}

定时器中断