Mechwarrior Mad Cat Project

Mech Warrior - Mad Cat.. Bipedal Robot project..


Sooo im new to robotics and arduino.. :sweat_smile:
But im jumpin in to the deep end.. im inspired by the "BirdBot" from the Max Planck institute.

But i need some advice on what motors/servos to use for a similer setup a s birdbot..?

and im planning to experiment with "Dynamic Stabilization" for when standing still.
and "Gyroscopic Stabilization" when walking. so chances are ihave more quistions in the future... :stuck_out_tongue_winking_eye:

But for now i just need help to chose the best motors/servos to drive leg movement and "tendent" movement like birdbot.

If any one have ideas.. experience.. or helpfull tips.. im all ears.. :slightly_smiling_face:

Start with 1 robot kit and get it working. Then a different one. You need to build up and understanding before you try such an ambitious project. In the end, you'll save money and frustration.

Actually I feel you are not ready to start the project at this point. We have no idea of your skill set or what resources you have available to you. I would suggest you start with some of the tutorials that are on line, sorry to say some are not so good but many are very good. Start by learning the basics, you need to control outputs and interpret inputs such as reading a switch, receiving a message etc. Start with the LED, they are not expensive and there is even one on most of the Arduinos. At this point you should have also found several tutorials on basic electronics that you have gone through. You should acquire a copy of the Arduino Cookbook and go through that. I have no clue as to how fast you will learn this but it will probably take a few months. During this process you will learn what an IDE is and how to use to generate and upload your code to the Arduino. Let us know how you progress.

I hope you're not expecting the fine folks here to rush to your rescue then. It's really hard to help people that are in way over their head. The problem is that the internet makes it look so easy, you think you're just going to jump in and skip all the basics. But you will fail if you do that. I've seen it happen to countless scores before you.

So far everyone has said, "Don't skip the basics." It is no different than learning to fly... do you just hop in your first airplane and takeoff? So... show what you can do so you can get the level of advice you can use...

Step one: evaluate your project and break it down to the smallest elements... Your turn.

P.S. The bird has a limp... left side over lifts as it extends.

I've actually got a GREAT example of what we're talking about going on right this moment.

I'm working on code for a self balancing robot right now. It's my first time building a self-balancer. I just started last night with the code for it so I'll show you the first few steps. Keep in mind that I've already spent a lot of time just running example codes with various parts to familiarize myself with their function. But last night was the first time I assembled it and started writing the actual code for the robot.

(Thanks to @PickyBiker for the 3-D print design and build!)

(For those keeping score, GPT_Stepper refers to the fact that the steppers are direct drive from the GPT timers on the UNO-R4-WiFi. It does NOT mean Chat-GPT wrote any of the code. Y'all know me better than that.)

This is the first code I started with:

See the Code
#include "GPT_Stepper.h"

const uint8_t rightStepPin = 2;
const uint8_t rightDirPin = 5;
const uint8_t leftStepPin = 4;
const uint8_t leftDirPin = 7;

GPT_Stepper leftStepper(leftStepPin, leftDirPin, 17000.0);
GPT_Stepper rightStepper(rightStepPin, rightDirPin, 17000.0);

float speed = 10000.0;

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("\n\nStarting BalanceBot_Test.ino\n\n");

  leftStepper.init();
  rightStepper.init();

}

void loop() {

  static unsigned long pm = millis();
  unsigned long cm = millis();
  if(cm - pm >= 5000){
    pm=cm;
    leftStepper.setSpeed(speed);
    rightStepper.setSpeed(speed);
    speed = -speed;
  }

}

All it does is run the motors in opposite directions every 5 seconds. That's it. Basic basic basic just see if the motors will run.

The next piece of code had no motors. It's just a trimmed down example from the library for the accelerometer I'm using. All it did was read a pitch value from an accelerometer. That's it. Just print the pitch to the screen. Basic basic small steps.

See the Code

#include "ICM_20948.h"

#define WIRE_PORT Wire1
#define AD0_VAL 1

ICM_20948_I2C icm;

void setup() {

  Serial.begin(115200);
  delay(500);
  Serial.println("\n\nICM_Test\n\n");
  WIRE_PORT.begin();
  WIRE_PORT.setClock(400000);

  setupIMU();
}

void loop() {
  static uint32_t lastMicros = micros();
  if (newData()) {
    Serial.println(readPitch());
    uint32_t time = micros();
    Serial.println(time - lastMicros);
    lastMicros = time;
  }
}


void setupIMU() {
  bool initialized = false;
  while (!initialized) {

    // Initialize the ICM-20948
    // If the DMP is enabled, .begin performs a minimal startup. We need to configure the sample mode etc. manually.

    icm.begin(WIRE_PORT, AD0_VAL);


    Serial.print(F("Initialization of the sensor returned: "));
    Serial.println(icm.statusString());

    if (icm.status != ICM_20948_Stat_Ok) {
      Serial.println(F("Trying again..."));

      delay(500);
    } else {
      initialized = true;
    }
  }
  Serial.println("\n\nConnected to IMU\n\n");
  bool success = true;
  success &= (icm.initializeDMP() == ICM_20948_Stat_Ok);
  success &= (icm.enableDMPSensor(INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR) == ICM_20948_Stat_Ok);
  success &= (icm.setDMPODRrate(DMP_ODR_Reg_Quat6, 0) == ICM_20948_Stat_Ok);
  // Enable the FIFO
  success &= (icm.enableFIFO() == ICM_20948_Stat_Ok);

  // Enable the DMP
  success &= (icm.enableDMP() == ICM_20948_Stat_Ok);

  // Reset DMP
  success &= (icm.resetDMP() == ICM_20948_Stat_Ok);

  // Reset FIFO
  success &= (icm.resetFIFO() == ICM_20948_Stat_Ok);

  // Check success
  if (success) {
    Serial.println("DMP enabled!");
  } else {
    Serial.println("Enable DMP failed!");
    Serial.println("Please check that you have uncommented line 29 (#define ICM_20948_USE_DMP) in ICM_20948_C.h...");
    while (1)
      ;  // Do nothing more
  }
}

icm_20948_DMP_data_t data;
bool newData() {
  icm.readDMPdataFromFIFO(&data);

  if ((icm.status == ICM_20948_Stat_Ok) || (icm.status == ICM_20948_Stat_FIFOMoreDataAvail))  // Was valid data available?
  {
    return true;
  }
  return false;
}

double readPitch() {

  double pitch = 0;

  if ((data.header & DMP_header_bitmap_Quat6) > 0)  // We have asked for GRV data so we should receive Quat6
  {
    // Q0 value is computed from this equation: Q0^2 + Q1^2 + Q2^2 + Q3^2 = 1.
    // In case of drift, the sum will not add to 1, therefore, quaternion data need to be corrected with right bias values.
    // The quaternion data is scaled by 2^30.

    //Serial.printf("Quat6 data is: Q1:%ld Q2:%ld Q3:%ld\r\n", data.Quat6.Data.Q1, data.Quat6.Data.Q2, data.Quat6.Data.Q3);

    // Scale to +/- 1
    double q1 = ((double)data.Quat6.Data.Q1) / 1073741824.0;  // Convert to double. Divide by 2^30
    double q2 = ((double)data.Quat6.Data.Q2) / 1073741824.0;  // Convert to double. Divide by 2^30
    double q3 = ((double)data.Quat6.Data.Q3) / 1073741824.0;  // Convert to double. Divide by 2^30

    // Convert the quaternions to Euler angles (roll, pitch, yaw)
    // https://en.wikipedia.org/w/index.php?title=Conversion_between_quaternions_and_Euler_angles&section=8#Source_code_2

    double q0 = sqrt(1.0 - ((q1 * q1) + (q2 * q2) + (q3 * q3)));

    double q2sqr = q2 * q2;

    // // roll (x-axis rotation)
    // double t0 = +2.0 * (q0 * q1 + q2 * q3);
    // double t1 = +1.0 - 2.0 * (q1 * q1 + q2sqr);
    // double roll = atan2(t0, t1) * 180.0 / PI;

    // pitch (y-axis rotation)
    double t2 = +2.0 * (q0 * q2 - q3 * q1);
    t2 = t2 > 1.0 ? 1.0 : t2;
    t2 = t2 < -1.0 ? -1.0 : t2;
    pitch = asin(t2) * 180.0 / PI;

    // // yaw (z-axis rotation)
    // double t3 = +2.0 * (q0 * q3 + q1 * q2);
    // double t4 = +1.0 - 2.0 * (q2sqr + q3 * q3);
    // double yaw = atan2(t3, t4) * 180.0 / PI;
  }

  return pitch;
}

Next was to combine those two. Still no PID, but can I read the accelerometer and run the motors at the same time. No wheels on the bot. Just seeing if the motors turn when I tilt it and making sure they go the right direction. Again, basic basic small steps. One at a time.

See the Code
#include "GPT_Stepper.h"

#include "ICM_20948.h"

#define WIRE_PORT Wire1
#define AD0_VAL 1

const uint8_t rightStepPin = 2;
const uint8_t rightDirPin = 5;
const uint8_t leftStepPin = 4;
const uint8_t leftDirPin = 7;

GPT_Stepper leftStepper(leftStepPin, leftDirPin, 17000.0, true);
GPT_Stepper rightStepper(rightStepPin, rightDirPin, 17000.0, false);

float speed = 10000.0;



ICM_20948_I2C icm;
icm_20948_DMP_data_t data;

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("\n\nStarting BalanceBot_Test.ino\n\n");

  leftStepper.init();
  rightStepper.init();

  WIRE_PORT.begin();
  WIRE_PORT.setClock(400000);

  setupIMU();
}

void loop() {
  controlLoop();
}


float pitchToSpeed(float pitch) {
  float pmin = -45.0;
  float pmax = 45.0;
  float smin = -10000;
  float smax = 10000;

  return (pitch - pmin) * (smax - smin) / (pmax - pmin) + smin;
}

void controlLoop() {
  if (newData()) {
    float speed = pitchToSpeed(readPitch());
    leftStepper.setSpeed(speed);
    rightStepper.setSpeed(speed);
  }
}

void setupIMU() {
  bool initialized = false;
  while (!initialized) {

    // Initialize the ICM-20948
    // If the DMP is enabled, .begin performs a minimal startup. We need to configure the sample mode etc. manually.

    icm.begin(WIRE_PORT, AD0_VAL);


    Serial.print(F("Initialization of the sensor returned: "));
    Serial.println(icm.statusString());

    if (icm.status != ICM_20948_Stat_Ok) {
      Serial.println(F("Trying again..."));

      delay(500);
    } else {
      initialized = true;
    }
  }
  Serial.println("\n\nConnected to IMU\n\n");
  bool success = true;
  success &= (icm.initializeDMP() == ICM_20948_Stat_Ok);
  success &= (icm.enableDMPSensor(INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR) == ICM_20948_Stat_Ok);
  success &= (icm.setDMPODRrate(DMP_ODR_Reg_Quat6, 0) == ICM_20948_Stat_Ok);
  // Enable the FIFO
  success &= (icm.enableFIFO() == ICM_20948_Stat_Ok);

  // Enable the DMP
  success &= (icm.enableDMP() == ICM_20948_Stat_Ok);

  // Reset DMP
  success &= (icm.resetDMP() == ICM_20948_Stat_Ok);

  // Reset FIFO
  success &= (icm.resetFIFO() == ICM_20948_Stat_Ok);

  // Check success
  if (success) {
    Serial.println("DMP enabled!");
  } else {
    Serial.println("Enable DMP failed!");
    Serial.println("Please check that you have uncommented line 29 (#define ICM_20948_USE_DMP) in ICM_20948_C.h...");
    while (1)
      ;  // Do nothing more
  }
}


bool newData() {
  icm.readDMPdataFromFIFO(&data);

  if ((icm.status == ICM_20948_Stat_Ok) || (icm.status == ICM_20948_Stat_FIFOMoreDataAvail))  // Was valid data available?
  {
    return true;
  }
  return false;
}

double readPitch() {

  double pitch = 0;

  if ((data.header & DMP_header_bitmap_Quat6) > 0)  // We have asked for GRV data so we should receive Quat6
  {
    // Q0 value is computed from this equation: Q0^2 + Q1^2 + Q2^2 + Q3^2 = 1.
    // In case of drift, the sum will not add to 1, therefore, quaternion data need to be corrected with right bias values.
    // The quaternion data is scaled by 2^30.

    //Serial.printf("Quat6 data is: Q1:%ld Q2:%ld Q3:%ld\r\n", data.Quat6.Data.Q1, data.Quat6.Data.Q2, data.Quat6.Data.Q3);

    // Scale to +/- 1
    double q1 = ((double)data.Quat6.Data.Q1) / 1073741824.0;  // Convert to double. Divide by 2^30
    double q2 = ((double)data.Quat6.Data.Q2) / 1073741824.0;  // Convert to double. Divide by 2^30
    double q3 = ((double)data.Quat6.Data.Q3) / 1073741824.0;  // Convert to double. Divide by 2^30

    // Convert the quaternions to Euler angles (roll, pitch, yaw)
    // https://en.wikipedia.org/w/index.php?title=Conversion_between_quaternions_and_Euler_angles&section=8#Source_code_2

    double q0 = sqrt(1.0 - ((q1 * q1) + (q2 * q2) + (q3 * q3)));

    double q2sqr = q2 * q2;

    // // roll (x-axis rotation)
    // double t0 = +2.0 * (q0 * q1 + q2 * q3);
    // double t1 = +1.0 - 2.0 * (q1 * q1 + q2sqr);
    // double roll = atan2(t0, t1) * 180.0 / PI;

    // pitch (y-axis rotation)
    double t2 = +2.0 * (q0 * q2 - q3 * q1);
    t2 = t2 > 1.0 ? 1.0 : t2;
    t2 = t2 < -1.0 ? -1.0 : t2;
    pitch = asin(t2) * 180.0 / PI;

    // // yaw (z-axis rotation)
    // double t3 = +2.0 * (q0 * q3 + q1 * q2);
    // double t4 = +1.0 - 2.0 * (q2sqr + q3 * q3);
    // double yaw = atan2(t3, t4) * 180.0 / PI;
  }

  return pitch;
}

I don't ever make the next move until the last one is working. By the time my robot stands up there will probably be dozens of these little simple one-step tests. Right now I am building a simple PID loop. I know it won't be able to stand, but I want to make sure I get it at least moving in the correct direction before I let it get too complicated. After it stands I'll work on making it roll forward and backwards. And then turning. One simple step at a time.

If there are any new parts or new bits of code that I haven't used before, then they get their own test sketch all by themselves to learn how they work before they get added to the larger code.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.