打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
基于marlin固件的SCARA机器人
 
https://www.arduino.cn/thread-48791-1-1.html本帖最后由 YANGGT 于 2017-8-28 11:55 编辑


其实这个项目很早就在做了,刚注册了Arduino社区,看到这个比赛就拿来水一下
:
附件好像只能上传1M的,所有文件到这里下载吧:https://www.thingiverse.com/thing:2339480。DSR.zip里面包含了全部资料。

1.关于marlin和SCARA
   先简单介绍一下marlin和SCARA机器人
   marlin固件是一个开源的3d打印机固件,除了支持普通的xyz结构,还支持极坐标结构,机械臂结构的3d打印机。详细的介绍可以看这里:http://reprap.org/wiki/List_of_Firmware#Marlin
   SCARA的本意是选择顺应性装配机器手臂,不过现在都习惯把平面关节结构的机器人叫做SCARA,SCARA总共有4个自由度(也可以是3个),二连杆+一个平动的Z轴+手腕,结构简单。

2.图纸和装配
  设计源文件是Solidworks2016的,如果是用的是低版本的或者其他cad软件,通用格式文件夹下有.stp和.igs格式的装配图。详细的装配过程我就不发了,说说一些注意事项和装配中遇到的问题@_@(都是因为设计缺陷产生的)。
  1.部分模型文件根据3D打印的误差预留了余量(不同的机子可能会有点不一样)
  2.主臂体积比较大,一次打印容易打弯,导致机械臂不平行,所以加入了一个拆分版的,两部分用销钉连接。
  3.文件中包含两种执行机构,一是手爪,二是笔夹,都是用销钉和副臂相连,文件中的笔夹孔径为20。也可以根据需要自己设计执行机构。
  4.主臂和副臂的限位开关没有固定的位置,选择不会干涉的地方沾好。
  5.主臂(X)的减速比是6,臂长125mm。副臂(Y)的减速比是5,臂长125mm。
  6.底座用4mm的板子切,底座上有两个孔是放磁铁的,用于和平台吸合,顶端有个开口,用来引出ramps主板上的拓展接口的
  7.同步带使用开口的,两端是固定在大同步轮上的(大同步轮是没有齿的),张紧可能会有些困难。
  8.光轴开始我用的是250mm的,后来发现晃得厉害,改成150的了。
  9.最开始用的是直线轴承,用久了发现会有间隙,推荐用铜套。
  10.设计的时候没考虑布线,所以布线就。。。
  11.设计的不好,仅供学习和参照。


3.接线
  主板用的是ramps1.4+arduino2560,常规的接线图网上可以找到很多了,这里讲讲怎么使用舵机和蓝牙模块
首先如果买的arduino2560质量较差,稳压芯片用的是ams1117的话,建议把ramps的二极管D1去掉,否则容易烧板子。不过这样ramps和2560就需要单独供电,想要同时供电的话,可以用一个稳压模块引过去。
因为二极管被去掉了,单靠usb的电是带不动2个舵机的,所以要在图中外接入5v。舵机从右到左依次为ABC,用(G5 A角度 B角度 C角度)控制, 手腕一定要接在A上,手爪的话随意。
串口通讯如图,可以接蓝牙或者串口屏,波特率是115200。
4.固件和调试
  固件基于marlin,做了部分修改,详细的笔记可以看这里https://my.oschina.net/u/3503462/blog/914027
  固件在配置中启用了3d打印机的屏幕,确保arduino ide已经有u8glib库。因为启用了这个功能,所以可能会出现不能编译的问题(我在arduino1.6.5版本下编译成功,但1.6.13就不行),如果编译不了,可以在配置中注释掉再试试。

调试方法:
上位机软件用3d打印机的就行,推荐用printrun
用于设置的G代码说明(使用大写)

M360 触发限位角度设置
M361 原点偏移设置 (中心转轴相对机械臂原点的坐标位置)
G5 舵机角度控制 (G5 A角度 B角度 C角度)

G4 延迟 (G4 S时间(秒))
G28 回归所有轴(home)
G92 当前坐标设定(G92 X坐标 Y坐标 Z坐标把当前机械手的位置设置成想要的坐标)
G95 切换绝对/相对坐标(相对坐标就是控制单独一轴的角度,切换后一定要home)

M17 启动步进电机
M84 解锁步进电机
M92 设置步进电机脉冲数 (M92 X? Y? Z? E?)下同) M105 获取温度
M112 紧急停止
M114 获取当前坐标和角度
M119 获取限位开关状态
M201 设置最大打印速度
M202 设置最大移动速度
M203 设置电机最大速度
M204 设置默认加速度
M500 保存设置到EEPROM(使用G代码设置如果没有保存到EEPROM,下次连接机械手的时候不再有效)
M501 读取EEPROM中的配置
M502 恢复出厂设置
M503 读取出厂设置

调试方法:触发限位开关的角度大致为笔记相对坐标定义图中的姿态3,在不干涉的前提下安装好限位开关

1.连接
2.M502恢复出厂设置
3.G28 home,检查是否正常。
4.G95 切换到角度模式,再G28,检查角度模式是否正常。
5.G28,home M360 X0 Y180 先假定当前角度
6.分别移动XY轴到0角度,读取当前坐标(X轴0角度为主臂与平台平行,Y轴0角度为副臂与主臂成直线)
7.X轴限位角度即负的坐标值,Y即180减坐标值,用M360 X? Y?设置限位角度, M500保存
8.使用G95可以把机械手的任意位置设置成想要的坐标。G28,移动机械手到想要设置坐标的位置,G95 X0 Y0 Z0就把现在的位置设置成原点,如果再输入M500,此位置就永久地保存为原点,否则只对此次连接有效。

使用方法画画教程使用激光刀路生成工具

1.导入图片后生成G代码
2.用记事本把所有的M03替换成G1 Z0 M05换成 G1 Z2-5(提笔高度)
3.由于不同的笔高度不一,因此需使用G95设置Z轴的原点。home后下降Z轴,直至刚好接触纸面,G95 Z0,如果一直使用这把笔可以用M500保存,这样下次就不用设置了。

写字教程(矢量图绘制)使用inkspace以及激光雕刻插件某旧版的inkspace才支持此插件

1.用cad绘制单线字体,由于不支持圆弧插补,所以要导出为图元文件(.sat)
2.导入到inkspace,使用插件里面的laser...(激光雕刻插件),页面中可以更改保存路径
3.找到保存的.nc文件,后续步骤同上。

3D打印教程

1.设置合适位置为XYZ轴原点
2.用切片软件生成G代码,并用记事本把前面十几行中的G28去掉



5.processing示教上位机
根据G代码结合python/processing可以给机器跟编程。下面是我用processing写的一个示教上位机。机械手的XY坐标由鼠标在白板中画画得到,Z轴坐标用鼠标滚轮控制,MOVE_UNIT是Z轴的移动单位(mm)teach开始示教,save保存gcode,鼠标右键清空屏幕。

[] 纯文本查看 复制代码
代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import processing.serial.*;
float x,y,z =100,a=90; //储存xyz坐标,以及第四轴角度
float unit =10;//移动单位
boolean teach =false;//示教模式
float e;//滚轮事件变量
String[] step =new String[1000];
String[] output;
int i=0;
Serial Port;
void setup()
{
  frameRate(15);
  String portName = Serial.list()[0];
  Port = new Serial(this, portName, 115200);//串口
  size(1000,650);//屏幕尺寸
  smooth();
  background(#FFFFFF);//背景色
  fill(#DBDBDB);//提示栏颜色
  noStroke();
  rect(0,0,1000,50);
  fill(#FFFFFF);
  textSize(30);
  text("MOVE_UNIT",10,35);//UI界面
  //text("save",815,35);
}
void draw()
 noStroke();
 /*---------------------键盘事件------------------*/
 if(keyPressed==true)
   {
     if(key=='h'||key=='H')//H键回零所有轴
      {
       Port.write("G28\r\n");
       x=y=0;
       z=100;
      }
     if(key=='x'||key=='X')//X键回零X轴
       {
         Port.write("G28 X0\r\n");
         x=0;
       }
     if(key=='y'||key=='Y')//Y键回零Y轴
       {
         Port.write("G28 Y0\r\n");
         y=0;
       }
     if(key=='z'||key=='Z')//Z键回零Z轴
       {
         Port.write("G28 Z0\r\n");
         z=150;
       }
      
      if((key=='a'||key=='A')&&a<=180+unit)//Z键回零Z轴
      {
         a=a+unit;
         Port.write("G5 A"+a+"\r\n");
        if(teach==true)
        {
         step[i]="G5 A"+a;
         i++;
        }
       }
       if((key=='d'||key=='D')&&a>=0+unit)//Z键回零Z轴
       {
         a=a-unit;
         Port.write("G5 A"+a+"\r\n");
         if(teach==true)
        {
         step[i]="G5 A"+a;
         i++;
        }
       }
        if(key=='w'||key=='W')//Z键回零Z轴
       {
         Port.write("G5 B60\r\n");
         if(teach==true)
        {
          step[i]="G5 B60";
          i++;
        }
       }
       if(key=='s'||key=='S')//Z键回零Z轴
       {
         Port.write("G5 B150\r\n");
        if(teach==true)
        {
          step[i]="G5 B150";
          i++;
        }
       }
     
      
     if(key==CODED)//编码键
       {
         if(keyCode==ALT)//清空屏幕
         {
            fill(#FFFFFF);
            noStroke();
            rect(0,50,1000,600);
         }
       }  
   }
/*-----------------------------------------------*/
/*---------------------鼠标事件------------------*/  
 if(mouseButton==LEFT&&mouseY>50) //鼠标左键
 {
   stroke(0,102);
   strokeWeight(3);    
   line(mouseX,mouseY,pmouseX,pmouseY);
   x=mouseX/4;
   y=(height-mouseY)/4;
             
   if(teach==true)//示教模式
   {
        
       Port.write("G1 X"+x+'Y'+y+"F12000\r\n"); //拖动鼠标移动并绘制
       step[i]="G1 X"+x+'Y'+y+"F1200";
       i++;
   }
   else
   {
      Port.write("G1 X"+x+'Y'+y+"F12000\r\n"); //拖动鼠标移动并绘制
   }  
 }
  
 /*-----------------------------------------------*/
 if((mouseX>900)&&(mouseX<1000)&&(mouseY>0)&&(mouseY<50))//save
  {
    fill(#DBDBDB);
    rect(900,0,100,50);
    fill(#FFFFFF);
    text("save",915,35);
  }
     
}
 void mouseWheel(MouseEvent event)//滚轮事件,移动Z轴
{
  e = event.getCount();
  if(e==1&&z>0+unit)
     z=z-unit;
   else if(e==-1&&z<150+unit)
     z=z+unit;
   Port.write("G1 Z"+z+"F1200\r\n");
   if(teach==true)//示教模式
   {
       step[i]="G1 Z"+z+"F1200";
       i++;
   }
  //println(z);
}
/*---------------------UI------------------*/
void mouseClicked()//UI按钮
{
  noStroke();
      
  if((mouseX>200)&&(mouseX<260)&&(mouseY>0)&&(mouseY<50))
     {
       fill(#DBDBDB);
       rect(250,0,55,50);
       rect(300,0,55,50);
       rect(350,0,55,50);
       fill(#FFFFFF);
       text("10",257,35);
       text("1",315,35);
       text("0.1",357,35);
       rect(200,0,55,50);
       fill(#DBDBDB);
       text("20",207,35);
       unit=20;  //  控制单位20
      }
  if((mouseX>250)&&(mouseX<310)&&(mouseY>0)&&(mouseY<50))
      {
        fill(#DBDBDB);
        rect(200,0,55,50);
        rect(300,0,55,50);
        rect(350,0,55,50);
        fill(#FFFFFF);
        text("20",207,35);
        text("1",315,35);
        text("0.1",357,35);
        rect(250,0,55,50);
        fill(#DBDBDB);
        text("10",257,35);
        unit=10;  //10
        }
  if((mouseX>300)&&(mouseX<360)&&(mouseY>0)&&(mouseY<50))
     {
       fill(#DBDBDB);
       rect(200,0,55,50);
       rect(250,0,55,50);     
       rect(350,0,55,50);
       fill(#FFFFFF);
       text("20",207,35);
       text("10",257,35);
       text("0.1",357,35);
       rect(300,0,55,50);
       fill(#DBDBDB);
       text("1",315,35);
       unit=1;  //1
       }
  if((mouseX>350)&&(mouseX<410)&&(mouseY>0)&&(mouseY<50))
      {
         fill(#DBDBDB);
        rect(200,0,55,50);
        rect(250,0,55,50);
        rect(300,0,55,50);
        fill(#FFFFFF);
        text("20",207,35);
        text("10",257,35);
        text("1",315,35);
        rect(350,0,55,50);
        fill(#DBDBDB);
        text("0.1",357,35);
        unit=0.1; //0.1
      }
  if((mouseX>800)&&(mouseX<900)&&(mouseY>0)&&(mouseY<50))//示教模式切换
  {                                                                                                            
    teach=!teach;
  }
  if((mouseX>900)&&(mouseX<1000)&&(mouseY>0)&&(mouseY<50))//save
  {
    fill(#FFFFFF);
    rect(900,0,100,50);
    fill(#DBDBDB);
    text("save",915,35);
    output =new String[i];
    for(int j=0;j<i;j++)
    {
      output[j]=step[j];
    }
    saveStrings("teach.gcode",output);
  }
  if(teach==false)//绘制示教模式按钮
  {
    fill(#DBDBDB);
    rect(800,0,100,50);
    fill(#FFFFFF);
    text("teach",815,35);
  }
  else
  {
    fill(#FFFFFF);
    rect(800,0,100,50);
    fill(#DBDBDB);
    text("teach",815,35);
  }
/*-----------------------------------------------*/
  //println(teach);
  println(a);
}


6.processing简单视觉抓取

我在手爪上装了个摄像头,用prcessing写了一个识别红绿蓝色块并顺序抓取的程序。演示过程可以看最后的视屏,摄像头的焦距有点不对,导致看到的范围很小,抓取的范围也很小。。
[C] 纯文本查看 复制代码
代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import processing.serial.*;
import processing.video.*;
Serial Port;
Capture cam;
float[][] kernel ={{0.111,0.111,0.111},
                   {0.111,0.111,0.111},
                   {0.111,0.111,0.111}
                   };//卷积核
//中间像素的灰度值等于周围像素的红色分量减去蓝绿色分量的平均值
//从而使没有红色特征的像素灰度值变为零
//相当于滤镜的效果,修改卷积核的参数,可以达到不一样的效果
int sub_x=0,sub_y=0;//物体中心的像素坐标
float pre_x=0,pre_y=0;//前一帧物体中心的像素坐标
float tar_x=80,tar_y=80;//目标坐标,机械手的坐标
boolean event=false;//坐标计算事件
int c=0;
void setup()//初始化
{
  String portName = Serial.list()[0];
  Port = new Serial(this, portName, 115200);//连接串口
  size(640,480);//屏幕大小
  String[] cameras = Capture.list();
  printArray(cameras);//打印可使用的相机
  cam = new Capture(this, cameras[0]);
  cam.start();//启动相机
  frameRate(30);//帧率
  Port.write("G5 B90\r\n");
  Port.write("G28\r\n");
  Port.write("G1 X80 Y80 Z40\r\n");
  Port.write("G5 B50\r\n");//移动到初始位置
  delay(6000);//延迟,避免摄像头刚打开时的波动
}
void draw()//循环
{
  if (cam.available() == true)
  {
    cam.read();
    image(cam, 0, 0, width, height);
    cam.loadPixels();
    int sum_x=0,sum_y=0;//所有红色像素坐标的和
    int m=1;//红色像素的个数
    for(int y=1;y<cam.height-1;y++)//排除第一行、最后一行、第一列、最后一列像素(这几个地方周围的像素不完整)
    {
      for(int x=1;x<cam.width-1;x++)//经过所有像素
      {
        float sum=0;//最终的灰度值
        for(int ky =-1;ky<=1;ky++)
        {
          for(int kx=-1;kx<=1;kx++)//周围9个像素(包括自身)
          {
          int pos =(y+ky)*cam.width+(x+kx);//将xy坐标换算成pixels[]数组中的位置,pixels[]数组是可以直接调用的储存像素的一维数组(从左到右从上到下)
          int R = (cam.pixels[pos]>> 16) & 0xFF; //取颜色分量,与red()功能相似
          int G = (cam.pixels[pos] >> 8) & 0xFF;
          int B = cam.pixels[pos] & 0xFF;
          float val=0;
          switch(c)//顺序抓取红蓝绿色块
          {
            case 0:
              val =R-1.5*B-1.5*G;
              break;
            case 1:
              val =B-1.5*R-1.5*G;
              break;
            case 2:
              val =G-0.5*B-1*R;
              break;
          }
             //1.5为比例系数,改大可以让红色更突出
          sum +=kernel[ky+1][kx+1]*val;//计算灰度值
          }
        }
        if(sum>0)//新的灰度值非零
        {
          sum_x=sum_x+x;
          sum_y=sum_y+y;//累加坐标
          m=m+1;//计数
        }
       }
     }
   pre_x=sub_x;
   pre_y=sub_y;//前一帧的物体中心
   sub_x=sum_x/m;
   sub_y=sum_y/m;//取平均,计算出物体中心像素坐标
    
   cam.updatePixels();
    fill(#FF0000);
  }
  line(sub_x,0,sub_x,480);
  line(0,sub_y,640,sub_y);//作物体中心的两条交叉线
  if(abs(sub_x-pre_x)<5&&abs(sub_y-pre_y)<5&&sub_x>5&&sub_y>5)//判断是否在移动(波动小于5个像素即为静止),是否存在物体(没有物体时默认为0,为避免波动,这里设置成5)
  {
    if(event==true)//如果物体存在,机械手静止,且事件被触发,则进行计算
    {
     tar_x=tar_x-(0.0625*sub_x-20);
     tar_y=tar_y-(15-0.0625*sub_y);//把像素坐标转化成绝对坐标,根据实际比例
     event=false;//关闭计算事件,即只执行一次计算
     Port.write("G1 X"+tar_x+" Y"+tar_y+"\r\n");//移动到目标位置
     delay(100);//延迟,减小波动
    }
  }
  else //如果机械手在移动,触发计算事件,但不执行计算,为机械手静止时计算做准备
   event=true;
    /*由于机械手的通讯以及动作上有延迟,速度远赶不上摄像头对图像识别的速度,
    所以仅让机械臂在静止时进行一次计算*/
  if(sub_x<5&&sub_y<5)
  {
    if(c<3)
      c=c+1;
     else
       c=0;
  }
  float mx=tar_x;
  float my=tar_y+32;//手抓中心对的坐标,摄像头和手抓有32的y偏移量
  if(sub_x>315&&sub_x<325&&sub_y>235&&sub_y<245)//当物体在画面中心时(+-5个像素)
  {
     //执行以下动作
     Port.write("G1 X"+mx+'Y'+my+"Z12 F1200\r\n");
     delay(1500);//充分延时
     for(int i=50;i<=150;i+=5)
     {
       Port.write("G5 B"+i+"\r\n");
       delay(50);
     }
      
     delay(500);
     Port.write("G1 Z30\r\n");
     delay(1000);
     switch(c)//顺序摆放红蓝绿色块
     {
       case 0:
         Port.write("G1 X150 Y120\r\n");break;
       case 1:
         Port.write("G1 X150 Y100\r\n");break;
       case 2:
         Port.write("G1 X150 Y80\r\n");break;
     }
     delay(1500);
     Port.write("G1 Z10\r\n");
     delay(500);
     for(int j=150;j>=50;j-=5)
     {
       Port.write("G5 B"+j+"\r\n");
       delay(50);
     }
     delay(500);
     Port.write("G1 X80 Y80 Z40\r\n");
     delay(1500);
    if(c<3)
      c=c+1;
     else
       c=0;
   }
   
  println(tar_x);
  println(tar_y);
}

6.演示视频
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
flash动画制作实例:打造粒子烟花效果
canvas响应点击事件
AHK 快餐店 [6] 之 颜色神偷
AS3中鼠标坐标与鼠标事件总结 - 设计模式 - Java - JavaEye论坛
react中实现可拖动div
Flash As3.0 —— 不同元件跟随鼠标移动效果
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服