4

My 10-year-old son went to a "Robotics Camp" and came home with a non-functional Arduino robot. I figured it shouldn't be too much trouble to get it working. He swears he had it operating at the camp, but our best efforts notwithstanding, we were unable to reproduce his successes at home to any degree at all. Currently we seem to have an ungainly, $400 paperweight.

I'm a .Net web-developer by day, but I'm completely uninitiated when it comes to Arduino. I did enough research to learn how to upload a basic sketch- the "Blink" sketch- to our board an verify that the board is functional. Upon uploading the Blink sketch we got a flashing red LED. I was able to go into the sketch and modify the the variable values and see the results in the duration of the LED flash after uploading the sketch again. So I'm confident that we have a good connection to the PC and a functioning Arduino board.

But beyond that I'm stumped. When I attempt to upload the program that my son brought home, the robot does nothing. I can see a red LED blink a few times on the Arduino, but other that that, nothing happens.

Here's some photos of our setup:

enter image description here enter image description here

Here's the sketch that my son brought home:

/***************************************************
  This is an example for our Adafruit 16-channel PWM & Servo driver
  Servo test - this will drive 16 servos, one after the other

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/products/815

  These displays use I2C to communicate, 2 pins are required to  
  interface. For Arduino UNOs, thats SCL -> Analog 5, SDA -> Analog 4

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.  
  BSD license, all text above must be included in any redistribution
 ****************************************************/

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <QueueList.h>

// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);

// Depending on your servo make, the pulse width min and max may vary, you
// want these to be as small/large as possible without hitting the hard stop
// for max range. You'll have to tweak them as necessary to match the servos you
// have!
#define SERVOMIN  300//150 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  400//600 // this is the 'maximum' pulse length count (out of 4096)

uint16_t servoMins[] = {160, 200, 500, 150, 150, 150, 0};
uint16_t servoMaxs[] = {600, 600, 700, 600, 600, 600, 0};
uint16_t servoHomes[] = {380, 400, 300, 340, 375, 375, 0};

enum GroupType {WithPrevious, NewGroup};

#define PAUSE_COMMAND 6
#define NUM_TRANSITIONS (PAUSE_COMMAND+1)


unsigned long currentTime;

class transition
{
  private:
    unsigned long startTime;
    unsigned long totalTime;
    uint16_t pulseLenStart;
    uint16_t pulseLenEnd;
    uint16_t currentPulseLen;
    int id;
    bool transitioning;

  public:
    void init(int id, uint16_t current)
    {
      this->id = id;
      currentPulseLen = current;
      totalTime = 0;
      startTime = 0;
      pulseLenStart = current;
      pulseLenEnd = current;
      transitioning = false;

    }

    uint16_t calculate(unsigned long currentTime)
    {
      float t = 1.0f;
      if (totalTime > 1e-3f)
        t = (currentTime - startTime) / (float)totalTime;
      t = (t > 1.0f) ? 1.0f : t;
      currentPulseLen = (uint16_t)(((float)pulseLenEnd - (float)pulseLenStart) * t + pulseLenStart);
/*      if (transitioning)
      {
        Serial.println("transitioning");
        Serial.println(id);
        Serial.println(t);
        Serial.println(currentTime);
        Serial.println(startTime);
        Serial.println(totalTime);
        Serial.println(pulseLenStart);
        Serial.println(pulseLenEnd);
        Serial.println(currentPulseLen);
      }
*/
      if (t >= 1.0f)
        transitioning = false;
      return currentPulseLen;
    }

    void createTransition (uint16_t pulseLen, unsigned long time)
    {
/*      Serial.println("create Transition:");
      Serial.println(id);
      Serial.println(pulseLen);
      Serial.println(time);
*/
      pulseLenStart = currentPulseLen;
      pulseLenEnd = pulseLen;
      totalTime = time;
      startTime = currentTime;
      transitioning = true;
    }

    bool isTransitioning()
    {
      return transitioning;
    }
};

transition transitions[NUM_TRANSITIONS];


struct Command
{
  uint16_t pulseLen;
  unsigned long totalTime;
  int commandType;
  GroupType groupType;

  void execute()
  {
    transitions[commandType].createTransition(pulseLen, totalTime);
  }
};
QueueList<Command> commandQueue;

void program ();
void restoreHomePositions (double seconds);

void setup()
{
  pwm.begin();

  Serial.begin(9600);
  Serial.println("Hello world");

  pwm.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  for (int servoNum =0; servoNum < NUM_TRANSITIONS; servoNum++)
  {
    //pwm.setPWM(servoNum, 0, servoHomes[servoNum]);
    transitions[servoNum].init(servoNum, servoHomes[servoNum]);
  }

  restoreHomePositions (0);
  program();
  restoreHomePositions (.75);


  yield();
}

void processCommandQueue();

void loop()
{
  currentTime = millis();

  processCommandQueue();

  for (int servoNum = 0; servoNum < NUM_TRANSITIONS; servoNum++)
  {
    if (transitions[servoNum].isTransitioning())
    {
      uint16_t pulselen = transitions[servoNum].calculate(currentTime);
      if (servoNum != PAUSE_COMMAND)
      {
        pwm.setPWM(servoNum, 0, pulselen);
        Serial.println("setPWM");
        Serial.println(servoNum);
        Serial.println(pulselen);
      }
    }    
  }
}

bool isTransitioning()
{
  for (int servoNum = 0; servoNum < NUM_TRANSITIONS; servoNum++)
  {
    if (transitions[servoNum].isTransitioning())
      return true;
  }
  return false;
}

int frame = 0;
void processCommandQueue()
{
  if (!commandQueue.isEmpty())
  {
    Serial.println("Frame");
    Serial.println(frame);
    frame++;
  }
  if (!commandQueue.isEmpty() && !isTransitioning())
  {
    do
    {
      Command top = commandQueue.pop();
      top.execute();
    }
    while (!commandQueue.isEmpty() && commandQueue.peek().groupType == WithPrevious);
  }
}

void move (int commandType, uint16_t pulseLen, double seconds, GroupType groupType = NewGroup)
{
  Command command;
  command.groupType = groupType;
  command.commandType = commandType;
  command.pulseLen = pulseLen;
  command.totalTime = (unsigned long)(seconds * 1000);
  Serial.println("Command:");
  Serial.println(groupType == NewGroup ? "NewGroup" : "WithPrevious");
  Serial.println(commandType);
  Serial.println(pulseLen);
  Serial.println(command.totalTime);
  commandQueue.push (command);
}

void rotateWrist (int percent, double seconds, GroupType groupType = NewGroup)
{
  percent = percent < -100 ? -100 : percent;
  percent = percent > 100 ? 100 : percent;
  uint16_t pulseLen = map (percent, -100, 100, servoMins[5], servoMaxs[5]);
  move (5, pulseLen, seconds, groupType);
}

void bendWrist (int percent, double seconds, GroupType groupType = NewGroup)
{
  percent = percent < -100 ? -100 : percent;
  percent = percent > 100 ? 100 : percent;
  uint16_t pulseLen = map (percent, -100, 100, servoMins[4], servoMaxs[4]);
  move (4, pulseLen, seconds, groupType);
}

void rotateForeArm (int percent, double seconds, GroupType groupType = NewGroup)
{
  percent = percent < -100 ? -100 : percent;
  percent = percent > 100 ? 100 : percent;
  uint16_t pulseLen = map (percent, -100, 100, servoMins[3], servoMaxs[3]);
  move (3, pulseLen, seconds, groupType);
}

void bendForeArm (int percent, double seconds, GroupType groupType = NewGroup)
{
  percent = percent < -100 ? -100 : percent;
  percent = percent > 100 ? 100 : percent;
  uint16_t pulseLen = map (percent, -100, 100, servoMins[2], servoMaxs[2]);
  move (2, pulseLen, seconds, groupType);
}

void bendUpperArm (int percent, double seconds, GroupType groupType = NewGroup)
{
  percent = percent < -100 ? -100 : percent;
  percent = percent > 100 ? 100 : percent;
  uint16_t pulseLen = map (percent, -100, 100, servoMins[1], servoMaxs[1]);
  move (1, pulseLen, seconds, groupType);
}

void rotateRobot (int percent, double seconds, GroupType groupType = NewGroup)
{
  percent = percent < -100 ? -100 : percent;
  percent = percent > 100 ? 100 : percent;
  uint16_t pulseLen = map (percent, -100, 100, servoMins[0], servoMaxs[0]);
  move (0, pulseLen, seconds, groupType);
}

void pause (double seconds)
{
  move (PAUSE_COMMAND, 0, seconds, NewGroup);
}

void restoreHomePositions (double seconds)
{
  rotateRobot (0, seconds, NewGroup);
  bendUpperArm (0, seconds, WithPrevious);
  bendForeArm (0, seconds, WithPrevious);
  rotateForeArm (0, seconds, WithPrevious);
  bendWrist (0, seconds, WithPrevious);
  rotateWrist (0, seconds, WithPrevious);

}


//This is where the program goes!

void program ()
{
    //Available commands
  /*rotateWrist (where, seconds, groupType)  
  bendWrist (where, seconds, groupType)
  rotateForeArm (where, seconds, groupType)
  bendForeArm (where, seconds, groupType)
  bendUpperArm (where, seconds, groupType)
  rotateRobot (where, seconds, groupType)  
  pause (seconds)*/

  //example 1 -- rotates the wrist servo 100% over 2 seconds,
  //starting a new transition group.
  rotateWrist (100, 2);
  //example 2 -- bends the upper arm and the forearm at the same time
  //moving both 45% over 1.5 seconds.
  bendUpperArm (45, 1.5);
  bendForeArm (45, 1.5, WithPrevious);
  bendForeArm(45, 1.7, WithPrevious);



}

  //To start a new group, the groupType should be either NewGroup or left blank
  //Any call after that set to WithPrevious will start the transition at the
  //same time as the first transition in the group.
  //As soon as a new call is made with a NewGroup groupType, a new transition group is m
// End of your program
  • I wanted to include more photos, but I don't have enough reputation. :( – T1M0THY Aug 24 '16 at 3:28
  • Plug the power pack in? What does the second board do? It is hard or impossible to help without some sort of schematic. Do you get any output in the serial monitor (eg. "Hello world"). I am guessing the second board is a motor driver board. You could get a cheap multimeter and start measuring voltages to see if anything is being sent to the motors. – Nick Gammon Aug 24 '16 at 5:11
  • 2
    It sounds like you have a working Arduino, so the money isn't wasted. This could be a good opportunity to disconnect the robot for now, and just bring yourselves up to speed with simpler sketches (programs). There are quite a few examples that ship with the IDE. Once you have a bit more confidence in how it all hangs together, getting the robot working should be simpler. – Nick Gammon Aug 24 '16 at 5:13
  • 3
    My first step would be to (after documenting the current connections!) disconnect everything and write a simple program to "wave" one of the servos. Then try connecting more parts from there. This sort of troubleshooting experience (when it works) is priceless by the way. – Andy Aug 24 '16 at 8:38
  • 1
    On my page about I2C I have a sketch that detects I2C devices (reply #6). It looks like the PWM board uses I2C, so run that sketch and see if it reports any devices (in particular 0x40). – Nick Gammon Aug 25 '16 at 4:40
1

I'm not familiar with the breadboard in your images. You didn't mention if you're familiar with how to use a breadboard and from what I can see I'm not even sure why its being used. It looks to me like you could just connect the jumper wires directly to the pins on the Arduino.

Was the Arduino ever unplugged from the breadboard after it was known to work properly?

Due to the angle of the photo it is hard to see which pins the wires are connected to. Can you identify which Arduino pins the six wires are connected to?

| improve this answer | |
  • The breadboard is a standard one with lines of five connected terminals radiating away perpendicular from a center channel (which is covered in that photo). Typically this is used to allow standard jumper wires to be connected to the board's header pins rather than having to use jumper wires with a female socket at one end, and it also allows multiple jumper wires easily to be connected to a single pin. – cjs Apr 23 '17 at 3:22
1

Please consider the following to assist in your understanding of the Arduino and of the servo operation. I recently "instructed" a summer camp of teens to construct the MeArm robot arm, using inexpensive servos and an Arduino.

The servos you have will operate without the servo driver, enabling you to isolate the problem. Consider to connect the signal wire for each servo to an assigned pin on the Arduino and test each servo with the built-in Sweep sketch. Your off-board power for the servos should share the ground with the Arduino, while the +V would connect to the power lead for the breadboard and hence to the servos.

Pins 6, 9, 10, 11 are PWM capable and you can modify in the Sweep sketch to operate each one independently as a way of testing the base "kit."

The code for Sweep is simplistic, while the servo driver has a good bit of "middle-man" calculations to accomplish more refined control.

Example from the summer camp:

[in setup] claw.attach(9); inout.attach(10); updown.attach(11); rightleft.attach(6);

[in loop] - the following was placed in a for-loop: claw.write(pos);

... and more code including delays and limits to pos to prevent servo chatter/slam.

This will likely show you that your servos are working (or not) and enable a better understanding of the basics, while perhaps increasing your chances of understanding the more complex and flexible servo board library of code.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.