Arduino 智能学习简易版 机械臂

文章目录

  • 一 总体思路
    • 1 功能原理
    • 2 硬件准备
    • 3 Arduino库
  • 二 代码设计
    • 1 舵机部分
    • 2 电位器
    • 3 记录路径法
    • 4 触控板与三色灯板
  • 三 展示效果
    • 视频链接

一 总体思路

1 功能原理

  • 实现功能:使用电位器控制每一个舵机运动,进而操作机械臂;设置学习模式,打开后可以记录手动操作的路径,并且能复位执行记录好的路径,能够路径信息打印出来调用。
  • 原理:电位器操纵舵机,使用Servo库自带的map映射,实现控制效果非常容易;
    学习模式:本项目设计了一种方法,记录每个舵机一次运动中的开始角度结束角度,加上舵机编号存入一个结构体数组中,具体实现详见代码部分。这种方法的优势在于,节省空间,支持机械臂复杂操作,可以流畅复位,理论上结构体数组范围可以开多大,就可以支持多少次的舵机运动,代码部分详细介绍,该方法仅涉及c/c++基本语法结构。

简单说3种模式:1 纯电位器操控,2 学习模式,可记录操作的路径,3 复位模式,不可操作,用来执行记录的路径。

2 硬件准备

  • NANO版
  • SG90伺服舵机
  • 触控板
  • 电位器
  • 3d打印机械臂结构(某宝)
  • 三色灯板

3 Arduino库

  • 仅需要Servo库,其余为简单代码

本项目除了记录舵机路径的方法之外,其余部分皆为基础。逻辑清晰,有c++基础的小白也比较容易实现。


二 代码设计

源代码较长,该部分分块进行介绍:舵机,电位器,路径记录法,触控板与三色灯板。

1 舵机部分

该部分主要包含:舵机复位方法init_pos,一个固定的抓取路径catchs(度数),和一些舵机基础全局变量。该部分为基础设定。此处有疑问可以参考我另一篇颜色识别机械臂博客中,有详细介绍。

再次强调:实现机械臂的前提一定先搞清楚自己手里舵机的角度范围和旋转方向,舵机参数都了解清楚后,才开始编码,这是最基础也是最重要的一步。本项目舵机引脚与角度范围值仅做参考,以自己实际情况为主。

#include <Servo.h>//1 舵机
Servo myservo1,myservo2,myservo3,myservo4;
int pos1 = 0;  //每个舵机的角度值,12345分别对应:底座,大臂,小臂,尖端,爪子舵机
int pos2 = 0;
int pos3 = 130; 
int pos4 = 80;
int speed = 7;  //机械臂运动速度
//setup里记得写连接舵机初始引脚,还有write初始角度pos变量/*
读取舵机角度参数,控制舵机流畅的回到初始位置
*/
void init_pos(){pos1=myservo1.read();  //当前各舵机角度值pos2=myservo2.read();pos3=myservo3.read();pos4=myservo4.read();for(int i=pos1;i>=0;i--){  //1号舵机,归位0myservo1.write(i);delay(speed);}myservo1.write(0);for(int i=pos3;i<=130;i++){    //3号舵机,归位130myservo3.write(i);delay(speed);}myservo3.write(130);for(int i=pos2;i>=0;i--){   //2号舵机,归位0myservo2.write(i);delay(speed);}myservo2.write(0);for(int i=pos4;i<=80;i++){  //4号舵机,归位80°myservo4.write(i);delay(speed);}myservo4.write(80);pos1=0;//更新pospos2=0;pos3=130;pos4=80;}/*
一个固定路径的机械臂抓取函数,可设置旋转度数spin。
我把这个设置成了开机动画,开机一动不动像木头一样太无趣了
*/
void catchs(int spin) {//先下放小臂for (pos3 = 130; pos3 >= 30; pos3 -= 1) {myservo3.write(pos3);delay(speed);}//打开爪子for (pos4 = 80; pos4 >= 0; pos4 -= 1) {myservo4.write(pos4);delay(speed);}//放大臂for (pos2 = 0; pos2 <= 70; pos2 += 1) {myservo2.write(pos2);delay(speed);}//夹取物品for (pos4 = 0; pos4 <= 80; pos4 += 1) {myservo4.write(pos4);delay(speed);}//抬起大臂for (pos2 = 70; pos2 >= 0; pos2 -= 1) {myservo2.write(pos2);delay(speed);}//转向for(int i=0;i<spin;i++){   //转动spin度,spin不同颜色可以手动设置转动区间myservo1.write(pos1++);delay(speed);} //大臂放下for (pos2 = 0; pos2 <= 15; pos2 += 1) {myservo2.write(pos2);delay(speed);}//打开爪子for (pos4 = 80; pos4 >= 0; pos4 -= 1) {myservo4.write(pos4);delay(speed);}init_pos();//机械臂流畅复位
}

2 电位器

加入电位器的目的是,可以通过手操电位器来控制舵机,也就是控制机械臂关节移动的效果。本项目用了4个SG90伺服舵机,故使用4个电位器操纵。在Arduino下载Servo库后,可以查看knob的示例文件,学习map映射操作舵机的方法。本处代码为,电位器控舵机部分,该部分很简单。

//2 电位器
int potpin1=A0; //potentiometer pin:电位器引脚
int potpin2=A1; //这里开个变量存引脚,是为了代码容易阅读
int potpin3=A2; //若直接写入具体引脚,引脚多了后,鬼知道这些引脚是干嘛用的
int potpin4=A3;
int val1,val2,val3,val4;   //存取电位器模拟引脚的数值
//使用电位器,setup中不需要写额外内容/*
重复判定是否操纵了电位器
*/
void loop(){//电位器底座控制与信息记录val1=analogRead(potpin1);  //读取模拟引脚值val1=map(val1,0,1023,0,120);//模拟值映射为舵机角度值,[0,1023]到[0,180]myservo1.write(val1);      //根据自己实际情况设映射范围//电位器大臂控制与信息记录val2=analogRead(potpin2);val2=map(val2,0,1023,0,70);myservo2.write(val2);//电位器小臂控制与信息记录val3=analogRead(potpin3);val3=map(val3,0,1023,30,130);myservo3.write(val3);//电位器爪子控制与信息记录val4=analogRead(potpin4);val4=map(val4,0,1023,0,80);myservo4.write(val4);//测试每个舵机实时角度,可观察电位器操作引起的舵机角度变化// delay(15);   // Serial.print("val1:\t");// Serial.print(val1);// Serial.print("\tval2:\t");// Serial.print(val2);// Serial.print("\tval3\t");// Serial.print(val3);// Serial.print("\tval4:\t");// Serial.println(val4);}

3 记录路径法

本方法为该项目最有趣的地方,也是最不好理解的地方,(嗯,因为我手搓的方法),简称该方法为路径穿梭。需要定义一个结构体,包含舵机编号,记录一次舵机运动中初始角度st与结束角度ed,然后用结构体数组记录全部舵机运动信息。简单原理就是:学习模式打开,然后结构体存入舵机运动信息,最后学习结束后,结构体里有舵机编号,有开始角度和结束角度,并且还是按运动顺序记录的,直接for循环遍历一遍,就能重复动作。

思路很直白,不过麻烦的点在于怎么精准的记录每个舵机运动的时候,每次开始和结束的角度,通过什么判断一次运动的开始与结束?我的思路是,设一个变量con实时记录这一刻哪一个舵机正在被操控,con赋值为舵机的编号。有了con之后,记录一个舵机的一次连续运动,我还需要为每一个舵机设一个变量fct,表示该舵机是否处于连续运动的状态,只要真假2种状态。然后开始一次记录:以1号舵机为例:

  • 舵机开始运动角度:当1号舵机角度发生变化,此时让con=1,如果fct=0时,表示现在1号舵机被操纵,并且1号舵机之前还未被操纵,说明这是1号舵机的初始角度,此时把1号舵机现在的角度计入结构体数组中,做初始角度即可;
  • 持续操纵: 当1号舵机角度发生变化,但是fct为真时,表明1号舵机在被持续操纵,不需要记录,及时更新舵机角度就行。
  • 舵机运动结束: 当检测1号舵机的舵机角度值没有变化时,此时有两中可能,一是我们没有进行任何电位器操作,不做处理;二实我们控制了1号舵机外的舵机。这就表示1号舵机这一次运动已经结束了,需要将结束信息记录道结构体中去。可以发现,这个方法是依据其它舵机被操纵来记录上一个舵机运动的结束角度,这会带来一个问题:最后一次操作的结束角度怎么办?于是就需要再添加一个判断,来确定本次是不是最后一次操作,如果是也记录一下结束角度。

本方法大概原理就这些,代码中很多地方用了一些小细节,比如fct赋值k啦,k自增的时机啦,改变舵机角度在代码中的位置啦,等等,都是有一些背后原因的,就不在详细介绍。用条件,循环加结构体强行模拟捏了一个方法,没什么算法思想,就纯逻辑,开源欢迎大家学习改进。

//3 记录路径信息结构体
struct memory{   //记录操纵电位器的过程中,每个舵机运动的初始与结束角度int num;       //舵机编号int st;        //初始变化角度;int ed;        //最终变化角度
}a[200];
int k=1;  //记录结构体储存记录数量,后面复位时要使用int con=0;                          //control:表示现在是哪个舵机在被控制,赋值为01234,0代表初始值
int fct1=-1,fct2=-1,fct3=-1,fct4=-1;//flag continue:表示舵机持续运动状态//-1:关闭,学习模式不可用;0:待机;非0:运动
/*
简称穿梭法:记录每个舵机每一次运动开始与运动结束时的角度位置,复位时起点直达重点。
优点:复位速度流畅,支持记录复杂操作,支持同时控制多个电位(容易撑爆数组),可输出舵机运动角度表
缺点:因为本方法的性质,会自动过滤一些无效操作,无法完全复制机械臂行动,比如同一个舵机运动起点与终点一致的运动
*/
void loop(){//电位器底座控制val1=analogRead(potpin1);val1=map(val1,0,1023,0,120);//if中表示当检测到1号舵机有操作时的情况:if(abs(myservo1.read()-val1)>=5){  //收舵机精度与map映射误差影响,防止连续多次记录1°记录,abs是求绝对值。con=1;                   //现在控制的是1号舵机;if(!fct1){              //记录1号舵机初始状态,fct1==0时才开始。fct1=k;               //在本次状态结束时,k可能已发生变化,不能a[k].ed记录,这里顺手废物利用一下fct1充当下标a[k].num=1;a[k].st=myservo1.read();//记录最早的一方k++;                  //k自在记录到一次开始后就增加。}myservo1.write(val1);  //先记录,再写入;否则a[k].st记录不到read()的角度了}else{                       //else中表示当1号舵机无操作时://(1)如果234舵机被控制了,并且1号舵机操纵态为打开,应结束1号机的运动并记录://   if条件:   con!=0,1说明现在其他舵机被控,fct1>0则说明1号舵机与运动态为打开;//(2)如果1号机仍然被控,但是触控板按下结束,也需结束1号机的运动并记录://   因为穿梭法记录原理:当下一个舵机被控时,才会结束上次的记录,所以最后一次是记不上的,//   需要特判学习模式结束时记录最后一次的结束操作;//   if条件:   con=1和触控板结束按下,可以确定本次为最后一次操作if((con!=1&&con!=0&&fct1>0)||((con==1)&&(ed_touchval==HIGH))){a[fct1].ed=val1;                             cout_road(fct1);    //这是一个打印本次路径的方法,几行Serial输出即可,略。作用是打印本次路径fct1=0;             //1号机运动态,归0。待机等待下一次记录;}}//电位器大臂控制val2=analogRead(potpin2);val2=map(val2,0,1023,0,70);//注意,因为穿梭法依据下一个舵机被控来结束上一次记录,并且fct1234都设为全局变量,//所以234号舵机不能写一个通用的函数方法调用,必须一个个写if(abs(myservo2.read()-val2)>=5){con=2;if(!fct2){fct2=k;a[k].num=2;a[k].st=myservo2.read();k++;}myservo2.write(val2);}else{if((con!=2&&con!=0&&fct2>0)||((con==2)&&(ed_touchval==HIGH))){a[fct2].ed=val2;cout_road(fct2);//测试!fct2=0;}}//电位器小臂控制val3=analogRead(potpin3);val3=map(val3,0,1023,30,130);if(abs(myservo3.read()-val3)>=10){con=3;if(!fct3){fct3=k;a[k].num=3;a[k].st=myservo3.read();k++;}myservo3.write(val3);}else{if((con!=3&&con!=0&&fct3>0)||((con==3)&&(ed_touchval==HIGH))){a[fct3].ed=val3;cout_road(fct3);//测试! fct3=0;}}//电位器爪子控制val4=analogRead(potpin4);val4=map(val4,0,1023,0,80);if(abs(myservo4.read()-val4)>=8){con=4;if(!fct4){fct4=k;a[k].num=4;a[k].st=myservo4.read();k++;}myservo4.write(val4);}else{if((con!=4&&con!=0&&fct4>0)||((con==4)&&(ed_touchval==HIGH))){a[fct4].ed=val4;cout_road(fct4);//测试! fct4=0;}}}

4 触控板与三色灯板

该部分也比较简单,添加2个触控板表示学习模式的开始与结束;使用三色灯板,不同颜色可以提示现在正处于哪种模式,方便我们观察。

//4 触控板与三色灯板
//三色灯
int lightr=13;   //红灯:复位中,请勿操作
int lightg=12;   //绿灯,手操模式
int lightb=11;  //蓝灯,学习模式
//触控板
int st_touchpin=3;  //开始学习pin
int ed_touchpin=2;  //结束学习pin
int st_touchval;  //读触控板1输入值:HIGH,LOW
int ed_touchval;  //读触控板2输入值:HIGH,LOW//初始引脚,输入输出模式,与初始灯光
void setup() {Serial.begin(9600);pinMode(st_touchpin,INPUT);//触控板引脚设置pinMode(ed_touchpin,INPUT);pinMode(lightr,OUTPUT);//rgb灯引脚设置pinMode(lightg,OUTPUT);pinMode(lightb,OUTPUT);digitalWrite(lightr,LOW); //初始绿灯,手操模式digitalWrite(lightg,HIGH);digitalWrite(lightb,LOW);catchs(60);  //机械臂开机热身动画:60度转身操~}
void loop(){//注意了,触控板状态判断这两个if结构,需要放到loop中的最下面//因为在前一个穿梭法中,记录好最后一次操作后,才可以进行复位操作//控制灯光变化与学习模式开和关if(st_touchval==HIGH){  //按下开始触摸板,开始记录路径digitalWrite(lightr,LOW); digitalWrite(lightg,LOW);digitalWrite(lightb,HIGH);//蓝灯,学习模式fct1=0;   //舵机持续运动状态设为:待机fct2=0;fct3=0;fct4=0;}if(ed_touchval==HIGH){  //按下关闭触控板,且学习模式已打开,任意fct必然>=0digitalWrite(lightr,HIGH);digitalWrite(lightg,LOW); //复位模式:红灯digitalWrite(lightb,LOW);Serial.println("动作回放中.......");  back(); //复位:该方法就是遍历一遍结构体数组,让对应编号的舵机遍历写入一遍角度Serial.println("动作回放结束,学习模式关闭!");digitalWrite(lightr,LOW); digitalWrite(lightg,HIGH);//回到操纵状态,绿灯digitalWrite(lightb,LOW);fct1=-1;   //-1表示所有舵机持续运动态:关闭fct2=-1;fct3=-1;fct4=-1;con=0;      //控制被控舵机初始化k=1;       //记录路径编号初始化}}

三 展示效果

视频链接

开机执行了我设置的左转60度健身操,手动狗头~

Ardunio智能学习机械臂

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/638531.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

this指针相关

一、类对象的存储方式 先说一下结论&#xff1a;一个类的大小&#xff0c;实际就是该类中”成员变量”之和&#xff0c;当然要注意内存对齐 注意空类和没有成员变量的类的大小&#xff0c;编译器给了这两种类一个字节来唯一标识这个类的对象。 class A { public:int _a;int _b…

【RL】(task3)A2C、A3C算法、JoyRL

note 文章目录 note一、A2C算法二、A3C算法时间安排Reference 一、A2C算法 在强化学习中&#xff0c;有一个智能体&#xff08;agent&#xff09;在某种环境中采取行动&#xff0c;根据其行动的效果获得奖励。目标是学习一种策略&#xff0c;即在给定的环境状态下选择最佳行动&…

Python多线程—threading模块

参考&#xff1a;《Python核心编程》 threading 模块的Thread 类是主要的执行对象&#xff0c;而且&#xff0c;使用Thread类可以有很多方法来创建线程&#xff0c;这里介绍以下两种方法&#xff1a; 创建 Thread 实例&#xff0c;传给它一个函数。派生 Thread 的子类&#xf…

计算机体系结构——多处理机系统

一、概述 重要概念 评估指标 通信延迟 通信延迟&#xff1d;发送开销&#xff0b;跨越时间&#xff0b;传输延迟&#xff0b;接收开销 跨越时间 数字信号从发送方的线路端传送到接收方的线路端所经过的时间。 传输时间 全部的消息量除以线路带宽。 多处理机的架构 根…

Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二)

Unity 之 Addressable可寻址系统 -- HybridCLRAddressable实现资源脚本全热更 -- 实战 前言实现原理一&#xff0c;HybridCLR相关操作1.1 安装HybridCLR1.2 打包程序集1.2 设置面板1.3 补充元数据1.4 代码加载流程 二&#xff0c;Addressable资源管理2.1 生成热更代码资源2.2 创…

【0246】深入分析PG内核Write-Ahead Log的实现机制

文章目录 1. 前言2. Write-Ahead Log2.1 WAL数据结构2.1.1 逻辑结构(Logical Structure)2.1.2 物理结构(Physical Structure)3. 参考文献1. 前言 Write-Ahead Log简写为WAL,即预写式日志,PG数据库内核引入WAL的原因基于以下几点: (1)如果发生停电、操作系统错误或数据…

攻防世界——answer_to_everything-writeup

__int64 __fastcall not_the_flag(int a1) {if ( a1 42 )puts("Cipher from Bill \nSubmit without any tags\n#kdudpeh");elseputs("YOUSUCK");return 0LL; } kdudpeh这个东西&#xff0c;根据题目提示sha1加密 import hashlib flagkdudpeh x hashlib…

网安防御保护入门

常见的网络安全术语&#xff1a; 漏洞&#xff08;脆弱性&#xff09;&#xff1a;可能被一个或多个威胁利用的资产或控制的弱点 攻击&#xff1a;企图破坏、泄露、篡改、损伤、窃取、未授权访问或未授权使用资产的行为 入侵&#xff1a;对网络或联网系统的未授权访问&#xff…

【Springboot】日志

1.日志的使用 日志主要用于记录程序运行的情况。我们从学习javase的时候就使用System.out.println();打印日志了&#xff0c;通过打印的日志来发现和定位问题&#xff0c;或根据日志来分析程序运行的过程。在Spring的学习中,也经常根据控制台的⽇志来分析和定位问题 。 日志除…

k8s-pvc/pv扩容记录

背景 一次聊天过程中&#xff0c;对方提及pvc的扩容&#xff0c;虽然有注意过 storageclass 有个AllowVolumeExpansion的配置&#xff08;有些csi插件是不支持该配置的&#xff0c;比如local-volume-provisoner&#xff09;&#xff0c;但是没有实际用过&#xff0c;所以还是心…

数据分析实战:城市房价分析

流程图&#xff1a; 1.读数据表 首先&#xff0c;读取数据集。 CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATtarget0.00632182.3100.5386.57565.24.09129615.3396.94.98240.0273107.0700.4696.42178.94.9671224217.8396.99.1421.60.0272907.0700.4697.18561.14.9671224217…

【MySQL数据库专项 一】一个例子讲清楚数据库三范式

好的&#xff0c;让我们以学校数据库中的一个表为例来说明第一范式&#xff08;1NF&#xff09;、第二范式&#xff08;2NF&#xff09;和第三范式&#xff08;3NF&#xff09;的概念。 什么是数据库三范式 数据库的范式&#xff08;Normalization&#xff09;是一组关于数据…

UI -- Vue2

Vue2 模板 Vue2 UI package.json同级components/slButton/element.vue <template><div class"d1"><span>测试123</span></div> </template><script>export default {name:"sl-button",data() {return {};}} …

大英第四册课后翻译答案

目录 Unit 1Unit 2Unit 3Unit 4小结&#xff1a; Unit 1 中庸思想&#xff08;Doctrine of the Mean&#xff09;是儒家思想的核心内容。孔子所谓的“中”不是指“折中”&#xff0c;而是指在认识和处理客观事物时的一种“适度”和“恰如其分”的方法。孔子主张不仅要把这种思…

街机模拟游戏逆向工程(HACKROM)教程:[14]68K汇编-标志寄存器

在M68K中&#xff0c;有许多条件分支指令&#xff0c;和jmp指令一样也会修改PC达到程序跳转或分支的目的&#xff0c;不过这些会根据一些情况或状态来选择是否跳转。而在M68K中&#xff0c;有一个特别的寄存器来标记这些情况。 CCR(状态标志寄存器) CCR寄存器是用来保存一些对…

微前端-无界wujie

无界微前端方案基于 webcomponent 容器 iframe 沙箱&#xff0c;能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户的核心诉求。 主项目安装无界 vue2项目&#xff1a;npm i wujie-vue2 -S vue3项目…

HttpServletRequest getHeader、getHeaders、getIntHeader、getDateHeader区别

request.getHeader(“name”)&#xff1a; 获取请求头中指定名称的单个值&#xff0c;多个值时通常是返回最先出现的那个值 String contentLength request.getHeader("Content-Length"); request.getHeaders(“name”)&#xff1a; 获取请求头中所有具有指定名称…

77. 组合 - 力扣(LeetCode)

题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 输入示例 n 4, k 2输出示例 [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]解题思路 我们使用回溯、深度优先遍历的思想&#xff0c;我们使用一个栈 path…

Kotlin的数据类

引言 我们在做项目中涉及到各种数据类的处理&#xff0c;很多很杂乱。难免一个人的知识点有盲点&#xff0c;所以想着做个整理。 定义 在平时的使用中&#xff0c;我们会用到一些类来保持一些数据或状态&#xff0c;我们习惯上成为bean或者entity&#xff0c;也有的定义为mod…

SpringSecurity+OAuth2.0 搭建认证中心和资源服务中心

目录 1. OAuth2.0 简介 2. 代码搭建 2.1 认证中心&#xff08;8080端口&#xff09; 2.2 资源服务中心&#xff08;8081端口&#xff09; 3. 测试结果 1. OAuth2.0 简介 OAuth 2.0&#xff08;开放授权 2.0&#xff09;是一个开放标准&#xff0c;用于授权第三方应用程序…