概述
如果有一只“虫”会让你爱不释手,那就是这款虫虫机器人六足版了!
新版虫虫用3个微型舵机的协调摆动来行走,通过红外测距来感知环境,还能够感知周围的光线亮度。另外,新版虫虫的中枢依旧是一颗强大的Beetle控制器和扩展板,而且是利用简单易用的Arduino IDE来编程。所有这些功能让虫虫的行走能够更加复杂,互动更加丰富。可以实现前进、后退、避障拐弯、巡光等功能。
简单的组装让你了解最基本的机器人原理和智能控制。喜欢DIY的用户还可以在原有代码的基础上改造出自己的互动模式。
产品规格
- 工作电压:5-6.4V(4节7号干电池)
- 控制器:Bluno Beetle (Bootloader: Arduino UNO)
- 组装后尺寸:180x153X120mm
- 重量:240g
配送清单
- 主控模组 (Bluno Beetle+Bluno Beetle shield) X1
- 9g Micro Servo X3
- GP2Y0A21 距离传感器 X1
- 传感器支架 X1
- 2节7号电池盒 X2
- 热缩管-黑色-直径6mm-100mm长 X1
- T型胶塞-规格M4 X8
- 钢丝200mm x 1mm X3
- 舵机连接器 X1
- 白色泡沫双面胶(L/W/H:78x61x3mm) X1
- 数字传感器线 X2
- 扎线带1.8x100mm X3
- 高品质 micro USB数据线 X1
- 安装说明书 x1
教程
按照我们的步骤,您将会制作出属于您自己的虫虫机器人。
必备工具
- 螺丝刀
- 螺丝刀,是机器人制作非常必要的工具。不用多说,用来拧螺丝的。建议你可以买一套完整的螺丝刀套件。面对不同型号的螺丝,使用起来就得心应手了。
- 尖嘴钳
- 这里用于弯曲钢丝用的,尽量少用手去折铁丝。尖嘴钳还常用于剪掉一些多余的线。
- 剪刀
- 剪刀用来剪贴纸和材料的。
组装步骤
- 详细组装步骤
虫虫代码
自动模式
自动模式代码
#include <Servo.h>
// creating the servo objects for front, rear and mid servo
Servo frontLeg;
Servo rearLeg;
Servo midLeg;
// setting the servo angle to 90° for startup
byte frontAngle = 90;
byte rearAngle = 90;
byte midAngle = 90;
// setting the delay value
byte delayWalk = 2;
byte delayTurn = 3;
// Analog sensor pins
int distanceSensor = A1;
int lightSensorLeft = A2;
int lightSensorRight = A0;
// Analog sensor reading
int sensorValue = 0;
// Values
int sensorValueLeft = 0;
int sensorValueRight = 0;
int left = 0;
int right = 0;
// Change the following value to decrease or increase the sensitivity.
// Bigger value for lower sensitivity and smaller value for highter sensitivity
int lightDifference = 60;
// Decrease the danger value when you want to INCREASE the collision trigger distance.
// Increase the danger value when you want to DECREASE the collision trigger distance
int danger = 450; // Increase this value when you want to DECREASE the collision trigger distance
// Arrays for sensor reading average calculation
int leftReadings[11];
int rightReadings[11];
// Booleans for decision
boolean lightLeft = false;
boolean lightRight = false;
// Setup function
void setup(){
// serial connection for debugging
Serial.begin(9600);
// attaching the servos to their pins
frontLeg.attach(9);
rearLeg.attach(10);
midLeg.attach(11);
// move servos to center position -> 90°
frontLeg.write(frontAngle);
rearLeg.write(rearAngle);
midLeg.write(midAngle);
delay(2000);
}
// Light detection and filtering
void scan()
{
int i;
// Take 5 readings on each sensor
for (i = 0; i < 11; i = i + 1) {
// read the value from the left and right sensor:
sensorValueLeft = analogRead(lightSensorLeft);
sensorValueRight = analogRead(lightSensorRight);
// add sensor readings of both sides to their respective array
leftReadings[i] = sensorValueLeft;
rightReadings[i] = sensorValueRight;
}
// calculate an average value for left and right sensor readings
// Sorting the left light sensor values
sort(leftReadings,11);
left = leftReadings[5];
sort(rightReadings,11);
right = rightReadings[5];
}
// Sort the array ascending
void sort(int a[], int size) {
for(int i=0; i<(size-1); i++) {
for(int o=0; o<(size-(i+1)); o++) {
if(a[o] > a[o+1]) {
int t = a[o];
a[o] = a[o+1];
a[o+1] = t;
}
}
}
}
// Desision depending on the light values
void decision(){
// Call the scan function to provide the sensor values of the left and right light sensor
scan();
// Compare the values and make a decision according the difference value
if (left > right){
// subtract right from left value to get a value to work with
left = left-right;
// check if that previous calculated value is greater than 50
if (left > lightDifference) // I will call this "Inside IF 1" in further comments.
{
lightLeft = true;
lightRight = false;
}
// That happens when the left value in "Inside IF 1" is lower than 50
else{
/* Is the calculated value for left lower than 50 then go forward. Why? Is that value lower than 50 then the difference
between the light on the left and right sensor are not that great. In this case it should be save to go forward.
*/
lightLeft = true;
lightRight = true;
}
}
// That happens when left is lower than right
else if (left < right){
// subtract left from right value to get a value to work with
right = right-left;
// check if that previous calculated value is greater than 50
if (right > lightDifference){ // I will call this "Inside IF 2" in further comments.
lightLeft = false;
lightRight = true;
}
else{
lightLeft = true;
lightRight = true;
}
}
/* That "else" happens when none of the above conditions occur and the left and right sensor shows the same readings.
This condition will probably never occur but we need it anyway.
*/
else{
// Go forward without questions
lightLeft = true;
lightRight = true;
}
}
// Walk forward //////////////////////////////////////////////////////////
void forward(){
for (midAngle = 70; midAngle < 100; midAngle +=1){
midLeg.write(midAngle);
delay(delayWalk);
}
for (frontAngle = 120; frontAngle > 50; frontAngle -= 1){
frontLeg.write(frontAngle);
rearLeg.write(frontAngle);
delay(delayWalk);
}
for (midAngle = 100; midAngle > 70; midAngle -=1){
midLeg.write(midAngle);
delay(delayWalk);
}
for (frontAngle = 50; frontAngle < 120; frontAngle += 1){
frontLeg.write(frontAngle);
rearLeg.write(frontAngle);
delay(delayWalk);
}
}
// Walk reverse //////////////////////////////////////////////////////////
void reverse(){
for (midAngle = 70; midAngle < 100; midAngle +=1){
midLeg.write(midAngle);
delay(delayWalk);
}
for (frontAngle = 50; frontAngle < 120; frontAngle += 1){
frontLeg.write(frontAngle);
rearLeg.write(frontAngle);
delay(delayWalk);
}
for (midAngle = 100; midAngle > 70; midAngle -=1){
midLeg.write(midAngle);
delay(delayWalk);
}
for (frontAngle = 120; frontAngle > 50; frontAngle -= 1){
frontLeg.write(frontAngle);
rearLeg.write(frontAngle);
delay(delayWalk);
}
}
// Left Turn //////////////////////////////////////////////////////////
void leftTurn(){
rearLeg.write(90);
for (midAngle = 70; midAngle < 110; midAngle += 1){
midLeg.write(midAngle);
delay(delayTurn);
}
for (frontAngle = 70; frontAngle < 110; frontAngle +=1){
frontLeg.write(frontAngle);
delay(delayTurn);
}
for (rearAngle = 110; rearAngle > 70; rearAngle -=1){
rearLeg.write(rearAngle);
delay(delayTurn);
}
for (midAngle = 110; midAngle > 70; midAngle -= 1){
midLeg.write(midAngle);
delay(delayTurn);
}
for (frontAngle = 110; frontAngle > 70; frontAngle -=1){
frontLeg.write(frontAngle);
delay(delayTurn);
}
for (rearAngle = 70; rearAngle < 110; rearAngle +=1){
rearLeg.write(rearAngle);
delay(delayTurn);
}
}
// Right Turn //////////////////////////////////////////////////////////
void rightTurn(){
frontLeg.write(90);
for (midAngle = 70; midAngle < 110; midAngle += 1){
midLeg.write(midAngle);
delay(delayTurn);
}
for (rearAngle = 70; rearAngle < 110; rearAngle +=1){
rearLeg.write(rearAngle);
delay(delayTurn);
}
for (frontAngle = 110; frontAngle > 70; frontAngle -=1){
frontLeg.write(frontAngle);
delay(delayTurn);
}
for (midAngle = 110; midAngle > 70; midAngle -= 1){
midLeg.write(midAngle);
delay(delayTurn);
}
for (rearAngle = 110; rearAngle > 70; rearAngle -=1){
rearLeg.write(rearAngle);
delay(delayTurn);
}
for (frontAngle = 70; frontAngle < 110; frontAngle +=1){
frontLeg.write(frontAngle);
delay(delayTurn);
}
}
// Stop walking
void stay(){
frontLeg.write(90);
midLeg.write(90);
rearLeg.write(90);
}
//////////////////////////////////////////////////////////////
void loop(){
// First scan for the light
scan();
// read the IR distance sensor value
sensorValue = analogRead(distanceSensor);
if (sensorValue < danger){
decision();
}
else {
frontLeg.write(90);
rearLeg.write(90);
midLeg.write(90);
delay(1000);
for (byte x=0; x<5; x+=1){
reverse();
}
delay(300);
for (byte x=1; x<5; x+=1){
leftTurn();
}
delay(300);
}
decision();
if (lightLeft == true && lightRight == true){
forward();
// Serial.println("left = right");
delay(5);
}
else if (lightLeft == false && lightRight == true){
rightTurn();
// Serial.println("left < right");
delay(5);
}
else if (lightLeft == true && lightRight == false){
leftTurn();
// Serial.println("left > right");
delay(5);
}
else {
stay();
// Serial.println ("Nothing to complain");
}
}
函数说明
- Servo.h库:
#include<Servo.h>,就是调用舵机的使用库,这个库已经在Arduino IDE 中了,可以打开Arduino-1.0.5/libraries/ Servo/Servo.h进行查看。
- 创建舵机对象:
frontLeg;Servo rearLeg;Servo midLeg;这三句创建三个舵机对象,分别对应前腿后退和中腿。frontAngle rearAngle midAngle 这三个值作为之后舵机的角度值,在这里呢我们先给他赋90°让舵机们一开始在中间的位置停着,不偏不倚。
- 各个定义:
int distanceSensor = A1;int lightSensorLeft = A2;int lightSensorRight = A0;这三句定义了我们虫虫机器人身上的距离传感器、左右光线传感器(光敏电阻),他们分别于模拟口的A1,A2,A0相连接。而int sensorValue = 0;int sensorValueLeft = 0;int sensorValueRight = 0;分别是红外距离传感器接收的模拟值、左右光敏电阻接收的模拟值存放数据的变量。
可是既然已经有了左传感器接受到的值和右眼传感器接收到的值的存放变量,为何还要有int left = 0;int right = 0;这两个left和right的定义呢?我们往下看。
- 左右传感器取值问题:
harp GP2Y0A21 传感器的输出很不稳定,在相同情况下读取传感器,我们不可能取得全部相同的值,因此必须多次读取传感器,然后选择使用它们的中间值,因为这样就不会过或者不及的情况 。所以,这里定义的left和right是用来放置左右光敏电阻所传递回来模拟值的中间值的。int leftReadings[11];int rightReadings[11];我们定义了这两个数组来存放接收到的传感器模拟值,而void scan()所定义的scan函数就是用来存放数组的。
接着的两句函数sort(leftReadings,11);left = leftReadings[5];和sort(rightReadings,11);right = rightReadings[5];先用了两个分类函数,然后取数组中第6个值作为真正的left或者right的值。这两个值就是我们想要的居于中间的值。可是,这个sort函数又是怎么工作的呢?怎么会一使用这个函数之后取数组中的第六个也就是数组排序中位置居于中间的值就能够使得取值为所有12个数据中的中间数呢?那么下面,我们就再仔细观察一下sort函数。
**传感器数据排序—冒泡法**:
void sort(int a[], int size) {
for(int i=0; i<(size-1); i++) {
for(int o=0; o<(size-(i+1)); o++) {
if(a[o] > a[o+1]) {
int t = a[o];
a[o] = a[o+1];
a[o+1] = t;
}
}
}
}
相信有过c语言基础的童鞋能够很容易看出这是一个“冒泡排序”的程序,当然,看不出来也没有关系,我们来一句一句分析一下。
第一句定义数组和数组的大小,也就是调用这个函数的时候要放入数组跟我们设定的大小,接下来是标志位I 从0增加到比size小一位,标志位o从0增加到size-(i+1),也就是说如果此时i=0,size=8,那么o就会从0增加到7而o[7]刚好是最后一个元素。接下来就是阐述冒泡法真谛的句子了,如果a[o]比a[o]的下一位要大,那么我们就把的位置互换。
我们用size=4,数组为a[]={7,5,1,2}来做个举例,这样会更容易弄明白,直接进入for函数,最开始i=0,接着是o=0,此时a[0]=7,而a[1]=5下一句比较我们发现此时a[o]是大于a[o+1]的,所以我们进入if函数,将两者调换,随后a[0]5,a[1]=7。接着是o自加1从0变成了1,而此时a[1]已经变成了7,所以此时的a[o]=7,而a[o+1]=a[2]没有变化,依然是1,然后又一比较明显7>1,所以a[2]=7,a[1]=1,。接下来是a[2]和a[3],当i=0时第一次排序的结果是{5,1,2,7}。
接下来是i=1的时候,与i=0时相同,唯一不同的地方在于这一次o最大是2也就是说最后一位不会参与比较,这一次的排序结果是{1,2,5,7},接下来是i=2和i=3是,流程都跟i=0一致,就不一一详述,并且这里因为数据有一点巧合,所以最终的结果就是{1,2,5,7}。
看完这个例子程序相信大家都已经有了一些了解了,其实冒泡排序就是一次一次排序,第一次排序将最大的一个数拍到最后,接着是第二大的一个数放在倒数第二位,就这样一步一步排序下去最后就形成了从小到大顺序。
这是冒泡排序的一个动态图,参看之下可能更容易理解。冒泡排序动态图片
- 虫虫行动判断:
虫虫行动的判断标志主要来自两个布尔型参数lightLeft和lightRight,布尔数据类型又称为逻辑数据类型,是一种只有两种取值的原始类型:真和假。如果lightLeft=true, lightRight=false,虫虫就启动左转程序,如果lightLeft= false, lightRight=true;虫虫就启动右转程序;如果lightLeft=true, lightRight= true,虫虫就直行。int lightDifference = 60;这个60就是我们所定义的灵敏度,int danger = 450;危险值为450指的的当传感器返回的值小于等于这个值得时候,我们的虫虫机器人就要转弯啦。当然如果想要增加碰撞的触发距离我们可以增加这个参数。
而对于左右的判断就要使用decision函数,来判断虫虫机器人左右光线传感器传回来的值。如果左边的光强度大于右边的光强度,就把两者的差量赋给左边;如果差量大于我们之前设置的灵敏度lightdifference时就给lightLeft赋“真”,lightRight赋“假”。如果差量不大于我们设置的灵敏度lightdifference就给lightLeft和lightRight都赋真。如果右边的值大于左边则将差值赋给right,如果此时right的值大于lightdifference则lightLeft赋“假”,lightRight赋“真”,如果right的值不大于lightdifference,则lightLeft和lightRight都赋真也就是要让虫虫保持直行。
- 虫虫如何行动:
这取决于定义的四个函数:forward()、reverse()、rightTurn()、leftTurn(),当然还有一个stay()停留函数起到让虫虫停留不动的作用。前进跟后退的代码参数相反,左转和右转的代码参数完全想法,下面我们分别看一下前行和左转函数。
void forward(){
for (midAngle = 70; midAngle < 100; midAngle +=1){
midLeg.write(midAngle);
delay(delayWalk);
}
for (frontAngle = 120; frontAngle > 50; frontAngle -= 1){
frontLeg.write(frontAngle);
rearLeg.write(frontAngle);
delay(delayWalk);
}
for (midAngle = 100; midAngle > 70; midAngle -=1){
midLeg.write(midAngle);
delay(delayWalk);
}
for (frontAngle = 50; frontAngle < 120; frontAngle += 1){
frontLeg.write(frontAngle);
rearLeg.write(frontAngle);
delay(delayWalk);
}
}
在这个函数中,我们先让虫虫的中腿翘起来,把重心便宜到一侧,然后这前腿的这一侧就会翘起来,而另一侧还是支撑在地上,而此时前腿的角度从120下降到50,也就是前腿将会从靠近重心的一端转动到翘起的一边,这样前腿就会带动虫虫的身体往前滑动了。而后腿也需要向前腿那样从靠近重心的一端转动到翘起的一边才能够推动虫虫的身体往前行动,虽然前后腿朝向不一样,但是舵机的朝向也是不一样的,所以后退的参数设置也跟前腿参数设置一样即可。
接着虫虫的中腿会向另一个方向翘起,要让虫虫依旧前行的话只需要设置跟之前相反的参数就行了。到这里或许有人会疑问,既然前半段函数已经能实现虫虫前行了,那为什么还要有后半段函数即让虫虫另一端翘起来前行呢?这是因为,如果只设置重心往一边偏移,那么虫虫看起来就会像是残疾虫一样,上半身只会往一边摆动(可以想象一下人一只脚受伤时走路的样子)有兴趣的朋友可以试一试只要前半段不要后半段代码,看会不会有上述情况~
在左转函数里我们先保证后后腿为90°的初始状态这样可以不影响前腿的行动,虫虫机器人会左转:rearLeg.write(90);接着跟前进后退一样,我们先让中腿翘起一端:
for (midAngle = 70; midAngle < 110; midAngle += 1){
midLeg.write(midAngle);
delay(delayTurn);
}
然后前腿从70增加到100,后退从100减少到70,这样可以让前后两条腿的旋转方向恰好相反,但是目的都是一样的,即让虫虫的身体往左转。
rearLeg.write(90);
for (midAngle = 70; midAngle < 110; midAngle += 1){
midLeg.write(midAngle);
delay(delayTurn);
}
for (frontAngle = 70; frontAngle < 110; frontAngle +=1){
frontLeg.write(frontAngle);
delay(delayTurn);
}
for (rearAngle = 110; rearAngle > 70; rearAngle -=1){
rearLeg.write(rearAngle);
delay(delayTurn);
}
接着中腿的另一端翘起来,然后前腿和后退的参数按照之前相反的参数进行转动,依然是让虫虫保持左转的趋势转动。
for (midAngle = 110; midAngle > 70; midAngle -= 1){
midLeg.write(midAngle);
delay(delayTurn);
}
for (frontAngle = 110; frontAngle > 70; frontAngle -=1){
frontLeg.write(frontAngle);
delay(delayTurn);
}
for (rearAngle = 70; rearAngle < 110; rearAngle +=1){
rearLeg.write(rearAngle);
delay(delayTurn);
}
**主循环函数**:
函数的一开始就调用scan函数,通过红外传感器得到距离模拟值。然后将此值与危险值作比较,如果是小于危险值(也就是说不用开始避障)则调用判断函数来判断虫虫应该直走还是左转。如果已经大于危险值了,则停留1秒接着调用后退函数和左转函数。这个if和else函数之后无论虫虫是否做出了行动都再调用一次判断函数,然后开始进行各种行为的判断。如果lightLeft=true, lightRight=false,虫虫就调用左转函数,如果lightLeft= false, lightRight=true;虫虫就调用右转函数;如果lightLeft=true, lightRight= true,虫虫就调用直行函数,如果都不满足,则虫虫调用停留函数。