Nintendo NES controlled drone

Hey y’all,

I am working on an underwater drone project that is controlled with an NES controller. I am going to be using an Arduino for the brains of this project, and I was hoping someone might be able to take a look at the code I have used to see if any problems jump out to them!

I have modified NES controller code from a Joseph Corleto, leaving many of his comments in the code for the time being. This is a three T200 thruster set up with two for turning and one for vertical movement. I would love to add a timer feature to the button presses so that they only last up to 3 seconds per button press, but am not sure how to add that in.

#include <Servo.h> // attaches servo lib to file then declares servos 
Servo left;
Servo right;
Servo up;

/*
================================================================================

    File........... NES Controller Test Code
    Purpose........ To demonstrate how to interface to an NES controller
    Author......... Joseph Corleto
 
================================================================================
   Notes
================================================================================
- The NES controller contains one 8-bit 4021 shift register inside. 

- This register takes parallel inputs and converts them into a serial output.

- This code first latches the data and then shifts in the first bit on the data line. 
  Then it clocks and shifts in on the data line until all bits are received.
  
- What is debugged are the button states of the NES controller.

- A logical "1" means the button is not pressed. A logical "0" means the button is
  pressed.
  
- This code shifts the first bit of data into the LSB.

- The order of shifting for the buttons is shown in the table below:

        Bit# | Button   
        --------------
          0  |   A  
        --------------
          1  |   B  
        --------------
          2  | Select   
        --------------
                      3  | Start  
        --------------
          4  |   Up  
        --------------
          5  |  Down  
        --------------
          6  |  Left   
        --------------
          7  | Right   
        --------------
        
- The NES controller pinout is shown below (looking into controllers
  connector end):
    __________
   /      |
  /       O 1 | 1 - Ground
        |           | 2 - Clock
  | 7 O   O 2 |   3 - Latch
  |           | 4 - Data Out
  | 6 O   O 3 | 5 - No Connection
  |           |   6 - No Connection
  | 5 O   O 4 |   7 -  5V
  |___________|

*/

//===============================================================================
//  Header Files
//===============================================================================

//===============================================================================
//  Constants
//===============================================================================
// Here we have a bunch of constants that will become clearer when we look at the
// readNesController() function. Basically, we will use these contents to clear
// a bit. These are chosen according to the table above.
const int A_BUTTON         = 0;
const int B_BUTTON         = 1;
const int SELECT_BUTTON    = 2;
const int START_BUTTON     = 3;
const int UP_BUTTON        = 4;
const int DOWN_BUTTON      = 5;
const int LEFT_BUTTON      = 6;
const int RIGHT_BUTTON     = 7;

//===============================================================================
//  Variables
//===============================================================================
byte nesRegister  = 0;    // We will use this to hold current button states
byte leftPin = 6; // attach left thruster to pin 6
byte rightPin = 9; // attach right thruster to pin 9
byte upPin = 11; // attach up thrudeter to pin 11

//===============================================================================
//  Pin Declarations
//===============================================================================
//Inputs:
int nesData       = 4;    // The data pin for the NES controller

//Outputs:
int nesClock      = 2;    // The clock pin for the NES controller
int nesLatch      = 3;    // The latch pin for the NES controller

//===============================================================================
//  Initialization
//===============================================================================
void setup() 
{
  // Initialize serial port speed for the serial terminal
  Serial.begin(9600);
  left.attach(leftPin);
  right.attach (rightPin);
  up.attach (upPin);
  // Set appropriate pins to inputs
  pinMode(nesData, INPUT);
  
  // Set appropriate pins to outputs
  pinMode(nesClock, OUTPUT);
  pinMode(nesLatch, OUTPUT);
  
  // Set initial states
  digitalWrite(nesClock, LOW);
  digitalWrite(nesLatch, LOW);

  left.writeMicroseconds(1500); //send stop signial to ESC. Arm the ESC.
  right.writeMicroseconds(1500);
  up.writeMicroseconds(1500);

  delay(4000); //delays so ESC can connect
}

//===============================================================================
//  Main
//===============================================================================
void loop() 
{
  // This function call will return the states of all NES controller's register
  // in a nice 8 bit variable format. Remember to refer to the table and
  // constants above for which button maps where!
  nesRegister = readNesController();
  
  // Slight delay before we debug what was pressed so we don't spam the
  // serial monitor.
  delay(180);
  
  // To give you an idea on how to use this data to control things for your
  // next project, look through the serial terminal code below. Basically,
  // just choose a bit to look at and decide what to do whether HIGH (not pushed)
  // or LOW (pushed). What is nice about this test code is that we mapped all
  // of the bits to the actual button name so no useless memorizing!
  if (bitRead(nesRegister, A_BUTTON) == 0) //lifts drone
    up.writeMicroseconds(1700); 
    
  if (bitRead(nesRegister, B_BUTTON) == 0) //pushes drone down
    up.writeMicroseconds(1300);
    
  if (bitRead(nesRegister, START_BUTTON) == 0) //not used in this set up left as is to make sure nothing errors 
    Serial.println("");
  
  if (bitRead(nesRegister, SELECT_BUTTON) == 0) //not used in this set up left as is to make sure nothing errors  
    Serial.println("");
    
  if (bitRead(nesRegister, UP_BUTTON) == 0) //pushes drone foward 
    left.writeMicroseconds(1700); 
    right.writeMicroseconds(1700); 
    
  if (bitRead(nesRegister, DOWN_BUTTON) == 0) //pushes drone backwards
    left.writeMicroseconds(1300); 
    right.writeMicroseconds(1300); 
    
  if (bitRead(nesRegister, LEFT_BUTTON) == 0) //turns drone to the left
    left.writeMicroseconds(1300); 
    right.writeMicroseconds(1700); 
  
  if (bitRead(nesRegister, RIGHT_BUTTON) == 0) //turns drone to the right 
    left.writeMicroseconds(1700); 
    right.writeMicroseconds(1300); 
}

//===============================================================================
//  Functions
//===============================================================================
///////////////////////
// readNesController //
///////////////////////
byte readNesController() 
{  
  // Pre-load a variable with all 1's which assumes all buttons are not
  // pressed. But while we cycle through the bits, if we detect a LOW, which is
  // a 0, we clear that bit. In the end, we find all the buttons states at once.
  int tempData = 255;
    
  // Quickly pulse the nesLatch pin so that the register grab what it see on
  // its parallel data pins.
  digitalWrite(nesLatch, HIGH);
  digitalWrite(nesLatch, LOW);
 
  // Upon latching, the first bit is available to look at, which is the state
  // of the A button. We see if it is low, and if it is, we clear out variable's
  // first bit to indicate this is so.
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, A_BUTTON);
    up.writeMicroseconds(1500);
    
  // Clock the next bit which is the B button and determine its state just like
  // we did above.
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, B_BUTTON);
    up.writeMicroseconds(1500);
  
  // Select button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, SELECT_BUTTON); //not used

  // Start button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, START_BUTTON); // not used 

  // Up button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, UP_BUTTON);
    left.writeMicroseconds(1500); 
    right.writeMicroseconds(1500); 
  
    
  // Down button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, DOWN_BUTTON);
    left.writeMicroseconds(1500); 
    right.writeMicroseconds(1500); 
  
  // Left button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, LEFT_BUTTON);  
    left.writeMicroseconds(1500); 
    right.writeMicroseconds(1500); 
    
    
  // Right button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, RIGHT_BUTTON);
    left.writeMicroseconds(1500); 
    right.writeMicroseconds(1500); 
  
    
  // After all of this, we now have our variable all bundled up
  // with all of the NES button states.*/
  return tempData;
}

The hope is that when the button is no longer pressed it reverts to its stop signal, and i hope over all this makes sense.

Thanks!

Hi @aguy63, welcome to the forum :slight_smile:

Sounds like an interesting project! :slight_smile:

I’m curious what kind of body you’re planning to use - I had a similar thruster setup planned for my StaROV concepts (the project is unfortunately currently on the back-burner).

I’ve formatted your code as a code block, to make it easier to read and understand. You can read more about that in the Formatting a Post/Comment section of the How to Use the Forums post :slight_smile:

In terms of issues with / suggestions for your existing code:

  • it’s generally bad practice to mix action code with reading code, and may cause issues with the reading timings
    • in the readNesController function you’re setting several servo PWM values while reading data from the controller, which should instead be handled elsewhere in the loop, after the controller inputs have been read.
  • I would probably be inclined to replace the clock ticks and data reading section with a loop, to avoid the repeated code, but that does come at the cost of working more explicitly with bit offsets rather than bit names
  • your current code structure means there’s no possibility of mixing motions that use the horizontal thrusters, and using a string of if blocks with no else clauses means only the last pushed relevant button in the sequence is considered
    • if it’s not possible for say up and left to be pressed at the same time then this is functionally irrelevant
    • if that is possible though, it may be worth switching to a mixing approach where two buttons can work together
      • e.g. up + right could have (1900, 1500), or (1700, 1500) if you’re limiting the thrust
  • if you’re happy to get a bit more complex, you could get finer control by keeping track of the ESC PWM values and using the button presses to adjust them over time, rather than having a button press correspond directly to a set of PWMs.
  • setting a timeout for each button press is likely best accomplished by
    1. re-configuring the code to track button press transitions, instead of just whether a button is currently pushed, which requires
      • a new byte for storing the previous state of the buttons, and
      • another new byte for storing whether a button press has occurred and not yet been handled
        • each bit should be cleared when the press is handled, so that a new press within the time limit can refresh the timeout
    2. storing the time for each (relevant) button when it transitions to a press
      • you can get the current time with the millis() function, which requires a new long for every time you want to store
      • you can reduce a bunch of redundancy by grouping together buttons that affect the same thrusters
        • e.g. check for “horizontal command” and “vertical command” events rather than having a timeout for each individual button, since it doesn’t matter if the up button has timed out when you’ve already pressed the down button which has overridden the original up action
    3. checking whether a press has timed out after the latest presses have been determined

Do you want a timeout (whereby you can release a button and the ‘command’ will continue until the timeout), or do you want an immediate ‘stop’ command once no more relevant buttons are being pressed for a given thruster?

Immediate stop is simpler, you can just have variables that start at 1500 each loop iteration, and are overwritten (or modified, if you want to do the mixing approach) by the button states, with the results written to the servos after all the buttons have been checked. If no buttons for a servo are pressed then its result will still be 1500, so the ESC will be commanded to stop.

Hello sorry for the delay things as always just get so busy!

As it stands now I am using a simple stacked square shape PVC body with two motors acting as thrust and the third turned to act as lift. I have 3d printed some mounting parts that wrap around the PVC and hold very well! Once I get this better put together I will try to post some photos.

After reading your suggestions I ended up recruiting a more experienced programmer to help me figure out how to make the programming work. The new code can be seen down below! We have actually hooked this up with our older t100 thrusters and have had a great outcome. We were even able to add in some decoding leds that will be mounted to the project area of the Arduino shield we are using to help us figure out if something beyond the controller is acting up.

/*
================================================================================

    File........... NES Controller Test Code
    Purpose........ To demonstrate how to interface to an NES controller
    Author......... Joseph Corleto
 
================================================================================
   Notes
================================================================================
- The NES controller contains one 8-bit 4021 shift register inside. 

- This register takes parallel inputs and converts them into a serial output.

- This code first latches the data and then shifts in the first bit on the data line. 
  Then it clocks and shifts in on the data line until all bits are received.
  
- What is debugged are the button states of the NES controller.

- A logical "1" means the button is not latched. A logical "0" means the button is
  latched.
  
- This code shifts the first bit of data into the LSB.

- The order of shifting for the buttons is shown in the table below:

        Bit# | Button   
        --------------
          0  |   A  
        --------------
          1  |   B  
        --------------
          2  | Select   
        --------------
          3  | Start  
        --------------
          4  |   Up  
        --------------
          5  |  Down  
        --------------
          6  |  Left   
        --------------
          7  | Right   
        --------------
        
- The NES controller pinout is shown below (looking into controllers
  connector end):
    __________
   /          |
  /       O 1 | 1 - Ground
  |           | 2 - Clock
  | 7 O   O 2 | 3 - Latch
  |           | 4 - Data Out
  | 6 O   O 3 | 5 - No Connection
  |           | 6 - No Connection
  | 5 O   O 4 | 7 - 5V
  |___________|

*/

//===============================================================================
//  Header Files
//===============================================================================
#include <Servo.h>        // attaches servo lib to file then declares servos 
// Set Servo Objects
Servo up;
Servo left;
Servo right;

//===============================================================================
//  Constants
//===============================================================================
// Here we have a bunch of constants that will become clearer when we look at the
// readNesController() function. Basically, we will use these contents to clear
// a bit. These are chosen according to the table above.
const int A_BUTTON         = 0;
const int B_BUTTON         = 1;
const int SELECT_BUTTON    = 2;  
const int START_BUTTON     = 3;
const int UP_BUTTON        = 4;
const int DOWN_BUTTON      = 5;
const int LEFT_BUTTON      = 6;
const int RIGHT_BUTTON     = 7;
const int MaxPress         = 3000; //Max Press time 3 seconds

const int CCW              = 1300; //CounterClockwise Speed
const int HCCW             = 1400; //Half CounterClockwise Speed
const int STOP             = 1500; //Stop
const int HCW              = 1600; //Half Clockwise Speed
const int CW               = 1700; //Clockwise Speed

//===============================================================================
//  Variables
//===============================================================================
byte nesRegister  = 0;        // We will use this to hold current button states

unsigned long delayA = 0;     // the time the delay started
unsigned long delayB = 0;     // the time the delay started
unsigned long delayUp = 0;    // the time the delay started
unsigned long delayDown = 0;  // the time the delay started
unsigned long delayLeft = 0;  // the time the delay started
unsigned long delayRight = 0; // the time the delay started

bool latchedA = false;        // true if butten still latched
bool latchedB = false;        // true if butten still latched
bool latchedUp = false;       // true if butten still latched
bool latchedDown = false;     // true if butten still latched
bool latchedLeft = false;     // true if butten still latched
bool latchedRight = false;    // true if butten still latched

//===============================================================================
//  Pin Declarations
//===============================================================================
//Inputs:
int nesData       = 4;    // The data pin for the NES controller

//Outputs:
int nesClock      = 2;    // The clock pin for the NES controller
int nesLatch      = 3;    // The latch pin for the NES controller

byte leftPin      = 6;    // attach left thruster to pin 6
byte rightPin     = 9;    // attach right thruster to pin 9
byte upPin       = 11;    // attach up thrudeter to pin 11

byte leftLed      = 7;    // attach left thruster to pin 6
byte rightLed     = 8;    // attach right thruster to pin 9
byte upLed       = 12;    // attach up thrudeter to pin 11

byte leftBLed     = 5;    // attach left thruster to pin 6
byte rightBLed   = 10;    // attach right thruster to pin 9
byte upBLed      = 13;    // attach up thrudeter to pin 11



//===============================================================================
//  Initialization
//===============================================================================
void setup() 
{
  // Initialize serial port speed for the serial terminal
  Serial.begin(9600);

  // Set appropriate pins to inputs
  pinMode(nesData, INPUT);
  
  // Set appropriate pins to outputs
  pinMode(nesClock, OUTPUT);
  pinMode(nesLatch, OUTPUT);

  pinMode(leftLed, OUTPUT);
  pinMode(rightLed, OUTPUT);
  pinMode(upLed, OUTPUT);
    
  pinMode(leftBLed, OUTPUT);
  pinMode(rightBLed, OUTPUT);
  pinMode(upBLed, OUTPUT);
  
  // Set initial states
  digitalWrite(nesClock, LOW);
  digitalWrite(nesLatch, LOW);
  
  digitalWrite(leftPin, LOW);
  digitalWrite(rightPin, LOW);
  digitalWrite(upPin, LOW);
  
  digitalWrite(leftLed, LOW);
  digitalWrite(rightLed, LOW);
  digitalWrite(upLed, LOW);
  
  digitalWrite(leftBLed, LOW);
  digitalWrite(rightBLed, LOW);
  digitalWrite(upBLed, LOW);


  // Set Servo Pins
  left.attach(leftPin);
  right.attach (rightPin);
  up.attach (upPin);

  //send stop signial to ESC. Arm the ESC.
  left.writeMicroseconds(STOP); 
  right.writeMicroseconds(STOP);
  up.writeMicroseconds(STOP);

  //delays so ESC can connect
  delay(7000);
}

//===============================================================================
//  Main
//===============================================================================
void loop() 
{
  // This function call will return the states of all NES controller's register
  // in a nice 8 bit variable format. Remember to refer to the table and
  // constants above for which button maps where!
  nesRegister = readNesController();
  
  // Slight delay before we debug what was latched so we don't spam the
  // serial monitor.
  // delay(180);
  
  // To give you an idea on how to use this data to control things for your
  // next project, look through the serial terminal code below. Basically,
  // just choose a bit to look at and decide what to do whether HIGH (not pushed)
  // or LOW (pushed). What is nice about this test code is that we mapped all
  // of the bits to the actual button name so no useless memorizing!
 
  // Set Drone Buttons along with delays
  if (bitRead(nesRegister, A_BUTTON) == 0) {      //lifts drone
    if (!latchedA){                                //Button Not Still latched 
      if (delayA == 0) {                           //First time button pressed  
        delayA = millis();   
      }else{
        if ((millis() - delayA) >= MaxPress) {
          delayA = 0;                              //turn off, over 3 seconds
          latchedA = true;                         //Button still pressed so set latched
        }
      }
    }
  } else {
    delayA = 0;                                    //turn off, no button pressed
    latchedA = false;
  }
 
  if (bitRead(nesRegister, B_BUTTON) == 0) {       //Lowers drone
    if (!latchedB){                                //Button Not Still latched 
      if (delayB == 0) {                           //First time button pressed
        delayB = millis(); 
      } else {
        if ((millis() - delayB) >= MaxPress) {
          delayB = 0;                              //turn off, over 3 seconds
          latchedB = true;                         //Button still pressed so set latched
        }
      }
    }
  } else {
    delayB = 0;                                    //turn off, no button pressed
    latchedB = false;
  }
 
  if (bitRead(nesRegister, UP_BUTTON) == 0) {      //Forward drone
    if (!latchedUp){                               //Button Not Still latched 
      if (delayUp == 0) {                          //First time button pressed
        delayUp = millis(); 
      }else{
        if ((millis() - delayUp) >= MaxPress) {
          delayUp = 0;                             //turn off, over 3 seconds
          latchedUp = true;                        //Button still pressed so set latched
        }
      }
    }
  }else{
    delayUp = 0;                                   //turn off, no button pressed
    latchedUp = false;
  }
   
  if (bitRead(nesRegister, DOWN_BUTTON) == 0) {    //Backward drone
    if (!latchedDown){                             //Button Not Still latched 
      if (delayDown == 0) {                        //First time button pressed
        delayDown = millis(); 
      }else{
        if ((millis() - delayDown) >= MaxPress) {
          delayDown = 0;                           //turn off, over 3 seconds
          latchedDown = true;                      //Button still pressed so set latched
        }
      }
    }
  }else{
    delayDown = 0;                                 //turn off, no button pressed
    latchedDown = false;
  }
   
  if (bitRead(nesRegister, LEFT_BUTTON) == 0) {    //Left drone
    if (!latchedLeft){                             //Button Not Still latched 
      if (delayLeft == 0) {                        //First time button pressed
        delayLeft = millis(); 
      }else{
        if ((millis() - delayLeft) >= MaxPress) {
          delayLeft = 0;                           //turn off, over 3 seconds
          latchedLeft = true;                      //Button still pressed so set latched
        }
      }
    }
  }else{
    delayLeft = 0;                                 //turn off, no button pressed
    latchedLeft = false;
  }
   
  if (bitRead(nesRegister, RIGHT_BUTTON) == 0) {   //Right drone
    if (!latchedRight){                            //Button Not Still latched 
      if (delayRight == 0) {                       //First time button pressed
        delayRight = millis(); 
      }else{
        if ((millis() - delayRight) >= MaxPress) {
          delayRight = 0;                          //turn off, over 3 seconds
          latchedRight = true;                     //Button still pressed so set latched
        }
      }
    }
  }else{
    delayRight = 0;                                //turn off, no button pressed
    latchedRight = false;
  }
 //Activete Drone Motors

  if (delayA && !delayB){                          //pushes drone up
    up.writeMicroseconds(CW);
    digitalWrite(upLed, HIGH);                     //debug led  
  }else{
    if (delayB && !delayA){                        //pushes drone down
      up.writeMicroseconds(CCW);
      digitalWrite(upBLed, HIGH);                  //debug bled
    }else{                      
      up.writeMicroseconds(STOP);                  //no up or down
      digitalWrite(upLed, LOW);                    //debug led
      digitalWrite(upBLed, LOW);                   //debug bled
    }
  }

  if (delayUp && !delayDown){                      //Forward
    if (delayLeft && !delayRight){
      left.writeMicroseconds(HCW);                 //Forward and Left
      digitalWrite(leftLed, HIGH);                 //debug led
      right.writeMicroseconds(CW); 
      digitalWrite(rightBLed, HIGH);                //debug led
    }else{
      if (delayRight && !delayLeft){
        left.writeMicroseconds(CW);                //Forward and Right
        digitalWrite(leftBLed, HIGH);               //debug led
        right.writeMicroseconds(HCW);
        digitalWrite(rightLed, HIGH);              //debug led 
      }else{
        left.writeMicroseconds(CW);                //Full Forward
        digitalWrite(leftLed, HIGH);               //debug led
        right.writeMicroseconds(CW); 
        digitalWrite(rightLed, HIGH);              //debug led
      }
    }
  }  
  if (delayDown && !delayUp){                      //Backward
    if (delayLeft && !delayRight){
      left.writeMicroseconds(HCCW);                //Backward and Left
      digitalWrite(leftBLed, HIGH);                //debug bled
      right.writeMicroseconds(CCW);
      digitalWrite(rightBLed, HIGH);               //debug bled 
    }else{
      if (delayRight && !delayLeft){
        left.writeMicroseconds(CCW);               //Backward and Right
        digitalWrite(leftBLed, HIGH);              //debug bled
        right.writeMicroseconds(HCCW);
        digitalWrite(rightBLed, HIGH);             //debug bled
      }else{
        left.writeMicroseconds(CCW);               //Full Backward
        digitalWrite(leftBLed, HIGH);              //debug bled
        right.writeMicroseconds(CCW);
        digitalWrite(rightBLed, HIGH);             //debug bled    
      }
    }
  }  
  if (!delayUp && !delayDown){                     //Only left or right
    if (delayRight && !delayLeft){
      left.writeMicroseconds(CW);                  //Right
      digitalWrite(leftLed, HIGH);                 //debug led
      right.writeMicroseconds(CCW);
      digitalWrite(rightBLed, HIGH);               //debug bled 
    }
    if (delayLeft && !delayRight){
      left.writeMicroseconds(CCW);                 //Left
      digitalWrite(leftBLed, HIGH);                //debug bled
      right.writeMicroseconds(CW); 
      digitalWrite(rightLed, HIGH);                //debug led
    }
  }
  if (!delayUp && !delayDown && !delayLeft && !delayRight){
    left.writeMicroseconds(STOP);                 //no left
    digitalWrite(leftLed, LOW);                   //debug led
    digitalWrite(leftBLed, LOW);                 //debug bled
    right.writeMicroseconds(STOP);                //no right
    digitalWrite(rightLed, LOW);                  //debug led
    digitalWrite(rightBLed, LOW);                //debug bled
  }
}

//===============================================================================
//  Functions
//===============================================================================
///////////////////////
// readNesController //
///////////////////////
byte readNesController() 
{  
  // Pre-load a variable with all 1's which assumes all buttons are not
  // latched. But while we cycle through the bits, if we detect a LOW, which is
  // a 0, we clear that bit. In the end, we find all the buttons states at once.
  int tempData = 255;
    
  // Quickly pulse the nesLatch pin so that the register grab what it see on
  // its parallel data pins.
  digitalWrite(nesLatch, HIGH);
  digitalWrite(nesLatch, LOW);
 
  // Upon latching, the first bit is available to look at, which is the state
  // of the A button. We see if it is low, and if it is, we clear out variable's
  // first bit to indicate this is so.
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, A_BUTTON);
    
  // Clock the next bit which is the B button and determine its state just like
  // we did above.
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, B_BUTTON);
  
  // Select button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, SELECT_BUTTON); //not used

  // Start button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, START_BUTTON); // not used 

  // Up button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, UP_BUTTON);
  
  // Down button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, DOWN_BUTTON);
  
  // Left button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, LEFT_BUTTON);  
    
  // Right button
  digitalWrite(nesClock, HIGH);
  digitalWrite(nesClock, LOW);
  if (digitalRead(nesData) == LOW)
    bitClear(tempData, RIGHT_BUTTON);

  // After all of this, we now have our variable all bundled up
  // with all of the NES button states.*/
  return tempData;
}

So far the last piece of the puzzle I need to figure out is a way to keep the tether lines/line from twisting up on itself. This drone is designed as an interactive for kids to get a feel for driving an underwater drone. Sometimes we get a kid who just likes to hold the Right button to make it spin (Thus the 3 second shut off). I am thinking of adding a slip ring to help keep that from happening. The slip ring would be added in the line far enough that water should never be a problem. You wouldn’t happen to have seen a better way to handle that have you?

As we have it now we have 3 lines going to each thruster and then zip tied to make one snake. The hope is to change that to one 9 connection line and introduce a Water Tight enclosure.

Haha, I certainly know that feeling…
The forum and community are pretty long-lived though, so don’t stress on our account :slight_smile:

Fair enough - I’m keen to see how it comes together :slight_smile:

Great to hear! :slight_smile:

The code may be a bit simpler to maintain by replacing some of the repeated blocks with some loops over arrays, but it’s great that you’ve got a functioning base to work from :slight_smile:

Hmm, excessive tether rotation can be a problem, but it’s not a problem we generally need to deal with from an “intentional misuse” standpoint. ArduSub’s orientation tracking gets used to also count full turns (in each direction), and the number of turns is reported to the operator. If someone is concerned there have been too many turns then they would generally turn the other way to untwist things.

A slip ring could work reasonably well, but it depends on your setup. The slip ring would need to be at the end of the usable tether length, which means you either need an underwater slip ring (which seem to currently not have great options available for low cost applications) at the vehicle end, or you need need your tether always fully unreeled/laid out and away from things it could wrap around to avoid tangles at the surface.

I guess the main alternative I can think of would be having the vehicle try to compensate for over rotation by ignoring control inputs and rotating in the opposite direction until it’s evened out, but that could be disruptive and quite unintuitive to deal with from the operator’s standpoint (and dangerous if it’s not possible to make it stop when it’s near something).

If you don’t have orientation sensing in the vehicle (e.g. a compass and/or gyroscope) then there’s additional difficulty from trying to estimate the rotation “offset”. A naive approach would be to just keep track of how much time is spent in each operating mode of the vehicle and just keep the net rotation time (one direction minus the other) close(ish) to zero, but I imagine that’s quite error prone since it doesn’t account at all for how the water is moving around the vehicle.