Skip to content

Mechanism

Design

The payload (water bottles) are stored in a revolving cylinder in order to minmize space occupied and reduce the number of required servos to release each individual payload. There are two servos, one responsible for rotating the cylinder to the correct payload, and the other for releasing it. An angle sensor is utilized to assign an angle to each payload, allowing the plane to differentiate between payloads 1-5. The system is controlled by an Arduino Nano, which is fed instructions from the Jetson.

Test Image

Arduino Code

The main role of the code is to take in some signal from the Jetson, and be able to interpret that as which payload to rotate to and drop. Due to Jetson limitations, we could only send a high or low signal from 4 pins. Considering that we had to uniquely represent 5 payloads, we chose to assign 3 pins to provide a binary representation of which payload we wanted to rotate to. Payload 1 would be 000, payload 2 would be 001, and so forth until we reach payload 5. Our 4th pin would then be dedicated to providing the drop signal, releasing the payload when high, and returning to its intial position when the signal was low. In order to know which bottle we want to drop, we have this function:

byte whichBottleRotate(){
  //Read from 4 GPI pins on the Jetson to determine which bottle and when to drop
  //Pin 1 will be when to drop
  //Pin 2-3 will be binary for bottle slot 1-5 i.e. bottle 4 will be 011
  byte bi1 = digitalRead(BINARY_1); //GPIO 298 (LSB)
  byte bi2 = digitalRead(BINARY_2); //GPIO 480
  byte bi3 = digitalRead(BINARY_3); //GPIO 486 (MSB)
  return bi1 | (bi2 << 1) | (bi3 << 2);
}
The logic behind interpreting the high and low signals from the GPIO pins from the Jetson have been condensed into one line:
return bi1 | (bi2 << 1) | (bi3 << 2);
The initial assignment for each variable is only a high or low signal, but that would only appear in the ones place of the byte it is stored in. So a high signal for bi1 would look something like 00000001, and the low signal would just be 00000000. But in order to represent, for example, 5, we would need numbers in the hundreds and tens place to so that we get 00000101. To get the value of our Most Significant Bit (MSB) bi3 into that place, we use the bitwise Left Shift operator (<<) to shift that value to the left by 2 places. We do the same to bi2, but only by 1 shift so that it sits in the tens place. Continuing with our example of getting binary 5, bi1 would be 00000001, bi2 would be 00000000, and bi3 would be 00000001. Once we bitwise shift bi2 and bi3, they would look like:
00000001
00000000
00000100
Now we would like to essentially add the 3 bytes together to form 0000101, which is possible using the bitwise OR operator( | ). OR returns a true, or 1, if any of the numbers it compares in that column is true, and returns a false, or zero, if they are all false. So going column by column, it checks if there is a one or not, and if so, keeps the one in the returned bit. Thus, the byte 0000101 is returned.

The returned byte can then be converted back into decimal form, indicating which payload we want to drop.

switch ((int)bottleIndex) {
    case 1:
      setPoint = BOTTLE_1_ANGLE;
      break;
    case 2:
      setPoint = BOTTLE_2_ANGLE;
      break;
    case 3:
      setPoint = BOTTLE_3_ANGLE;
      break;
    case 4:
      setPoint = BOTTLE_4_ANGLE;
      break;
    case 5:
      setPoint = BOTTLE_5_ANGLE;
      break;
    default:
      Serial.println("ERROR: bottleIndex " + String((int)bottleIndex));
      binarySignalPrint();
  }
The angles are measured by an angle sensor sitting above the axle. The code responsible for designating the bottle angles is as follows:
void controller(double setPoint) {
    //controller gains
    double pGain = 10;
    //min and max meaningful pwm values for rotatingServo
    int rotatingServo_max_pwm = 1600;
    int rotatingServo_min_pwm = 1400;
    double currAngle = angleSensor.readAngle();
    double errorDegrees = (fmod(currAngle - setPoint + 540, 360) - 180);
    double P = -errorDegrees*pGain;

    double controllerOutput = P; //+I+D later if needed
    double pwmOutput = constrain(1500 + controllerOutput, rotatingServo_min_pwm, rotatingServo_max_pwm);
    rotatingServo.writeMicroseconds(pwmOutput);
    Serial.print("setPoint: ");
    Serial.print(setPoint,1);
    Serial.print("  currAngle: ");
    Serial.print(currAngle,1);
    Serial.print("  pwm output: ");
    Serial.println(pwmOutput,0);
}