本帖最后由 吹口琴的钢铁侠 于 2016-7-1 23:56 编辑
机械臂是不少人入门机器人制作的第一个项目,这也是我第一次比较近地接触机器人的控制,本以为和其他的Arduino项目类似,只要找一个函数库,引用一下,生成个实例然后就能很轻易地把机械臂操控自如了;然而真正做起来才发现机械臂这东西做起来用很多很多坑,这篇文章很大程度上也只是描述了机械臂的一些坑而已,并没有很好的达到我的预期目标。
概述 首先是买一个机械臂,实验室导师买了一个淘宝上很常见很普通的六自由度机械臂,想着这东西既然都遍地了,淘宝上的应该不至于会出大问题,然而这就碰到了第一个坑。
假如你想要自如地控制机械臂或者说描述你现在的手,你需要六个量,包括三个位置量和三个姿态量,也就是所谓的六个自由度,六个自由度不多不少正好能让机械臂复制你的手(这是有数学方法可以证明的);但是淘宝上常见的机械臂所谓的六个自由度,其中五个是真的自由度,剩下一个控制的是夹手部分的开合,这部分常被成为末端执行器,是不能算在自由度中的,也就是说六个舵机只有五个有效自由度。同时还要注意一下机械臂的材料,常见的有铝材,塑料,亚克力等...
其次是舵机,买了机械臂之后,每个所谓的自由度一般都是一个舵机在控制,而一般的舵机都是一百八十度旋转的范围,这个范围不是大问题,问题主要在于舵机的扭力,也就是说他能转动又不会对自己造成损伤的最大的力,这个力一般就是你用力和舵机反方向较劲的时候的那个力度,根据机械臂的大小和材料选择合适的舵机,最简单的原则就是:在预算范围内买最贵的舵机= =。
控制舵机
这个类有六个成员函数,粗略的讲一下: - attach() 把一个引脚绑定到一个舵机的实例对象上去,注意这个库会限制PWM的使用,详细请看官方文档
- write() 参数是角度值,使舵机转到指定角度,比如0度和180度就是两边,90度就是中间
- writeMicroseconds() 一般舵机有1000到2000,或者700到2300的范围来转动,实际测试一下就行了,1500通常是中间值
- read() 这个地方比较坑,它会返回一个角度值,原本我以为是当前实时角度值,后来发现它只是上次调用write()函数的最后一个角度值,真正使用的时候真的没什么卵用
- attached() 返回这个舵机实例是否已经绑定了一个引脚
- detach() 解除这个舵机实例与引脚的绑定。
其中这个write默认就是把writeMicroseconds()中的544~2400映射到了角度中的0~180度,这么一来当你用write()来写角度的时候,就会很难对舵机进行很细致的操作,0度到1度会差很多,尤其是精细一点的舵机会更夸张,这1度远远不是它能达到的最小的变化角度,所以我在后面使用的都是writeMicroseconds()这个函数。
简单使用的时候,连上信号线,5v,接地,然后用官方例程跑一下改一改就马上能熟悉上手了,比如下面的sweep。 [mw_shl_code=cpp,true]/* Sweep
by BARRAGAN < http://barraganstudio.com>
This example code is in the public domain.
modified 8 Nov 2013
by Scott Fitzgerald
http://www.arduino.cc/en/Tutorial/Sweep
*/
#include <Servo.h>
Servo myservo; // create servo object to control a servo
// twelve servo objects can be created on most boards
int pos = 0; // variable to store the servo position
void setup() {
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
void loop() {
for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
}[/mw_shl_code]
此时你装好了六个舵机,给每个舵机都连好了三条线,上传好程序开始兴冲冲地调试,然而发现舵机一抖一抖的,而且Arduino一闪一闪的,一看就是没吃饱饭的样子,没错,这里要注意舵机的供电,因为给机械臂输出力真的是件挺费能量的事,这时候你可能需要一个大功率的电源,可以考虑某些可调电源,也可以考虑某些笔记本电源加一个升压模块,注意电压在舵机允许范围内取相对高一点就行。
现在你可以让舵机按照你指定的角度,让机械臂做出各种酷炫的造型了,虽然你偶尔发现有些角度下某几个舵机受力过大,开始发出抖动,不要紧张,这是正常现象,是正常的廉价舵机的现象;但是更严重的问题是,你开始在一个程序里写下好几个造型的切换时,有几个切换动作比较大,你设置的间隔时间太短,造成了两个问题。
一是机械臂还没到预想的形态,在转动过程中,被你直接要求转动到另一个方向,这一方面会导致它表演起来很丑; 另一方面会产生第二个问题,会造成更加明显的惯性现象,想象一下,你想让它从0度转到180度,于是它全速运转,然而还在150度的时候,你让他转回到0度,于是他需要朝着另一个方向全速运转,可想而知这之间需要克服的惯性以及对舵机造成的损伤之大。
为了解决这两个问题,我们首先想到,能不能得知舵机的当前角度值(当然不是通过read()函数的方法),差了点资料,有些舵机会多根线,这种舵机就能直接读取,也就意味着可以通过读取到的角度值来判断舵机是否达到预期的那个姿态,达到之后再做下一步变换,然而这种舵机也比一般的舵机贵上不少..也有其他的方法,比如角度传感器,我没有实践,这里也不好说。
另一方面,对于突然间大距离的转动造成的惯性问题,我们可以把这个大距离改成小距离,比如说原来是从1度转到160度,一下子要跨越159度,但是我们可以把这个159度分割成159份,每次转动1度,转动159次,这样确实可以有效的使机械臂转动起来更加平滑,惯性也更小,不过呢,这个1度,有时候并不是一个最小的数字,这里还是要用writeMicroseconds()这个函数,从500到2500之间,每份小到10的单位,就能让舵机稍微转动一个角度,使它达到应该是更小的惯性吧。。
Arduino的速度 第一次测试时我使用的是UNO,前期测试时数据量小,也没什么性能上的明显缺陷感,然而到后期,Leapmotion开起来实时的数据一发过来,UNO会有明显的卡壳,有时候直接充满他的串口缓冲区,导致不少数据丢失,然后机械臂也会不同程度的发生抽风现象= =。于是后来开始用Mega,在以不太过分的速度发送串口数据的情况下,完全可以用作机械臂的控制,虽然后来又买了一个STM32主控的舵机控制板,然而实际效果并没有比Mega好很多......
最后考虑以上的这些所有的问题之后,我们就能写出这个项目中的Arduino端代码了。 首先在setup中,我们设定了一个比较合适的初始位置(至少能让机械臂平稳地立在那个位置),在loop中,就是在不断读取来自串口的数据,每次数据的格式类似于“1800,1800,1500,800,1200,1200\n”,把给每个舵机的数据分割开来之后,就用上面说的把每个大距离分成小距离的方法来控制舵机,至于上位机是怎么产生每次的数据的下一篇帖子再讲了。 [mw_shl_code=cpp,true]#include <Servo.h>
Servo myservo[6];
int i = 0;
int oldpos = 0;
int pos[6] = {1800,1800,1500,800,1200,1200};
void setup(){
Serial.begin(9600);
Serial.println("start");
for(i=2;i<8;i++){
myservo[i-2].attach(i);
}
for(i=2;i<8;i++){
myservo[i-2].writeMicroseconds(pos[i-2]);
}
}
void loop(){
while (Serial.available() > 0) {
i = 0;
while(i<6){
pos = Serial.parseInt();
i++;
}
if (Serial.read() == '\n') {
for(i=2;i<8;i++){
oldpos = myservo[i-2].readMicroseconds();
if(oldpos > pos[i-2]){
while(oldpos > pos[i-2]){
oldpos -= 50;
myservo[i-2].writeMicroseconds(oldpos);
delay(50);
}
}else{
if(oldpos == pos[i-2]){
myservo[i-2].writeMicroseconds(oldpos);
}
while(oldpos < pos[i-2]){
oldpos += 50;
myservo[i-2].writeMicroseconds(oldpos);
delay(50);
}
}
}
}
}
}
[/mw_shl_code]
|