Random Movement of Stepper for R2D2 Robot - Help!!

190 views
Skip to first unread message

sup...@aussierobotics.com

unread,
Aug 19, 2016, 3:22:12 AM8/19/16
to accelstepper
Hi,

I am really hoping someone here can help me out with this. I have made a 1:1 scale R2D2 Robot and wanted to use a stepper motor to control R2's head movement. So far I have a joystick working the way I want, as in his head will always return to centre. If I move the joystick left his head will turn left but when I let the joystick go R2's head will return to centre. At some point I am going to have to have some code to home R2's head when he is first turned on but for now I am just working on getting his head to do some random head turns and that's where I am stuck now!

Below is my test code, I have made a note where I need some help so I hope someone can give me some advice. Right now when I press the PS Button I get the random sounds I want and I can see that the stepper is moving or clicking as if it is trying to move so somewhere in this part of the code I am going wrong and I don't know what else to try.

Greg


Enter code here...#include <AccelStepper.h>
#include <MultiStepper.h>
#include <MP3Trigger.h>
 MP3Trigger trigger;
#include <SPI.h>
#include <Wire.h>
 
#include "servoshock.h"
#define MS1 4
#define MS2 5 

const int domeBaudeRate = 9600;     //Set the baude rate for the Kangaroo
                                   
const int slaveSelect = SS;         //Set the slave select pin for the ServoShock. 
                                    //Set jumper JP2 on the Shield to D10 if using digital output 10.
                             
AccelStepper stepper(1,2,3);
ServoShock Servoshock1(slaveSelect);  //Create instance of ServoShock
byte vol = 20; // 0 = full volume, 255 off
boolean isInAutomationMode = false;
unsigned long automateMillis = 0;
byte automateDelay = random(5, 20);
int turnDirection = 20;
byte automateAction = 0;
int domeRight = 100;
int domeLeft  = -100;
   
void setup(){
 digitalWrite(slaveSelect,HIGH); 
 SPI.setDataMode(SPI_MODE0);
 SPI.setClockDivider(SPI_CLOCK_DIV32);
 SPI.setBitOrder(MSBFIRST);
 SPI.begin();
 trigger.setup(&Serial);
 trigger.setVolume(vol);      //anything with trigger is for the mp3trigger
 pinMode(MS1, OUTPUT);
 pinMode(MS2, OUTPUT);
 
 digitalWrite(MS1, LOW);
 digitalWrite(MS2, LOW);
 stepper.setMaxSpeed(1000);
 stepper.setAcceleration(1000);
 
 }
 
void loop(){
  Servoshock1.update();
 
  if (Servoshock1.inPacket.psButton){     //Toggle automation mode with the PS Button
     if (isInAutomationMode){
        isInAutomationMode = false;
        automateAction = 0;
        trigger.play(1);
      } else {
        isInAutomationMode = true;
        trigger.play(2);
      }
    }
   if (isInAutomationMode){
    unsigned long currentMillis = millis();
    if (currentMillis - automateMillis > (automateDelay * 1000)){
      automateMillis = millis();
      automateAction = random(1, 5);
      if (automateAction > 1){
        trigger.play(random(32,52));
      }
//=================================================================================
//                    Below is where is need some help!
//
//   The idea here is that the robots head would turn at random at the value of
//   turnDirection from one side then stop untill it was ready to turn all the way to
//   the other side and stop again.
//  
//=================================================================================
      if (automateAction < 4){                     
        stepper.moveTo(turnDirection);  //set the direction of the turn
        stepper.setSpeed(500);          //speed at which the motor is turning
       
        delay(750);
         
        stepper.moveTo(0);              //this is to stop the motor before it makes its next turn
        stepper.setCurrentPosition(0);
        stepper.setSpeed(0);
      if (turnDirection > 0){
        turnDirection = domeRight;
      } else {
        turnDirection = domeLeft;
      }
    }
         
        stepper.run();
        automateDelay = random(5,20);
      }
    }
 
 
//============================================================================
//
//                        Joystick Control
//
//============================================================================ 
 
  int domeMotor = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);
   
  stepper.moveTo(domeMotor);
  stepper.setSpeed(800);
  stepper.runSpeedToPosition();
}

R2_ServoShock_TestV1_Stepper.ino

gregor

unread,
Aug 19, 2016, 3:27:14 PM8/19/16
to accelstepper
Hi, 

I don't fully understand your code. Is this your actual .ino file or just some relevant code copied into a single file?

      if (automateAction < 4){                      
        stepper.moveTo(turnDirection);  //set the direction of the turn
        stepper.setSpeed(500);          //speed at which the motor is turning
        
        delay(750);
          
        stepper.moveTo(0);              //this is to stop the motor before it makes its next turn
        stepper.setCurrentPosition(0);
        stepper.setSpeed(0);

what is the purpose of this piece of code? the first two AccelStepper calls will have no effect.

also, your stepper.run() call seems to be within this block, where it will only be called once:
 if (currentMillis - automateMillis > (automateDelay * 1000)){ /* */ }
put the stepper.run() call at the end of your loop() function and try again. 

I think this code should be inside an if block (or similar), otherwise it will be called during each execution of the loop function: 
  int domeMotor = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);
    
  stepper.moveTo(domeMotor);
  stepper.setSpeed(800);
  stepper.runSpeedToPosition();

please also tell us what exactly is going wrong. 

sup...@aussierobotics.com

unread,
Aug 20, 2016, 4:21:40 AM8/20/16
to accelstepper
Hi,

Thanks for your reply!

Yes it is my code all but the following part where I did copy that from some examples trying to get it working which it does not!

if (automateAction < 4){                      
        stepper
.moveTo(turnDirection);  //set the direction of the turn
        stepper
.setSpeed(500);          //speed at which the motor is turning
       
        delay
(750);
         
        stepper
.moveTo(0);              //this is to stop the motor before it makes its next turn
        stepper
.setCurrentPosition(0);
        stepper
.setSpeed(0);

I have this working with a DC motor but it has no position sensors and just moves the motor from side to side at random and is not that great. This is why I thought a steeper might be better as you can control how far it will move! The code with the DC motor is as follows.

if (Servoshock1.inPacket.psButton){       //Toggle automation mode with the PS Button.
     if (isInAutomationMode){
        isInAutomationMode = false;
        automateAction = 0;
        trigger.play(1);
      }
  else
      {
        isInAutomationMode = true;
        trigger.play(2);
      }
    }
   if (isInAutomationMode){
    unsigned long currentMillis = millis();
    if (currentMillis - automateMillis > (automateDelay * 1000)){
      automateMillis = millis();
      automateAction = random(1, 5);
      if (automateAction > 1){
        trigger.play(random(32,52));
      }
      if (automateAction < 4){
        Syren10.motor(1, turnDirection);      //This is the speed that the dome will turn at
        delay(750);                           //Longer delay the further dome will travel side to side
        Syren10.motor(1, 0);                  //Turn of dome motor
                 
     if (turnDirection > 0){                  //Speed of the dome. + and - for CW / CCW rotation
        turnDirection = -45;
      } else {
        turnDirection = 30;
        
      }
    }  
   
    automateDelay = random(5,20);             //Delay between dome movement and sound
   }
 }

The above code is executed every time the PS Button is pressed and the automation of the dome (R2's head) and sounds start. If the PS button is pressed again this part will not be active as such. I can still make the dome spin via the joystick and that is why that part below is not inside an if block as such.

int domeMotor = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);
    
  stepper.moveTo(domeMotor);
  stepper.setSpeed(800);
  stepper.runSpeedToPosition();

In the main part of the code after this line:

if (automateAction < 4){

This is where I am trying to place some code so that the dome (R2's head) will spin slowly to one side, stop for a given time then move to a new position on the other side. Automate Action is a random number between 1 and 5. if its greater than one R2 will make sounds, but if its less than 4 R2 will turn his head.

if (turnDirection > 0){                  //Speed of the dome. + and - for CW / CCW rotation
        turnDirection = -45;
      } else {
        turnDirection = 30;

The idea above with turn direction states which side R2's head is going to turn, left or right. So its this part where I need some help as I have no idea what to put there to get this to work.  So I am think that because of when this code is executed by pressing the PS button the stepper.run() may have to be inside this and before or just after automateDelay = random(5,20);  to kep it within this block of code.

So do you have any ideas as to how I might start this code for the automation of the head turning. I have used code from  some of the examples including the one from the random example however all that happends is the stepper motor just hum. 

If you can help that would be great. Hope you can make some understanding of what I have put here. I will attach my full working code with my DC motor so you can have a better understanding of what is going on. Well I hope so!

Thanks Greg 
R2_ServoShock_290516.ino

gregor

unread,
Aug 20, 2016, 10:30:19 AM8/20/16
to accelstepper
Hi, 

On Saturday, August 20, 2016 at 10:21:40 AM UTC+2, sup...@aussierobotics.com wrote:
Hi,

Thanks for your reply!

Yes it is my code all but the following part where I did copy that from some examples trying to get it working which it does not!

if (automateAction < 4){                      
        stepper
.moveTo(turnDirection);  //set the direction of the turn
        stepper
.setSpeed(500);          //speed at which the motor is turning
       
        delay
(750);
         
        stepper
.moveTo(0);              //this is to stop the motor before it makes its next turn
        stepper
.setCurrentPosition(0);
        stepper
.setSpeed(0);


the stepper motor does not actually move while the code is in this branch. with moveTo() and move() you only set the target position for the stepper motor. you need to call run() in a loop or runToPosition() to move the stepper. Try to replace the delay() in this code with runToPosition(), the stepper should then move correctly. However, I do not recommend using runToPosition() because it blocks code execution until the movement is finished. it's better to have a run() at the end of your loop and design your code for that (see below). also, if you use acceleration, do not use setSpeed() unless you really have to. setSpeed() is for use with runSpeed(). you may not need to reset your stepper position after moving.
The code is not only executed once, it is executed each time the loop function is executed and isInAutomationMode == true. you should add a timeout to the if condition: 
if (isInAutomationMode && ((millis() - lastAutomation) > automationDelay)) {


I can still make the dome spin via the joystick and that is why that part below is not inside an if block as such.

int domeMotor = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);
    
  stepper.moveTo(domeMotor);
  stepper.setSpeed(800);
  stepper.runSpeedToPosition();

 the problem with this is that it will override the stepper motor settings you have set earlier in the loop() function. you should put this inside an if- block.

In the main part of the code after this line:

if (automateAction < 4){

This is where I am trying to place some code so that the dome (R2's head) will spin slowly to one side, stop for a given time then move to a new position on the other side. Automate Action is a random number between 1 and 5. if its greater than one R2 will make sounds, but if its less than 4 R2 will turn his head.

if (turnDirection > 0){                  //Speed of the dome. + and - for CW / CCW rotation
        turnDirection = -45;
      } else {
        turnDirection = 30;

The idea above with turn direction states which side R2's head is going to turn, left or right. So its this part where I need some help as I have no idea what to put there to get this to work.  So I am think that because of when this code is executed by pressing the PS button the stepper.run() may have to be inside this and before or just after automateDelay = random(5,20);  to kep it within this block of code.

So do you have any ideas as to how I might start this code for the automation of the head turning. I have used code from  some of the examples including the one from the random example however all that happends is the stepper motor just hum. 

If you can help that would be great. Hope you can make some understanding of what I have put here. I will attach my full working code with my DC motor so you can have a better understanding of what is going on. Well I hope so!

Thanks Greg 

generally, my approach is to only call run() or runSpeed() in a single location in the code, which usually is at the end of the loop() function. I do everything relevant for stepper movement before that, like this:
void loop()
{
//control input goes here
Servoshock1.update();                     //ServoShock update must be at start of main loop

digitalWrite(OEPIN, HIGH);                 //Disable power to the Adafruit Servo Driver 
//more control input...

if (isInAutomationMode && ((millis() - lastAutomation) > automationDelay)) {
//setup automatic movement here
//...

if (automateAction < 4) {
stepper.moveTo(/*target position of choice*/);
}
}

if (/*override by manual control input*/) {
int targetPosition = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);

stepper.moveTo(targetPosition);
}

stepper.run();
}

a few other things:
you may want to reconsider your variable names. for example, you use the variable names 'domeMotor' and 'turnDirection' for stepper motor positions, which is misleading.
you could move the control input code to a separate function which you call at the beginning of the loop function. this makes the code more readable and easier to debug:
void controlInput() 
{
if (millis() - lastInput > 100) //you usually do not need to evaluate user input in each loop. this limits it to 10 times / second
return;

lastInput = millis();

//your control input handling goes here...
Servoshock1.update();                     //ServoShock update must be at start of main loop

if (Servoshock1.inPacket.psButton) {     //Toggle automation mode with the PS Button
if (isInAutomationMode) {
isInAutomationMode = false;
automateAction = 0;
}
else {
isInAutomationMode = true;
}
}

//...

if (isInAutomationMode && ((millis() - lastAutomation) > automationDelay)) {
//setup automatic movement here
//...

if (automateAction < 4) {
stepper.moveTo(/*targetPosition of choice*/); //
}
}

if (/*override by manual control input*/) {
int domeMotor = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);

stepper.moveTo(domeMotor);
}
}


void loop()
{
controlInput();

//anything else you want to do (sounds, lights, limit switches, ...)
//...

stepper.run();
}

good luck!

sup...@aussierobotics.com

unread,
Aug 29, 2016, 3:24:28 AM8/29/16
to accelstepper
Hi,

Wow that's for your help....it's been very kind of you!

Well I have changed some of the variable names as you stated and how now got the random movement working.....well sort of! I think I am close but been new to Arduino  I am not sure how to go about it. At present I can now turn on and off the automate part of the program however in the code below trigger.play(1) and trigger.play(2) are running over between each random sound play. So I have some questions for you? 

if (Servoshock1.inPacket.psButton){     //Toggle automation mode with the PS Button
     
if (isInAutomationMode){
        isInAutomationMode
= false;
        automateAction
= 0;
        trigger
.play(1);
     
} else {
        isInAutomationMode
= true;
        trigger
.play(2);
     
}
   
}
   


Is there a way that once psButton is pressed and that isInAtuomationMode = true that it will stay in that part and not jump out until psButton is pressed again and isInAutomationMode = false. That said the stepper will still have to be turning at random and we have stepper.run() at the end of the if statement.

Now I have tried replacing the if with while in the line of code below however been trying to think of a way to jump out of that while loop and I don't know enough about Arduino as you could say I am still rather green to it lol.

if (isInAutomationMode){
   
unsigned long currentMillis = millis();


In the joystick part of the program I have added an if statement that is overriding the automate part for manual stepper movement, however I was hoping that when it was in automation mode that the stepper movement would random. If I pressed the psbutton that wouls end the automation and I would have manual control over the stepper.

if (Servoshock1.inPacket.cross){
     
int targetPosition = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);
      stepper
.moveTo(targetPosition);
   
}


In your last post you stated that the line of code below should be placed in an if block to add a timeout to the if condition. Can I ask where in the code should that be placed and what lines of the code would be replaced if that is to be added.

if (isInAutomationMode && ((millis() - lastAutomation) > automationDelay)) {

I know that you stated that I might want to look at placing some of this into separate function which I would call at the beginning of the loop function. I am going to do that but first was just trying to get this to work the way I want it and then move on from there.

So I hope I have explained myself ok here. I guess I am wanting the code to do this. If I press a button, the code will then jump to the automate part and do all the random sounds and random stepper movement their until the button is pressed again which will then jump out of the automate part and give me manual control over the stepper motors and sounds. So is there any advice on what this code might look like to achieve this? You help would be very kind. I have placed the test code here again to look over.

Greg
 


R2_ServoShock_TestV2_Stepper.ino

gregor

unread,
Aug 29, 2016, 6:44:25 AM8/29/16
to accelstepper
Hi,


On Monday, August 29, 2016 at 9:24:28 AM UTC+2, sup...@aussierobotics.com wrote:
Hi,

Wow that's for your help....it's been very kind of you!

Well I have changed some of the variable names as you stated and how now got the random movement working.....well sort of! I think I am close but been new to Arduino  I am not sure how to go about it. At present I can now turn on and off the automate part of the program however in the code below trigger.play(1) and trigger.play(2) are running over between each random sound play. So I have some questions for you? 

if (Servoshock1.inPacket.psButton){     //Toggle automation mode with the PS Button
     
if (isInAutomationMode){
        isInAutomationMode
= false;
        automateAction
= 0;
        trigger
.play(1);
     
} else {
        isInAutomationMode
= true;
        trigger
.play(2);
     
}
   
}
   


Is there a way that once psButton is pressed and that isInAtuomationMode = true that it will stay in that part and not jump out until psButton is pressed again and isInAutomationMode = false. That said the stepper will still have to be turning at random and we have stepper.run() at the end of the if statement.
 
Now I have tried replacing the if with while in the line of code below however been trying to think of a way to jump out of that while loop and I don't know enough about Arduino as you could say I am still rather green to it lol.

you can exit while loops with break:
while(someCondition)
{
 
//do stuff

 
if (someOtherCondition)
   
break;
}
However, I would try to not do it that way, because that meant having run() in multiple locations in the code which I try to avoid. Instead, I would just skip parts of the code while automatic movement is active:
void controlInput()
{
   if (millis() - lastInput > 100)         //you usually do not need to evaluate user input in each loop. this limits it to 10 times / second
   return;
   
   lastInput = millis();
   
   //your control input handling goes here...
   Servoshock1.update();                     //ServoShock update must be at start of main loop
   
   if (Servoshock1.inPacket.psButton) {     //Toggle automation mode with the PS Button
       if (isInAutomationMode) {
           isInAutomationMode = false;
           automateAction = 0;
           trigger.play(1);
       }
       else {
           isInAutomationMode = true;
           trigger.play(2);
       }
   }
   
   //...
   
   if (isInAutomationMode && ((millis() - lastAutomation) > automationDelay)) {
       //setup automatic movement here
       //...
       
       if (automateAction < 4) {
           stepper.moveTo(/*targetPosition of choice*/);   //
       }
   }
   
   if (/*override by manual control input*/) {
       int domeMotor = map(Servoshock1.inPacket.lStickX, 0, 255, 200, -200);
       
       stepper.moveTo(domeMotor);
   }
   
   //ignore other inputs if automatic mode is active
   if (isInAutomationMode)
       return;
   
   //the rest of the input handling goes here  
   //...
}

void loop()
{
   controlInput();
   
   //anything else you want to do (sounds, lights, limit switches, ...)
   //...
   
   stepper.run();
}

 
In the joystick part of the program I have added an if statement that is overriding the automate part for manual stepper movement, however I was hoping that when it was in automation mode that the stepper movement would random. If I pressed the psbutton that wouls end the automation and I would have manual control over the stepper.
I thought that you wanted to override automatic movement without disabling the automatic mode. If you don't need the override, remove it; it should not have any effect on the rest of the code.

In your last post you stated that the line of code below should be placed in an if block to add a timeout to the if condition. Can I ask where in the code should that be placed and what lines of the code would be replaced if that is to be added.

if (isInAutomationMode && ((millis() - lastAutomation) > automationDelay)) {

 this line is very similar to this code you already had in your original code, but I overlooked earlier:
if (isInAutomationMode){
unsigned long currentMillis = millis();
Reply all
Reply to author
Forward
0 new messages