Incremental Servo movement via Joystick Input

Hey guys
I hope you are doing well? I might need some help in coding :wink:

Intro:
I designed in CAD and built with my 3D printer a pretty cool Nerf Sentry. Since I'm a mechanical engineer the mechanical and also the electrical design of the thing was "easy" but now with the coding of the Arduino I'm a bit confused and clueless, since it is my first big Arduino project.

Here are some pictures and Hardware setup
*Since new in this forum, I'm allowed to post only one picture :stuck_out_tongue: *

Hardware Setup:

The Hardware itself functions as it should. With Arduino example sketches I can move all the motors, steppers and Servos.
The only thing there is, that once I'm not working with MICROSTEP the Steppers are rattling quiet loud.

Questions & UPDATE: See Reply: Things work now
This code was snipped together from different sources and example sketches. II tried to format and comment it very clearly. I hope you get my attempt of using millis() and calling the functions for simultaneous operation. My biggest problem right now is that I'm confused using steppers with this motorshield and how I implemented them with AccelStepper.h:

-->See Code with function description in the beginning

  1. So in Function void shoot () the Stepper1 should move a determined amount of steps forward... but rightnow nothing happens.
    1.1) Also this delay() should not be used in the code.

So what can I use to get this behavior:

  • Once the Joystick Button is pushed, the DC Motors are powered on, the StepperMotor (Stepper1) moves X steps and the DC Motors are powered off again. (Repeat this once the button is pushed again after a determined break time)
  1. In function void moveXSentry() the Stepper (Stepper2) should move according to the Joystick input in a defined space. Rightnow nothing happens again :stuck_out_tongue:
    How can I set this function right?

  2. The void idle() function is not done jet: Basically the turret should stop moving once all darts are fired. How can I setup something like this?
    Is there a possibility to actually switch to an Input from lets say a 2nd jostick?

Well that's it for the beginning. I think it's not a big issue, just dump code I copied together.
Let me know I I can provide more information.

Cheers NTC

Code

Here is my code with a functional description in the beginning:

// V3.0 Sentry Code

// this sketch does the following:
//    1) Function: void shoot()
//    When Jostick Button is pushed;
//     - it turns on two DC Motors by switching on a NO relais module for a short time (<1s)
//     - it simoultaneously moves a Stepper motor (STM1) a determined amount of steps FORWARD
//     - it counts down the amount of darts left (18 darts in total)
//    --> A spring which is tensioned by the STM1 pushes a FoamDart into the two DC Motors and shoots out
//    --> The Jostick button has a delay (buttonInterval) so that it is not possible to shoot rappidly

//    2) Function: void moveYSentry ()
//    - it detects Y Input of a jostick on A2
//    - it controls the position of SERVO01 and so the pitch axis of the sentry
//    --> If jostick is neutral the sentry moves back on 90° Position (0-180°)
//
//    3) Function: void moveXSentry ()
//    - it detects X Input of a jostick on A1
//    - it controls a Stepper Motor (STM2) and so the yaw axis of the sentry
//    --> The sentry should not be allowed to turn 360° since cabling issues
//    --> Amount of RotarySteps should define movement boundaries (f.e. (MaxLeft 0 ... 2500 middle...5000 MaxRight)
//
//    4) Function: void idle ()
//    - Once all darts ar fired the sentry should not mover or shoot anymore
//
//  --> For simultaneous operation of all the functions millis(); is used
//========================================

// ----------LIBRARIES--------------

#include <Wire.h>
#include <Servo.h>
#include <Adafruit_MotorShield.h>
#include <AccelStepper.h>
#include <utility/Adafruit_MS_PWMServoDriver.h>


// --------CONNECTIONS---------------
const int MotorPin = 7;   // Motor Relais Pin

const int SERVO1Pin = 9;   // Pitch Servo2 slot Pin 10

const int JoyStick_B = 3;   // D3 Joystick Button Pin
const int JoyStick_X = 1;   //  A1 Jostick Input  - pitch Axis = Up (0) - Center (507..08) - Down (1023)
const int JoyStick_Y = 2;   //  A2 Jostick Input  - Yaw Axis = Left (1023) - Center (508..509) - Right (0)


// --------CONSTANTS---------------

const int buttonInterval = 500;       // number of millisecs between button readings
const int JostickSignalDelay = 150;   // Delay in ms for stability (needed?)
const int servoPositionS1 = 90;        // the current angle of the servo - starting at 90.


//------------ VARIABLES---------------------

int x_NS;             
int y_NS;             
int stateB_NS = 0;         // State Yostick Button
int rotarysteps = 2500;  //Amonunt of steps for Yaw-Axis

int ammunition = 18;      // Total of 18 darts

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()

unsigned long previousButtonMillis = 0; // time when button press last checked

unsigned long previousServoMillis = 0; // the time when the servo was last moved

unsigned long previousJoystickMillis = 0; // the time when the Joystick move was last checked


//------------ OBJECTS---------------------

Adafruit_MotorShield AFMS = Adafruit_MotorShield();   // Create the motor shield object with the default I2C address

Servo SERVO1;    // Pitch Servo created


//------------ STEPPER SETUP---------------

// Connect a stepper motor with 200 steps per revolution (1.8 degree)
// to motor port #1 (M1 and M2) or #2 (M3 and M4)
Adafruit_StepperMotor *STM1 = AFMS.getStepper(200, 1); // MagazineMover
Adafruit_StepperMotor *STM2 = AFMS.getStepper(200, 2); // Turn Sentry

// wrappers for the first motor
void forwardstep1() {
  STM1->onestep(FORWARD, MICROSTEP);
}
void backwardstep1() {
  STM1->onestep(BACKWARD, MICROSTEP);
}
// wrappers for the second motor
void forwardstep2() {
  STM2->onestep(FORWARD, MICROSTEP);
}
void backwardstep2() {
  STM2->onestep(BACKWARD, MICROSTEP);
}

// Now we'll wrap the 2 steppers in an AccelStepper object
AccelStepper stepper1(forwardstep1, backwardstep1);
AccelStepper stepper2(forwardstep2, backwardstep2);

//========================================

//------------ VOID SETUP ---------------------------------------------------------

void setup() {
  Serial.begin(9600);
  Serial.println("Starting V3_NerfSentry_1.ino");

  SERVO1.attach(SERVO1Pin);
  SERVO1.write(servoPositionS1); // sets the initial position

  AFMS.begin();

  //Magazine Mover
  stepper1.setMaxSpeed(2000.0);
  stepper1.setSpeed(500.0);

  //Turred Mover
  stepper2.setMaxSpeed(3000.0);
  stepper2.setAcceleration(750.0);

  pinMode(MotorPin, OUTPUT);      // Set Pinmode for Relais Pin 9
  digitalWrite(MotorPin, LOW);    // Turns DC-Motors off in the beginning

}


//------------ VOID LOOP ---------------------------------------------------

void loop() {

  // Notice that none of the action happens in loop() apart from reading millis()
  // it just calls the functions that have the action code

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
  //   use the same time for all LED flashes to keep them synchronized

  readJoystick();
  moveXSentry();
  moveYSentry();
  shoot();
  idle();

}

//------------FUNCTIONS---------------------------------------------------------

void readJoystick() {

  if (currentMillis - previousJoystickMillis >= JostickSignalDelay) {
    x_NS = analogRead(JoyStick_X);
    y_NS = analogRead(JoyStick_Y);
    stateB_NS = digitalRead(JoyStick_B);
    Serial.print(x_NS , DEC);
    Serial.print(",");
    Serial.print(y_NS , DEC);
    Serial.print(",");
    Serial.println(stateB_NS , DEC);

    previousJoystickMillis += JostickSignalDelay;
  }
}

//========================================
// - Move ROTATION Sentry function

void moveXSentry() {
  if (analogRead(JoyStick_X) >= 550 && rotarysteps == 5000) { // If Jostick is moved left
    stepper2.setSpeed(50);
    stepper2.runSpeed();
    rotarysteps++;
  }

  if (analogRead(JoyStick_X) <= 500 && rotarysteps == 0) {   // If Jostick is moved lef
    stepper2.setSpeed(-50);
    stepper2.runSpeed();
    rotarysteps--;
  }
}

//========================================
// - Move Pitch Sentry function

void moveYSentry() {

  y_NS = analogRead(JoyStick_Y);     // UP-Down reads the value of the potentiometer (value between 0 and 1023)
  y_NS = map(y_NS, 0, 1023, 180, 0); // scale it to use it with the servo (value between 0 and 180)
  SERVO1.write(y_NS);               // sets the servo position according to the scaled value
}

//========================================
// - Shoot function

void shoot() {
  // this only reads the button state after the button interval has elapsed
  //  this avoids multiple shots if the button bounces

  stateB_NS = digitalRead(JoyStick_B);

  if (millis() - previousButtonMillis >= buttonInterval) {
    if (stateB_NS == HIGH) {
      digitalWrite(MotorPin, HIGH);
      stepper1.setCurrentPosition((long) 500)  //Amount of steps the motor needs to move forward
      delay (1000);
      digitalWrite(MotorPin, LOW);

      ammunition--;

    }

    previousButtonMillis += buttonInterval;
  }
}


//========================================
// - EmptyMagazine function

void idle() {

  if (ammunition == 0) {        // If no darts left,
    // Jostick not operating anymore
  }
}

//========================================END===============================

Hi NTC,

This is a very good first posting. Congratulations.
A picture, a detailed description and code posted as a code-section.
Very well done.

The rattling on halfstep or fullstep are "normal" for standard stepper-motor-drivers.
They just switch on/off the coils and this makes noise when rotating the stepper-motor.
More modern advanced stepper-drivers like a TMC2209 have a silent-mode where you hear almost nothing from the stepper-motor.

You should do some basic tests if you can drive a single stepper-motor at all.
If this is working adding the second stepper-motor if you can drive the second stepper-motor at all.

The method goes on in this way. Adding one thing at a time, testing thouroghly and successful tests
Adding one thing at a time, testing thouroghly and successful tests
best regards Stefan

Thank you for your answer.

So I figured out almost everything and the code is running good with the code I post in this reply.

One thing I want to improve and I need help is the following:
In the Pitch Axis with the Servo I want to have incremental movement. Means: if I move the joystick up the Servo should pitch the sentry gradually up and hold the position. Else if I pull the joystick down the pitch should decrease and stay at the position when joystick is not moved.

I thought the most simple solution is:
Add 1 degree to the ServoPosition1 when joystick is moved up and subtract 1 from the desired position when joystick is moved down.

So I implemented this in the void moveYStepSentry() function but once I start the sketch with this function activated the servo moves once and everything freezes up. By this I mean the Servo is not reacting anymore and the serial monitor stops plotting values.

What do I do wrong?

Thank you for support. Cheers

// V3.0 Sentry Code

// this sketch does the following:
//    1) Function: void shoot()
//    When Joystick Button is pushed;
//     - it turns on two DC Motors by switching on a NO relais module for a short time (<1s)
//     - it simoultaneously moves a Stepper motor (STM1) a determined amount of steps FORWARD
//     - it counts down the amount of darts left (18 darts in total)
//    --> A spring which is tensioned by the STM1 pushes a FoamDart into the two DC Motors and shoots out
//    --> The Joystick button has a delay (buttonInterval) so that it is not possible to shoot rappidly

//    2)A Function: void moveYSentry ()
//    - it detects Y Input of a joystick on A2
//    - it controls the position of SERVO01 and so the pitch axis of the sentry
//    --> If joystick is neutral the sentry moves back on 90° Position (0-180°)

//    2)B Function: void moveYStepSentry ()
//    - it detects Y Input of a joystick on A2
//    - it controls incremental the position of SERVO01 and so the pitch axis of the sentry
//    --> If joystick is neutral the sentry holds position Position (0-180°)
//
//    3) Function: void moveXSentry ()
//    - it detects X Input of a joystick on A1
//    - it controls a Stepper Motor (STM2) and so the yaw axis of the sentry
//    --> The sentry should not be allowed to turn 360° since cabling issues
//    --> Amount of RotarySteps should define movement boundaries (f.e. (MaxLeft 0 ... 2500 middle...5000 MaxRight)
//
//    4) Function: void idle ()
//    - Once all darts are fired the sentry should not mover or shoot anymore
//
//  --> For simultaneous operation of all the functions millis(); is used
//========================================

// ----------LIBRARIES--------------

#include <Wire.h>
#include <Servo.h>
#include <Adafruit_MotorShield.h>
#include <AccelStepper.h>
#include <utility/Adafruit_MS_PWMServoDriver.h>


// --------CONNECTIONS---------------
const int MotorPin = 7;   // Motor Relais Pin

const int SERVO1Pin = 9;   // Pitch Servo2 slot Pin 10

const int JoyStick_B = 3;   // D3 Joystick Button Pin
const int JoyStick_X = 1;   //  A1 Jostick Input  - pitch Axis = Up (0) - Center (507..08) - Down (1023)
const int JoyStick_Y = 2;   //  A2 Jostick Input  - Yaw Axis = Left (1023) - Center (508..509) - Right (0)


// --------CONSTANTS---------------

const int buttonInterval = 2000;       // number of millisecs between button readings
const int JostickSignalDelay = 150;   // Delay in ms for stability (needed?)
int servoPositionS1 = 90;        // the current angle of the servo - starting at 90.


//------------ VARIABLES---------------------

int x_NS;
int y_NS;
int stateB_NS = 0;         // State Yostick Button
int rotarysteps = 2500;  //Amonunt of steps for Yaw-Axis

int ammunition = 18;      // Total of 18 darts

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()

unsigned long previousButtonMillis = 0; // time when button press last checked

unsigned long previousServoMillis = 0; // the time when the servo was last moved

unsigned long previousJoystickMillis = 0; // the time when the Joystick move was last checked


//------------ OBJECTS---------------------

Adafruit_MotorShield AFMS = Adafruit_MotorShield();   // Create the motor shield object with the default I2C address

Servo SERVO1;    // Pitch Servo created


//------------ STEPPER SETUP---------------

// Connect a stepper motor with 200 steps per revolution (1.8 degree)
// to motor port #1 (M1 and M2) or #2 (M3 and M4)
Adafruit_StepperMotor *STM1 = AFMS.getStepper(200, 1); // MagazineMover
Adafruit_StepperMotor *STM2 = AFMS.getStepper(200, 2); // Turn Sentry

//========================================

//------------ VOID SETUP ---------------------------------------------------------

void setup() {
  Serial.begin(9600);
  Serial.println("Starting V3_NerfSentry_1.ino");

  SERVO1.attach(SERVO1Pin);
  SERVO1.write(servoPositionS1); // sets the initial position

  AFMS.begin();

  STM1->setSpeed(1000);

  pinMode(MotorPin, OUTPUT);      // Set Pinmode for Relais Pin 9
  digitalWrite(MotorPin, LOW);    // Turns DC-Motors off in the beginning

}


//------------ VOID LOOP ---------------------------------------------------

void loop() {

  // Notice that none of the action happens in loop() apart from reading millis()
  // it just calls the functions that have the action code

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
  //   use the same time for all LED flashes to keep them synchronized

  readJoystick();
  moveXSentry();
  //moveYSentry();
  moveYStepSentry();
  shoot();
  idle();

}

//------------FUNCTIONS---------------------------------------------------------

void readJoystick() {

  if (currentMillis - previousJoystickMillis >= JostickSignalDelay) {
    x_NS = analogRead(JoyStick_X);
    y_NS = analogRead(JoyStick_Y);
    stateB_NS = digitalRead(JoyStick_B);
    Serial.print(x_NS , DEC);
    Serial.print(",");
    Serial.print(y_NS , DEC);
    Serial.print(",");
    Serial.println(stateB_NS , DEC);

    previousJoystickMillis += JostickSignalDelay;
  }
}

//========================================
// - Move ROTATION Sentry function

void moveXSentry() {
  x_NS = analogRead(JoyStick_X);
  //if (currentMillis - previousJoystickMillis >= JostickSignalDelay) {}

  if ((x_NS > 500) && (x_NS < 523))
  {
    STM2->release();
  }

  else
  {
    while ((x_NS <= 500))
    {
      STM2->step(1, FORWARD, MICROSTEP);
      x_NS = analogRead(JoyStick_X);
    }
    while ((x_NS >= 523))
    {
      STM2->step(1, BACKWARD, MICROSTEP);
      x_NS = analogRead(JoyStick_X);

    }
  }
}

//========================================
// - Move Pitch Sentry function

void moveYSentry() {

  y_NS = analogRead(JoyStick_Y);     // UP-Down reads the value of the potentiometer (value between 0 and 1023)
  y_NS = map(y_NS, 0, 1023, 180, 0); // scale it to use it with the servo (value between 0 and 180)
  SERVO1.write(y_NS);               // sets the servo position according to the scaled value
}

//========================================
// - Move Pitch Sentry Inkremental function

void moveYStepSentry() {

  y_NS = analogRead(JoyStick_Y);     // UP-Down reads the value of the potentiometer (value between 0 and 1023)

  if ((y_NS > 500) && (y_NS < 523))
  {
    //Do nothing
  }
  else
  {
    while ((y_NS < 500))
    {
      if (servoPositionS1<180)
       {
        servoPositionS1++;
        SERVO1.write(servoPositionS1);
       }
      else
      SERVO1.write(servoPositionS1);
    }

    while ((y_NS > 523))
    {
      if (servoPositionS1>0)
       {
        servoPositionS1--;
        SERVO1.write(servoPositionS1);
       }
      else
      SERVO1.write(servoPositionS1);
    }

  }
}


//========================================
// - Shoot function

void shoot() {
  stateB_NS = digitalRead(JoyStick_B);
  //Serial.println(previousButtonMillis);
  //Serial.println(buttonInterval);

  if (millis() - previousButtonMillis >= buttonInterval) {
    if (stateB_NS == HIGH) {
      digitalWrite(MotorPin, HIGH);
      STM1->step(25, BACKWARD, MICROSTEP); //23-25 steps per shot
      STM1->release();
      digitalWrite(MotorPin, LOW);

      ammunition--;
      previousButtonMillis += buttonInterval;
    }
  }
}


//========================================
// - EmptyMagazine function

void idle() {

  if (ammunition == 0) {        // If no darts left,
    // Jostick not operating anymore
  }
}


//========================================END=======================================

as a general advice: adding serial debug-output will show you what is really going on in your code

I don't have your libraries installed but I'm pretty sure it compiles

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// Take it for granted at the moment scroll down to void setup
// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *

// V3.0 Sentry Code

// this sketch does the following:
//    1) Function: void shoot()
//    When Joystick Button is pushed;
//     - it turns on two DC Motors by switching on a NO relais module for a short time (<1s)
//     - it simoultaneously moves a Stepper motor (STM1) a determined amount of steps FORWARD
//     - it counts down the amount of darts left (18 darts in total)
//    --> A spring which is tensioned by the STM1 pushes a FoamDart into the two DC Motors and shoots out
//    --> The Joystick button has a delay (buttonInterval) so that it is not possible to shoot rappidly

//    2)A Function: void moveYSentry ()
//    - it detects Y Input of a joystick on A2
//    - it controls the position of SERVO01 and so the pitch axis of the sentry
//    --> If joystick is neutral the sentry moves back on 90° Position (0-180°)

//    2)B Function: void moveYStepSentry ()
//    - it detects Y Input of a joystick on A2
//    - it controls incremental the position of SERVO01 and so the pitch axis of the sentry
//    --> If joystick is neutral the sentry holds position Position (0-180°)
//
//    3) Function: void moveXSentry ()
//    - it detects X Input of a joystick on A1
//    - it controls a Stepper Motor (STM2) and so the yaw axis of the sentry
//    --> The sentry should not be allowed to turn 360° since cabling issues
//    --> Amount of RotarySteps should define movement boundaries (f.e. (MaxLeft 0 ... 2500 middle...5000 MaxRight)
//
//    4) Function: void idle ()
//    - Once all darts are fired the sentry should not mover or shoot anymore
//
//  --> For simultaneous operation of all the functions millis(); is used
//========================================

// ----------LIBRARIES--------------

#include <Wire.h>
#include <Servo.h>
#include <Adafruit_MotorShield.h>
#include <AccelStepper.h>
#include <utility/Adafruit_MS_PWMServoDriver.h>


// --------CONNECTIONS---------------
const int MotorPin = 7;   // Motor Relais Pin

const int SERVO1Pin = 9;   // Pitch Servo2 slot Pin 10

const int JoyStick_B = 3;   // D3 Joystick Button Pin
const int JoyStick_X = 1;   //  A1 Jostick Input  - pitch Axis = Up (0) - Center (507..08) - Down (1023)
const int JoyStick_Y = 2;   //  A2 Jostick Input  - Yaw Axis = Left (1023) - Center (508..509) - Right (0)


// --------CONSTANTS---------------

const int buttonInterval = 2000;       // number of millisecs between button readings
const int JostickSignalDelay = 150;   // Delay in ms for stability (needed?)
int servoPositionS1 = 90;        // the current angle of the servo - starting at 90.


//------------ VARIABLES---------------------

int x_NS;
int y_NS;
int stateB_NS = 0;         // State Yostick Button
int rotarysteps = 2500;  //Amonunt of steps for Yaw-Axis

int ammunition = 18;      // Total of 18 darts

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()

unsigned long previousButtonMillis = 0; // time when button press last checked

unsigned long previousServoMillis = 0; // the time when the servo was last moved

unsigned long previousJoystickMillis = 0; // the time when the Joystick move was last checked


//------------ OBJECTS---------------------

Adafruit_MotorShield AFMS = Adafruit_MotorShield();   // Create the motor shield object with the default I2C address

Servo SERVO1;    // Pitch Servo created


//------------ STEPPER SETUP---------------

// Connect a stepper motor with 200 steps per revolution (1.8 degree)
// to motor port #1 (M1 and M2) or #2 (M3 and M4)
Adafruit_StepperMotor *STM1 = AFMS.getStepper(200, 1); // MagazineMover
Adafruit_StepperMotor *STM2 = AFMS.getStepper(200, 2); // Turn Sentry

//========================================

//------------ VOID SETUP ---------------------------------------------------------

void setup() {
  Serial.begin(9600);
  Serial.println("Starting V3_NerfSentry_1.ino");

  SERVO1.attach(SERVO1Pin);
  SERVO1.write(servoPositionS1); // sets the initial position

  AFMS.begin();

  STM1->setSpeed(1000);

  pinMode(MotorPin, OUTPUT);      // Set Pinmode for Relais Pin 9
  digitalWrite(MotorPin, LOW);    // Turns DC-Motors off in the beginning

}


//------------ VOID LOOP ---------------------------------------------------

void loop() {
  dbgi("1: top of loop", 0, 1000);
  // Notice that none of the action happens in loop() apart from reading millis()
  // it just calls the functions that have the action code

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
  //   use the same time for all LED flashes to keep them synchronized

  dbgi("2: before entering readJoystick()", 2, 1000);
  readJoystick();

  dbgi("3: before entering moveXSentry()", 3, 1000);
  moveXSentry();
  //moveYSentry();

  dbgi("4: before entering moveYStepSentry()", 4, 1000);
  moveYStepSentry();

  dbgi("5: before entering shoot()", 5, 1000);
  shoot();

  dbgi("6: before entering idle()", 6, 1000);
  idle();

}

//------------FUNCTIONS---------------------------------------------------------

void readJoystick() {

  if (currentMillis - previousJoystickMillis >= JostickSignalDelay) {
    x_NS = analogRead(JoyStick_X);
    y_NS = analogRead(JoyStick_Y);
    stateB_NS = digitalRead(JoyStick_B);
    /*
      Serial.print(x_NS , DEC);
      Serial.print(",");
      Serial.print(y_NS , DEC);
      Serial.print(",");
      Serial.println(stateB_NS , DEC);
    */
    previousJoystickMillis += JostickSignalDelay;
  }
}

//========================================
// - Move ROTATION Sentry function

void moveXSentry() {
  x_NS = analogRead(JoyStick_X);
  //if (currentMillis - previousJoystickMillis >= JostickSignalDelay) {}

  if ((x_NS > 500) && (x_NS < 523))
  {
    STM2->release();
  }

  else
  {
    while ((x_NS <= 500))
    {
      STM2->step(1, FORWARD, MICROSTEP);
      x_NS = analogRead(JoyStick_X);
    }
    while ((x_NS >= 523))
    {
      STM2->step(1, BACKWARD, MICROSTEP);
      x_NS = analogRead(JoyStick_X);

    }
  }
}

//========================================
// - Move Pitch Sentry function

void moveYSentry() {

  y_NS = analogRead(JoyStick_Y);     // UP-Down reads the value of the potentiometer (value between 0 and 1023)
  y_NS = map(y_NS, 0, 1023, 180, 0); // scale it to use it with the servo (value between 0 and 180)
  SERVO1.write(y_NS);               // sets the servo position according to the scaled value
}

//========================================
// - Move Pitch Sentry Inkremental function

void moveYStepSentry() {
  dbgi("0: entering moveYStepSentry()", y_NS,1000);
  dbgi("0: entering moveYStepSentry()", servoPositionS1,1000);

  y_NS = analogRead(JoyStick_Y);     // UP-Down reads the value of the potentiometer (value between 0 and 1023)
  dbgi("1: YStep", y_NS,1000);
  if ((y_NS > 500) && (y_NS < 523))
  {
    dbgi("2: if ((y_NS > 500) && (y_NS < 523)) is true do nothing", y_NS, 1000);
  }
  else
  {
    while ((y_NS < 500))
    {
      dbgi("3: inside while ((y_NS < 500)", y_NS, 1000);
      if (servoPositionS1 < 180)
      {
        dbgi("4: if (servoPositionS1 < 180)", servoPositionS1, 1000);
        servoPositionS1++;
        SERVO1.write(servoPositionS1);
      }
      else {
        dbgi("5: else to if (servoPositionS1 < 180)", servoPositionS1, 1000);
        SERVO1.write(servoPositionS1);
      }
    }

    while ((y_NS > 523))
    {
      dbgi("6: inside while ((y_NS > 523))", servoPositionS1, 1000);
      if (servoPositionS1 > 0)
      {
        dbgi("7: if (servoPositionS1 > 0) is true", servoPositionS1, 1000);
        servoPositionS1--;
        SERVO1.write(servoPositionS1);
      }
      else {
        dbgi("5: else to if (servoPositionS1 > 0)", servoPositionS1, 1000);
        SERVO1.write(servoPositionS1);
      }
    }

  }
  dbgi("99: leaving moveYStepSentry()", y_NS,1000);  
  dbgi("99: leaving moveYStepSentry()", servoPositionS1,1000);  
}


//========================================
// - Shoot function

void shoot() {
  stateB_NS = digitalRead(JoyStick_B);
  //Serial.println(previousButtonMillis);
  //Serial.println(buttonInterval);

  if (millis() - previousButtonMillis >= buttonInterval) {
    if (stateB_NS == HIGH) {
      digitalWrite(MotorPin, HIGH);
      STM1->step(25, BACKWARD, MICROSTEP); //23-25 steps per shot
      STM1->release();
      digitalWrite(MotorPin, LOW);

      ammunition--;
      previousButtonMillis += buttonInterval;
    }
  }
}


//========================================
// - EmptyMagazine function

void idle() {

  if (ammunition == 0) {        // If no darts left,
    // Jostick not operating anymore
  }
}


//========================================END=======================================

Hey Stefan

Thank you very much for your reply. The idea to print Debug Info is great.
I loaded the code and tried it out.
The result and behavior is unfortunately the same as before: Once I move the Y-Input on the Joystick the Sentry moves once to a position and then nothing happens anymore - no of the functions are working anymore

Serial Output is then:

Somehow servoPositionS1 gets set to 0 and wont be changed anymore?

you should post debug-output as a code-section

after printing to the serial monitor uncheck autoscroll
click into serial monitor
press Ctrl-A, Ctrl-C

click </>-button in the forum
insert clipboard-content by pressing ctrl-V

sure!

That is exactly what I have written:
adding debug-output no change inside the code except adding debug-output

As you can see that your code repeats printing

6: inside while ((y_NS > 523))"
5: else to if (servoPositionS1 > 0)

over and over again.
This means you have narrowed down the problem to that part of the code.
So take a close look at this part of the code and you will find the reason why these lines get repeated over and over again

1 Like

shouldn't you just map the joystick value to a servo position?

the joystick would affect the rate of change as well as hold a position. not sure about a dead zone, but it can be handled

Ciao gcjr

Yes this is what the function void moveYSentry () does with the effect, that once the joystick is centered the sentryServo is moving back to 90°.
But I use the Pushbutton on the joystick for shooting and it is difficult to push it while maintaining a Y-Position on the Joystick.
That's why I want the sentry to hold its position once the joystick is centered.

then shouldn't moveYSentry() simpy increment the servo position when forward, decrement when pulled back and make no change when centered?

... at some rate using a timer to limit how fast it in/decrements values?

This kind of functionality can be achieved with a step-chain based on a switch-case-statement.

There ar two ways to learn it:
trying to apply it immidiately to your project fiddling around trying this thying that

or

learning the basic principle with a tutorial and then apply it to your project.
the basic principle is to have one big loop running and depending on certain conditions to switch between different modes of operation

mode: adjust sentry
mode: firing with sentry holding position
mode: whatever elses modes are nescessary

best regards Stefan

Yes i think the problem is something that gcjr said as well.
Is see these problems:

  1. The servoPositionS1 value gets incremented too fast, so that it is already at 180° (fully up) or at 0° (fully down) in just a few milliseconds.
    So I need a slower increment function ---> Will google that. Delay() is no solution

  2. The servoPositionS1 value can get out of range or even negative since its functional range is only between 0 - 90.
    I tried to cope this already in the code by modifying the function:

// - Move Pitch Sentry Incremental function

void moveYStepSentry()
{
  dbgi("0: entering moveYStepSentry()", y_NS, 1000);
  dbgi("0: entering moveYStepSentry()", servoPositionS1, 1000);

  y_NS = analogRead(JoyStick_Y);     // UP-Down reads the value of the potentiometer (value between 0 and 1023)
  dbgi("1: YStep", y_NS, 1000);
  if ((y_NS > 500) && (y_NS < 523))
  {
    dbgi("2: if ((y_NS > 500) && (y_NS < 523)) is true do nothing", y_NS, 1000);
  }
  else
  {
    while ((y_NS < 500)) // Joystick down
    {
      dbgi("3: inside while ((y_NS < 500)", y_NS, 1000);
      if (servoPositionS1 > 180)
      {
        //if at max position do nothing and set back value to max
        servoPositionS1 = 180;
      }
      else
      {
        dbgi("4: Increment & Write (y_NS <500)", servoPositionS1, 1000);
        servoPositionS1++; // This happens too fast
        SERVO1.write(servoPositionS1);
      }
    }

    while ((y_NS > 520)) // Joystick up
    {
      dbgi("5: inside while ((y_NS > 520)", y_NS, 1000);
      if (servoPositionS1 < 0)
      {
        //if at min position do nothing and set back value to min
        servoPositionS1 = 0;
      }
      else
      {
        dbgi("6: Decrement & Write (y_NS >520)", servoPositionS1, 1000);
        servoPositionS1--; // This happens too fast
        SERVO1.write(servoPositionS1);
      }
    }
  }

  dbgi("99: leaving moveYStepSentry()", y_NS, 1000);
  dbgi("99: leaving moveYStepSentry()", servoPositionS1, 1000);
}

The sentry still has the same behavior. Once Y-Joystick is moved the thing crashes and gets stuck in

"3: inside while ((y_NS < 500)" y_NS=493
"4: Increment & Write (y_NS <500)" servoPositionS1=180

So what are the reasons to refuse reading this state-machine-tutorial?

a state-machine can do

a state-machine can help to avoid

and will help keeping it inside

and the state-machine will remove the bug of

what else do you need for advertising to start learning how state-machines work????

If you start reading a tutorial that explains the principle function of how state-machines work

are you

  • afraid of your head could explode and you die?

  • afraid of it will cause unhealable braincancer?

here it is:

best regards Stefan

1 Like

consider this example using buttons

i don't know the range of you servo. if each increment is 1 deg, a 100 msec timer would take 9 seconds to move 90 deg. so the timer period needs to be scaled appropriately


const byte PinBut [] = { A1, A2 };
#define N_BUT   sizeof(PinBut)

unsigned long msecLst;

#define MAX     1024
int val = MAX/2;

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    if ((msec - msecLst) < 100)
        return;

    for (unsigned n = 0; n < N_BUT; n++)  {
        if (LOW == digitalRead (PinBut [0]))  {
            if (MAX > val)
                val++;
        }
        else if (LOW == digitalRead (PinBut [1]))  {
            if (0 < val)
                val--;
        }
        else
            return;
    }

    Serial.println (val);
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < N_BUT; n++)
        pinMode (PinBut [n], INPUT_PULLUP);
}

Hey guys

So many thanks to your input guys! I implemented a Switch Case thingy according to Stefans Tutorial and it works! No braincancer, no death. :slight_smile:
Thank you guys for your input and time!

If you want I can make a video of the sentry working. :stuck_out_tongue:

Here is the code that works:

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// Take it for granted at the moment scroll down to void setup
// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *

// V5.0 Sentry Code

// this sketch does the following:
//    1) Function: void shoot()
//    When Joystick Button is pushed;
//     - it turns on two DC Motors by switching on a NO relais module for a short time (<1s)
//     - it simoultaneously moves a Stepper motor (STM1) a determined amount of steps FORWARD
//     - it counts down the amount of darts left (18 darts in total)
//    --> A spring which is tensioned by the STM1 pushes a FoamDart into the two DC Motors and shoots out
//    --> The Joystick button has a delay (buttonInterval) so that it is not possible to shoot rappidly

//    2)A Function: void moveYSentry ()
//    - it detects Y Input of a joystick on A2
//    - it controls the position of SERVO01 and so the pitch axis of the sentry
//    --> If joystick is neutral the sentry moves back on 90° Position (0-180°)

//    2)B Function: void moveYStepSentry ()
//    - it detects Y Input of a joystick on A2
//    - it controls incremental the position of SERVO01 and so the pitch axis of the sentry
//    --> If joystick is neutral the sentry holds position Position (0-180°)
//
//    3) Function: void moveXSentry ()
//    - it detects X Input of a joystick on A1
//    - it controls a Stepper Motor (STM2) and so the yaw axis of the sentry
//    --> The sentry should not be allowed to turn 360° since cabling issues
//    --> Amount of RotarySteps should define movement boundaries (f.e. (MaxLeft 0 ... 2500 middle...5000 MaxRight)
//
//    4) Function: void idle ()
//    - Once all darts are fired the sentry should not mover or shoot anymore
//
//  --> For simultaneous operation of all the functions millis(); is used
//========================================

// ----------LIBRARIES--------------

#include <Wire.h>
#include <Servo.h>
#include <Adafruit_MotorShield.h>
#include <AccelStepper.h>
#include <utility/Adafruit_MS_PWMServoDriver.h>


// --------CONNECTIONS---------------
const int MotorPin = 7;   // Motor Relais Pin

const int SERVO1Pin = 9;   // Pitch Servo2 slot Pin 10

const int JoyStick_B = 3;   // D3 Joystick Button Pin
const int JoyStick_X = 1;   //  A1 Jostick Input  - pitch Axis = Up (0) - Center (507..08) - Down (1023)
const int JoyStick_Y = 2;   //  A2 Jostick Input  - Yaw Axis = Left (1023) - Center (508..509) - Right (0)


// --------CONSTANTS---------------

const int buttonInterval = 2000;       // number of millisecs between button readings
const int JostickSignalDelay = 150;   // Delay in ms for stability (needed?)
int servoPositionS1 = 90;        // the current angle of the servo - starting at 90.

// --------SWITCHCASE---------------

unsigned long myCounter = 0;

const byte DoNothing      = 0;
const byte MoveUp         = 1;
const byte MoveDown       = 2;
const byte Shoot          = 3;

byte myStateVar;

//------------ VARIABLES---------------------

int x_NS;
int y_NS;
int stateB_NS = 0;         // State Yostick Button
int rotarysteps = 2500;  //Amonunt of steps for Yaw-Axis

int ammunition = 18;      // Total of 18 darts

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()

unsigned long previousButtonMillis = 0; // time when button press last checked

unsigned long previousServoMillis = 0; // the time when the servo was last moved

unsigned long previousJoystickMillis = 0; // the time when the Joystick move was last checked


//------------ OBJECTS---------------------

Adafruit_MotorShield AFMS = Adafruit_MotorShield();   // Create the motor shield object with the default I2C address

Servo SERVO1;    // Pitch Servo created


//------------ STEPPER SETUP---------------

// Connect a stepper motor with 200 steps per revolution (1.8 degree)
// to motor port #1 (M1 and M2) or #2 (M3 and M4)
Adafruit_StepperMotor *STM1 = AFMS.getStepper(200, 1); // MagazineMover
Adafruit_StepperMotor *STM2 = AFMS.getStepper(200, 2); // Turn Sentry

//========================================

//------------ VOID SETUP ---------------------------------------------------------

void setup() {
  Serial.begin(9600);
  Serial.println("Starting V3_NerfSentry_1.ino");

  SERVO1.attach(SERVO1Pin);
  SERVO1.write(servoPositionS1); // sets the initial position

  AFMS.begin();

  STM1->setSpeed(1000);

  pinMode(MotorPin, OUTPUT);      // Set Pinmode for Relais Pin 9
  digitalWrite(MotorPin, LOW);    // Turns DC-Motors off in the beginning

}


//------------ VOID LOOP ---------------------------------------------------

void loop() {
  dbgi("1: top of loop", 0, 1000);
  // Notice that none of the action happens in loop() apart from reading millis()
  // it just calls the functions that have the action code

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
  //   use the same time for all LED flashes to keep them synchronized

  dbgi("2: before entering readJoystick()", 2, 1000);
  readJoystick();

  dbgi("3: before entering moveXSentry()", 3, 1000);
  moveXSentry();

  //moveYSentry();

  dbgi("4: before entering moveYStepSentry()", 4, 1000);
  moveYStepSentry();

  dbgi("5: before entering shoot()", 5, 1000);
  shoot();

  dbgi("6: before entering idle()", 6, 1000);
  idle();

}

//------------FUNCTIONS---------------------------------------------------------

void readJoystick()
{

  if (currentMillis - previousJoystickMillis >= JostickSignalDelay) {
    x_NS = analogRead(JoyStick_X);
    y_NS = analogRead(JoyStick_Y);
    stateB_NS = digitalRead(JoyStick_B);
    /*
      Serial.print(x_NS , DEC);
      Serial.print(",");
      Serial.print(y_NS , DEC);
      Serial.print(",");
      Serial.println(stateB_NS , DEC);
    */
    previousJoystickMillis += JostickSignalDelay;
  }
}

//========================================
// - Move ROTATION Sentry function

void moveXSentry()
{
  x_NS = analogRead(JoyStick_X);
  //if (currentMillis - previousJoystickMillis >= JostickSignalDelay) {}

  if ((x_NS > 500) && (x_NS < 523))
  {
    STM2->release();
  }

  else
  {
    while ((x_NS <= 500))
    {
      STM2->step(1, FORWARD, MICROSTEP);
      x_NS = analogRead(JoyStick_X);
    }
    while ((x_NS >= 523))
    {
      STM2->step(1, BACKWARD, MICROSTEP);
      x_NS = analogRead(JoyStick_X);

    }
  }
}

//========================================
// - Move Pitch Sentry function

void moveYSentry()
{

  y_NS = analogRead(JoyStick_Y);     // UP-Down reads the value of the potentiometer (value between 0 and 1023)
  y_NS = map(y_NS, 0, 1023, 180, 0); // scale it to use it with the servo (value between 0 and 180)
  SERVO1.write(y_NS);               // sets the servo position according to the scaled value
}



//========================================
// - Move Pitch Sentry Inkremental function

void moveYStepSentry()
{
  dbgi("0: entering moveYStepSentry()", y_NS, 1000);
  dbgi("0: entering moveYStepSentry()", servoPositionS1, 1000);

  switch (myStateVar)
  {
    case DoNothing:
      Serial.println( F("Do Nothing") );
      y_NS = analogRead(JoyStick_Y);

      if ((y_NS > 500) && (y_NS < 523))
      {
        // do nothing
        dbgi("0: Do Nothing", servoPositionS1, 1000);
        myStateVar = MoveUp;
      }

      myStateVar = MoveUp;
      break;



    case MoveUp:
      Serial.println( F("Move Up") );
      y_NS = analogRead(JoyStick_Y);

      if ((y_NS > 523) && (servoPositionS1 <= 180))
      {
        servoPositionS1++;
        SERVO1.write(servoPositionS1);
        dbgi("1: Move Up", servoPositionS1, 1000);
        myStateVar = MoveDown;
      }

     myStateVar = MoveDown;
      break;



    case MoveDown:
      Serial.println( F("Move Down") );
      y_NS = analogRead(JoyStick_Y);

      if ((y_NS < 500) && (servoPositionS1 >= 0))
      {
        servoPositionS1--;
        SERVO1.write(servoPositionS1);
        dbgi("1: Move Down", servoPositionS1, 1000);
        myStateVar = DoNothing;
      }
      myStateVar = DoNothing;
      break;
  }

}


//========================================
// - Shoot function

void shoot()
{
  stateB_NS = digitalRead(JoyStick_B);
  //Serial.println(previousButtonMillis);
  //Serial.println(buttonInterval);

  if (millis() - previousButtonMillis >= buttonInterval) {
    if (stateB_NS == HIGH) {
      digitalWrite(MotorPin, HIGH);
      STM1->step(3, FORWARD, MICROSTEP);
      STM1->step(25, BACKWARD, MICROSTEP); //23-25 steps per shot
      STM1->release();
      digitalWrite(MotorPin, LOW);

      ammunition--;
      previousButtonMillis += buttonInterval;
    }
  }
}


//========================================
// - EmptyMagazine function

void idle()
{

  if (ammunition == 0) {        // If no darts left,
    // Jostick not operating anymore
  }
}


//========================================END=======================================

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