Hanker

当圆梦小车遇上 Arduino ……

0
阅读(3764)

[提要] 本文介绍了用 Arduino 控制圆梦小车第三代和第四代的细节,对于那些想做小车又怕缺少单片机知识的人来说,这是个福音!

又找到一把“牛刀”——  Arduino

实际上,2 年前就知道了 Arduino,但当时没有在意,关键是对 AVR 不太感兴趣,因为下载程序需要专用的下载工具,且价格不菲(相对于当时定位的小车价格而言)。同时国内高校鲜有用 AVR 教学的,因此没有仔细研究它。

不选择的原因还有一个:因为我设计小车的目的是为学习嵌入式控制提供实践平台,目标客户是大学生,当初认为 Arduino 不够专业,不符合大学生的需求。

后来,一个朋友给我推荐了 Arduino 的 PID 控制库函数,因为他参考这个库实现了轮式驱动单元的 PID 调速,效果不错。我下载一看,被“震”住了,居然在 8 位机上用的是 C++ 的类方式编写的,相当专业啊!那时我已有所动摇(关于 PID 调速,另撰文探讨)。

最近,在几个朋友的一再推荐下,我决定尝试一下。

决定尝试的另一个原因是:圆梦小车升级至第三代“轮式驱动单元”以及第四代“FIRA小车”后,都将控制器的选择权交给了用户。

因为,从前两代小车的经验看,很难让客户接受某一种控制器,一是各人喜好不同,二是学习需求也会改变,最关键的是在 8 位机上很少有大家认同的平台,像 Linux、Android 那样被广泛接受,并有很多大虾为其提供丰富的资源。

而 Arduino 经过这两年的发展,已经羽翼丰满,其认可度和资源用 Google 搜索一下就可知晓。

为了使新的圆梦小车能够为客户所接受,我尝试用STM32做了一个控制器,并移植了 uCOSII,虽然基本完成了,可从我自己做的体验看,对于初学者,有些难 :(  所以,选择一个简单易学的控制器仍是困扰我的难题之一。

既然 Arduino 热度如此之高,且朋友(上海新车间)介绍:说此控制器初衷是为不懂电子和计算机的艺术家所设计,以简单易用为前提,我想应该可以“尝尝” ^_^

浏览了 Arduino 家族的若干产品后,选择了 Arduino Nano,因为轮式驱动单元是用杜邦线引出的,Nano是插针输出,便于连接。而 FIRA 小车配套有万能试验板,用 Nano 更是方便,随即买了一块 Nano (Arduino 产品软、硬件都是开源的,所以谁都可以生产,我买的是南京本地的,算是“照顾本地经济发展吧:P”)。

东西拿到手后,在 Arduino 的官网上下载开发环境,按照提示安装后,先大概浏览了一下它的语法参考,就着手尝试。

硬件连接很简单,因为用的是 USB 口,所以连电源也不需要,下载了一个开发环境所附的例子:闪灯(Blink),因为 Nano 上能看到效果的只有一个 LED(这点倒是和我前面设计的小车控制器类似^_^),很顺利,小眼睛眨巴了 :D

研究了一下程序,尝试修改参数,发现有反应,很简单,而且好理解,程序如下:

void setup() {               
  // initialize the digital pin as an output.
  // Pin 13 has an LED connected on most Arduino boards:
  pinMode(13, OUTPUT);    
}

void loop() {
  digitalWrite(13, HIGH);   // set the LED on
  delay(1000);              // wait for a second
  digitalWrite(13, LOW);    // set the LED off
  delay(1000);              // wait for a second
}

从拿到东西,到让其工作,前后不到一小时(包括下载程序,安装环境),绝对算得上易用!

一个最大的好处,它化解了程序下载工具的问题,用 USB(实际是串口)实现了程序下载,无须购置 AVR 的开发工具。

初战告捷,倍受鼓舞。晚上回家后就对其语法参考深入研究,发现简单易懂,有基本的编程概念即可对付。

按捺不住,当晚即编写了轮式驱动单元的驱动程序,不但考虑了简单的电机、舵机驱动,还涉及了脉冲采集,且使用了中断。

特别说明的是,其对舵机的支持十分“给力”!两句话就实现了。

而电机的 PWM 驱动也十分符合我的设计,其 PWM 频率为 490Hz,虽然略高于我设计的 125 Hz,但也算是在范围内,如果是几千赫兹,估计有些麻烦。

中断处理的实现也很容易,和前面做的 uCOSII 中断很类似,估计都是这个思路。

编写好的程序如下:

/*
  This is the first program for YM3 smart Car.
  using Nano.
 
  resource:
  PIN2 --- input, interrupt, encode pulse sample
 
  PIN6 ---  output, Motor PWM, YM3's Ctrl1
  PIN7 ---  output, Motor Ctrl, YM3's Ctrl2
  PIN8 ---  output, Motor Ctrl. YM3's Ctrl3
 
  PIN9 ---  output, Servo

  Motor control logic:
  Ctrl1   Ctrl2   Ctrl3   Drv1    Drv2    Drv3    Drv4    电机状态
  X       0       0     0/截止  0/导通  0/截止  0/导通    刹车
  PWM     1       0     PWM     1/截止  0/截止  0/导通    正转
  PWM     0       1     0/截止  0/导通  PWM     1/截止    反转
  0       1       1     0/截止  1/截止  0/截止  1/截止    惰行
  1       1       1     1/导通  1/截止  1/导通  1/截止    刹车
 
*/

#define FORWARD  1
#define BACKWARD 2
#define BRAKE    3
#define FLOAT    4

int pulse = 0;        // interrupt No

int Ctrl1 = 6;          // for Motor
int Ctrl2 = 7;
int Ctrl3 = 8;

int ServoPWM = 9;       // for Servo
Servo myServo;

int LED = 13;

int gc_iRunCount;       // pulse counter
int gc_iRunNum;         // set Run pulses

void setup()
{
  // init pulse Sample
  attachInterrupt(pulse, pulseSample,FALLING);
 
  // init Motor Control pin
  pinMode(Ctrl1, OUTPUT);
  pinMode(Ctrl2, OUTPUT);
  pinMode(Ctrl3, OUTPUT);

  // init Servo Control
  myServo.attach(ServoPWM);
 
  pinMode(LED,OUTPUT);
}

void loop()
{
  int iFlashTime = 500;
 
  myServo.write(45);
  delay(1000);
  gc_iRunNum = 130;
  Motor(FORWARD,100);
  while(gc_iRunNum>0)
  {
    digitalWrite(LED,HIGH);
    delay(iFlashTime);
    digitalWrite(LED,LOW);
    delay(iFlashTime);
  }
 
  delay(1000);
  myServo.write(136);
  delay(1000);
  gc_iRunNum = 130;
  Motor(BACKWARD,100);
  while(1)
  {
    digitalWrite(LED,HIGH);
    delay(iFlashTime);
    digitalWrite(LED,LOW);
    delay(iFlashTime);
    if(gc_iRunNum == 0)
    {
      gc_iRunNum = 10;
      iFlashTime = 1000;
      myServo.write(90);
    }
  }
}

void pulseSample()
{
  gc_iRunCount++;
 
  if(gc_iRunNum > 0)
  {
    gc_iRunNum--;
    if(gc_iRunNum == 0)
    {
      Motor(BRAKE,0);
    }
  }
}

void Motor(int iMode,unsigned char ucPwmVal)
{
  switch (iMode)
  {
    case FORWARD:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,HIGH);
      digitalWrite(Ctrl3,LOW);
      analogWrite(Ctrl1,ucPwmVal); // Start Motor
      break;
    }

    case BACKWARD:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,LOW);
      digitalWrite(Ctrl3,HIGH);
      analogWrite(Ctrl1,ucPwmVal); // Start Motor
      break;
    }
   
    case BRAKE:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,LOW);
      digitalWrite(Ctrl3,LOW);
      break;
    }

    case FLOAT:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,HIGH);
      digitalWrite(Ctrl3,HIGH);
      break;
    }
   
    default: break;
  }
}

让人兴奋的是,基本未作修改,程序就实现了所想要的功能,只是略微调试了一下行走的脉冲数,使得小车掉头准确些。

以下就是小车的动作视频:


轮式驱动单元顺利实现了用 Arduino Nano 控制,让我很开心,再接再厉,尝试第四代 —— FIRA 小车的控制。选用带电源管理的基础配置,利用配套的万能试验板。

因为 Nano 控制器还想用在其它地方,所以设计了插座,以便拆卸。同时用杜邦线实现连接,主要是尝试一下,不想用焊接方式,那样缺少灵活性。

“作品”如下:


基于这个作品,编写了如下程序:

/*
  This is the first program for YM4 smart Car.
  using Nano.
 
  resource:
  PIN2 --- input, interrupt, Left encode pulse sample
  PIN3 --- input, interrupt, Right encode pulse sample
 
  PIN6 ---  output, Motor PWM, Left's Ctrl1
  PIN7 ---  output, Motor Ctrl, Left's Ctrl2
  PIN8 ---  output, Motor Ctrl. Left's Ctrl3
 
  PIN9 ---  output, Servo ,
 
  PIN10 ---  output, Motor PWM, Right's Ctrl1
  PIN11 ---  output, Motor Ctrl, Right's Ctrl2
  PIN12 ---  output, Motor Ctrl. Right's Ctrl3

  Motor control logic:
  Ctrl1   Ctrl2   Ctrl3   Drv1    Drv2    Drv3    Drv4    电机状态
  X       0       0     0/截止  0/导通  0/截止  0/导通    刹车
  PWM     1       0     PWM     1/截止  0/截止  0/导通    正转
  PWM     0       1     0/截止  0/导通  PWM     1/截止    反转
  0       1       1     0/截止  1/截止  0/截止  1/截止    惰行
  1       1       1     1/导通  1/截止  1/导通  1/截止    刹车
 
*/

#define FORWARD  1
#define BACKWARD 2
#define BRAKE    3
#define FLOAT    4

#define LEFT     0
#define RIGHT    1

int L_pulse = 0;       // interrupt No
int R_pulse = 1;       // interrupt No

int L_Ctrl1 = 6;       // Pin No, for Left Motor
int L_Ctrl2 = 7;
int L_Ctrl3 = 8;

int R_Ctrl1 = 10;     // Pin No, for Right Motor
int R_Ctrl2 = 11;
int R_Ctrl3 = 12;

int LED = 13;         // Pin No, for LED

int gc_iLeftRunCount;   // Left pulse counter
int gc_iLeftRunNum;     // set Left Run pulses

int gc_iRightRunCount;  // Right pulse counter
int gc_iRightRunNum;    // set Right Run pulses

void setup()
{
  // init pulse Sample
  attachInterrupt(L_pulse, LpulseSample,FALLING);
  attachInterrupt(R_pulse, RpulseSample,FALLING);
 
  // init Motor Control pin
  pinMode(L_Ctrl1, OUTPUT);
  pinMode(L_Ctrl2, OUTPUT);
  pinMode(L_Ctrl3, OUTPUT);

  pinMode(R_Ctrl1, OUTPUT);
  pinMode(R_Ctrl2, OUTPUT);
  pinMode(R_Ctrl3, OUTPUT);
 
  pinMode(LED,OUTPUT);
}

void loop()
{
  int iFlashTime = 500;
 
  gc_iLeftRunNum = 105;
  Motor(LEFT,FORWARD,100);
  while(gc_iLeftRunNum>0)
  {
    digitalWrite(LED,HIGH);
    delay(iFlashTime);
    digitalWrite(LED,LOW);
    delay(iFlashTime);
  }
 
  delay(1000);
  gc_iRightRunNum = 103;
  Motor(RIGHT,FORWARD,100);
  while(1)
  {
    digitalWrite(LED,HIGH);
    delay(iFlashTime);
    digitalWrite(LED,LOW);
    delay(iFlashTime);
    if(gc_iRightRunNum == 0)
    {
      gc_iRightRunNum = 10;
      iFlashTime = 1000;
    }
  }
}

void LpulseSample()
{
  gc_iLeftRunCount++;
 
  if(gc_iLeftRunNum > 0)
  {
    gc_iLeftRunNum--;
    if(gc_iLeftRunNum == 0)
    {
      Motor(LEFT,BRAKE,0);
    }
  }
}

void RpulseSample()
{
  gc_iRightRunCount++;
 
  if(gc_iRightRunNum > 0)
  {
    gc_iRightRunNum--;
    if(gc_iRightRunNum == 0)
    {
      Motor(RIGHT,BRAKE,0);
    }
  }
}

void Motor(unsigned char ucSide,unsigned char ucMode,unsigned char ucPwmVal)
{
  int Ctrl1,Ctrl2,Ctrl3;
  if(ucSide == LEFT)
  {
    Ctrl1 = L_Ctrl1;
    Ctrl2 = L_Ctrl2;
    Ctrl3 = L_Ctrl3;
  }
  else
  {
    Ctrl1 = R_Ctrl1;
    Ctrl2 = R_Ctrl2;
    Ctrl3 = R_Ctrl3;
  }
 
  switch (ucMode)
  {
    case FORWARD:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,HIGH);
      digitalWrite(Ctrl3,LOW);
      analogWrite(Ctrl1,ucPwmVal); // Start Motor
      break;
    }

    case BACKWARD:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,LOW);
      digitalWrite(Ctrl3,HIGH);
      analogWrite(Ctrl1,ucPwmVal); // Start Motor
      break;
    }
   
    case BRAKE:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,LOW);
      digitalWrite(Ctrl3,LOW);
      break;
    }

    case FLOAT:
    {
      digitalWrite(Ctrl1,LOW);     // OFF H Bridge
      digitalWrite(Ctrl2,HIGH);
      digitalWrite(Ctrl3,HIGH);
      break;
    }
   
    default: break;
  }
}

从程序中可以看出,基本是基于轮式驱动单元的控制程序修改的,只是增加了一路电机驱动,取消了舵机。实现的动作是左、右转360 度。

同样是顺利实现预期的目标,视频如下:

顺利实现上述控制后,我对 Arduino 已十分有好感了,数字输入、输出,PWM 输出、舵机控制都已验证,就差模拟输入了,于是在 FIRA 小车的基础上增加了 2 个光敏电阻,想通过光强度控制小车实现灯光导航。

增加的光敏电阻如下:


最简单的分压方式采集,串联一个50K的可变电阻,以便调整分压比适应光线的强弱。光敏电阻接正电源,即光线越强,分压输出越高。

程序如下:

/*
  This is the first program for YM4 smart Car.
  using Nano.
 
  resource:
  (略,同上) 
  AnalogIn 1  -- Left Light in
  AnalogIn 2  -- Right light in
 
*/

(略,同上)

int LeftLight = 1;      // Left light Analog in
int RightLight = 2;     // Right light Analog in

void setup()
{
  // init pulse Sample
  attachInterrupt(L_pulse, LpulseSample,FALLING);
  attachInterrupt(R_pulse, RpulseSample,FALLING);
 
  // init Motor Control pin
  pinMode(L_Ctrl1, OUTPUT);
  pinMode(L_Ctrl2, OUTPUT);
  pinMode(L_Ctrl3, OUTPUT);

  pinMode(R_Ctrl1, OUTPUT);
  pinMode(R_Ctrl2, OUTPUT);
  pinMode(R_Ctrl3, OUTPUT);
 
  pinMode(LED,OUTPUT);
 
  Serial.begin(19200);
}

void loop()
{
  int iLeftLightVal0,iRightLightVal0;
  int iLeftLightVal,iRightLightVal;
  int a,b;
  int iL_Pwm,iR_Pwm;
 
  digitalWrite(LED,LOW);
  delay(3000);
  digitalWrite(LED,HIGH);
  iLeftLightVal0 = analogRead(LeftLight);
  iRightLightVal0 = analogRead(RightLight);
  a = iLeftLightVal0 + iRightLightVal0;
  Serial.println(a,DEC);
  delay(2000);
  digitalWrite(LED,LOW);
  delay(3000);
  digitalWrite(LED,HIGH);
 
  while(1)
  {
    iLeftLightVal = analogRead(LeftLight);
    iRightLightVal = analogRead(RightLight);

    b = iLeftLightVal + iRightLightVal;
    Serial.println(b,DEC);
   
    if(b < a)
    {
      iL_Pwm = 120;
      iR_Pwm = 100;
      Motor(LEFT,FORWARD,iL_Pwm);
      Motor(RIGHT,FORWARD,iR_Pwm);
      digitalWrite(LED,HIGH);
      
      if(iLeftLightVal<iRightLightVal)
      {
        Motor(RIGHT,FORWARD,60);
        while(iLeftLightVal<iRightLightVal)
        {
          iLeftLightVal = analogRead(LeftLight)+5;
          iRightLightVal = analogRead(RightLight);
        }
        Motor(RIGHT,FORWARD,iR_Pwm);
      }
     
      if(iRightLightVal<iLeftLightVal)
      {
        Motor(LEFT,FORWARD,70);
        while(iRightLightVal<iLeftLightVal)
        {
          iLeftLightVal = analogRead(LeftLight);
          iRightLightVal = analogRead(RightLight)+5;
        }
        Motor(LEFT,FORWARD,iL_Pwm);
      }
    }
    else
    {
      iL_Pwm = 0;
      iR_Pwm = 0;
      Motor(LEFT,BRAKE,0);
      Motor(RIGHT,BRAKE,0);
      digitalWrite(LED,LOW);
    }
  }
}

void LpulseSample()
{
(略,同上)
}

void RpulseSample()
{
(略,同上)
}

void Motor(unsigned char ucSide,unsigned char ucMode,unsigned char ucPwmVal)
{
(略,同上)
}

因为没有设计控制按钮以及通讯控制命令,只好将就用定时的方式实现“记忆”、“动作”的过程,用 LED 作为操作提示。

由于控制逻辑略为“复杂”,主要是追光操作和到预先记忆的位置停止两个控制的混叠,所以程序调试略费周折,但 Arduino 所提供的模拟输入功能很完美,如果不借助 Arduino 的函数,没有单片机基础的人很难搞定内置 AD ,它所提供的函数确实方便了使用,基本达到了“应用程序和硬件无关”的境界。

做好的小车追光视频如下:


通过上述三个尝试,我基本确定将 Arduino 作为第四代小车初级控制器,只有一点想进一步落实,因为小车的移动性,如果能通过无线下载程序该多好!

为此,我对 Nano 的程序下载电路和控制逻辑进行了研究,首先尝试了用我的“USB 转 UART接口”(CH341A)是否可以实现程序下载,试验很满意,顺利完成,在这个基础上,加入我的无线模块,将程序略作修改,大功告成,无线程序下载实现了!


我所想做的“娱乐性机器人足球平台”又落实了一个环节,将立即着手整合无线模块和 Nano 控制器,再设计一个初级控制板,使没有单片机基础的人也能让 FIRA 小车跑起来!