当前位置: 首页 > news >正文

零基础入门学用Arduino 第二部分(二)

重要的内容写在前面:

  1. 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。
  2. 个人把这个教程学完之后,整体感觉是很好的,如果有条件的可以先学习一些相关课程,学起来会更加轻松,相关课程有数字电路(强烈推荐先学数电,不然可能会有一些地方理解起来很困难)、模拟电路等,然后就是C++(注意C++是必学的)
  3. 文章中的代码都是跟着老师边学边敲的,不过比起老师的版本我还把注释写得详细了些,并且个人认为重要的地方都有详细的分析。
  4. 一些函数的介绍有参考太极创客官网给出的中文翻译,为了便于现查现用,把个人认为重要的部分粘贴了过来并做了一些修改。
  5. 如有错漏欢迎指正。

视频链接:2-1 MeArm项目概述_哔哩哔哩_bilibili

太极创客官网:太极创客 – Arduino, ESP8266物联网的应用、开发和学习资料

四、开发机械臂程序

1、准备工作

(1)电路部分按下图所示连接即可。

(2)连接完成后将下面的初始化调整程序下载到开发板中,让舵机转轴转到规定的初始位置。

#include <Servo.h> Servo base, rArm, fArm, claw ;  //建立4个舵机对象void setup() 
{ base.attach(11);     // base 伺服舵机连接引脚11 舵机代号'b'rArm.attach(10);     // rArm 伺服舵机连接引脚10 舵机代号'r'fArm.attach(9);      // fArm 伺服舵机连接引脚9  舵机代号'f'claw.attach(6);      // claw 伺服舵机连接引脚6  舵机代号'c'Serial.begin(9600);
} 
void loop() 
{ base.write(90); // 将base(底盘)舵机设置为初始位置delay(100);rArm.write(90); // 将rArm(后臂)舵机设置为初始位置delay(100);fArm.write(90); // 将fArm(前臂)舵机设置为初始位置delay(100);claw.write(90); // 将claw(钳子)舵机设置为初始位置delay(3000); 
} 

(3)将4个MeArm舵机摇臂按以下示意图装配到舵机上。在MeArm机械臂安装过程中不要让调整好的舵机摇臂转动,如不小心转动了已经调整好的舵机摇臂,需要将摇臂恢复图示状态或使用MeArm舵机初始化调整程序再次对舵机进行初始化调整。

(4)根据图纸或说明书或视频教程安装机械臂,安装完毕后查看电路连接是否出现问题(比如正负极短接、舵机引线接错、舵机未与Arduino共地等问题),然后再运行调试程序,看看机械臂会不会产生异动或者异响,及时调整舵机摇臂的位置或者更换有问题的舵机。

2、通过串口控制机械臂(一步到位)

(1)连接完成后将下面的程序下载到开发板中。

①全局变量及包含的头文件:

#include <Servo.h>                //使用servo库
Servo base, fArm, rArm, claw ;    //创建4个servo对象
//建立4个int型变量存储当前电机角度值,初始角度值为设备启动后初始状态所需要的电机角度数值
int basePos = 90;
int rArmPos = 90;
int fArmPos = 90;
int clawPos = 90;
//存储电机极限值(const指定该数值为常量,常量数值在程序运行中不能改变)
const int baseMIN = 0;
const int baseMAX = 180;
const int rArmMIN = 45;
const int rArmMAX = 180;
const int fArmMIN = 35;
const int fArmMAX = 120;
const int clawMIN = 25;
const int clawMAX = 100;

②初始化工作部分:

void setup()
{base.attach(11);     // base 伺服舵机连接引脚11 舵机代号'b'delay(200);          // 稳定性等待rArm.attach(10);     // rArm 伺服舵机连接引脚10 舵机代号'r'delay(200);          // 稳定性等待fArm.attach(9);      // fArm 伺服舵机连接引脚9  舵机代号'f'delay(200);          // 稳定性等待claw.attach(6);      // claw 伺服舵机连接引脚6  舵机代号'c'delay(200);          // 稳定性等待Serial.begin(9600); Serial.println("Welcome to Taichi-Maker Robot Arm Tutorial.");   
}

③循环工作部分:

void loop()
{//使用串口监视器输入电机指令控制机械臂电机if (Serial.available() > 0) { //指令举例: b45,将底盘舵机调整到45度位置char serialCmd = Serial.read();  //获取串口接收缓存中的一个字符armDataCmd(serialCmd);           //更改所记录的“当前舵机角度”}//根据记录的当前舵机角度进行设置base.write(basePos);delay(10);fArm.write(fArmPos); delay(10);rArm.write(rArmPos); delay(10);claw.write(clawPos);  delay(10);   
}

④更改所记录的“当前舵机角度”:

void armDataCmd(char serialCmd)
{Serial.print("serialCmd = ");Serial.print(serialCmd);  int servoData = Serial.parseInt();   //获取串口接收缓存中的整数数据作为角度值switch(serialCmd)    //根据命令的第一个字符判断需要控制哪个舵机{case 'b':  if(servoData > baseMAX) servoData = baseMAX;  //判断是否越上界if(servoData < baseMIN) servoData = baseMIN;  //判断是否越下界basePos = servoData;  //更改当前舵机角度Serial.print("  Set base servo value: ");Serial.println(servoData);break;case 'c':  if(servoData > clawMAX) servoData = clawMAX;  //判断是否越上界if(servoData < clawMIN) servoData = clawMIN;  //判断是否越下界clawPos = servoData;  //更改当前舵机角度Serial.print("  Set claw servo value: ");Serial.println(servoData);break;case 'f':  if(servoData > fArmMAX) servoData = fArmMAX;  //判断是否越上界if(servoData < fArmMIN) servoData = fArmMIN;  //判断是否越下界fArmPos = servoData;  //更改当前舵机角度Serial.print("  Set fArm servo value: ");Serial.println(servoData);break;case 'r':  if(servoData > rArmMAX) servoData = rArmMAX;  //判断是否越上界if(servoData < rArmMIN) servoData = rArmMIN;  //判断是否越下界rArmPos = servoData;  //更改当前舵机角度Serial.print("  Set rArm servo value: ");Serial.println(servoData);break;case 'o':  reportStatus();break;default:Serial.println(" Unknown Command.");}  
}
void reportStatus()
{Serial.println("");Serial.println("");Serial.println("++++++ Robot-Arm Status Report +++++");Serial.print("Claw Position: clawPos = "); Serial.println(claw.read());Serial.print("Base Position: basePos = "); Serial.println(base.read());Serial.print("Rear  Arm Position: rArmPos = "); Serial.println(rArm.read());Serial.print("Front Arm Position: fArmPos = "); Serial.println(fArm.read());Serial.println("++++++++++++++++++++++++++++++++++++");Serial.println("");
}

(2)然后进行人工调试。

①通过串口助手向Arduino发送内容“b45”,机械臂的base舵机摇臂将立刻旋转至45°的位置,同理可调试其它3个舵机。

②通过串口助手向Arduino发送内容“b200”,由于200°超出base舵机的上界180°,机械臂的base舵机摇臂将立刻旋转至上界180°的位置,同理可调试其它3个舵机的上下界。(需要注意的是,上下界指的是机械臂舵机能达到的不损坏机械臂时的最大/小角度,这个角度可以对四个舵机分别进行调试而得出,每个机械臂的舵机旋转上下界可能略有差异,但只要每个舵机都经过正确的初始化调整,差异应该是很小的)

3、通过串口控制机械臂(有缓慢转动的过程)

(1)在上例中,通过Arduino直接控制舵机旋转,会发现舵机摇臂旋转的速度非常快,然而现实中大多自动工作的机械臂都是缓慢转动的,如果每一个动作都是“一气呵成”,这将增加非常多不必要的麻烦与危险,为了让机械臂缓慢转动,可以将一次大幅度的转动分成若干次小幅度的转动完成,每次小幅度转动间隔一定的时间,这样即可实现机械臂的缓慢转动

(2)连接完成后将下面的初始化调整程序下载到开发板中,然后进行人工调试。

①全局变量及包含的头文件:

#include <Servo.h>                //使用servo库
Servo base, fArm, rArm, claw ;    //创建4个servo对象
//建立4个int型变量存储当前电机角度值,初始角度值为设备启动后初始状态所需要的电机角度数值
int basePos = 90;
int rArmPos = 90;
int fArmPos = 90;
int clawPos = 90;
//存储电机极限值(const指定该数值为常量,常量数值在程序运行中不能改变)
const int baseMIN = 0;
const int baseMAX = 180;
const int rArmMIN = 45;
const int rArmMAX = 180;
const int fArmMIN = 35;
const int fArmMAX = 120;
const int clawMIN = 25;
const int clawMAX = 100;

②初始化工作部分:

void setup()
{base.attach(11);     //base 伺服舵机连接引脚11 舵机代号'b'delay(200);          //稳定性等待rArm.attach(10);     //rArm 伺服舵机连接引脚10 舵机代号'r'delay(200);          //稳定性等待fArm.attach(9);      //fArm 伺服舵机连接引脚9  舵机代号'f'delay(200);          //稳定性等待claw.attach(6);      //claw 伺服舵机连接引脚6  舵机代号'c'delay(200);          //稳定性等待Serial.begin(9600); Serial.println("Welcome to Taichi-Maker Robot Arm Tutorial.");   
}

③循环工作部分:

void loop()
{//使用串口监视器输入电机指令控制机械臂电机if (Serial.available() > 0) { //指令举例: b45,将底盘舵机调整到45度位置char serialCmd = Serial.read();  //获取串口接收缓存中的一个字符armDataCmd(serialCmd);           //更改所记录的“当前舵机角度”}//根据记录的当前舵机角度进行设置base.write(basePos);delay(10);fArm.write(fArmPos); delay(10);rArm.write(rArmPos); delay(10);claw.write(clawPos);  delay(10);   
}

④更改所记录的“当前舵机角度”:(reportStatus函数的实现沿用上例即可)

void armDataCmd(char serialCmd)
{Serial.print("serialCmd = ");Serial.print(serialCmd);  int servoData = Serial.parseInt();  //获取串口接收缓存中的整数数据作为角度值int fromPos, toPos;switch(serialCmd)   //根据命令的第一个字符判断需要控制哪个舵机{case 'b':  fromPos = base.read();    //读取base舵机的当前角度值toPos = servoData;        //命令中的角度值作为调整后角度值if(servoData > baseMAX) servoData = baseMAX;  //判断是否越上界if(servoData < baseMIN) servoData = baseMIN;  //判断是否越下界if (fromPos <= toPos) //如果“起始角度值”小于“目标角度值” ,每15ms向目标转动1°for (int i=fromPos; i<=toPos; i++){base.write(i);delay(15);}else                //否则“起始角度值”大于“目标角度值”,每15ms向目标转动1°for (int i=fromPos; i>=toPos; i--){base.write(i);delay(15);}basePos = servoData;Serial.print("  Set base servo value: ");Serial.println(servoData);break;case 'c':fromPos = claw.read();    //读取claw舵机的当前角度值toPos = servoData;        //命令中的角度值作为调整后角度值if(servoData > clawMAX) servoData = clawMAX;  //判断是否越上界if(servoData < clawMIN) servoData = clawMIN;  //判断是否越下界if (fromPos <= toPos) //如果“起始角度值”小于“目标角度值” ,每15ms向目标转动1°for (int i=fromPos; i<=toPos; i++){claw.write(i);delay(15);}else                //否则“起始角度值”大于“目标角度值”,每15ms向目标转动1°for (int i=fromPos; i>=toPos; i--){claw.write(i);delay(15);}clawPos = servoData;Serial.print("  Set claw servo value: ");Serial.println(servoData);break;  case 'f':  fromPos = fArm.read();    //读取fArm舵机的当前角度值toPos = servoData;        //命令中的角度值作为调整后角度值if(servoData > fArmMAX) servoData = fArmMAX;  //判断是否越上界if(servoData < fArmMIN) servoData = fArmMIN;  //判断是否越下界if (fromPos <= toPos) //如果“起始角度值”小于“目标角度值” ,每15ms向目标转动1°for (int i=fromPos; i<=toPos; i++){fArm.write(i);delay(15);}else                //否则“起始角度值”大于“目标角度值”,每15ms向目标转动1°for (int i=fromPos; i>=toPos; i--){fArm.write(i);delay(15);}fArmPos = servoData;Serial.print("  Set fArm servo value: ");Serial.println(servoData);break;case 'r':  fromPos = rArm.read();    //读取rArm舵机的当前角度值toPos = servoData;        //命令中的角度值作为调整后角度值if(servoData > rArmMAX) servoData = rArmMAX;  //判断是否越上界if(servoData < rArmMIN) servoData = rArmMIN;  //判断是否越下界if (fromPos <= toPos) //如果“起始角度值”小于“目标角度值” ,每15ms向目标转动1°for (int i=fromPos; i<=toPos; i++){rArm.write(i);delay(15);}else                //否则“起始角度值”大于“目标角度值”,每15ms向目标转动1°for (int i=fromPos; i>=toPos; i--){rArm.write(i);delay(15);}rArmPos = servoData;Serial.print("  Set rArm servo value: ");Serial.println(servoData);break;case 'o': reportStatus();break;default: Serial.println(" Unknown Command.");}  
}

(3)然后进行人工调试。

①通过串口助手向Arduino发送内容“b45”,机械臂的base舵机摇臂将缓慢地旋转至45°的位置,同理可调试其它3个舵机。

②通过串口助手向Arduino发送内容“b200”,由于200°超出base舵机的上界180°,机械臂的base舵机摇臂将缓慢地旋转至180°的位置,同理可调试其它3个舵机的上下界。(需要注意的是,每个机械臂的舵机旋转上下界可能略有差异,但只要每个舵机都经过正确的初始化调整,差异应该是很小的)

4、通过串口控制机械臂(有设置快捷指令)

(1)电路连接完成后将下面的程序下载到开发板中。

①全局变量及包含的头文件:

#include <Servo.h>                //使用servo库
Servo base, fArm, rArm, claw ;    //创建4个servo对象//存储电机极限值(const指定该数值为常量,常量数值在程序运行中不能改变)
const int baseMin = 0;
const int baseMax = 180;
const int rArmMin = 45;
const int rArmMax = 180;
const int fArmMin = 35;
const int fArmMax = 120;
const int clawMin = 25;
const int clawMax = 100;int DSD = 15; //Default Servo Delay (默认电机运动延迟时间)
//此变量用于控制电机运行速度,增大此变量数值将降低电机运行速度,从而控制机械臂动作速度

②初始化工作部分:

void setup()
{base.attach(11);     //base 伺服舵机连接引脚11 舵机代号'b'delay(200);          //稳定性等待rArm.attach(10);     //rArm 伺服舵机连接引脚10 舵机代号'r'delay(200);          //稳定性等待fArm.attach(9);      //fArm 伺服舵机连接引脚9  舵机代号'f'delay(200);          //稳定性等待claw.attach(6);      //claw 伺服舵机连接引脚6  舵机代号'c'delay(200);          //稳定性等待base.write(90); delay(10);     //base 伺服舵机旋转角度初始化+稳定性等待fArm.write(90); delay(10);     //fArm 伺服舵机旋转角度初始化+稳定性等待rArm.write(90); delay(10);     //rArm 伺服舵机旋转角度初始化+稳定性等待claw.write(90); delay(10);     //claw 伺服舵机旋转角度初始化+稳定性等待Serial.begin(9600); Serial.println("Welcome to Taichi-Maker Robot Arm Tutorial");   
}

③循环工作部分:

void loop()
{if (Serial.available() > 0) {  char serialCmd = Serial.read();  //获取指令中的第一个字符armDataCmd(serialCmd);           //根据串行指令执行相应操作}
}void armDataCmd(char serialCmd)
{if (serialCmd == 'b' || serialCmd == 'c' || serialCmd == 'f' || serialCmd == 'r')  //如果第一个字符是舵机代号{int servoData = Serial.parseInt();    //获取指令中的整数数据servoCmd(serialCmd, servoData, DSD);  //调用机械臂舵机运行函数(参数:舵机名,目标角度,单次延迟时间)} else {switch(serialCmd){    case 'o':  //输出舵机状态信息reportStatus();break;case 'i':  //机械臂初始化armIniPos();break;     default:   //未知指令反馈Serial.println("Unknown Command.");}}  
}

④机械臂舵机运行函数:

void servoCmd(char servoName, int toPos, int servoDelay)
{  Servo servo2go;  //创建servo对象//串口监视器输出接收指令信息Serial.println("");Serial.print("+Command: Servo ");Serial.print(servoName);Serial.print(" to ");Serial.print(toPos);Serial.print(" at servoDelay value ");Serial.print(servoDelay);Serial.println(".");Serial.println("");  int fromPos; //建立变量,存储电机起始运动角度值switch(servoName)   //根据命令的第一个字符判断需要控制哪个舵机{case 'b':if(toPos >= baseMin && toPos <= baseMax){  //判断是否越界,越界就报错servo2go = base;        //把对象base拷贝到servo2gofromPos = base.read();  //获取当前base电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: Base Servo Value Out Of Limit!");return;}case 'c':if(toPos >= clawMin && toPos <= clawMax){  //判断是否越界,越界就报错servo2go = claw;        //把对象claw拷贝到servo2gofromPos = claw.read();  //获取当前claw电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: Claw Servo Value Out Of Limit!");return;}case 'f':if(toPos >= fArmMin && toPos <= fArmMax){  //判断是否越界,越界就报错servo2go = fArm;        //把对象fArm拷贝到servo2gofromPos = fArm.read();  //获取当前fArm电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: fArm Servo Value Out Of Limit!");return;}case 'r':if(toPos >= rArmMin && toPos <= rArmMax){  //判断是否越界,越界就报错servo2go = rArm;        //把对象rArm拷贝到servo2gofromPos = rArm.read();  //获取当前rArm电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: rArm Servo Value Out Of Limit!");return;}  }//通过对象servo2go指挥电机运行if (fromPos <= toPos) //如果“起始角度值”小于“目标角度值”for (int i=fromPos; i<=toPos; i++){servo2go.write(i);delay(servoDelay);}else                  //否则“起始角度值”大于“目标角度值”for (int i=fromPos; i>=toPos; i--){servo2go.write(i);delay(servoDelay);}
}

⑤报告舵机当前角度函数:

void reportStatus()
{Serial.println("");Serial.println("");Serial.println("+ Robot-Arm Status Report +");Serial.print("Claw Position: "); Serial.println(claw.read());Serial.print("Base Position: "); Serial.println(base.read());Serial.print("Rear  Arm Position:"); Serial.println(rArm.read());Serial.print("Front Arm Position:"); Serial.println(fArm.read());Serial.println("++++++++++++++++++++++++++");Serial.println("");
}

⑥机械臂重新初始化函数:

void armIniPos()
{Serial.println("+Command: Restore Initial Position.");int robotIniPosArray[4][3] =   //使用二维数组存储4个舵机的初始化信息{/*  舵机代号 目标角度 单次延迟  */{    'b',     90,    DSD},{    'r',     90,    DSD},{    'f',     90,    DSD},{    'c',     90,    DSD} };   for (int i = 0; i < 4; i++)  //调用4次机械臂舵机运行函数,分别初始化4个舵机{servoCmd(robotIniPosArray[i][0], robotIniPosArray[i][1], robotIniPosArray[i][2]);}
}

(2)然后进行人工调试。

①通过串口助手向Arduino发送内容“b45”,机械臂的base舵机摇臂将缓慢地旋转至45°的位置,同理可调试其它3个舵机。

②通过串口助手向Arduino发送内容“b200”,由于200°超出base舵机的上界180°,机械臂的base舵机不会有任何动作,同时Arduino通过串口报指令有误,同理可调试其它3个舵机。

③通过串口助手向Arduino发送内容“o”,Arduino将通过串口发送四个舵机当前的状态。

④通过串口助手向Arduino发送内容“i”,Arduino将控制四个舵机恢复初始状态。

5、通过串口控制机械臂(设有手柄控制方式)

(1)电路连接完成后将下面的程序下载到开发板中。

①全局变量及包含的头文件:

#include <Servo.h>                //使用servo库
Servo base, fArm, rArm, claw ;    //创建4个servo对象//存储电机极限值(const指定该数值为常量,常量数值在程序运行中不能改变)
const int baseMin = 0;
const int baseMax = 180;
const int rArmMin = 45;
const int rArmMax = 180;
const int fArmMin = 35;
const int fArmMax = 120;
const int clawMin = 25;
const int clawMax = 100;int DSD = 15; //Default Servo Delay (默认电机运动延迟时间)
//此变量用于控制电机运行速度,增大此变量数值将降低电机运行速度,从而控制机械臂动作速度bool mode;          //记录当前的模式:mode = 1 —— 指令模式,mode = 0 —— 手柄模式
int moveStep = 3;  //每一次按下手柄按键的舵机移动量(仅适用于手柄模式)

②初始化工作部分:

void setup()
{base.attach(11);     //base 伺服舵机连接引脚11 舵机代号'b'delay(200);          //稳定性等待rArm.attach(10);     //rArm 伺服舵机连接引脚10 舵机代号'r'delay(200);          //稳定性等待fArm.attach(9);      //fArm 伺服舵机连接引脚9  舵机代号'f'delay(200);          //稳定性等待claw.attach(6);      //claw 伺服舵机连接引脚6  舵机代号'c'delay(200);          //稳定性等待base.write(90); delay(10);     //base 伺服舵机旋转角度初始化+稳定性等待fArm.write(90); delay(10);     //fArm 伺服舵机旋转角度初始化+稳定性等待rArm.write(90); delay(10);     //rArm 伺服舵机旋转角度初始化+稳定性等待claw.write(90); delay(10);     //claw 伺服舵机旋转角度初始化+稳定性等待Serial.begin(9600); Serial.println("Welcome to Taichi-Maker Robot Arm Tutorial");   
}

③循环工作部分:

void loop()
{if (Serial.available()>0) {  char serialCmd = Serial.read();  //获取指令中的第一个字符if(mode == 1)  //根据mode判断现在处于什么模式{armDataCmd(serialCmd);  //指令模式} else {armJoyCmd(serialCmd);   //手柄模式}}
}

④指令模式下的处理逻辑:

void armDataCmd(char serialCmd)
{//判断用户是否因搞错模式而输入错误的指令信息(即指令模式下输入手柄按键信息)if (   serialCmd == 'w' || serialCmd == 's' || serialCmd == 'a' || serialCmd == 'd'|| serialCmd == '5' || serialCmd == '4' || serialCmd == '6' || serialCmd == '8' ){Serial.println("+Warning: Robot in Instruction Mode..."); delay(100);while(Serial.available() > 0) char wrongCommand = Serial.read();  //清除串口缓存中的错误指令return;}                if (serialCmd == 'b' || serialCmd == 'c' || serialCmd == 'f' || serialCmd == 'r'){int servoData = Serial.parseInt();servoCmd(serialCmd, servoData, DSD);  //调用机械臂舵机运行函数(参数:舵机名,目标角度,单次延迟)} elseswitch(serialCmd){   case 'm':    //切换至手柄模式 mode = 0; Serial.println("Command: Switch to Joy-Stick Mode.");break;case 'o':    //输出舵机状态信息reportStatus();break;case 'i':    //机械臂初始化armIniPos();break;  default:     //未知指令反馈Serial.println("Unknown Command.");}  
}

⑤手柄模式下的处理逻辑:

void armJoyCmd(char serialCmd)
{//判断用户是否因搞错模式而输入错误的指令信息(即手柄模式下输入舵机指令)if (serialCmd == 'b' || serialCmd == 'c' || serialCmd == 'f' || serialCmd == 'r'){Serial.println("+Warning: Robot in Joy-Stick Mode...");delay(100);while(Serial.available()>0) char wrongCommand = Serial.read();  //清除串口缓存中的错误指令return;} int baseJoyPos, rArmJoyPos, fArmJoyPos, clawJoyPos;switch(serialCmd){case 'a':  //Base向左Serial.println("Received Command: Base Turn Left");                baseJoyPos = base.read() - moveStep;  //目标角度=当前角度-单次操作移动角度servoCmd('b', baseJoyPos, DSD);break; //调用机械臂舵机运行函数 case 'd':  //Base向右Serial.println("Received Command: Base Turn Right");                baseJoyPos = base.read() + moveStep;  //目标角度=当前角度+单次操作移动角度servoCmd('b', baseJoyPos, DSD);break; //调用机械臂舵机运行函数        case 's':  //rArm向下Serial.println("Received Command: Rear Arm Down");                rArmJoyPos = rArm.read() + moveStep;  //目标角度=当前角度+单次操作移动角度servoCmd('r', rArmJoyPos, DSD);break; //调用机械臂舵机运行函数              case 'w':  //rArm向上Serial.println("Received Command: Rear Arm Up");     rArmJoyPos = rArm.read() - moveStep;  //目标角度=当前角度-单次操作移动角度servoCmd('r', rArmJoyPos, DSD);break; //调用机械臂舵机运行函数  case '8':  //fArm向上Serial.println("Received Command: Front Arm Up");        fArmJoyPos = fArm.read() + moveStep;  //目标角度=当前角度+单次操作移动角度servoCmd('f', fArmJoyPos, DSD);break; //调用机械臂舵机运行函数  case '5':  //fArm向下Serial.println("Received Command: Front Arm Down");        fArmJoyPos = fArm.read() - moveStep;  //目标角度=当前角度-单次操作移动角度servoCmd('f', fArmJoyPos, DSD);break; //调用机械臂舵机运行函数  case '4':  //Claw关闭Serial.println("Received Command: Claw Close Down");        clawJoyPos = claw.read() + moveStep;  //目标角度=当前角度+单次操作移动角度servoCmd('c', clawJoyPos, DSD);break; //调用机械臂舵机运行函数  case '6':  //Claw打开Serial.println("Received Command: Claw Open Up");     clawJoyPos = claw.read() - moveStep;  //目标角度=当前角度-单次操作移动角度servoCmd('c', clawJoyPos, DSD);break; //调用机械臂舵机运行函数  case 'm':   //切换至指令模式 mode = 1; Serial.println("Command: Switch to Instruction Mode.");break;case 'o':   //输出舵机状态信息reportStatus();break;case 'i':   //机械臂初始化armIniPos();break;default:    //未知指令反馈Serial.println("Unknown Command.");return;}  
}

⑥报告舵机当前角度函数:

void reportStatus()
{Serial.println("");Serial.println("");Serial.println("+ Robot-Arm Status Report +");Serial.print("Claw Position: "); Serial.println(claw.read());Serial.print("Base Position: "); Serial.println(base.read());Serial.print("Rear  Arm Position:"); Serial.println(rArm.read());Serial.print("Front Arm Position:"); Serial.println(fArm.read());Serial.println("++++++++++++++++++++++++++");Serial.println("");
}

⑦机械臂重新初始化函数:

void armIniPos()
{Serial.println("+Command: Restore Initial Position.");int robotIniPosArray[4][3] =   //使用二维数组存储4个舵机的初始化信息{/*  舵机代号 目标角度 单次延迟  */{    'b',     90,    DSD},{    'r',     90,    DSD},{    'f',     90,    DSD},{    'c',     90,    DSD} };   for (int i = 0; i < 4; i++)  //调用4次机械臂舵机运行函数,分别初始化4个舵机{servoCmd(robotIniPosArray[i][0], robotIniPosArray[i][1], robotIniPosArray[i][2]);}
}

⑧机械臂舵机运行函数:

void servoCmd(char servoName, int toPos, int servoDelay)
{  Servo servo2go;  //创建servo对象//串口监视器输出接收指令信息Serial.println("");Serial.print("+Command: Servo ");Serial.print(servoName);Serial.print(" to ");Serial.print(toPos);Serial.print(" at servoDelay value ");Serial.print(servoDelay);Serial.println(".");Serial.println("");  int fromPos; //建立变量,存储电机起始运动角度值switch(servoName){case 'b':if(toPos >= baseMin && toPos <= baseMax){  //判断是否越界,越界就报错servo2go = base;        //把对象base拷贝到servo2gofromPos = base.read();  //获取当前base电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: Base Servo Value Out Of Limit!");return;}case 'c':if(toPos >= clawMin && toPos <= clawMax){  //判断是否越界,越界就报错servo2go = claw;        //把对象claw拷贝到servo2gofromPos = claw.read();  //获取当前claw电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: Claw Servo Value Out Of Limit!");return;}case 'f':if(toPos >= fArmMin && toPos <= fArmMax){  //判断是否越界,越界就报错servo2go = fArm;        //把对象fArm拷贝到servo2gofromPos = fArm.read();  //获取当前fArm电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: fArm Servo Value Out Of Limit!");return;}case 'r':if(toPos >= rArmMin && toPos <= rArmMax){  //判断是否越界,越界就报错servo2go = rArm;        //把对象rArm拷贝到servo2gofromPos = rArm.read();  //获取当前rArm电机角度值用于“电机运动起始角度值”break;} else {Serial.println("+Warning: rArm Servo Value Out Of Limit!");return;}  }//通过对象servo2go指挥电机运行if (fromPos <= toPos) //如果“起始角度值”小于“目标角度值”for (int i=fromPos; i<=toPos; i++){servo2go.write(i);delay(servoDelay);}else                  //否则“起始角度值”大于“目标角度值”for (int i=fromPos; i>=toPos; i--){servo2go.write(i);delay(servoDelay);}
}

(2)根据程序注释进行人工调试。

6、配合HC-06蓝牙模块控制机械臂

(1)按照下图所示将电路连接好。

(2)沿用上例的程序即可,手机连接上HC-06蓝牙模块,接着打开配套软件的手柄操作界面,设置好每个键所对应的指令信息,在机械臂处于手柄操作模式的前提下对其进行调试。

相关文章:

零基础入门学用Arduino 第二部分(二)

重要的内容写在前面&#xff1a; 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。个人把这个教程学完之后&#xff0c;整体感觉是很好的&#xff0c;如果有条件的可以先学习一些相关课程&#xff0c;学起来会更加轻松&#xff0c;相关课程有数字电路…...

旅游行业电商平台:数字化转型的引擎与未来发展趋势

引言 旅游行业数字化转型的背景和重要性 随着信息技术的飞速发展&#xff0c;数字化转型成为各行业发展的必然趋势。旅游行业&#xff0c;作为一个高度依赖信息和服务的领域&#xff0c;数字化转型尤为重要。通过数字化手段&#xff0c;旅游行业能够实现资源的高效配置、服务的…...

Ubuntu 22.04安装 docker

安装过程和指令 # 1.升级 apt sudo apt update # 2.安装docker sudo apt install docker.io docker-compose # 3.将当前用户加入 docker组 sudo usermod -aG docker ${USER} # 4. 重启 # 5. 查看镜像 docker ps -a 或者 docker images # 6. 下载镜像 docker pull hello-world …...

【Gitlab】访问默认PostgreSQL数据库

本地访问PostgreSQL gitlab有可以直接访问内部PostgreSQL的命令 sudo gitlab-rails dbconsole # 或者 sudo gitlab-psql -d gitlabhq_production效果截图 常用SQL # 查看用户状态 select id,name,email,state,last_sign_in_at,updated_at,last_credential_check_at,last_act…...

乐鑫ESP32-C3芯片应用,启明云端WT32C3-S5模组:简化产品硬件设计

在数字化浪潮的推动下&#xff0c;物联网(IoT)正迅速成为连接现实世界与数字世界的桥梁。芯片作为智能设备的心脏&#xff0c;其重要性不言而喻。 乐鑫推出的ESP32-C3芯片以其卓越的性能和丰富的功能&#xff0c;为智能物联网领域带来了新的活力&#xff0c;我将带您深入了解这…...

算法刷题【二分法】

题目&#xff1a; 注意题目中说明了数据时非递减的&#xff0c;那么这样就存在二分性&#xff0c;能够实现logn的复杂度。二分法每次只能取寻找特定的某一个值&#xff0c;所以我们要分别求左端点和有端点。 分析第一组用例得到结果如下: 成功找到左端点8 由此可知&#xff0…...

.NET MAUI Sqlite程序应用-数据库配置(一)

项目名称:Ownership&#xff08;权籍信息采集&#xff09; 一、安装 NuGet 包 安装 sqlite-net-pcl 安装 SQLitePCLRawEx.bundle_green 二、创建多个表及相关字段 Models\OwnershipItem.cs using SQLite;namespace Ownership.Models {public class fa_rural_base//基础数据…...

基于WPF技术的换热站智能监控系统09--封装水泵对象

1、添加用户控件 2、编写水泵UI 控件中用到了Viewbox控件&#xff0c;Viewbox控件是WPF中一个简单的缩放工具&#xff0c;它可以帮助你放大或缩小单个元素&#xff0c;同时保持其宽高比。通过样式和属性设置&#xff0c;你可以创建出既美观又功能丰富的用户界面。在实际开发中…...

GLM+vLLM 部署调用

GLMvLLM 部署调用 vLLM 简介 vLLM 框架是一个高效的大型语言模型&#xff08;LLM&#xff09;推理和部署服务系统&#xff0c;具备以下特性&#xff1a; 高效的内存管理&#xff1a;通过 PagedAttention 算法&#xff0c;vLLM 实现了对 KV 缓存的高效管理&#xff0c;减少了…...

leetcode 122 买卖股票的最佳时机||(动态规划解法)

题目分析 题目描述的已经十分清楚了&#xff0c;不做过多阐述 算法原理 状态表示 我们假设第i天的最大利润是dp[i] 我们来画一下状态机 有两个状态&#xff0c;买入后和卖出后&#xff0c;我们就可以使用两个dp表来解决问题 f[i]表示当天买入后的最大利润 g[i]表示当天卖出…...

C++设计模式---组合模式

1、介绍 组合模式&#xff08;Composite&#xff09;是一种结构型设计模式&#xff0c;也被称为部分-整体模式。它将复杂对象视为由多个简单对象&#xff08;称为“组件”&#xff09;组成的树形结构&#xff0c;这些组件能够共享相同的行为。每个组件都可能包含一个或多个子组…...

工厂方法模式(大话设计模式)C/C++版本

工厂方法模式 C 参考&#xff1a;https://www.cnblogs.com/Galesaur-wcy/p/15926711.html #include <iostream> #include <memory> using namespace std;// 运算类 class Operation { private:double _NumA;double _NumB;public:void SetNumA(){cout << &…...

[NCTF 2018]flask真香

打开题目后没有提示框&#xff0c;尝试扫描后也没有什么结果&#xff0c;猜想是ssti。所以尝试寻找ssti的注入点并判断模版。 模版判断方式&#xff1a; 在url地址中输入{7*7} 后发现不能识别执行。 尝试{{7*7}} ,执行成功&#xff0c;继续往下走注入{{7*7}}&#xff0c;如果执…...

性能测试3【搬代码】

1.Linux服务器性能分析命令及详解 2.GarafanainfluxDB监控jmeter数据 3.GarafanaPrometheus监控服务器和数据库性能 4.性能瓶颈分析以及性能调优方案详解 一、无界面压测时&#xff0c; top load average:平均负载 htop 二、Garafana监控平台 传统项目&#xff1a;centosphpm…...

<tesseract><opencv><Python>基于python和opencv,使用ocr识别图片中的文本并进行替换

前言 本文是在python中,利用opencv处理图片,利用tesseractOCR来识别图片中的文本并进行替换的一种实现方法。 环境配置 系统:windows 平台:visual studio code 语言:python 库:pyqt5、opencv、tesseractOCR 代码介绍 本文程序功能实现,主要依赖于tesseractOCR这个库,…...

海南云亿商务咨询有限公司解锁抖音电商新纪元

在当今数字化浪潮中&#xff0c;抖音电商以其独特的魅力和强大的用户基础&#xff0c;迅速成为企业营销的新宠。海南云亿商务咨询有限公司&#xff0c;作为专注于抖音电商服务的领先企业&#xff0c;凭借专业的团队和丰富的经验&#xff0c;为众多企业提供了高效、精准的电商服…...

arm64架构 统信UOS搭建PXE无盘启动Linux系统(麒麟桌面为例)

arm64架构 统信UOS搭建PXE无盘启动Linux系统&#xff08;麒麟桌面为例&#xff09; 搞了好久搞得头疼哎 1、准备服务器UOS服务器 准备服务IP 这里是192.168.1.100 1.1、安装程序 yum install -y dhcp tftp tftp-server xinetd nfs-utils rpcbind 2、修改配置 2.1、修改dhcpd.c…...

SpringBoot 实现 阿里云语音通知(SingleCallByTts)

目录 一、准备工作1.开通 阿里云语音服务2.申请企业资质3.创建语音通知模板&#xff0c;审核通过4.调用API接口---SingleCallByTts5.调试API接口---SingleCallByTts 二、代码实现1.导入依赖 com.aliyun:aliyun-java-sdk-dyvmsapi:3.0.22.创建工具类&#xff0c;用于发送语音通知…...

IDEA 连接GitHub仓库并上传项目(同时解决SSH问题)

目录 1 确认自己电脑上已经安装好Git 2 添加GitHub账号 2.1 Setting -> 搜索GitHub-> ‘’ -> Log In with Token 2.2 点击Generate 去GitHub生成Token 2.3 勾选SSH后其他不变直接生成token 2.4 然后复制token添加登录账号即可 3 点击导航栏中VCS -> Create…...

vue/react/js 常用的原生获取当前页面的url网址的相关方法

目录 第一章 场景 第二章 总结 第一章 场景 最近实现需求时遇到这么一种情况&#xff1a; 本地url —— 线上url —— 需求&#xff1a;需要将token清除掉 注意事项&#xff1a;token不是#/后面的参数&#xff0c;说明并不是我们前端返回的&#xff0c;vue路由的方法使用不…...

java-final 关键字

## Java中的final关键字 ### 1. final关键字的基本概念 final是Java中一个非常重要的关键字&#xff0c;用于声明常量、阻止继承和重写&#xff0c;确保类、方法和变量的不可变性。具体来说&#xff0c;final关键字可以用来修饰类、方法和变量&#xff08;包括成员变量和局部…...

ARM32开发--IIC软实现

知不足而奋进 望远山而前行 目录 文章目录 前言 开发流程 GD32F4软件I2C初始化 GD32F4软件I2C引脚功能 写操作 读操作 总结 前言 在嵌入式系统开发中&#xff0c;软件实现的I2C通信协议扮演着至关重要的角色。本文将深入探讨如何在GD32F4系列微控制器上实现软件I2C功能…...

在有向无环图(DAG)中实现拓扑排序与最短路径和最长路径算法

有向无环图&#xff08;DAG&#xff09;是一类非常重要的图结构&#xff0c;广泛应用于任务调度、数据依赖分析等领域。本文将介绍如何在DAG中实现拓扑排序、单源最短路径和单源最长路径算法&#xff0c;并提供完整的Java代码示例。 图结构定义 首先&#xff0c;我们定义一个…...

SQLServer按照年龄段进行分组查询数据

1.按照年龄段对数据进行分组&#xff0c; 将人群分为&#xff1a;青年&#xff0c;中年&#xff0c;老年三种类型&#xff0c;人群类型加上其他分组字段如&#xff1a;性别&#xff0c;进行多条件分组,统计各个年龄段多少人 Select case sex when 1 then ‘男’ when 2 then …...

开放式耳机哪个品牌质量比较好?2024高性价比机型推荐!

随着音乐技术的不断发展&#xff0c;开放式耳机已成为音乐发烧友们的另外一种选择。从最初的简单音质&#xff0c;到如今的高清解析&#xff0c;开放式耳机不断进化升级。音质纯净&#xff0c;佩戴舒适&#xff0c;无论是街头漫步还是家中放松时候&#xff0c;都能带给你身临其…...

Blender骨骼创建

骨骼系统 建立 使用Shift A添加骨骼或在添加|骨架中添加一段骨骼 骨骼的三种模式 -物体模式&#xff1a;做动画&#xff0c;摆人物pose时在该模式 -编辑模式&#xff1a;进行骨骼搭建&#xff08;选择一段骨骼&#xff0c;然后按E挤出一段骨骼并进行调整&#xff09; -姿…...

DevExpress WPF中文教程:Grid - 如何完成列和编辑器配置(设计时)?

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…...

高考完的三个月想自学点编程,有没有什么建议

&#x1f446;点击关注 获取更多编程干货&#x1f446; 对于刚刚完成高考的学生来说&#xff0c;无论未来是否选择计算机科学作为专业方向&#xff0c;自学编程技能是一项非常有价值的投资&#xff0c;掌握编程知识能够帮助同学们为将来的学习和科研 实践奠定一个基础。 随着…...

运维开发(DevOps):加速软件交付的关键方法

1. 什么是运维开发 运维开发&#xff08;DevOps&#xff09;是将软件开发&#xff08;Development&#xff09;与信息技术运维&#xff08;Operations&#xff09;的流程整合在一起的实践方法。DevOps的目标是通过增强开发和运维团队之间的协作&#xff0c;提高软件产品的发布…...

Vue前端环境搭建:从四个方面、五个方面、六个方面和七个方面深度解析

Vue前端环境搭建&#xff1a;从四个方面、五个方面、六个方面和七个方面深度解析 在构建Vue.js项目时&#xff0c;搭建一个稳定且高效的前端环境至关重要。这不仅关乎项目的顺利推进&#xff0c;更直接影响开发者的效率和代码质量。本文将从四个方面、五个方面、六个方面和七个…...