工控网首页
>

应用设计

>

机器人小强一号的制作过程

机器人小强一号的制作过程

2013/9/9 10:14:38

从对机器人感兴趣开始,买了一堆书,也看了很多资料,决定先从最简单的车轮机器人开始做。普通电脑虽然强大,但是不能直接采集外界环境的物理数据,也不能直接控制电机。查找了下,虽然可以直接在电脑上接USB接口的数据采集器用于采集传感器的数据,也可以使用PCI的电机控制卡来驱动各种电机,不过成本有些高,暂时不考虑了。找了一圈,发现还是单片机方案廉价,顺便可以学习下单片机控制和作一些电路实验。于是最终决定实验方案采用单片机来做了。

零件
下面是采购的零件:
8位单片机Atmega168开发板。
电路面包板和连线用于驱动车轮的两个减速电机。没有找到合适的齿轮,自己也没有加工设备,图简单,所以直接用集成减速齿轮的电机代替了。

距离传感器,作为机器人的主要传感器,用来探测障碍物的距离。使用的是常用的红外距离传感器作为机器人的眼睛。

舵机,用来控制机器人“眼睛”的方向。

下一步就可以验证简单的功能了。

 

实验

 接下来先搞清楚怎么用单片机。
Hello world
 先让单片机跑起来再说。习惯了操作系统下编程,第一次用单片机还真有些不习惯,担心遇到问题,不过比较顺利。这个程序用来点亮

单片机上的一个LED。

代码:
int ledPin = 13; // LED connected to digital pin 13
<!--[if !supportEmptyParas]--> <!--[endif]-->
void setup()
{
 pinMode(ledPin, OUTPUT); // sets the digital pin as output
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
void loop()
{
 digitalWrite(ledPin, HIGH); // sets the LED on
 delay(1000); // waits for a second
 digitalWrite(ledPin, LOW); // sets the LED off
 delay(1000); // waits for a second
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
以间隔1000ms的时间把单片机的第13 个Pin脚循环输出高低电平,从而达到点亮和熄灭LED功能。很简单,就是验证下单片机是否可用。
控制舵机
舵机就是遥控模型上用来控制的,其实就是一个带闭环控制的电机,能转动到指定的角度。


工作原理是:控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获 得的直流偏置电压与电位器的电压比较,获得电压差输出。


最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动 电位器旋转,使得电压差为0,电机停止转动。
 在程序中控制舵机的转动角度,就是生成不同脉宽的PWM就行了。

上面的代码简单的生成一个PWM 驱动舵机旋转,比较简单。 代码中用delay来实现延时,如果是同时控制多个舵机,用这样的代码是不行的,需要自己计算时间。因为用delay整个单片机都“休息”了,无法执行任何代码。在实际实现中,还需要不断的读取传感器输入和进行其他处理。

红外距离传感器
传感器介绍:
SHARP红外距离传感器,用于模型或机器人制作,可以用来测量距离。每个模块赠送一根15cm长PH2.0的单头排线.
<!--[if !supportEmptyParas]--> <!--[endif]-->
技术规格:
<!--[if !supportEmptyParas]--> <!--[endif]-->
探测距离:10-80cm
工作电压:4-5.5V
标准电流消耗:33-50 mA
输出量:模拟量输出,输出电压和探测距离成比例

接好电源,把模拟信号输出端接在单片机的模拟输入Pin就行了。
 测试代码:
int potPin = 2;
int ledPin = 13;
int val = 0;
<!--[if !supportEmptyParas]--> <!--[endif]-->
void setup() {
 pinMode(ledPin, OUTPUT);
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
void loop() {
 val = analogRead(potPin); 
 digitalWrite(ledPin, HIGH); 
 delay(val); 
 digitalWrite(ledPin, LOW);
delay(val);
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
运行结果,led随着传感器距离的变化闪烁间隔也随着变化。
控制电机
 常用的直流电机使用L293D来控制,使用H293的电路如下:

不过在验证是发现,对于小电机,直接用单片机的PWM端子来输出不同电压的来控制速度也是可行的,虽然不太精确,但是对于实现运动特征是足够了。单片机无法输出真正的线性模拟电压,PWM是通过调整空占比间隔来模拟不同电压值的。

测量了电机在空载和负载情况下电流都只有几十毫安,所以先采取这种简单的办法验证。

直接把电机接到单片机的Gnd和 PWM Pin 9接口,然后在程序中直接向 Pin 9输出就可以让电机运转了。
int value = 0;  
int ledpin = 9;
<!--[if !supportEmptyParas]--> <!--[endif]-->
void setup()

 // nothing for setup
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
void loop()

 for(value = 0 ; value <= 255; value+=5) 
 { 
 analogWrite(ledpin, value); 
 delay(30); 
 } 
 for(value = 255; value >=0; value-=5) 
 { 
 analogWrite(ledpin, value); 
 delay(30); 
 }
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
上面的程序用不同的输出值驱动电机,可以看到电机的旋转效果。
<!--[if !supportEmptyParas]--> <!--[endif]-->
单片机和主要部件的验证基本就完成了,下一步要开始程序设计了。

软件设计
思路    
在进行代码编写之前,想找到一种简介的实现低级智能机器人的方式。目前的工业机器人采用的是精确控制的方法,在程序中固化每种处理逻辑。用程序员的思维来完成对机器人的操作。
对于程序员来说,机器人无非就是外接了物理传感输入和运动控制输出的一台电脑,不管是64位多核CPU或者8位单片机,只是性能的不同而已。理论上,都能通过程序设计来达到预定的目标。如果按照计算机上的普通程序来设计,那么大量的功能代码会相互耦,每次为了增加一个新的运动功能或者任务,都需要编制新的代码,而新的代码给原来的环路反馈系统带来新的逻辑分支,所有的步骤都得经过程序员的验证和设计。这样的设计对于精确计算的PC程序或者擅长固定场景精确动作的工业机器人来说,都没有问题。但是对于这里要实现的“小强一号”机器人,是处在一个动态的变化环境当中,如果按照传统的软件设计逻辑,会存在大量耦合的逻辑分支,导致程序扩展性很差。举例来说,为了完成一个沿墙壁行走的功能,必须对各种传感器输入情况进行判断,然后做出预置的动作序列。

如果要执行的动作和需要判断的情况很多,那么程序逻辑分支就比较复杂,扩展性也比较差,有没有更好的适合机器人软件设计的方法呢?
换一种思维来思考的话,我们要做的不是电脑程序,只是一个实验机器人。这个实验机器人能做到自然界昆虫级的“智能”就很不错了。 试想,一只蚂蚁,一只蟑螂会去思考这么复杂的逻辑分支吗?去考虑各种情况下应该采取的动作? 不太可能。我们用了复杂的想法去解决简单的事情。
机器人和计算机的主要区别在于,计算机是按照预先规划的指令运行,而机器人需要根据变化的环境选择合适动作。我们首先可以简化动作模型,我把机器人的执行任务分成3个层次:任务-》行为-》动作任务——机器人完成的一个期望目标,比如“在一个范围内收集地面上的垃圾”可以界定为一个机器人的任务。
行为——行为是对任务的分解,行为是机器人根据环境变化选择要做什么。比如碰到墙壁需要进行躲避。电源不足需要充电,就可以划分为一个行为。
动作——机器人最终控制电机等输出设备做出的动作,比如前进,后退,转弯。转动摄像头都可以划分成一个动作单元。
说明:任务由一系列的行为构成;行为有一个或者多个动作组成,动作是立即执行的最小单元。在任意一个时刻,只有一个动作可以得到执行。
软件实现的对象就是分解出简单的行为,针对每个行为单独实现,互相之间无干涉、无耦合。当多个行为要输出互相矛盾的动作时,通过一个行为优先级表来决定优先级高的行为来做出动作。在这个思路中,参考了基于行为编程的理论,关于详细的基于行为编程的描述,可以在网上搜索“Behavior-based robotics”。
    通过这种设计思路,可以实现对动物反射特性的模拟,不需要把精力放在复杂的逻辑处理上,只需要根据分解出的基本行为实现一个一个小的行为单元。每个行为单元只关心自己的传感输入和需要输出的控制动作就行了。确定了设计思路,下一部分开始针对小强一号来设计软件了。

参考资料,有兴趣的可以阅读:
http://www-robotics.usc.edu/~maja/publications/mitcs.ps.gz   (这篇文章描述了如何分解行为,需要安装 GSView)
http://www.research.ibm.com/people/j/jhc/pubs/jhc-design.pdf
http://www-robotics.usc.edu/~maja/bbs.html
《基于行为的机器人实战指南》 中译版

强一号结构非常简单,传感器只采用了个红外距离传感器作为机器人的“眼睛”,使用两个电机进行运动,距离传感器连接在一个舵机上,作为“头部”可以左右范围的转动。机器人的行为最简单的就是巡航行为,巡航行为就是给电机电流,电机转动而已。那么发生碰撞怎么办?在巡航行为中,不用考虑碰撞,否则又会变成复杂的判断逻辑。为了避免碰撞,设计一个独立的避免碰撞的“逃离”行为。逃离行为从距离传感器获取障碍物距离信息,简单的判断前方是否有障碍,进行避让。由于多个行为都可能控制电机,所以需要一个仲裁器根据优先级来判断到底执行那个逻辑部分输出的控制指令。

下面用图来说明这种情况:

如果需要增加其他更加复杂的智能,那么只需要增加对应的行为单元就行了,不用修改已有的逻辑结构。每个行为单元只需要处理自己关心的传感器和需要控制的动作执行部分。这种方式的好处就是程序逻辑变得非常简单,功能单元之间不会存在互相影响的弊端。      

下面的代码是“巡航”行为:

class BhCruis:public Behavior

{

public:

      

publicSPAN style="mso-tab-count: 2">           

    BhCruis(const char* name):Behavior(name){

       };

       BhCruis();

       void Run();

       void Setup();

};

void BhCruis::Setup(){

}

void BhCruis::Run(){

    int bid = GetId();

    GO_speed_left[bid] = 255;

    GO_speed_right[bid] = 255;

    GT_beh_action[ACTION_TYPE_MOTOR][bid] = true;

}

上面的代码很简单,就是让两个电机以同样的速度晕装,其他都不用考虑。对于某些行为,需要引入稍微复杂一些的处理,比如发现障碍物,首先要停下来,然后判断是应该往左还是往右,做出决定之后,再进行具体的动作。针对这种不能在一个时刻完成,要经过几个不同的状态才能处理完成的行为,可以用状态机来解决。下图是“逃离”行为的状态机:


对于行为实现自己的状态机,在行为实现的代码内部实现,而不影响其他行为。这种结构可以让每个行为子关心自己的实现方式,从而最大的降低软件复杂度,方便功能添加和修改。逃离行为状态机实现原型代码:  void 

void BhEscape::Run(){

    int bid = GetId();

    // 状态机实现

    switch(state){

        case 0: // 前方距离过小

            if(GI_distance[DISTANCE_FORWORD] < 30){

                GO_speed_left[bid] = 0;

                GO_speed_right[bid] = 0;

                GT_beh_action[ACTION_TYPE_MOTOR][bid] = true;

                timestart = millis();

                state++;   // 状态变化

            }else{

                GT_beh_action[ACTION_TYPE_MOTOR][bid] = false;

            }

          

            break;

        case 1SPAN style="mso-spacerun: yes">  // 观察左右距离

            if(millis() - timestart > 1000){

                GO_eyeAngle[bid] = 0;

                GT_beh_action[ACTION_TYPE_HEAD][bid] = true;

                timestart = millis();

                state++;

            }

        case 2SPAN style="mso-spacerun: yes">  // 观察左右距离

            if(millis() - timestart > 1000){

                GO_eyeAngle[bid] = 180;

                GT_beh_action[ACTION_TYPE_HEAD][bid] = true;

                timestart = millis();

                state++;

            }

        case 3SPAN style="mso-spacerun: yes">  // 决定往哪边转弯

            if(millis() - timestart > 1000){            

                GO_eyeAngle[bid] = Angle_Center_90;

                GT_beh_action[ACTION_TYPE_HEAD][bid] = true;

               

                if(GI_distance[DISTANCE_LEFT_1] > GI_distance[DISTANCE_RIGHT_1]){

                    // 左转

                    GO_speed_left[bid] = 0;

                    GO_speed_right[bid] = 255;

                }else{

                    //右转

                    GO_speed_left[bid] = 255;

                    GO_speed_right[bid] = 0;

                }

                GT_beh_action[ACTION_TYPE_MOTOR][bid] = true;

                state = 0;

                Delay(2000);

            }

          

        default:

            break;

    }

}

    这种基于行为单元的实现方法比较类似生物的反射行为,完整的代码在下载专区进行下载。完成了基本的代码,下一部分需要构建机器人的机构和测试了。

结构和测试 实验机器人的结构就地取材,用了一个拼装玩具小车的零件,如下图:
为了安装电机和传感器,重新拼装了下,变成这个样子:
有了基本的结构,可以测试功能了,验证功能是否正常。
功能测试完成,放到地上实际跑一下:
有点好玩,基本达到可以巡航和壁障的功能,不过功能太过于简单,只能相当于一个玩具。

投诉建议

提交

查看更多评论
其他资讯

查看更多

自动化机床的故障排除技术浅析

安川焊接机器人编程

ABB机器人控制软件RobotWare应用手册SafeMove(英文)

ABB IRB7600 机器人维护信息

ABB IRC5P机器人培训教材