Files
WAVE_ROVER/RoArm-M2_module.h

1303 lines
42 KiB
C

#define ANG2DEG 0.017453292
// Instantiate a servo control object.
SMS_STS st;
// place holder.
void serialCtrl();
// Used to store the feedback information from the servo.
struct ServoFeedback {
bool status;
int pos;
int speed;
int load;
float voltage;
float current;
float temper;
byte mode;
};
ServoFeedback servoFeedback[5];
// [0] BASE_SERVO_ID
// [1] SHOULDER_DRIVING_SERVO_ID
// [2] SHOULDER_DRIVEN_SERVO_ID
// [3] ELBOW_SERVO_ID
// [4] GRIPPER_SERVO_ID
// input the angle in radians, and it returns the number of servo steps.
double calculatePosByRad(double radInput) {
return round((radInput / (2 * M_PI)) * ARM_SERVO_POS_RANGE);
}
double ang2deg(double inputAng) {
return (inputAng / 180) * M_PI;
}
// input the number of servo steps and the joint name
// return the joint angle in radians.
double calculateRadByFeedback(int inputSteps, int jointName) {
double getRad;
switch(jointName){
case BASE_JOINT:
getRad = -(inputSteps * 2 * M_PI / ARM_SERVO_POS_RANGE) + M_PI;
break;
case SHOULDER_JOINT:
getRad = (inputSteps * 2 * M_PI / ARM_SERVO_POS_RANGE) - M_PI;
break;
case ELBOW_JOINT:
getRad = (inputSteps * 2 * M_PI / ARM_SERVO_POS_RANGE) - (M_PI / 2);
break;
case EOAT_JOINT:
getRad = inputSteps * 2 * M_PI / ARM_SERVO_POS_RANGE;
break;
}
return getRad;
}
// input the ID of the servo,
// and get the information saved in servoFeedback[5].
// returnType: false - return everything.
// true - return only when failed.
bool getFeedback(byte servoID, bool returnType) {
if(st.FeedBack(servoID)!=-1) {
servoFeedback[servoID - 11].status = true;
servoFeedback[servoID - 11].pos = st.ReadPos(-1);
servoFeedback[servoID - 11].speed = st.ReadSpeed(-1);
servoFeedback[servoID - 11].load = st.ReadLoad(-1);
servoFeedback[servoID - 11].voltage = st.ReadVoltage(-1);
servoFeedback[servoID - 11].current = st.ReadCurrent(-1);
servoFeedback[servoID - 11].temper = st.ReadTemper(-1);
servoFeedback[servoID - 11].mode = st.ReadMode(servoID);
if(!returnType){
if(InfoPrint == 1){
jsonInfoHttp.clear();
jsonInfoHttp["T"] = 1005;
jsonInfoHttp["id"] = servoID;
jsonInfoHttp["status"] = 1;
String getInfoJsonString;
serializeJson(jsonInfoHttp, getInfoJsonString);
Serial.println(getInfoJsonString);
}
}
else{
return true;
}
return true;
} else{
servoFeedback[servoID - 11].status = false;
if(InfoPrint == 1){
jsonInfoHttp.clear();
jsonInfoHttp["T"] = 1005;
jsonInfoHttp["id"] = servoID;
jsonInfoHttp["status"] = 0;
String getInfoJsonString;
serializeJson(jsonInfoHttp, getInfoJsonString);
Serial.println(getInfoJsonString);
}
return false;
}
}
// input the old servo ID and the new ID you want it to change to.
void changeID(byte oldID, byte newID) {
if(oldID == 254){
st.unLockEprom(oldID);
st.writeByte(oldID, SMS_STS_ID, newID);
st.LockEprom(newID);
if(InfoPrint == 1) {Serial.print("change: ");Serial.print(oldID);Serial.println(" succeed");}
return;
}
if(!getFeedback(oldID, true)) {
if(InfoPrint == 1) {Serial.print("change: ");Serial.print(oldID);Serial.println(" failed");}
return;
}
else {
st.unLockEprom(oldID);
st.writeByte(oldID, SMS_STS_ID, newID);
st.LockEprom(newID);
if(InfoPrint == 1) {Serial.print("change: ");Serial.print(oldID);Serial.println(" succeed");}
return;
}
}
// ctrl the torque lock of a servo.
// input the servo ID and command: 1-on : produce torque.
// 0-off: release torque.
void servoTorqueCtrl(byte servoID, u8 enableCMD){
st.EnableTorque(servoID, enableCMD);
}
// set the current position as the middle position of the servo.
// input the ID of the servo that you wannna set middle position.
void setMiddlePos(byte InputID){
st.CalibrationOfs(InputID);
}
// to release all servos' torque for 10s.
void emergencyStopProcessing() {
st.EnableTorque(254, 0);
}
// position check.
// it will wait for the servo to move to the goal position.
void waitMove2Goal(byte InputID, s16 goalPosition, s16 offSet){
while(servoFeedback[InputID - 11].pos < goalPosition - offSet ||
servoFeedback[InputID - 11].pos > goalPosition + offSet){
if (!servoFeedback[InputID - 11].status) {
servoTorqueCtrl(254, 0);
break;
}
getFeedback(InputID, true);
delay(10);
}
}
// initialize bus servo libraris and uart2ttl.
void RoArmM2_servoInit(){
Serial1.begin(1000000, SERIAL_8N1, S_RXD, S_TXD);
st.pSerial = &Serial1;
while(!Serial1) {}
if(InfoPrint == 1){Serial.println("ServoCtrl init succeed.");}
}
// check the status of every servo,
// if all status are ok, set the RoArmM2_initCheckSucceed as 1.
// 0: used to init check, print everything.
// 1: used to check while working, print when failed.
void RoArmM2_initCheck(bool returnType) {
RoArmM2_initCheckSucceed = false;
RoArmM2_initCheckSucceed = getFeedback(BASE_SERVO_ID, true) &&
getFeedback(SHOULDER_DRIVING_SERVO_ID, true) &&
getFeedback(SHOULDER_DRIVEN_SERVO_ID, true) &&
getFeedback(ELBOW_SERVO_ID, true);
if(!returnType){
if(InfoPrint == 1 || RoArmM2_initCheckSucceed){Serial.println("All bus servos status checked.");}
else if(InfoPrint == 1 || !RoArmM2_initCheckSucceed){Serial.println("Bus servos status check: failed.");}
}
else if(returnType && RoArmM2_initCheckSucceed){}
else if(returnType && !RoArmM2_initCheckSucceed){
if(InfoPrint == 1){Serial.println("Check failed.");}
}
}
// set all servos PID as the RoArm-M2 settings.
bool setServosPID(byte InputID, byte InputP) {
if(!getFeedback(InputID, true)){return false;}
st.unLockEprom(InputID);
st.writeByte(InputID, ST_PID_P_ADDR, InputP);
st.LockEprom(InputID);
return true;
}
// move every joint to its init position.
// it moves only when RoArmM2_initCheckSucceed is 1.
void RoArmM2_moveInit() {
if(!RoArmM2_initCheckSucceed){
if(InfoPrint == 1){Serial.println("Init failed, skip moveInit.");}
return;
}
else if(InfoPrint == 1){Serial.println("Stop moving to initPos.");}
// move BASE_SERVO to middle position.
if(InfoPrint == 1){Serial.println("Moving BASE_JOINT to initPos.");}
st.WritePosEx(BASE_SERVO_ID, ARM_SERVO_MIDDLE_POS, ARM_SERVO_INIT_SPEED, ARM_SERVO_INIT_ACC);
// release SHOULDER_DRIVEN_SERVO torque.
if(InfoPrint == 1){Serial.println("Unlock the torque of SHOULDER_DRIVEN_SERVO.");}
servoTorqueCtrl(SHOULDER_DRIVEN_SERVO_ID, 0);
// move SHOULDER_DRIVING_SERVO to middle position.
if(InfoPrint == 1){Serial.println("Moving SHOULDER_JOINT to initPos.");}
st.WritePosEx(SHOULDER_DRIVING_SERVO_ID, ARM_SERVO_MIDDLE_POS, ARM_SERVO_INIT_SPEED, ARM_SERVO_INIT_ACC);
// check SHOULDER_DRIVEING_SERVO position.
if(InfoPrint == 1){Serial.println("...");}
waitMove2Goal(SHOULDER_DRIVING_SERVO_ID, ARM_SERVO_MIDDLE_POS, 30);
// wait for the jitter to go away.
delay(1200);
// set the position as the middle of the SHOULDER_DRIVEN_SERVO.
if(InfoPrint == 1){Serial.println("Set this pos as the middle pos for SHOULDER_DRIVEN_SERVO.");}
setMiddlePos(SHOULDER_DRIVEN_SERVO_ID);
// SHOULDER_DRIVEN_SERVO starts producing torque.
if(InfoPrint == 1){Serial.println("SHOULDER_DRIVEN_SERVO starts producing torque.");}
servoTorqueCtrl(SHOULDER_DRIVEN_SERVO_ID, 1);
delay(10);
// move ELBOW_SERVO to middle position.
if(InfoPrint == 1){Serial.println("Moving ELBOW_SERVO to middle position.");}
st.WritePosEx(ELBOW_SERVO_ID, ARM_SERVO_MIDDLE_POS, ARM_SERVO_INIT_SPEED, ARM_SERVO_INIT_ACC);
waitMove2Goal(ELBOW_SERVO_ID, ARM_SERVO_MIDDLE_POS, 20);
if(InfoPrint == 1){Serial.println("Moving GRIPPER_SERVO to middle position.");}
st.WritePosEx(GRIPPER_SERVO_ID, ARM_SERVO_MIDDLE_POS, ARM_SERVO_INIT_SPEED, ARM_SERVO_INIT_ACC);
delay(1000);
}
// // // single joint ctrl for simple uses, base on radInput // // //
// use this function to compute the servo position to ctrl base joint.
// returnType 0: only returns the base joint servo position and save it to goalPos[0],
// servo will NOT move.
// 1: returns the base joint servo position and save it to goalPos[0],
// servo moves.
// input the angle in radius(double), the speedInput(u16) is servo steps/second,
// the accInput(u8) is the acceleration of the servo movement.
// radInput increase, move to left.
int RoArmM2_baseJointCtrlRad(byte returnType, double radInput, u16 speedInput, u8 accInput) {
radInput = -constrain(radInput, -M_PI, M_PI);
s16 computePos = calculatePosByRad(radInput) + ARM_SERVO_MIDDLE_POS;
goalPos[0] = computePos;
if(returnType){
st.WritePosEx(BASE_SERVO_ID, goalPos[0], speedInput, accInput);
}
return goalPos[0];
}
// use this function to compute the servo position to ctrl shoudlder joint.
// returnType 0: only returns the shoulder joint servo position and save it to goalPos[1] and goalPos[2],
// servo will NOT move.
// 1: returns the shoulder joint servo position and save it to goalPos[1] and goalPos[2],
// servo moves.
// input the angle in radius(double), the speedInput(u16) is servo steps/second,
// the accInput(u8) is the acceleration of the servo movement.
// radInput increase, it leans forward.
int RoArmM2_shoulderJointCtrlRad(byte returnType, double radInput, u16 speedInput, u8 accInput) {
radInput = constrain(radInput, -M_PI/2, M_PI/2);
s16 computePos = calculatePosByRad(radInput);
goalPos[1] = ARM_SERVO_MIDDLE_POS + computePos;
goalPos[2] = ARM_SERVO_MIDDLE_POS - computePos;
if(returnType == 1){
st.WritePosEx(SHOULDER_DRIVING_SERVO_ID, goalPos[1], speedInput, accInput);
st.WritePosEx(SHOULDER_DRIVEN_SERVO_ID, goalPos[2], speedInput, accInput);
}
else if(returnType == SHOULDER_DRIVING_SERVO_ID){
return goalPos[1];
}
else if(returnType == SHOULDER_DRIVEN_SERVO_ID){
return goalPos[2];
}
}
// use this function to compute the servo position to ctrl elbow joint.
// returnType 0: only returns the elbow joint servo position and save it to goalPos[3],
// servo will NOT move.
// 1: returns the elbow joint servo position and save it to goalPos[3],
// servo moves.
// input the angle in radius(double), the speedInput(u16) is servo steps/second,
// the accInput(u8) is the acceleration of the servo movement.
// angleInput increase, it moves down.
int RoArmM2_elbowJointCtrlRad(byte returnType, double radInput, u16 speedInput, u8 accInput) {
s16 computePos = calculatePosByRad(radInput) + 1024;
goalPos[3] = constrain(computePos, 512, 3071);
if(returnType){
st.WritePosEx(ELBOW_SERVO_ID, goalPos[3], speedInput, accInput);
}
return goalPos[3];
}
// use this function to compute the servo position to ctrl grab/hand joint.
// returnType 0: only returns the hand joint servo position and save it to goalPos[4],
// servo will NOT move.
// 1: returns the hand joint servo position and save it to goalPos[4],
// servo moves.
// ctrl type 0: status ctrl. - cmd 0: release
// 1: grab
// 1: position ctrl. - cmd: input angle in radius.
int RoArmM2_handJointCtrlRad(byte returnType, double radInput, u16 speedInput, u8 accInput) {
s16 computePos = calculatePosByRad(radInput);
goalPos[4] = constrain(computePos, 700, 3396);
if (returnType) {
st.WritePosEx(GRIPPER_SERVO_ID, goalPos[4], speedInput, accInput);
}
return goalPos[4];
}
// use this function to ctrl the max torque of base joint.
void RoArmM2_baseTorqueCtrl(int inputTorque) {
st.unLockEprom(BASE_SERVO_ID);
st.writeWord(BASE_SERVO_ID, SMS_STS_TORQUE_LIMIT_L, constrain(inputTorque, ST_TORQUE_MIN, ST_TORQUE_MAX));
st.LockEprom(BASE_SERVO_ID);
}
// use this function to ctrl the max torque of shoulder joint.
void RoArmM2_shoulderTorqueCtrl(int inputTorque) {
st.unLockEprom(SHOULDER_DRIVING_SERVO_ID);
st.writeWord(SHOULDER_DRIVING_SERVO_ID, SMS_STS_TORQUE_LIMIT_L, constrain(inputTorque, ST_TORQUE_MIN, ST_TORQUE_MAX));
st.LockEprom(SHOULDER_DRIVING_SERVO_ID);
st.unLockEprom(SHOULDER_DRIVEN_SERVO_ID);
st.writeWord(SHOULDER_DRIVEN_SERVO_ID, SMS_STS_TORQUE_LIMIT_L, constrain(inputTorque, ST_TORQUE_MIN, ST_TORQUE_MAX));
st.LockEprom(SHOULDER_DRIVEN_SERVO_ID);
}
// use this function to ctrl the max torque of elbow joint.
void RoArmM2_elbowTorqueCtrl(int inputTorque) {
st.unLockEprom(ELBOW_SERVO_ID);
st.writeWord(ELBOW_SERVO_ID, SMS_STS_TORQUE_LIMIT_L, constrain(inputTorque, ST_TORQUE_MIN, ST_TORQUE_MAX));
st.LockEprom(ELBOW_SERVO_ID);
}
// use this function to ctrl the max torque of hand joint.
void RoArmM2_handTorqueCtrl(int inputTorque) {
st.unLockEprom(GRIPPER_SERVO_ID);
st.writeWord(GRIPPER_SERVO_ID, SMS_STS_TORQUE_LIMIT_L, constrain(inputTorque, ST_TORQUE_MIN, ST_TORQUE_MAX));
st.LockEprom(GRIPPER_SERVO_ID);
}
// dynamic external force adaptation.
// mode: 0 - stop: reset every limit torque to 1000.
// 1 - start: set the joint limit torque.
// b, s, e, h = bassJoint, shoulderJoint, elbowJoint, handJoint
// example:
// starts. input the limit torque of every joint.
// {"T":112,"mode":1,"b":50,"s":50,"e":50,"h":50}
// stop
// {"T":112,"mode":0,"b":1000,"s":1000,"e":1000,"h":1000}
void RoArmM2_dynamicAdaptation(byte inputM, int inputB, int inputS, int inputE, int inputH) {
if (inputM == 0) {
RoArmM2_baseTorqueCtrl(ST_TORQUE_MAX);
RoArmM2_shoulderTorqueCtrl(ST_TORQUE_MAX);
RoArmM2_elbowTorqueCtrl(ST_TORQUE_MAX);
RoArmM2_handTorqueCtrl(ST_TORQUE_MAX);
} else if (inputM == 1) {
RoArmM2_baseTorqueCtrl(inputB);
RoArmM2_shoulderTorqueCtrl(inputS);
RoArmM2_elbowTorqueCtrl(inputE);
RoArmM2_handTorqueCtrl(inputH);
}
}
// this function uses relative radInput to set a new X+ axis.
// dirInput:
// 0
// X+
// -90 - ^ - 90
// |
// -180 180
void setNewAxisX(double angleInput) {
double radInput = (angleInput / 180) * M_PI;
RoArmM2_shoulderJointCtrlRad(1, 0, 500, 20);
waitMove2Goal(SHOULDER_DRIVING_SERVO_ID, goalPos[1], 20);
RoArmM2_elbowJointCtrlRad(1, 0, 500, 20);
waitMove2Goal(ELBOW_SERVO_ID, goalPos[3], 20);
RoArmM2_baseJointCtrlRad(1, 0, 500, 20);
waitMove2Goal(BASE_SERVO_ID, goalPos[0], 20);
delay(1000);
RoArmM2_baseJointCtrlRad(1, -radInput, 500, 20);
waitMove2Goal(BASE_SERVO_ID, goalPos[0], 20);
delay(1000);
setMiddlePos(BASE_SERVO_ID);
delay(5);
}
// Simple Linkage IK:
// input the position of the end and return angle.
// O----O
// /
// O
// ---------------------------------------------------
// | /beta /delta |
// O----LB---------X------ |
// | / omega. | \LB |
// LA . < ----------------|
// |alpha . bIn \LB -EP <delta |
// /psi. \LB -EP |
// | /. lambda | |
// O- - - - - aIn - - - - X - |
// ---------------------------------------------------
// alpha, beta > 0 ; delta <= 0 ; aIn, bIn > 0
void simpleLinkageIkRad(double LA, double LB, double aIn, double bIn) {
double psi, alpha, omega, beta, L2C, LC, lambda, delta;
if (fabs(bIn) < 1e-6) {
psi = acos((LA * LA + aIn * aIn - LB * LB) / (2 * LA * aIn)) + t2rad;
alpha = M_PI / 2.0 - psi;
omega = acos((aIn * aIn + LB * LB - LA * LA) / (2 * aIn * LB));
beta = psi + omega - t3rad;
} else {
L2C = aIn * aIn + bIn * bIn;
LC = sqrt(L2C);
lambda = atan2(bIn, aIn);
psi = acos((LA * LA + L2C - LB * LB) / (2 * LA * LC)) + t2rad;
alpha = M_PI / 2.0 - lambda - psi;
omega = acos((LB * LB + L2C - LA * LA) / (2 * LC * LB));
beta = psi + omega - t3rad;
}
delta = M_PI / 2.0 - alpha - beta;
SHOULDER_JOINT_RAD = alpha;
ELBOW_JOINT_RAD = beta;
EOAT_JOINT_RAD_BUFFER = delta;
nanIK = isnan(alpha) || isnan(beta) || isnan(delta);
}
// AI prompt:
// *** this function is written with AI. ***
// '''
// 我需要一个C语言函数,在一个平面直角坐标系中,输入一个坐标点(x,y),返回值有两个:
// 1. 这个坐标点距离坐标系原点的距离。
// 2. 这个点与坐标系原点所连线段与x轴正方向的夹角,夹角范围在(-PI, PI)之间。
// '''
// AI prompt:
// I need a C language function. In a 2D Cartesian coordinate system,
// input a coordinate point (x, y). The function should return two values:
// The distance from this coordinate point to the origin of the coordinate system.
// The angle, in radians, between the line connecting this point and the origin
// of the coordinate system and the positive direction of the x-axis.
// The angle should be in the range (-π, π).
void cartesian_to_polar(double x, double y, double* r, double* theta) {
*r = sqrt(x * x + y * y);
*theta = atan2(y, x);
}
// AI prompt:
// *** this function is written with AI. ***
// 我现在需要一个功能与上面函数相反的函数:
// 输入机械臂三个关节的轴的角度(弧度制),返回当前机械臂末端点的坐标点。
// 你在回答的过程中可以告诉我你还有什么其它需要的信息。
// '''
// use this two functions to compute the position of coordinate point
// by inputing the jointRad.
// 这个函数用于将极坐标转换为直角坐标
void polarToCartesian(double r, double theta, double &x, double &y) {
x = r * cos(theta);
y = r * sin(theta);
}
// this function is used to compute the position of the end point.
// input the angle of every joint in radius.
// compute the positon and save it to lastXYZ by default.
void RoArmM2_computePosbyJointRad(double base_joint_rad, double shoulder_joint_rad, double elbow_joint_rad, double hand_joint_rad) {
if (EEMode == 0) {
// the end of the arm.
double r_ee, x_ee, y_ee, z_ee;
// compute the end position of the first linkage(the linkage between baseJoint and shoulderJoint).
double aOut, bOut, cOut, dOut, eOut, fOut;
polarToCartesian(l2, ((M_PI / 2) - (shoulder_joint_rad + t2rad)), aOut, bOut);
polarToCartesian(l3, ((M_PI / 2) - (elbow_joint_rad + shoulder_joint_rad)), cOut, dOut);
r_ee = aOut + cOut;
z_ee = bOut + dOut;
polarToCartesian(r_ee, base_joint_rad, eOut, fOut);
x_ee = eOut;
y_ee = fOut;
lastX = x_ee;
lastY = y_ee;
lastZ = z_ee;
}
else if (EEMode == 1) {
double aOut, bOut, cOut, dOut, eOut, fOut, gOut, hOut;
double r_ee, z_ee;
polarToCartesian(l2, ((M_PI / 2) - (shoulder_joint_rad + t2rad)), aOut, bOut);
polarToCartesian(l3, ((M_PI / 2) - (elbow_joint_rad + shoulder_joint_rad + t3rad)), cOut, dOut);
polarToCartesian(lE, -((hand_joint_rad + tErad) - M_PI - (M_PI/2 - shoulder_joint_rad - elbow_joint_rad)), eOut, fOut);
r_ee = aOut + cOut + eOut;
z_ee = bOut + dOut + fOut;
polarToCartesian(r_ee, base_joint_rad, gOut, hOut);
lastX = gOut;
lastY = hOut;
lastZ = z_ee;
lastT = hand_joint_rad - (M_PI - shoulder_joint_rad - elbow_joint_rad) + (M_PI / 2);
}
}
// EEmode funcs change here.
// get position by servo feedback.
void RoArmM2_getPosByServoFeedback() {
getFeedback(BASE_SERVO_ID, true);
getFeedback(SHOULDER_DRIVING_SERVO_ID, true);
getFeedback(ELBOW_SERVO_ID, true);
getFeedback(GRIPPER_SERVO_ID, true);
radB = calculateRadByFeedback(servoFeedback[BASE_SERVO_ID - 11].pos, BASE_JOINT);
radS = calculateRadByFeedback(servoFeedback[SHOULDER_DRIVING_SERVO_ID - 11].pos, SHOULDER_JOINT);
radE = calculateRadByFeedback(servoFeedback[ELBOW_SERVO_ID - 11].pos, ELBOW_JOINT);
radG = calculateRadByFeedback(servoFeedback[GRIPPER_SERVO_ID - 11].pos, EOAT_JOINT);
RoArmM2_computePosbyJointRad(radB, radS, radE, radG);
if (EEMode == 0) {
lastT = radG;
}
}
// feedback info in json.
void RoArmM2_infoFeedback() {
jsonInfoHttp.clear();
jsonInfoHttp["T"] = 1051;
jsonInfoHttp["x"] = lastX;
jsonInfoHttp["y"] = lastY;
jsonInfoHttp["z"] = lastZ;
jsonInfoHttp["b"] = radB;
jsonInfoHttp["s"] = radS;
jsonInfoHttp["e"] = radE;
jsonInfoHttp["t"] = lastT;
// jsonInfoHttp["goalX"] = goalX;
// jsonInfoHttp["goalY"] = goalY;
// jsonInfoHttp["goalZ"] = goalZ;
// jsonInfoHttp["goalT"] = goalT;
jsonInfoHttp["torB"] = servoFeedback[BASE_SERVO_ID - 11].load;
jsonInfoHttp["torS"] = servoFeedback[SHOULDER_DRIVING_SERVO_ID - 11].load - servoFeedback[SHOULDER_DRIVEN_SERVO_ID - 11].load;
jsonInfoHttp["torE"] = servoFeedback[ELBOW_SERVO_ID - 11].load;
jsonInfoHttp["torH"] = servoFeedback[GRIPPER_SERVO_ID - 11].load;
String getInfoJsonString;
serializeJson(jsonInfoHttp, getInfoJsonString);
Serial.println(getInfoJsonString);
}
// AI prompt:
// 在平面直角坐标系中,有一点A,输入点A的X,Y坐标和角度参数theta(弧度制),点A绕直角坐标系原点
// 逆时针转动theta作为点B,返回点B的XY坐标值,我需要C语言的函数。
// AI prompt:
// In a 2D Cartesian coordinate system, there is a point A.
// Input the X and Y coordinates of point A and an angle parameter theta (in radians).
// Point A rotates counterclockwise around the origin of the Cartesian coordinate
// system by an angle of theta to become point B. Return the XY coordinates of point B.
// I need a C language function.
void rotatePoint(double theta, double *xB, double *yB) {
double alpha = tErad + theta;
*xB = lE * cos(alpha);
*yB = lE * sin(alpha);
}
// AI prompt:
// 在平面直角坐标系种,有一点A,输入点A的X,Y坐标值,输入一个距离参数S,
// 点A向原点方向移动S作为点B,返回点B的坐标值。我需要C语言的函数。
void movePoint(double xA, double yA, double s, double *xB, double *yB) {
double distance = sqrt(pow(xA, 2) + pow(yA, 2));
if(distance - s <= 1e-6) {
*xB = 0;
*yB = 0;
}
else {
double ratio = (distance - s) / distance;
*xB = xA * ratio;
*yB = yA * ratio;
}
}
// ---===< Muti-assembly IK config here >===---
// change this func and goalPosMove()
// Coordinate Ctrl: input the coordinate point of the goal position to compute
// the goalPos of every joints.
void RoArmM2_baseCoordinateCtrl(double inputX, double inputY, double inputZ, double inputT){
if (EEMode == 0) {
cartesian_to_polar(inputX, inputY, &base_r, &BASE_JOINT_RAD);
simpleLinkageIkRad(l2, l3, base_r, inputZ);
RoArmM2_handJointCtrlRad(0, inputT, 0, 0);
}
else if (EEMode == 1) {
rotatePoint((inputT - M_PI), &delta_x, &delta_y);
movePoint(inputX, inputY, delta_x, &beta_x, &beta_y);
cartesian_to_polar(beta_x, beta_y, &base_r, &BASE_JOINT_RAD);
simpleLinkageIkRad(l2, l3, base_r, inputZ + delta_y);
EOAT_JOINT_RAD = EOAT_JOINT_RAD_BUFFER + inputT;
}
}
// update last position for later use.
void RoArmM2_lastPosUpdate(){
lastX = goalX;
lastY = goalY;
lastZ = goalZ;
lastT = goalT;
}
// use jointCtrlRad functions to compute goalPos for every servo,
// then use this function to move the servos.
// cuz the functions like baseCoordinateCtrl is not gonna make servos move.
void RoArmM2_goalPosMove(){
RoArmM2_baseJointCtrlRad(0, BASE_JOINT_RAD, 0, 0);
RoArmM2_shoulderJointCtrlRad(0, SHOULDER_JOINT_RAD, 0, 0);
RoArmM2_elbowJointCtrlRad(0, ELBOW_JOINT_RAD, 0, 0);
if (EEMode == 1) {
RoArmM2_handJointCtrlRad(0, EOAT_JOINT_RAD, 0, 0);
}
st.SyncWritePosEx(servoID, 5, goalPos, moveSpd, moveAcc);
}
void RoArmM2_uiCtrl(float inputE, float inputZ, float inputR) {
simpleLinkageIkRad(l2, l3, inputE, inputZ);
BASE_JOINT_RAD = ang2deg(inputR);
RoArmM2_goalPosMove();
}
// ctrl a single joint abs angle(rad).
// joint: 1-BASE_JOINT + ->left
// 2-SHOULDER_JOINT + ->down
// 3-ELBOW_JOINT + ->down
// 4-EOAT_JOINT + ->grab/down
// inputRad: input the goal angle in radius of the joint.
// inputSpd: move speed, steps/second.
// inputAcc: acceleration, steps/second^2.
void RoArmM2_singleJointAbsCtrl(byte jointInput, double inputRad, u16 inputSpd, u8 inputAcc){
switch(jointInput){
case BASE_JOINT:
RoArmM2_baseJointCtrlRad(1, inputRad, inputSpd, inputAcc);
BASE_JOINT_RAD = inputRad;
break;
case SHOULDER_JOINT:
RoArmM2_shoulderJointCtrlRad(1, inputRad, inputSpd, inputAcc);
SHOULDER_JOINT_RAD = inputRad;
break;
case ELBOW_JOINT:
RoArmM2_elbowJointCtrlRad(1, inputRad, inputSpd, inputAcc);
ELBOW_JOINT_RAD = inputRad;
break;
case EOAT_JOINT:
RoArmM2_handJointCtrlRad(1, inputRad, inputSpd, inputAcc);
EOAT_JOINT_RAD = inputRad;
break;
}
RoArmM2_computePosbyJointRad(BASE_JOINT_RAD, SHOULDER_JOINT_RAD, ELBOW_JOINT_RAD, EOAT_JOINT_RAD);
}
// ctrl all joints together.
// when all joints in initPos(middle position), it looks like below.
// -------L3------------O==L2B===
// ^ |
// | |
// ELBOW_JOINT |
// L2A
// |
// |
// ^ |
// | SHOULDER_JOINT -> O
// Z+ |
// | L1
// | |
// <---X+--Y+ BASE_JOINT -> X
//
//
// -------L3------------O==L2B==O <- BASE_JOINT
// ^
// <---X+--Z+ |
// | ELBOW_JOINT
// Y+
// |
// v
void RoArmM2_allJointAbsCtrl(double inputBase, double inputShoulder, double inputElbow, double inputHand, u16 inputSpd, u8 inputAcc){
RoArmM2_baseJointCtrlRad(0, inputBase, inputSpd, inputAcc);
RoArmM2_shoulderJointCtrlRad(0, inputShoulder, inputSpd, inputAcc);
RoArmM2_elbowJointCtrlRad(0, inputElbow, inputSpd, inputAcc);
RoArmM2_handJointCtrlRad(0, inputHand, inputSpd, inputAcc);
for (int i = 0;i < 5;i++) {
moveSpd[i] = inputSpd;
moveAcc[i] = inputAcc;
}
st.SyncWritePosEx(servoID, 5, goalPos, moveSpd, moveAcc);
for (int i = 0;i < 5;i++) {
moveSpd[i] = 0;
moveAcc[i] = 0;
}
}
// ctrl the movement in a smooth way.
// | .. <-numEnd
// | . |
// | .
// | . |
// | .
// | . |
// |. . <-numStart
// ----------------------
// 0 1 rateInput
double besselCtrl(double numStart, double numEnd, double rateInput){
double numOut;
numOut = (numEnd - numStart)*((cos(rateInput*M_PI+M_PI)+1)/2) + numStart;
return numOut;
}
// use this function to get the max deltaSteps.
// get the max offset between [goal] and [last] position.
double maxNumInArray(){
if (EEMode == 0) {
double deltaPos[4] = {abs(goalX - lastX),
abs(goalY - lastY),
abs(goalZ - lastZ),
abs(goalT - lastT)*10};
double maxVal = deltaPos[0];
for(int i = 0; i < (sizeof(deltaPos) / sizeof(deltaPos[0])); i++){
maxVal = max(deltaPos[i],maxVal);
}
return maxVal;
} else if (EEMode == 1) {
double deltaPos[4] = {abs(goalX - lastX),
abs(goalY - lastY),
abs(goalZ - lastZ),
abs(goalT - lastT)*200};
double maxVal = deltaPos[0];
for(int i = 0; i < (sizeof(deltaPos) / sizeof(deltaPos[0])); i++){
maxVal = max(deltaPos[i],maxVal);
}
return maxVal;
}
}
// use this function to move the end of the arm to the goal position.
void RoArmM2_movePosGoalfromLast(float spdInput){
double deltaSteps = maxNumInArray();
double bufferX;
double bufferY;
double bufferZ;
double bufferT;
static double bufferLastX;
static double bufferLastY;
static double bufferLastZ;
static double bufferLastT;
for(double i=0;i<=1;i+=(1/(deltaSteps*1))*spdInput){
bufferX = besselCtrl(lastX, goalX, i);
bufferY = besselCtrl(lastY, goalY, i);
bufferZ = besselCtrl(lastZ, goalZ, i);
bufferT = besselCtrl(lastT, goalT, i);
RoArmM2_baseCoordinateCtrl(bufferX, bufferY, bufferZ, bufferT);
if(nanIK){
// IK failed
goalX = bufferLastX;
goalY = bufferLastY;
goalZ = bufferLastZ;
goalT = bufferLastT;
RoArmM2_baseCoordinateCtrl(goalX, goalY, goalZ, goalT);
RoArmM2_goalPosMove();
RoArmM2_lastPosUpdate();
return;
}
else{
// IK succeed.
bufferLastX = bufferX;
bufferLastY = bufferY;
bufferLastZ = bufferZ;
bufferLastT = bufferT;
}
RoArmM2_goalPosMove();
delay(2);
}
RoArmM2_baseCoordinateCtrl(goalX, goalY, goalZ, goalT);
RoArmM2_goalPosMove();
RoArmM2_lastPosUpdate();
}
// ctrl a single axi abs pos(mm).
// the init position is
// axiInput: 1-X, posInput:initX
// 2-Y, posInput:initY
// 3-Z, posInput:initZ
// 4-T, posInput:initT
// initX = l3+l2B
// initY = 0
// initZ = l2A
// initT = M_PI
// default inputSpd = 0.25
void RoArmM2_singlePosAbsBesselCtrl(byte axiInput, double posInput, double inputSpd){
switch(axiInput){
case 1: goalX = posInput;break;
case 2: goalY = posInput;break;
case 3: goalZ = posInput;break;
case 4: goalT = posInput;break;
}
RoArmM2_movePosGoalfromLast(inputSpd);
}
// ctrl all axis abs position.
// initX = l3+l2B
// initY = 0
// initZ = l2A
// initT = M_PI
// default inputSpd = 0.36
void RoArmM2_allPosAbsBesselCtrl(double inputX, double inputY, double inputZ, double inputT, double inputSpd){
goalX = inputX;
goalY = inputY;
goalZ = inputZ;
goalT = inputT;
RoArmM2_movePosGoalfromLast(inputSpd);
}
// ChatGPT prompt:
// '''
// 我需要一个函数,输入圆心坐标点、半径和比例,当比例从0到1变化时,函数输出的坐标点可以组成一个完整的圆。
// '''
// I need a function that inputs the center coordinate point,
// radius and scale(t), and when the scale(t) changes from 0 to 1,
// the coordinate points output by the function can form a complete circle.
// '''
//
// example:
// for(float i=0;i<=1;i+=0.001){
// getCirclePointYZ(0, initZ, 100, i);
// RoArmM2_goalPosMove();
// delay(5);
// }
void getCirclePointYZ(double cx, double cy, double r, double t) {
double theta = t * 2 * M_PI;
goalY = cx + r * cos(theta);
goalZ = cy + r * sin(theta);
}
// delay cmd.
void RoArmM2_delayMillis(int inputTime) {
delay(inputTime);
}
// set the P&I/PID of a joint.
void RoArmM2_setJointPID(byte jointInput, float inputP, float inputI) {
switch (jointInput) {
case BASE_JOINT:
st.writeByte(BASE_SERVO_ID, ST_PID_P_ADDR, inputP);
st.writeByte(BASE_SERVO_ID, ST_PID_I_ADDR, inputI);
break;
case SHOULDER_JOINT:
st.writeByte(SHOULDER_DRIVING_SERVO_ID, ST_PID_P_ADDR, inputP);
st.writeByte(SHOULDER_DRIVING_SERVO_ID, ST_PID_I_ADDR, inputI);
st.writeByte(SHOULDER_DRIVEN_SERVO_ID, ST_PID_P_ADDR, inputP);
st.writeByte(SHOULDER_DRIVEN_SERVO_ID, ST_PID_I_ADDR, inputI);
break;
case ELBOW_JOINT:
st.writeByte(ELBOW_SERVO_ID, ST_PID_P_ADDR, inputP);
st.writeByte(ELBOW_SERVO_ID, ST_PID_I_ADDR, inputI);
break;
case EOAT_JOINT:
st.writeByte(GRIPPER_SERVO_ID, ST_PID_P_ADDR, inputP);
st.writeByte(GRIPPER_SERVO_ID, ST_PID_I_ADDR, inputI);
break;
}
}
// reset the P&I/PID of RoArm-M2.
void RoArmM2_resetPID() {
RoArmM2_setJointPID(BASE_JOINT, 16, 0);
RoArmM2_setJointPID(SHOULDER_JOINT, 16, 0);
RoArmM2_setJointPID(ELBOW_JOINT, 16, 0);
RoArmM2_setJointPID(EOAT_JOINT, 16, 0);
}
// input the angle in deg, and it returns the number of servo steps.
int calculatePosByDeg(double degInput) {
return round((degInput / 360) * ARM_SERVO_POS_RANGE);
}
// ctrl a single joint abs angle.
// jointInput: 1-BASE_JOINT
// 2-SHOULDER_JOINT
// 3-ELBOW_JOINT
// 4-HAND_JOINT
// inputRad: input the goal angle in deg of the joint.
// inputSpd: move speed, angle/second.
// inputAcc: acceleration, angle/second^2.
void RoArmM2_singleJointAngleCtrl(byte jointInput, double inputAng, u16 inputSpd, u8 inputAcc){
Serial.println("---");
Serial.print(jointInput);Serial.print("\t");Serial.print(inputAng);Serial.print("\t");
Serial.print(inputSpd);Serial.print("\t");Serial.print(inputAcc);Serial.println();
inputSpd = abs(inputSpd);
inputAcc = abs(inputAcc);
switch(jointInput){
case BASE_JOINT:
BASE_JOINT_ANG = inputAng;
Serial.println(inputAng);
BASE_JOINT_RAD = ang2deg(inputAng);
Serial.println(BASE_JOINT_RAD);
RoArmM2_baseJointCtrlRad(1, BASE_JOINT_RAD, calculatePosByDeg(inputSpd), calculatePosByDeg(inputAcc));
break;
case SHOULDER_JOINT:
SHOULDER_JOINT_ANG = inputAng;
SHOULDER_JOINT_RAD = ang2deg(inputAng);
RoArmM2_shoulderJointCtrlRad(1, SHOULDER_JOINT_RAD, calculatePosByDeg(inputSpd), calculatePosByDeg(inputAcc));
break;
case ELBOW_JOINT:
ELBOW_JOINT_ANG = inputAng;
ELBOW_JOINT_RAD = ang2deg(inputAng);
RoArmM2_elbowJointCtrlRad(1, ELBOW_JOINT_RAD, calculatePosByDeg(inputSpd), calculatePosByDeg(inputAcc));
break;
case EOAT_JOINT:
EOAT_JOINT_ANG = inputAng;
EOAT_JOINT_RAD = ang2deg(inputAng);
RoArmM2_handJointCtrlRad(1, EOAT_JOINT_RAD, calculatePosByDeg(inputSpd), calculatePosByDeg(inputAcc));
break;
}
RoArmM2_computePosbyJointRad(BASE_JOINT_RAD, SHOULDER_JOINT_RAD, ELBOW_JOINT_RAD, EOAT_JOINT_RAD);
}
// ctrl all joints together.
// when all joints in initPos(middle position), it looks like below.
// -------L3------------O==L2B===
// ^ |
// | |
// ELBOW_JOINT |
// L2A
// |
// |
// ^ |
// | SHOULDER_JOINT -> O
// Z+ |
// | L1
// | |
// <---X+--Y+ BASE_JOINT -> X
//
//
// -------L3------------O==L2B==O <- BASE_JOINT
// ^
// <---X+--Z+ |
// | ELBOW_JOINT
// Y+
// |
// v
void RoArmM2_allJointsAngleCtrl(double inputBase, double inputShoulder, double inputElbow, double inputHand, u16 inputSpd, u8 inputAcc){
BASE_JOINT_ANG = inputBase;
BASE_JOINT_RAD = ang2deg(inputBase);
SHOULDER_JOINT_ANG = inputShoulder;
SHOULDER_JOINT_RAD = ang2deg(inputShoulder);
ELBOW_JOINT_ANG = inputElbow;
ELBOW_JOINT_RAD = ang2deg(inputElbow);
EOAT_JOINT_ANG = inputHand;
EOAT_JOINT_RAD = ang2deg(inputHand);
RoArmM2_baseJointCtrlRad(0, BASE_JOINT_RAD, 0, 0);
RoArmM2_shoulderJointCtrlRad(0, SHOULDER_JOINT_RAD, 0, 0);
RoArmM2_elbowJointCtrlRad(0, ELBOW_JOINT_RAD, 0, 0);
RoArmM2_handJointCtrlRad(0, EOAT_JOINT_RAD, 0, 0);
inputSpd = abs(calculatePosByDeg(inputSpd));
inputAcc = abs(calculatePosByDeg(inputAcc));
for (int i = 0;i < 5;i++) {
moveSpd[i] = inputSpd;
moveAcc[i] = inputAcc;
}
st.SyncWritePosEx(servoID, 5, goalPos, moveSpd, moveAcc);
}
void constantCtrl(byte inputMode, byte inputAxis, byte inputCmd, byte inputSpd) {
const_mode = inputMode;
if (const_mode == CONST_ANGLE) {
const_spd = abs(inputSpd) * 0.0005;
} else if (const_mode == CONST_XYZT) {
const_spd = abs(inputSpd) * 0.1;
}
switch (inputAxis) {
case BASE_JOINT:
const_cmd_base_x = inputCmd;
break;
case SHOULDER_JOINT:
const_cmd_shoulder_y = inputCmd;
break;
case ELBOW_JOINT:
const_cmd_elbow_z = inputCmd;
break;
case EOAT_JOINT:
const_cmd_eoat_t = inputCmd;
break;
}
}
// RoArmM2_infoFeedback()
void constantHandle() {
if (!const_cmd_base_x && !const_cmd_shoulder_y && !const_cmd_elbow_z && !const_cmd_eoat_t) {
const_goal_base = radB;
const_goal_shoulder = radS;
const_goal_elbow = radE;
const_goal_eoat = radG;
goalX = lastX;
goalY = lastY;
goalZ = lastZ;
goalT = lastT;
return;
}
if (const_cmd_base_x == MOVE_INCREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_base += const_spd;
if (const_goal_base > M_PI) {
const_goal_base = M_PI;
const_cmd_base_x = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalX += const_spd;
}
} else if (const_cmd_base_x == MOVE_DECREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_base -= const_spd;
if (const_goal_base < -M_PI) {
const_goal_base = -M_PI;
const_cmd_base_x = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalX -= const_spd;
}
}
if (const_cmd_shoulder_y == MOVE_INCREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_shoulder += const_spd;
if (const_goal_shoulder > M_PI/2) {
const_goal_shoulder = M_PI/2;
const_cmd_shoulder_y = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalY += const_spd;
}
} else if (const_cmd_shoulder_y == MOVE_DECREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_shoulder -= const_spd;
if (const_goal_shoulder < -M_PI/2) {
const_goal_shoulder = -M_PI/2;
const_cmd_shoulder_y = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalY -= const_spd;
}
}
if (const_cmd_elbow_z == MOVE_INCREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_elbow += const_spd;
if (const_goal_elbow > M_PI) {
const_goal_elbow = M_PI;
const_cmd_elbow_z = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalZ += const_spd;
}
} else if (const_cmd_elbow_z == MOVE_DECREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_elbow -= const_spd;
if (const_goal_elbow < -M_PI/4) {
const_goal_elbow = -M_PI/4;
const_cmd_elbow_z = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalZ -= const_spd;
}
}
if (const_cmd_eoat_t == MOVE_INCREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_eoat += const_spd;
if (const_goal_eoat > M_PI*7/4) {
const_goal_eoat = M_PI*7/4;
const_cmd_eoat_t = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalT += const_spd/200;
}
} else if (const_cmd_eoat_t == MOVE_DECREASE) {
if (const_mode == CONST_ANGLE) {
const_goal_eoat -= const_spd;
if (const_goal_eoat < -M_PI/4) {
const_goal_eoat = -M_PI/4;
const_cmd_eoat_t = MOVE_STOP;
}
}
else if (const_mode == CONST_XYZT) {
goalT -= const_spd/200;
}
}
if (const_mode == CONST_ANGLE) {
RoArmM2_allJointAbsCtrl(const_goal_base, const_goal_shoulder, const_goal_elbow, const_goal_eoat, 0, 0);
} else if (const_mode == CONST_XYZT) {
static double bufferLastX;
static double bufferLastY;
static double bufferLastZ;
static double bufferLastT;
RoArmM2_baseCoordinateCtrl(goalX, goalY, goalZ, goalT);
if (nanIK) {
// IK failed
goalX = bufferLastX;
goalY = bufferLastY;
goalZ = bufferLastZ;
goalT = bufferLastT;
RoArmM2_baseCoordinateCtrl(bufferLastX, bufferLastY, bufferLastZ, bufferLastT);
RoArmM2_goalPosMove();
RoArmM2_lastPosUpdate();
return;
}
else {
bufferLastX = goalX;
bufferLastY = goalY;
bufferLastZ = goalZ;
bufferLastT = goalT;
}
RoArmM2_goalPosMove();
RoArmM2_lastPosUpdate();
}
}
// // // // // // // // // // // // // // // //
// // // <TEST FUNCTIONS for RoArm-M2> // // //
// // // // // // // // // // // // // // // //
// example:
// RoArmM2_Test_drawSqureYZ(-100, 0, 50, 300);
// void RoArmM2_Test_drawSqureXZ(int squre_x, int squre_y, int squre_l){
// for(double i=0;i<=squre_l;i+=0.1){
// simpleLinkageIkRad(l2, l3, l3+l2B + squre_x, l2A-i+squre_y);
// RoArmM2_goalPosMove();
// delay(3);
// }
// delay(1500);
// for(double i=0;i<=squre_l;i+=0.1){
// simpleLinkageIkRad(l2, l3, l3+l2B-i + squre_x , l2A- squre_l+squre_y);
// RoArmM2_goalPosMove();
// delay(3);
// }
// delay(1500);
// for(double i=0;i<=squre_l;i+=0.1){
// simpleLinkageIkRad(l2, l3, l3+l2B- squre_l +squre_x, l2A- squre_l +i+squre_y);
// RoArmM2_goalPosMove();
// delay(3);
// }
// delay(1500);
// for(double i=0;i<=squre_l;i+=0.1){
// simpleLinkageIkRad(l2, l3, l3+l2B- squre_l +i +squre_x, l2A +squre_y);
// RoArmM2_goalPosMove();
// delay(3);
// }
// delay(1500);
// }
// void RoArmM2_Test_drawSqureYZ(int squre_x, int squre_y, int squre_z, int squre_l){
// for(double i=0;i<=squre_l;i+=0.1){
// RoArmM2_baseCoordinateCtrl(l3+l2B+squre_x, squre_y-squre_l/2+i, l2A+squre_z);
// RoArmM2_goalPosMove();
// delay(2);
// }
// delay(1250);
// for(double i=0;i<=squre_l;i+=0.1){
// RoArmM2_baseCoordinateCtrl(l3+l2B+squre_x, squre_y+squre_l/2, l2A+squre_z-i);
// RoArmM2_goalPosMove();
// delay(2);
// }
// delay(1250);
// for(double i=0;i<=squre_l;i+=0.1){
// RoArmM2_baseCoordinateCtrl(l3+l2B+squre_x, squre_y+squre_l/2-i, l2A+squre_z-squre_l);
// RoArmM2_goalPosMove();
// delay(2);
// }
// delay(1250);
// for(double i=0;i<=squre_l;i+=0.1){
// RoArmM2_baseCoordinateCtrl(l3+l2B+squre_x, squre_y-squre_l/2, l2A+squre_z-squre_l+i);
// RoArmM2_goalPosMove();
// delay(2);
// }
// delay(1250);
// }
// void RoArmM2_Test_drawCircleYZ(){
// for(float i=0; i<=1; i+=0.001){
// getCirclePointYZ(initY, initZ-100, 100, i);
// RoArmM2_baseCoordinateCtrl(initX-100, goalY, goalZ);
// RoArmM2_goalPosMove();
// delay(3);
// }
// }