当圆梦小车遇上 Arduino ……
0赞[提要] 本文介绍了用 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 小车跑起来!
