嵌入式开发--智能机械臂

        在教学的过程中,经常有同学会问:“嵌入式的这些课程,我们学习了以后能用来做些什么呢?”。于是便想着做个小项目,能让同学们能用学到的嵌入式知识来完成它,加深对嵌入式学习的理解。

我以前搞过一阵子工业自动化生产的设计和制造,于是便想到了做一个能完成大多数基本功能的机械臂。机械手臂目前在机械人技术领域中得到广泛的应用,在工业制造、医学治疗、娱乐服务、教学应用、军事以及太空探索等领域都能见到它的身影。很多工业机器人,实际上也只是比较复杂的机械臂而已。

通过本项目能帮助学生了解嵌入式课程在工程技术方面的实际运用。巩固和加深C语言,Python语言和Linux系统的操作和编程能力。了解arduino和树莓派GPIO接口和通讯方式。

    

一、效果展示

1:图片展示:

2,视频展示

http://v.youku.com/v_show/id_XMzUxMDYxMjQzMg==.html

二、开发周期

         从这个机械臂的整个项目构思,到最后项目的基本完成,前后大约用了45个小时。这里面没有包括原材料的采购的时间。由于项目过程中碰到过一些小问题,解决和优化花了一些额外的时间。实际学生完成这个项目的时间估计在40小时左右。应该比较适合嵌入式刚入门的初学者。

三、开发环境

         该机械臂的控制系统分为2部分:arduino板和树莓派。

         arduino:

         arduinoIDE 1.8.5 (windows 7及以上)

        

树莓派:

Linux系统

Python2.7及相应GPIO库

        

四、需要具备的知识

1)  windows和linux系统的操作

2)  C语言基础

3)  Python语言基础

4)  了解一点单片机知识

5)  了解一点树莓派及GPIO接口知识

五、开发过程

整个机械臂的系统由机械臂本身,aduino及其扩展板,以及一个树莓派组成。

这个机械臂完全是模仿人的手臂来设计的,一共有6个自由度,机械臂上一共安装有6个舵机。最下面的base和shoulder是模仿人肩部关节动作的;elbow是模仿肘部关节动作的;wristflex和wristrot是模仿人腕部关节动作的;最后的gripper是模仿手的夹持动作的。

aduino的板子和aduino的扩展板是整套机械臂的控制核心。它的作用就是接受输入4路控制信号,经过aduino内部的程序运算,输出6路数字信号来控制6个舵机动作。4路模拟信号分别是:从上到下选择舵机,从下到上选择舵机,以及左旋转和右旋转。通过这4路信号我们可以对每个舵机做出操作。程序在电脑上编好后可以通过arduino板上的USB接口烧进去。

此外,我们还连接了一个lcd显示屏,用来显示当前机械臂的运行状态。

树莓派,它就相当于一个linux系统的小电脑。它有一个40针的GPIO端口,这里为了方便操作,我把这个GPIO端口引出来了。我们通过在树莓派上编程-这里我们用的是python语言编的。我们通过编程来让GPIO向aduino发出信号,来控制机械臂的动作。

这里有个小问题,树莓派GPIO给出的信号是3.3v的,而aduino能接受的信号是5v的,两者不兼容。在这里我们用了一个8路电平转换模块,来让树莓派出来的3.3v信号变成5v的,再传给aduino,这样就解决了问题。

这样一来,我们用树莓派给出控制信号给aduino,aduino经过程序计算,给出数字信号控制舵机,机械臂就运行起来了。

六、代码展示

1)arduino控制代码:

#include <Servo.h>      //调用一些库文件

#include <TimedAction.h>

#include <Wire.h>

#include <LiquidCrystal_I2C.h>

//定义舵机位置名称,并编号。

const int base =      0;    //底座舵机   

const int shoulder =   1;         

const int elbow =     2;       

const int wristflex =   3;       

const int wristrot =    4;         

const int gripper =    5;        //夹持器舵机

 

const int stdDelay =   20;        //舵机运动延时(单位ms)

const int maxServos =  6;        //舵机的数量

const int centerPos =  90;       //舵机中位位置

 

int keyDelay = 100;              //每个按键之间的最大延时时间ms

int buttonDelay = 50;           //每个按键之间的最小延时时间ms

int thisServo = base;           //定义起始电机

unsigned long key_millis = 0;     

unsigned long button_millis = 0;

 

typedef struct{              //数组框架结构

 byte neutral;             //中位角度,起始位置

 byte minPos;             //最小角度

 byte maxPos;            //最大角度

 byte delaySpeed;         //延时时间

 byte curPos;            //舵机当前角度

} ServoPos;              //结构体名称

 

ServoPos servosPos[] = {    //对舵机限位

  {90, 180, 10, stdDelay, 0 }, //中位90,最小角度180,最大角度10,范围0~180度。

  {90, 180, 10, stdDelay, 0 },

  {90, 180, 60, stdDelay, 0 },

  {90, 170, 50, stdDelay, 0 },

  {90, 180, 10, 10, 0 },

  {90, 125, 55, 5, 0 }

};

 

byte serv = 90;

int counter = 0;

int curServo = 0;

int sMove[] = {0, 90, 0};

int sAttach[] = {0, 0};

 

LiquidCrystal_I2C lcd(0x27,20,4);     //0x27 D7~D0端口开关设置0x表示十六进制27转换成16进制数是00100111  1代表开,0代表关, 20列,4行,行号从零算起,第一行行号0,第二行行号1.

Servo servos[maxServos];

int destServoPos[maxServos];

int currentServoPos[maxServos];

 

void doServoFunc() {

  intx = curServo;

 TimedAction servoMove[maxServos] = TimedAction(100, doServoFunc);

 if(destServoPos[x] == currentServoPos[x])

   servoMove[x].disable();      

 if(destServoPos[x] > currentServoPos[x])

   currentServoPos[x]++;

 else

   currentServoPos[x]--;

 

 servosPos[x].curPos = constrain(currentServoPos[x], servosPos[x].maxPos,servosPos[x].minPos);

 currentServoPos[x] = servosPos[x].curPos;

 servos[x].write(currentServoPos[x]);

 jointPos(x, currentServoPos[x]);

}

 

TimedAction servoMove[maxServos] =TimedAction(100, doServoFunc);   // 延时,延时时间为声明时间。

TimedAction keys = TimedAction(10,keypadFunc);

 

void setup() {                            //设置

 delay(200);

 Wire.begin();

 lcd.init();                            //LCD初始化            

 lcd.backlight();                //LCD背光灯打开

 delay(500);

 lcd.on();                     //LCD开机

 setupDisplay();               //调用子程序,设置显示内容门,后面有定义。

 

 for(int i=0; i<maxServos; i++) {         

   servos[i].write(servosPos[i].neutral);//舵机归中位

   servosPos[i].curPos = servosPos[i].neutral;//设置舵机当前角度为初始角度

   servos[i].attach(i+4);//初始化舵机控制引脚,即第一个舵机为D4引脚控制

   destServoPos[i] = centerPos;//指定的舵机为90度

   currentServoPos[i] = centerPos;//当前的舵机为90度

   servoMove[i].disable();//舵机停止移动

 }     

}

 

void loop() {

 for(int x=0; x<maxServos; x++) {

   curServo = x;

   servoMove[x].check();

  }

 //timer.run();           

 keys.check();

 navSwitchFunc();

}

 

void servoTestFunc() {

 if(counter % 2) {

   Move(thisServo, servosPos[thisServo].minPos,servosPos[thisServo].delaySpeed);

  }

 else

   Move(thisServo, servosPos[thisServo].maxPos,servosPos[thisServo].delaySpeed);

 counter++;

}

 

void writeServo() {

  intservoNum = sMove[0];

 if(servoNum >=0 && servoNum <= maxServos) {

   destServoPos[servoNum] = sMove[1];

   servoMove[servoNum].enable();

   servoMove[servoNum].setInterval(sMove[2]);

  }

}

 

void setServoAttach() {

  intservo = 1;    // sAttach[0]

  intmode = 2;     // sAttach[1]

 if(servo >= 0 && servo <= maxServos) {

   if (mode == 1)

     servos[servo].attach(servo+4);

   else

     servos[servo].detach();

  }

}

 

void Move(int servoNum, int servoPosition,int delayTime) {  //舵机驱动指令

 sMove[0] = servoNum;            //所驱动舵机号

 sMove[1] = servoPosition;         //舵机的目标位置

 sMove[2] = delayTime;           //每个舵机运动的延迟时长

 writeServo();

}

 

void Attach(int servoNum, int servoMode) {

 sAttach[0] = servoNum;

 sAttach[1] = servoMode;

}

 

void navSwitchFunc() {                //键盘检测子程序

   if (millis() > button_millis + buttonDelay) {

   button_millis = millis();

   if(digitalRead(A0) == LOW) {     // 当A0引脚低电平

     thisServo--;                   //电机号自加1

     thisServo = constrain(thisServo, 0, 5); //电机控制范围

     jointPos(thisServo, servosPos[thisServo].curPos);

     delay(200);                 //延时200毫秒

    }

   if(digitalRead(A1)== LOW) {     //当A1引脚低电平

     thisServo++;                 //电机号自加1

      thisServo = constrain(thisServo, 0, 5); //电机控制范围

     jointPos(thisServo, servosPos[thisServo].curPos);

     delay(200);                 //延时200毫秒

    }

   if(digitalRead(A2) == LOW) {     //当A2引脚低电平

     byte t = thisServo;

     servosPos[t].curPos--;       //电机角度自减1

     servosPos[t].curPos = constrain(servosPos[t].curPos,servosPos[t].maxPos, servosPos[t].minPos);

     jointPos(t, servosPos[t].curPos);

    }

   if(digitalRead(A3) == LOW) {    //当A3引脚低电平

     byte t = thisServo;

     servosPos[t].curPos++;         //电机角度自减1

     servosPos[t].curPos = constrain(servosPos[t].curPos,servosPos[t].maxPos, servosPos[t].minPos);

     jointPos(t, servosPos[t].curPos);

    }

  }

}

 

void keypadFunc() {

 byte keypad = lcd.keypad();

 lcd.command(0);               

  if(keypad !=0) {

   if (millis() > key_millis + keyDelay) {

     keypress(keypad);

     key_millis = millis();

    }

  }

}

 

void keypress (byte keypad) {  //读取按键按下情况。      

 byte t;

 lcd.setCursor(15, 1);

 switch(keypad) {

   case 1:

     lcd.print("1");

     t = base;

     servosPos[t].curPos--;

     break;

   case 2:

     lcd.print("2");

     t = shoulder;

     servosPos[t].curPos--;

     break;

   case 3:

     lcd.print("3");

     t = elbow;

     servosPos[t].curPos--;

     break;

   case 5:

     lcd.print("4");

     t = base;

     servosPos[t].curPos++;

     break;

   case 6:

     lcd.print("5");

     t = shoulder;

     servosPos[t].curPos++;

     break;

   case 7:

     lcd.print("6");

     t = elbow;

     servosPos[t].curPos++;

     break;

   case 9:

     lcd.print("7");

     t = wristflex;

     servosPos[t].curPos--;

     break;

   case 10:

     lcd.print("8");

     t = wristrot;

     servosPos[t].curPos--;

     break;

   case 11:

     lcd.print("9");

     t = gripper;

     servosPos[t].curPos--;

     break;

   case 13:

     lcd.print("*");

     t = wristflex;

     servosPos[t].curPos++;

     break;

   case 14:

     lcd.print("0");

     t = wristrot;

     servosPos[t].curPos++;

     break;

   case 15:

     lcd.print("#");

     t = gripper;

     servosPos[t].curPos++;

     break;  

  }

 servosPos[t].curPos = constrain(servosPos[t].curPos,servosPos[t].maxPos, servosPos[t].minPos);

 jointPos(t, servosPos[t].curPos);

}

 

void jointPos(byte t, byte pos) {    //定义两个byte类型的变量,t,pose.

 lcd.setCursor(6, 1);//电机所在位置名称

 switch(t) {

   case base:               // 1, 4

     lcd.print("bse");   //LCD显示bse

     break;

   case shoulder:           // 2,5    

     lcd.print("shl");  //LCD显示shl

     break;

   case elbow:              // 3, 6

     lcd.print("elb");   //LCD显示elb

     break;

   case wristflex:          // 7, *

     lcd.print("wfx");  //LCD显示wfx

      break;

   case wristrot:           // 8,0   

     lcd.print("wrt");  //LCD显示wrt

     break;

   case gripper:            // 9, #

     lcd.print("grp");   //LCD显示grp

     break;

  }

 

 lcd.setCursor(2, 3);// 设置显示位置第4行,3列就是说前面空2格。

 lcd.print(" you are welcome ");

 lcd.setCursor(6, 2);//pose后面显示的角度位置。

 lcd.print(pos, DEC); //当按键按下后显示角度值。

 servos[t].write(pos);

}

 

void setupDisplay() {    //子程序定义,LCD显示内容,开机立即显示。

 lcd.clear();          //LCD清屏

 lcd.blink_off();       //LCD光标闪烁关

 lcd.home();         //

 lcd.setCursor(1, 1);  //设置显示位置第2行,2列就是说前面空1格。

 lcd.print("key: ");   //显示内容 “key”

 lcd.setCursor(4, 0);   //设置显示位置第一行,5列就是说前面空4格。

 lcd.print("alsrobotbase     ");//显示内容”alsrobotbase”

 lcd.setCursor(1, 2);         // 设置显示位置

 lcd.print("Pos: ");          //显示内容”Pos”

}

 

2)树莓派python控制程序:run.py

# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO

import time

 

GPIO.setmode(GPIO.BCM)

GPIO.setup(22, GPIO.OUT)

GPIO.setup(27, GPIO.OUT)

GPIO.setup(24, GPIO.OUT)

GPIO.setup(23, GPIO.OUT)

 

GPIO.output(22, GPIO.HIGH)

GPIO.output(27, GPIO.HIGH)

GPIO.output(24, GPIO.HIGH)

GPIO.output(23, GPIO.HIGH)

 

def down( n ):

        forcount in range(0, n):

               GPIO.output(22, GPIO.LOW)

               time.sleep(0.08)

               GPIO.output(22, GPIO.HIGH)

               time.sleep(0.2)

        return

 

def up( n ):

        forcount in range(0, n):

               GPIO.output(27, GPIO.LOW)

               time.sleep(0.08)

               GPIO.output(27, GPIO.HIGH)

               time.sleep(0.2)

        return

 

def  right( t ):

       GPIO.output(24, GPIO.LOW)

       time.sleep(t)

       GPIO.output(24, GPIO.HIGH)

        return

 

def  left( t ):

       GPIO.output(23, GPIO.LOW)

       time.sleep(t)

        GPIO.output(23,GPIO.HIGH)

        return

 

up(1)

right(1)

up(1)

left(0.5)

up(1)

left(1.3)

up(2)

right(1)

 

down(5)

left(4)

 

up(1)

left(2)

up(1)

right(2)

up(1)

right(1)

up(2)

left(2)

 

down(2)

left(1)

down(1)

left(2)

down(1)

right(2)

down(1)

right(8)

 

up(1)

left(2)

up(1)

right(2)

up(2)

left(3)

right(3)

down(1)

right(1)

up(2)

right(2)

 

down(2)

left(1)

down(1)

left(2)

down(1)

right(2)

down(1)

left(4)

 

up(1)

left(1)

up(1)

right(0.5)

up(1)

right(1.3)

up(2)

left(1)

down(5)