Eagle CAD Training

Written By: Muhannad Al Mohamed (E&C DM)

Objective

This blog is intended to introduce Eagle CAD designing application to members to be able to design schematics for electronic circuits. The members of the E&C division are required to know how to make schematics of customed printed circuit boards (PCB) for their corresponding projects. These schematics can be used to design the layout version of the PCB in the Eagle CAD layout part. The layout of the PCB can then be used to generate Gerber files; which can be sent to fabrication houses to make the PCBs.

Creating New Sketch

To start a new sketch a new project should be created and a name should be given to it. After that, a new schematic should be created within the project.

Creating a new schematic

Adding Parts

To add a part from the EagleCAD library, the add icon should be clicked where the list of all the parts would show in a window.

Add Icon

Parts can be chosen directly from the libraries by double-clicking on each part. The search window can be used to easily find a specific part. However, the EagleCAD search engine is literal; which means that any misspelling in the part name will not show any result. To avoid that, stars should be typed before and after the part’s name.

Placing parts

Placing a part is simple, one click should input parts into the schematic and by clicking escape twice it would quit the adding mode. To rotate parts, right click before placing any part. Rotation of a part or a group of parts can be obtained by clicking the rotate icons.

Rotating parts

Connecting Pins

To connect placed parts the net icon should be clicked. The edge of each pin should be clicked when connecting parts to ensure that you are connecting to the part not just drawing a line on it.

Net Icon

When connecting to a part make sure that a connecting window shows up asking for permission to connect to a part. When a connecting is done, double-clicking the crouser will quit the connecting mode.

Connecting Window

When connecting a line with a line, make sure that a junction shows at the intersection. If there is no junction showing, a junction can be added by clicking the junction icon.

Junction Icon

Unconnected lines

Connected lines

Labeling Pins and Parts

Parts can be given labels that can show names and values in both the schematic and PCB layout designs. To add a name label click on the name label and click on the desired part. A value label can be added to parts as well by clicking on the value label. Just a reminder, the value label is still just a name and does not have any relation to the part’s characteristics.

Name Icon

Value Icon

Pins can be named as well by giving them labels by clicking on the label icon and clicking on the desired pin.If a pin is named exactly with the same name to another pin, a window should pop up asking to connect the pins. If clicked true, the pins would be automatically connected without showing a line.  The labels appearance can be changed by clicking on the tag icon and the size of the label can be changed as well.

Label Icon

Regular and Tag Icons

Changing Size Icon

EagleCAD Libraries

EagleCAD has a lot of different libraries from various retailers and manufacturers. It also has the regular parts such as supplies, passive components, .. etc. However, sometimes the desired part is not found in the EagleCAD built-in library so in that case a new library should be added.

Adding Libraries

To add a library, its “.lbr” file should be downloaded first from the manufacturers’ website or from different websites that provide eagle libraries:

Once the “.lbr” file is downloaded it should be placed in the “lbr” folder in the application’s folders. After that, the library should be indicated as used in the library manager window.

Placing library in lbr Folder

Library Manager

Using a Library

Testing

Verify Connected Pins

If the pins were labeled with similar names, their connection can be checked by clicking the show icon. Once the show icon is clicked, by clicking on any pin it would show the connected pins the clicked pin in bold font. It is very helpful to trace pins and check if all the parts are connected correctly.

Show Icon

Connected Pins

Checking Errors

After creating the schematic, the design should be checked if it has any errors. By clicking on the ERC icon a window should pop up showing any electrical errors.  Make sure that you have no errors befor going to the PCD layout part of designing.

ERC Icon

Training Material

Schematic Training

Further trainings on creating EagleCAD schematics can be found here:

PCB Layout Training

Training on the making of the layout version of the custom PCB can be found in this blog written by Charles Banuelos (MFG DM).

Fall 2017: ModWheels Electronics Design

By: Matt Shellhammer (Electronics & Control Engineer)

Approved by: Lucas Gutierrez (Project Manager)

Discussion

Due to complications with the fabricated 3DoT v5.03 and the limited amount of time we were required to resort to the use of the Sparkfun pro micro and motor driver for the final implementation. The IR sensor shield was used in our design to preform line following throughout the maze to keep the car within the maze and on a straight path. The IR sensor shield was also used to detect intersections so ModWheels could monitor its location within the maze and travel the correct path (either predefined or defined with Arxterra). The servo was used in the front of our chassis within a custom built servo holder to steer the ModWheels car. It was used with a steering mechanism that was based on the Sandblaster 3D sand buggy front wheel steering mechanism. The turns were then implemented using the micro servo as well as the two GM6 motors and two magnetic Hall Effect encoders to perform electronic differential steering and electronic differential turning.

Electronic Design

Electronic Devices Used

  1. Substitute for 3DoT v5.03
    1. Sparkfun Pro Micro (3.3V/8MHz – 32U4)
    2. Sparkfun Motor Driver (TB6612FNG)
  2. IR sensor shield (designed by Charles & Muhannad)
  3. Micro Servo
  4. Two Extended Shaft GM6 Gear motors
  5. Two Magnetic Hall Effect Encoders

Figure: Wiring Diagram

Fall 2017: ModWheels Hardware Design

By: Natalie Arevalo (Design & Manufacturing Engineer)

Approved By: Lucas Gutierrez (Project Manager)

Discussion

Various modifications were done to the original chassis to be able to complete the mission proposed the customer. Major modifications were done to the wooden chassis to make it compatible with the 5.03 3DoT board. Openings on the top part of the chassis were made so that all of the ports and headers could be accessed. In addition, another opening was made to be able to place the battery into the 3DoT board. Furthermore, an opening in between the GM6 motor placing was made to have the wires of the motors and their encoders be brought up and over the top part of the chassis to plug into the board. Another piece was cut out of the top part of the chassis to allow for the servo to be able to be raised and lowered as it was connected to the front axle. Additionally, six 3mm holes were strategically placed in which dawls would be later glued into. Three of these holes and the respective dawls were set aside for an anchoring point for the servo holder. The other three holes and their dawls were used to hold up 1.5×1.5 in platform to become a phone holder. Two more 5mm holes were also placed in the back to become zip tie holders to keep wires secured. Finally, openings were made for the motor encoders adjacent to the existing openings for the motors

The bottom part of the chassis also had several modifications done to it as well. Matching opening were made adjacent to the existing openings for the motors as it was done with the top part. Openings were also made for the Bluetooth module on the bottom of the 3DoT board and for the header for the IR/color shield. Two 5mm holes were made in the middle of the part place a zip tie that would be used to secure the wires from the IR/color shield. In addition to these holes, two 3mm holes were made in the front of the part for the placement of the front axle. Finally, on both the top and the bottom parts of the chassis, 3mm holes were made along the sides of the chassis to replace the wooden tabs with plastic spacers with screws and nuts to keep the two pieces separated.

Now, several parts were also molded and 3D printed as part of the hardware design. More specifically, the front axle and the servo holder were designed and 3D printed. The front axle was modeled after the axle of the sand buggy and was scaled down to fit our design. The front rack of this axle had tabs added to it as place to where the proximity sensor would be attached. The other parts that make up the attachment point for the wheel and then to the front rack were super glued to be over a little more than 90 degrees. The front axle was assembled together using screws and nuts and the proximity sensor was attached in the same way. To continue, the servo holder was designed to look like an engine block. The design included hollow motor heads as well as a hollow block that allowed the servo to be placed inside and then raised or lowered as needed to be attached to the front axle. On the right side of the of the servo holder, tabs were also added to hold the wires of the proximity sensor out of the way.

Some final modifications that were done in the hardware design involve the wheels and the mounting of the IR/color shield. The wheels in the front had plastic sleeve placed through their point of attachment where a screw was slid thought and then screwed into the front axle. The back wheels’ points of attachment were changed from a round circle into a ‘D’ shape to press fit into the ‘D’ shaped axle of the dual shaft motors. As for the mounting of the IR/color shield was made with a piece of female header and ribbon cable. The piece of the female header and the ribbon cable were hot glued together. This unit was then hot glued to the bottom of the chassis so that when the IR/color shield would be connected it would be as far forward as possible.

Fall 2017: ModWheels Final Software

By: Matt Shellhammer (Electronics & Control Engineer)

Approved by: Lucas Gutierrez (Project Manager)

Discussion

Due to complications with the fabricated 3DoT v5.03 and the limited amount of time we were required to resort to the use of the Sparkfun pro micro and motor driver for the final implementation. The IR sensor shield was used in our design to preform line following throughout the maze to keep the car within the maze and on a straight path. The IR sensor shield was also used to detect intersections so ModWheels could monitor its location within the maze and travel the correct path (either predefined or defined with Arxterra). The servo was used in the front of our chassis within a custom built servo holder to steer the ModWheels car. It was used with a steering mechanism that was based on the Sandblaster 3D sand buggy front wheel steering mechanism. The turns were then implemented using the micro servo as well as the two GM6 motors and two magnetic Hall Effect encoders to perform electronic differential steering and electronic differential turning.

Software

Software Implemented on Final

Final_old.ino

////////////////////////////////////////////////////////////////
//  Name     : ModWheels Final Project (Sparkfun Pro Micro)   //
//  Author   : Matt Shellhammer                               //
//  Date     : 11 December, 2017                              //
//  Version  : 1.0                                            //
////////////////////////////////////////////////////////////////

#define __PROG_TYPES_COMPAT__
#include <avr/pgmspace.h>
#include <Wire.h>
#include <EEPROM.h>
#include "Adafruit_TCS34725.h"
#include "Final.h"
#include <Configure.h>
#include <FuelGauge.h>
#include <Motor.h>
#include <Packet.h>
#include <Robot3DoTBoard.h>
#include <TB6612FNG.h>
#include <TelecomClass.h>
#include <Watchdog.h>
#include <SparkFun_VL6180X.h>

// Define motor driver pins
#define AIN1 14 // Pro-micro pin 14 (PB3)
#define BIN1 7  // Pro-micro pin 7  (PE6)
#define AIN2 4  // Pro-micro pin 4  (PD4)
#define BIN2 8  // Pro-micro pin 8  (PB4)
#define PWMA 5  // Pro-micro pin 5  (PC6)
#define PWMB 6  // Pro-micro pin 6  (PD7)
#define STBY 9  // Pro-micro pin 9  (PB5)
// Define Encoders
#define LME A0  // Left motor encoder (PF7)
#define RME A1  // Right motor encoder (PF6)
// Define IR proximity sensor using library
#define VL6180X_ADDRESS 0x29
VL6180xIdentification identification;
VL6180x sensor(VL6180X_ADDRESS);
// Define servo pin
#define servo_pin 10  // Pro-micro pin 10 (PB6)

void setup() {
 Serial.begin(115200); //Start Serial at 115200bps
  Wire.begin(); //Start I2C library
  delay(100); // delay .1s
  sensor.VL6180xDefautSettings(); //Load default settings for proximity IR.
  delay(1000); // delay 1s
  // DDRx & PORTx for motor drivers
  DDRD |= (1<<PD4)|(1<<PD7);
  DDRE |= (1<<PE6);
  DDRB |= (1<<PB3)|(1<<PB4)|(1<<PB5);
  DDRC |= (1<<PC6);
  // DDRx & PORTx for Encoders
  DDRF &= ~((1<<PF6)|(1<<PF7));
  PORTF |= (1<<PF6)|(1<<PF7);
  // DDRx & PORTx for Servo
  DDRB |= (1<<PB6);
  // DDRx & PORTx for IR Shield
  DDRF &= ~((1<<PF4)|(1<<PF5));
  PORTF |= (1<<PF4)|(1<<PF5);
  delay(5000);
}

void loop() {
  static bool turn_flag = false;          // turn flag for turns with ModWheels
  static uint8_t type = 0;                // initially outside of the maze
  static uint8_t count1 = 0;              // first counter used
  static uint16_t count2 = 0;             // second counter used
  static myRobot_t ModWheels;             // create an instance of myRobot_t called ModWheels
  static sensors_t sensors_inst;          // create an instance of sensors_t called sensors_inst
  static motors_t motors_inst;            // create an instance of motors_t called motors_inst
  static PID_t PID_inst;                  // create an instance of PID_t called PID_inst
  sensors_inst = readSensors(sensors_inst);             // Read sensor values
  PID_inst = PIDcalc(sensors_inst, PID_inst);           // Calculate PID value
  motors_inst = errorCorrection(PID_inst, motors_inst); // Correct for error with motors
  forward(motors_inst);                                 // send new speed to motors to move forward

  // Check if at an intersection
  if ((sensors_inst.IR0 >= 0.92)&&(-sensors_inst.IR1 >= 0.92)){
    if (turn_flag == true){turn_flag = false;go_straight(75);}
    else{
      ModWheels = enterRoom(ModWheels);
      type = roomType(ModWheels);
      ModWheels = whichWay(type, ModWheels);
      if (ModWheels.turn != 0){turn_flag = true;}
    }
  }
}

Final.h

// PID constants
#define Kp 1600
#define Ki 0.5
#define Kd 1600
// Bias speed and angle
#define base_speed 150
#define base_angle 96

struct coord_t{
  uint8_t row = 0x13; // Robot is initially outside of the maze
  uint8_t col = 0x00; // Robot is initially outside of the maze
};

struct myRobot_t{
  uint8_t dir = 0x03;   // Robot is initially facing north
  uint8_t turn = 0x00;  // First action is no turn
  coord_t maze;
  uint8_t room = 0x00;  // Initial room is empty
  uint8_t bees = 0x00;  // No bees present
  uint8_t mode = 0;
};

struct sensors_t{
  float IR0; // Left IR sensor (normalized)
  float IR1; // Right IR sensor (normalized)
};

struct motors_t{
  int16_t leftSpeed = base_speed;   // Initial motor speed: 60
  int16_t rightSpeed = base_speed;  // Initial motor speed: 60
  int16_t servoAngle = base_angle;  // Initial servo angle: 95
};

// Define PID structure
struct PID_t{
  float present = 0;
  float set_point = 0;
  float proportional = 0;
  float integral = 0;
  float derivative = 0;
  int16_t PIDvalue = 0;
  float error = 0;
  float previous_error = 0;
};

const uint8_t hit_table[] PROGMEM =
  {0x08,  // South (dir == 0b00)
   0x02,  // East (dir == 0b01)
   0x04,  // West (dir == 0b10)
   0x01}; // North (dir == 0b11)

//Compass   S     E     W     N
//dir       00    01    10    11
const uint8_t turn_table[] PROGMEM =
          {0b00, 0b01, 0b10, 0b11, // 00 no turn
           0b10, 0b00, 0b11, 0b01, // 01 turn right
           0b01, 0b11, 0b00, 0b10, // 10 turn left
           0b11, 0b10, 0b01, 0b00  // 11 turn around
           };

//  row   col   dir
const int8_t map_table[] PROGMEM =
    {1  ,  0, // 00
     0  ,  1, // 01
     0  , -1, // 10
    -1  ,  0  // 11
    };

const int maze_length = 399;
const uint8_t theMaze[] PROGMEM =
// 00  01   02   03   04   05   06   07   08   09   0A   0B   0C   0D   0E   0F   10   11   12   13   14
{0x05,0x09,0x09,0x09,0x09,0x09,0x01,0x03,0x05,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x29,0x09,0x09,0x09,0x02,  // 00
 0x0C,0x09,0x09,0x03,0x05,0x09,0x0A,0x06,0x06,0x05,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x03,0x05,0x03,0x06,  // 01
 0x05,0x09,0x0B,0x06,0x06,0x05,0x09,0x0A,0x06,0x0C,0x09,0x09,0x09,0x09,0x09,0x01,0x0B,0x0C,0x0A,0x06,0x06,  // 02
 0x06,0x0D,0x09,0x0A,0x06,0x06,0x05,0x03,0x0C,0x09,0x09,0x03,0x05,0x09,0x09,0x0A,0x05,0x09,0x09,0x08,0x02,  // 03
 0x06,0x05,0x09,0x09,0x0A,0x06,0x06,0x0C,0x09,0x09,0x09,0x0A,0x0C,0x09,0x09,0x03,0x06,0x05,0x09,0x09,0x0A,  // 04
 0x06,0x0C,0x03,0x05,0x09,0x02,0x06,0x05,0x09,0x09,0x09,0x09,0x09,0x09,0x03,0x06,0x06,0x0C,0x03,0x05,0x03,  // 05
 0x06,0x05,0x0A,0x0C,0x03,0x06,0x06,0x06,0x05,0x01,0x03,0x07,0x05,0x03,0x06,0x06,0x06,0x05,0x0A,0x06,0x06,  // 06
 0x06,0x0C,0x09,0x03,0x0E,0x0C,0x08,0x02,0x06,0x06,0x06,0x06,0x06,0x06,0x0C,0x02,0x06,0x0C,0x09,0x02,0x06,  // 07
 0x06,0x05,0x0B,0x0C,0x09,0x09,0x09,0x08,0x02,0x06,0x06,0x06,0x06,0x0C,0x09,0x0A,0x04,0x09,0x0B,0x06,0x06,  // 08
 0x0C,0x08,0x09,0x09,0x09,0x09,0x01,0x01,0x02,0x06,0x0C,0x08,0x08,0x09,0x01,0x09,0x08,0x09,0x03,0x06,0x06,  // 09
 0x05,0x01,0x09,0x09,0x0B,0x07,0x06,0x04,0x02,0x0C,0x09,0x09,0x09,0x03,0x04,0x09,0x03,0x07,0x06,0x06,0x06,  // 0A
 0x06,0x0C,0x09,0x09,0x09,0x02,0x06,0x04,0x02,0x0D,0x09,0x09,0x09,0x0A,0x0C,0x03,0x06,0x06,0x06,0x06,0x06,  // 0B
 0x06,0x05,0x09,0x09,0x09,0x0A,0x06,0x0C,0x0A,0x05,0x09,0x09,0x09,0x09,0x03,0x06,0x06,0x06,0x06,0x06,0x06,  // 0C
 0x06,0x0C,0x09,0x09,0x09,0x03,0x04,0x09,0x09,0x08,0x0B,0x05,0x03,0x05,0x0A,0x06,0x06,0x06,0x06,0x06,0x06,  // 0D
 0x04,0x09,0x09,0x09,0x09,0x08,0x02,0x05,0x01,0x09,0x03,0x06,0x06,0x06,0x05,0x0A,0x0E,0x06,0x06,0x06,0x06,  // 0E
 0x06,0x05,0x09,0x09,0x09,0x09,0x0A,0x0E,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x05,0x09,0x0A,0x06,0x06,0x06,  // 0F
 0x06,0x0C,0x09,0x09,0x09,0x09,0x09,0x09,0x0A,0x06,0x06,0x06,0x06,0x0E,0x0E,0x06,0x05,0x09,0x0A,0x06,0x06,  // 10
 0x04,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x0A,0x0C,0x0A,0x06,0x05,0x09,0x0A,0x06,0x0D,0x09,0x0A,0x06,  // 11
 0x04,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x08,0x08,0x09,0x09,0x08,0x09,0x09,0x09,0x0A,  // 12
};

Finalsub.ino

////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// Physical Robot Code ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
void readIR(){
  uint8_t IR = 0;
  bool IRflag = false;
  IR = sensor.getDistance();
  delay(10); // delay .1s 
  while (IR < 153){          // Don't hit something
    analogWrite(PWMA, 0);
    analogWrite(PWMB, 0);
    IR = sensor.getDistance();
    delay(10); // delay .1s
    IRflag = true;
  }
  if (IRflag == true){
    delay(3000);
    IRflag = false;
  }
}

// readSensors subroutine returns normalized sensor values averaged by 10 points
sensors_t readSensors(sensors_t sensors){
  int n = 10;     // number of ADC samples
  float x_i;      // ADC input for sample i
  float A_1;      // current i running average
  float A_0;      // previous i-1 running average

  // read left IR sensor
  A_0 = 0.0;
  for (int16_t i = 1; i <= n; i++) {
    x_i = analogRead(A2)/1024.0;
    A_1 = A_0 + (x_i - A_0)/i;
    A_0 = A_1;
  }
  sensors.IR0 = A_0;

  // read right IR sensor
  A_0 = 0.0;
  for (int16_t i = 1; i <= n; i++) {
    x_i = analogRead(A3)*(-1.0/1024.0);
    A_1 = A_0 + (x_i - A_0)/i;
    A_0 = A_1;
  }
  sensors.IR1 = A_0;
  return sensors;
}

PID_t PIDcalc(sensors_t sensors, PID_t PID){
  // Calculates error
  PID.present = (sensors.IR0-0.02) + sensors.IR1;
  PID.error = PID.present - PID.set_point;

  // Calculates proportional error
  PID.proportional = PID.error;

  // Calculates integrated error
  PID.integral += PID.error;
 
  // Calculate change in error
  PID.derivative = PID.error - PID.previous_error;
  PID.previous_error = PID.error;

  // Calculate compensator value
  // this should be an integer from -128 to 127 which is why the casting function is used
  PID.PIDvalue = int8_t(Kp*PID.proportional + Ki*PID.integral + Kd*PID.derivative);
  return PID;
}

// errorCorrection: corrects motor speed based on PID value (limits between <50,220>)
motors_t errorCorrection(PID_t PID, motors_t motors){
  motors.leftSpeed = base_speed - PID.PIDvalue;
  motors.rightSpeed = base_speed + PID.PIDvalue;
  motors.servoAngle = base_angle - (0.5*PID.PIDvalue);

  // Limit the left and right motor speeds between <50,220>
  if (motors.rightSpeed > 220) {motors.rightSpeed = 220;}
  if (motors.leftSpeed > 220) {motors.leftSpeed = 220;}
  if (motors.rightSpeed < 50) {motors.rightSpeed = 50;}
  if (motors.leftSpeed < 50) {motors.leftSpeed = 50;}
  if (motors.servoAngle > 110) {motors.servoAngle = 110;}
  if (motors.servoAngle < 80) {motors.servoAngle = 80;}
  return motors;
}

void readEncoders(uint16_t leftCount, uint16_t rightCount){
  bool LMEV; uint16_t LMEV_count = 0;
  bool RMEV; uint16_t RMEV_count = 0;
  bool LAST_LMEV = digitalRead(LME);
  bool LAST_RMEV = digitalRead(RME);
  uint16_t count = 0;
  while((LMEV_count < leftCount) && (RMEV_count < rightCount)){
    LMEV = digitalRead(LME);
    RMEV = digitalRead(RME);
    if (LMEV != LAST_LMEV){
      LMEV_count++;
    }
    if (RMEV != LAST_RMEV){
      RMEV_count++;
    }
    LAST_LMEV = LMEV;
    LAST_RMEV = RMEV;
//    count++;
//    if(count == 30){readIR();count = 0;} // Read IR sensor every 30 loops
  }
}

void forward(motors_t motors){
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN2, HIGH);
  digitalWrite(AIN1, LOW);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, motors.leftSpeed);
  analogWrite(PWMB, motors.rightSpeed);
  analogWrite(servo_pin, motors.servoAngle);
}

void turn_right() {
  go_straight(180);
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 200);
  analogWrite(PWMB, 120);
  analogWrite(servo_pin,127);
  readEncoders(1100,667);
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, 120);
  analogWrite(PWMB, 200);
  analogWrite(servo_pin,75);
  readEncoders(600,1000);
  go_back(420);
}

void turn_left() {
  go_straight(100);
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 120);
  analogWrite(PWMB, 200);
  analogWrite(servo_pin,75);
  readEncoders(840,1400);
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, 200);
  analogWrite(PWMB, 120);
  analogWrite(servo_pin,120);
  readEncoders(1300,780);
  go_back(400);
}

void turn_around(){
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 200);
  analogWrite(PWMB, 120);
  analogWrite(servo_pin,120);
  readEncoders(1100,611);
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, 120);
  analogWrite(PWMB, 200);
  analogWrite(servo_pin,75);
  readEncoders(650,1150);
}

void stopRobot(uint16_t d) {
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 0);
  analogWrite(PWMB, 0);
  delay(d);
}

void go_straight(uint16_t count){
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN2, HIGH);
  digitalWrite(AIN1, LOW);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, base_speed);
  analogWrite(PWMB, base_speed);
  analogWrite(servo_pin, base_angle);
  readEncoders(count,count);
}

void go_back(uint16_t count){
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(AIN1, HIGH);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, base_speed);
  analogWrite(PWMB, base_speed);
  analogWrite(servo_pin, base_angle);
  readEncoders(count,count);
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////// Virtual Robot Code ///////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
myRobot_t enterRoom(myRobot_t robot){
  robot = turnInMaze(robot);
  robot = stepInMaze(robot);
  robot = roomInMaze(robot);
  return robot;
}

// Returns updated direction based on current direction and turn value
// values returned in robot structure
myRobot_t turnInMaze(myRobot_t robot){
  // index = 4*turn_val + dir_val
  uint8_t index = (robot.turn << 2) + robot.dir;
  robot.dir = pgm_read_byte_near(turn_table + index);
  return robot;
}

// Returns updated row and column values after taking a step in current direction
// values returned in robot structure
myRobot_t stepInMaze(myRobot_t robot){
  // index = 2*robot.dir
  uint8_t index = (robot.dir << 1);
  robot.maze.row += pgm_read_byte_near(map_table + index);      // Add either -1, 0, or 1 to current row value
  robot.maze.col += pgm_read_byte_near(map_table + index + 1);  // Add either -1, 0, or 1 to current column value
  return robot;
}

// Returns updated room and bees values using current row and column values
// values returned in robot structure
myRobot_t roomInMaze(myRobot_t robot){
  // index = 21*robot.maze.row + robot.maze.col
  uint16_t index = (21*robot.maze.row) + robot.maze.col;
  uint8_t maze_val = pgm_read_byte_near(theMaze + index);
  robot.room = maze_val & 0x0F;                   // clear upper nibble and store as the room value
  uint8_t temp_bees = (maze_val & 0xF0) >> 4;     // clear lower nibble and store as the temp bees value
  robot.bees += temp_bees;                        // add temp_bees to curret bees value
  return robot;
}

// Room Type subroutine
uint8_t roomType(myRobot_t robot){
  bool leftWall = leftHit(robot);       // Test if hiting left wall
  bool hit = hitWall(robot);            // Test if facing wall
  bool rightWall = rightHit(robot);     // Test if hiting right wall
  uint8_t room = (uint8_t(leftWall) << 2)|(uint8_t(hit) << 1)|uint8_t(rightWall);   // Convert to room type
  return room;
}

// Returns true if there is a wall and false if there is no wall
bool hitWall(myRobot_t robot){
  // index = dir_val
  robot = roomInMaze(robot);                                    // Determine room value
  uint8_t wallVal = pgm_read_byte_near(hit_table + robot.dir);  // Determine wall bit based on direction
  uint8_t outVal = bool(wallVal & robot.room);                  // Clear all bits other than the wall robot is facing
  if (outVal == 0){return false;}                               // If the robot is not hiting a wall outVal will equal zero
  else {return true;}                                           // and the subroutine will return false, else it returns true.
}

// Returns true if there is a wall and false if there is no wall
// on the right side of the robot
bool rightHit(myRobot_t robot){
  robot.turn = 0x01;          // Modify turn value to turn right
  robot = turnInMaze(robot);  // Call turnInMaze to turn the robot (virtually)
  bool hit = hitWall(robot);  // Test hit wall
  return hit;
}

// Returns true if there is a wall and false if there is no wall
// on the left side of the robot
bool leftHit(myRobot_t robot){
  robot.turn = 0x02;          // Modify turn value to turn left
  robot = turnInMaze(robot);  // Call turnInMaze to turn the robot (virtually)
  bool hit = hitWall(robot);  // Test hit wall
  return hit;
}

// Should be modified to create a permanent solution to putting the 32U4 to sleep
// Also, could possibly do something before sleeping.
void inForest(myRobot_t robot){
  if (robot.maze.row == 0xFF){
    bool sleep = true;
    stopRobot(300000);
//    sleep();
  }
}

myRobot_t whichWay(uint8_t RoomType, myRobot_t robot){ // Update for shortest path
  stopRobot(500);
  if ((RoomType == 0)||(RoomType == 1)||(RoomType == 5)){go_straight(55);robot.turn = 0b00;}
  else if (RoomType == 3){turn_left();robot.turn = 0b10;}
  else if (RoomType == 6){turn_right();robot.turn = 0b01;}
  else if (RoomType == 7){turn_around();robot.turn = 0b11;}
  else if (RoomType == 4){turn_right();robot.turn = 0b01;}
  return robot;
}

Software developed for final implementation with 3DoT and Arxterra (Untested)

Final_3DoT_503.ino

////////////////////////////////////////////////////////////////
//  Name     : ModWheels Final Project (Sparkfun Pro Micro)   //
//  Author   : Matt Shellhammer                               //
//  Date     : 11 December, 2017                              //
//  Version  : 1.0                                            //
////////////////////////////////////////////////////////////////

#define __PROG_TYPES_COMPAT__
#include <avr/pgmspace.h>
#include <Wire.h>
#include <EEPROM.h>
#include "Adafruit_TCS34725.h"
#include "Final.h"
#include <Configure.h>
#include <FuelGauge.h>
#include <Motor.h>
#include <Packet.h>
#include <Robot3DoTBoard.h>
#include <TB6612FNG.h>
#include <TelecomClass.h>
#include <Watchdog.h>
#include <SparkFun_VL6180X.h>

// Define motor driver pins
#define AIN1 12
#define AIN2 4
#define PWMA 6
#define BIN1 9
#define BIN2 5
#define PWMB 10
#define STBY 8
// Define Encoders
#define LME A0
#define RME A1
// Define IR proximity sensor using library
#define VL6180X_ADDRESS 0x29
VL6180xIdentification identification;
VL6180x sensor(VL6180X_ADDRESS);
// Define servo pin
#define servo_pin SERVO_11

// Custom commands configuration
// Setup command addresses
#define MODE    0x40
#define ALLOW   0x41

// Define 3DoT Robot
Robot3DoTBoard Robot3DoT;
motors_t ArxterraMotors;
myRobot_t ArxterraRobot;
uint16_t EEPROM_Idx = 0x0000; // first EEPROM index is 0x0000

// Setup Custom 3Dot Commands
void moveHandler (uint8_t cmd, uint8_t param[], uint8_t n);
void modeHandler (uint8_t cmd, uint8_t param[], uint8_t n);
void allowHandler (uint8_t cmd, uint8_t param[], uint8_t n);
const uint8_t CMD_LIST_SIZE = 3;
Robot3DoTBoard::cmdFunc_t onCommand[CMD_LIST_SIZE] = {{MODE,modeHandler}, {MOVE,moveHandler}, {ALLOW,allowHandler}};

void setup() {
  Robot3DoT.begin();
  Robot3DoT.setOnCommand(onCommand, CMD_LIST_SIZE);
  Serial.begin(115200); //Start Serial at 115200bps
  Wire.begin(); //Start I2C library
  delay(100); // delay .1s
  sensor.VL6180xDefautSettings(); //Load default settings to get started.
  delay(1000); // delay 1s
  // Set the PIN Mode for motor drivers
  pinMode(PWMA, OUTPUT);
  pinMode(AIN1, OUTPUT);
  pinMode(AIN2, OUTPUT);
  pinMode(PWMB, OUTPUT);
  pinMode(BIN1, OUTPUT);
  pinMode(BIN2, OUTPUT);
  pinMode(STBY, OUTPUT);
  // Set the PIN Mode for Encoders
  pinMode(LME, INPUT);
  pinMode(RME, INPUT);
  // Set the PIN Mode for Servo
  pinMode(servo_pin, OUTPUT);
  asm("in r24, 0x35");
  asm("ori r24, 0x80");
  asm("out 0x35, r24");
  asm("out 0x35, r24");
  delay(5000);
}

void loop() {
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Variable Declaration ///////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
  static bool decision;                   // create a variable called decision
  static bool turn_flag = false;          // turn flag for turns with ModWheels
  static uint8_t type = 0;                // initially outside of the maze
  static uint8_t count1 = 0;              // first counter used
  static uint16_t count2 = 0;             // second counter used
  static myRobot_t ModWheels;             // create an instance of myRobot_t called ModWheels
  static sensors_t sensors_inst;          // create an instance of sensors_t called sensors_inst
  static motors_t motors_inst;            // create an instance of motors_t called motors_inst
  static PID_t PID_inst;                  // create an instance of PID_t called PID_inst
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// Shortest Path (Autonomous) /////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if(ArxterraRobot.mode == 0){
    Robot3DoT.loop();
    if(resetRobot == true){
      myRobot_t ModWheels;
      resetRobot = false;
    }
    count2++;
    if(count2 == 30){readIR();count2 = 0;} // Read IR proximity sensor every 30 loops
    sensors_inst = readSensors(sensors_inst);             // Read sensor values
    PID_inst = PIDcalc(sensors_inst, PID_inst);           // Calculate PID value
    motors_inst = errorCorrection(PID_inst, motors_inst); // Correct for error with motors
    forward(motors_inst);                                 // send new speed to motors to move forward
  
  // Check if in forest
    inForest(ModWheels);
  
    // Check if at an intersection
    if ((sensors_inst.IR0 >= 0.95)&&(-sensors_inst.IR1 >= 0.95)){
      if (turn_flag == true){turn_flag = false;go_straight(75);}
      else{
        ModWheels = enterRoom(ModWheels);
        type = roomType(ModWheels);
        ModWheels = whichWay(type, ModWheels);
        if (ModWheels.turn != 0){turn_flag = true;}
      }
    }
  }


////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////// Write to EEPROM (Arxterra control) /////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if(ArxterraRobot.mode == 1){
    Robot3DoT.loop();
    // No decision to be made when in a Hallway or outside of the maze (go straight)
    if ((type == 0)||(type == 5)){decision = false;ArxterraRobot.turn = 0x00;}
    // No decision to be made when in a left corner (turn left)
    if (type == 3){decision = false;ArxterraRobot.turn = 0x10;}
    // No decision to be made when in a right corner (turn right)
    if (type == 6){decision = false;ArxterraRobot.turn = 0x01;}
    // No decision to be made when at a dead end (turn around)
    if (type == 7){decision = false;ArxterraRobot.turn = 0x11;}
    else{decision = true;}

    // Call write data to EEPROM when a decision is made on the Arxterra App at a decision point
    if ((decision == true)&&(EEPROM_Idx < 0x400)){
      // Call Arxterra custom command to request a turn value (update ArxterraRobot.turn)
      // Store dir facing and turn value
      EEPROM_write(EEPROM_Idx, ArxterraRobot.dir);EEPROM_Idx++;
      EEPROM_write(EEPROM_Idx, ArxterraRobot.turn);EEPROM_Idx++;
    }
    ArxterraRobot = enterRoom(ArxterraRobot); // Update ArxterraRobot
    type = roomType(ArxterraRobot);           // Determine room type
  }


////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////// Read from EEPROM (Autonomous playback) /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if(ArxterraRobot.mode == 2){
    Robot3DoT.loop();
    // No decision to be made when in a Hallway or outside of the maze (go straight)
    if ((type == 0)||(type == 5)){decision = false;ArxterraRobot.turn = 0x00;}
    // No decision to be made when in a left corner (turn left)
    if (type == 3){decision = false;ArxterraRobot.turn = 0x10;}
    // No decision to be made when in a right corner (turn right)
    if (type == 6){decision = false;ArxterraRobot.turn = 0x01;}
    // No decision to be made when at a dead end (turn around)
    if (type == 7){decision = false;ArxterraRobot.turn = 0x11;}
    else{decision = true;}

    // Call read data to EEPROM when at a decision point
    if ((decision == true)&&(EEPROM_Idx < 0x400)){
      // Call Arxterra custom command to request a turn value
      // Store dir facing and turn value
      uint8_t temp_dir = EEPROM_read(EEPROM_Idx);EEPROM_Idx++;
  //    if (temp_dir != ArxterraRobot.dir){//ERROR: Do something}
      ArxterraRobot.turn = EEPROM_read(EEPROM_Idx);EEPROM_Idx++;
    }
      ArxterraRobot = enterRoom(ArxterraRobot); // Update robot_inst
      type = roomType(ArxterraRobot);           // Determine room type
  }


////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// RC mode (Outside of the maze) ////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if(ArxterraRobot.mode == 3){
    Robot3DoT.loop();
  }
}

Final.h

// PID constants
#define Kp 4000
#define Ki 0.8
#define Kd 4500
// Bias speed and angle
#define base_speed 150
#define base_angle 96

// Global reset robot flag
bool resetRobot = false;

struct coord_t{
  uint8_t row = 0x13; // Robot is initially outside of the maze
  uint8_t col = 0x00; // Robot is initially outside of the maze
};

struct myRobot_t{
  uint8_t dir = 0x03;   // Robot is initially facing north
  uint8_t turn = 0x00;  // First action is no turn
  coord_t maze;
  uint8_t room = 0x00;  // Initial room is empty
  uint8_t bees = 0x00;  // No bees present
  uint8_t mode = 0;
};

struct sensors_t{
  float IR0; // Left IR sensor (normalized)
  float IR1; // Right IR sensor (normalized)
};

struct motors_t{
  int16_t leftSpeed = base_speed;   // Initial motor speed: 60
  int16_t rightSpeed = base_speed;  // Initial motor speed: 60
  int16_t servoAngle = base_angle;  // Initial servo angle: 95
};

// Define PID structure
struct PID_t{
  float present = 0;
  float set_point = 0;
  float proportional = 0;
  float integral = 0;
  float derivative = 0;
  int16_t PIDvalue = 0;
  float error = 0;
  float previous_error = 0;
};

const uint8_t hit_table[] PROGMEM =
  {0x08,  // South (dir == 0b00)
   0x02,  // East (dir == 0b01)
   0x04,  // West (dir == 0b10)
   0x01}; // North (dir == 0b11)

//Compass   S     E     W     N
//dir       00    01    10    11
const uint8_t turn_table[] PROGMEM =
          {0b00, 0b01, 0b10, 0b11, // 00 no turn
           0b10, 0b00, 0b11, 0b01, // 01 turn right
           0b01, 0b11, 0b00, 0b10, // 10 turn left
           0b11, 0b10, 0b01, 0b00  // 11 turn around
           };

//  row   col   dir
const int8_t map_table[] PROGMEM =
    {1  ,  0, // 00
     0  ,  1, // 01
     0  , -1, // 10
    -1  ,  0  // 11
    };

const int maze_length = 399;
const uint8_t theMaze[] PROGMEM =
// 00  01   02   03   04   05   06   07   08   09   0A   0B   0C   0D   0E   0F   10   11   12   13   14
{0x05,0x09,0x09,0x09,0x09,0x09,0x01,0x03,0x05,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x29,0x09,0x09,0x09,0x02,  // 00
 0x0C,0x09,0x09,0x03,0x05,0x09,0x0A,0x06,0x06,0x05,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x03,0x05,0x03,0x06,  // 01
 0x05,0x09,0x0B,0x06,0x06,0x05,0x09,0x0A,0x06,0x0C,0x09,0x09,0x09,0x09,0x09,0x01,0x0B,0x0C,0x0A,0x06,0x06,  // 02
 0x06,0x0D,0x09,0x0A,0x06,0x06,0x05,0x03,0x0C,0x09,0x09,0x03,0x05,0x09,0x09,0x0A,0x05,0x09,0x09,0x08,0x02,  // 03
 0x06,0x05,0x09,0x09,0x0A,0x06,0x06,0x0C,0x09,0x09,0x09,0x0A,0x0C,0x09,0x09,0x03,0x06,0x05,0x09,0x09,0x0A,  // 04
 0x06,0x0C,0x03,0x05,0x09,0x02,0x06,0x05,0x09,0x09,0x09,0x09,0x09,0x09,0x03,0x06,0x06,0x0C,0x03,0x05,0x03,  // 05
 0x06,0x05,0x0A,0x0C,0x03,0x06,0x06,0x06,0x05,0x01,0x03,0x07,0x05,0x03,0x06,0x06,0x06,0x05,0x0A,0x06,0x06,  // 06
 0x06,0x0C,0x09,0x03,0x0E,0x0C,0x08,0x02,0x06,0x06,0x06,0x06,0x06,0x06,0x0C,0x02,0x06,0x0C,0x09,0x02,0x06,  // 07
 0x06,0x05,0x0B,0x0C,0x09,0x09,0x09,0x08,0x02,0x06,0x06,0x06,0x06,0x0C,0x09,0x0A,0x04,0x09,0x0B,0x06,0x06,  // 08
 0x0C,0x08,0x09,0x09,0x09,0x09,0x01,0x01,0x02,0x06,0x0C,0x08,0x08,0x09,0x01,0x09,0x08,0x09,0x03,0x06,0x06,  // 09
 0x05,0x01,0x09,0x09,0x0B,0x07,0x06,0x04,0x02,0x0C,0x09,0x09,0x09,0x03,0x04,0x09,0x03,0x07,0x06,0x06,0x06,  // 0A
 0x06,0x0C,0x09,0x09,0x09,0x02,0x06,0x04,0x02,0x0D,0x09,0x09,0x09,0x0A,0x0C,0x03,0x06,0x06,0x06,0x06,0x06,  // 0B
 0x06,0x05,0x09,0x09,0x09,0x0A,0x06,0x0C,0x0A,0x05,0x09,0x09,0x09,0x09,0x03,0x06,0x06,0x06,0x06,0x06,0x06,  // 0C
 0x06,0x0C,0x09,0x09,0x09,0x03,0x04,0x09,0x09,0x08,0x0B,0x05,0x03,0x05,0x0A,0x06,0x06,0x06,0x06,0x06,0x06,  // 0D
 0x04,0x09,0x09,0x09,0x09,0x08,0x02,0x05,0x01,0x09,0x03,0x06,0x06,0x06,0x05,0x0A,0x0E,0x06,0x06,0x06,0x06,  // 0E
 0x06,0x05,0x09,0x09,0x09,0x09,0x0A,0x0E,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x05,0x09,0x0A,0x06,0x06,0x06,  // 0F
 0x06,0x0C,0x09,0x09,0x09,0x09,0x09,0x09,0x0A,0x06,0x06,0x06,0x06,0x0E,0x0E,0x06,0x05,0x09,0x0A,0x06,0x06,  // 10
 0x04,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x0A,0x0C,0x0A,0x06,0x05,0x09,0x0A,0x06,0x0D,0x09,0x0A,0x06,  // 11
 0x04,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x08,0x08,0x09,0x09,0x08,0x09,0x09,0x09,0x0A,  // 12
};

Finalsub.ino

////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// Physical Robot Code ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
void readIR(){
  uint8_t IR = 0;
  bool IRflag = false;
  IR = sensor.getDistance();
  delay(10); // delay .1s 
  while (IR < 153){          // Don't hit something
    analogWrite(PWMA, 0);
    analogWrite(PWMB, 0);
    IR = sensor.getDistance();
    delay(10); // delay .1s
    IRflag = true;
  }
  if (IRflag == true){
    delay(3000);
    IRflag = false;
  }
}

// readSensors subroutine returns normalized sensor values averaged by 10 points
sensors_t readSensors(sensors_t sensors){
  int n = 10;     // number of ADC samples
  float x_i;      // ADC input for sample i
  float A_1;      // current i running average
  float A_0;      // previous i-1 running average

  // read left IR sensor
  A_0 = 0.0;
  for (int16_t i = 1; i <= n; i++) {
    x_i = analogRead(A2)/1024.0;
    A_1 = A_0 + (x_i - A_0)/i;
    A_0 = A_1;
  }
  sensors.IR0 = A_0;

  // read right IR sensor
  A_0 = 0.0;
  for (int16_t i = 1; i <= n; i++) {
    x_i = analogRead(A3)*(-1.0/1024.0);
    A_1 = A_0 + (x_i - A_0)/i;
    A_0 = A_1;
  }
  sensors.IR1 = A_0;
  return sensors;
}

PID_t PIDcalc(sensors_t sensors, PID_t PID){
  // Calculates error
  PID.present = (sensors.IR0) + sensors.IR1;
  PID.error = PID.present - PID.set_point;

  // Calculates proportional error
  PID.proportional = PID.error;

  // Calculates integrated error
  PID.integral += PID.error;

  // Calculate change in error
  PID.derivative = PID.error - PID.previous_error;
  PID.previous_error = PID.error;

  // Calculate compensator value
  // this should be an integer from -128 to 127 which is why the casting function is used
  PID.PIDvalue = int8_t(Kp*PID.proportional + Ki*PID.integral + Kd*PID.derivative);
  return PID;
}

// errorCorrection: corrects motor speed based on PID value (limits between <50,220>)
motors_t errorCorrection(PID_t PID, motors_t motors){
  motors.leftSpeed = base_speed - PID.PIDvalue;
  motors.rightSpeed = base_speed + PID.PIDvalue;
  motors.servoAngle = base_angle - (0.6*PID.PIDvalue);

  // Limit the left and right motor speeds between <50,220>
  if (motors.rightSpeed > 220) {motors.rightSpeed = 220;}
  if (motors.leftSpeed > 220) {motors.leftSpeed = 220;}
  if (motors.rightSpeed < 50) {motors.rightSpeed = 50;}
  if (motors.leftSpeed < 50) {motors.leftSpeed = 50;}
  if (motors.servoAngle > 110) {motors.servoAngle = 110;}
  if (motors.servoAngle < 80) {motors.servoAngle = 80;}
  return motors;
}

void readEncoders(uint16_t leftCount, uint16_t rightCount){
  bool LMEV; uint16_t LMEV_count = 0;
  bool RMEV; uint16_t RMEV_count = 0;
  bool LAST_LMEV = digitalRead(LME);
  bool LAST_RMEV = digitalRead(RME);
  uint16_t count = 0;
  while((LMEV_count < leftCount) && (RMEV_count < rightCount)){
    LMEV = digitalRead(LME);
    RMEV = digitalRead(RME);
    if (LMEV != LAST_LMEV){
      LMEV_count++;
    }
    if (RMEV != LAST_RMEV){
      RMEV_count++;
    }
    LAST_LMEV = LMEV;
    LAST_RMEV = RMEV;
//    count++;
//    if(count == 30){readIR();count = 0;} // Read IR sensor every 30 loops
  }
}

void forward(motors_t motors){
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN2, HIGH);
  digitalWrite(AIN1, LOW);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, motors.leftSpeed);
  analogWrite(PWMB, motors.rightSpeed);
  analogWrite(servo_pin, motors.servoAngle);
}

void turn_right() {
  go_straight(270);
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 200);
  analogWrite(PWMB, 120);
  analogWrite(servo_pin,120);
  readEncoders(1100,611);
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, 120);
  analogWrite(PWMB, 200);
  analogWrite(servo_pin,75);
  readEncoders(650,1150);
  go_back(370);
}

void turn_left() {
  go_straight(140);
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 120);
  analogWrite(PWMB, 200);
  analogWrite(servo_pin,75);
  readEncoders(760,1370);
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, 200);
  analogWrite(PWMB, 120);
  analogWrite(servo_pin,120);
  readEncoders(1100,611);
  go_back(500);
}

void turn_around(){
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 200);
  analogWrite(PWMB, 120);
  analogWrite(servo_pin,120);
  readEncoders(1100,611);
  digitalWrite(AIN1, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, 120);
  analogWrite(PWMB, 200);
  analogWrite(servo_pin,75);
  readEncoders(650,1150);
}

void stopRobot(uint16_t d) {
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN1, LOW);
  digitalWrite(AIN2, HIGH);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, 0);
  analogWrite(PWMB, 0);
  delay(d);
}

void go_straight(uint16_t count){
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN2, HIGH);
  digitalWrite(AIN1, LOW);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  analogWrite(PWMA, base_speed);
  analogWrite(PWMB, base_speed);
  analogWrite(servo_pin, base_angle);
  readEncoders(count,count);
}

void go_back(uint16_t count){
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN2, LOW);
  digitalWrite(AIN1, HIGH);
  digitalWrite(BIN1, HIGH);
  digitalWrite(BIN2, LOW);
  analogWrite(PWMA, base_speed);
  analogWrite(PWMB, base_speed);
  analogWrite(servo_pin, base_angle);
  readEncoders(count,count);
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////// Virtual Robot Code ///////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
myRobot_t enterRoom(myRobot_t robot){
  robot = turnInMaze(robot);
  robot = stepInMaze(robot);
  robot = roomInMaze(robot);
  return robot;
}

// Returns updated direction based on current direction and turn value
// values returned in robot structure
myRobot_t turnInMaze(myRobot_t robot){
  // index = 4*turn_val + dir_val
  uint8_t index = (robot.turn << 2) + robot.dir;
  robot.dir = pgm_read_byte_near(turn_table + index);
  return robot;
}

// Returns updated row and column values after taking a step in current direction
// values returned in robot structure
myRobot_t stepInMaze(myRobot_t robot){
  // index = 2*robot.dir
  uint8_t index = (robot.dir << 1);
  robot.maze.row += pgm_read_byte_near(map_table + index);      // Add either -1, 0, or 1 to current row value
  robot.maze.col += pgm_read_byte_near(map_table + index + 1);  // Add either -1, 0, or 1 to current column value
  return robot;
}

// Returns updated room and bees values using current row and column values
// values returned in robot structure
myRobot_t roomInMaze(myRobot_t robot){
  // index = 21*robot.maze.row + robot.maze.col
  uint16_t index = (21*robot.maze.row) + robot.maze.col;
  uint8_t maze_val = pgm_read_byte_near(theMaze + index);
  robot.room = maze_val & 0x0F;                   // clear upper nibble and store as the room value
  uint8_t temp_bees = (maze_val & 0xF0) >> 4;     // clear lower nibble and store as the temp bees value
  robot.bees += temp_bees;                        // add temp_bees to curret bees value
  return robot;
}

// Room Type subroutine
uint8_t roomType(myRobot_t robot){
  bool leftWall = leftHit(robot);       // Test if hiting left wall
  bool hit = hitWall(robot);            // Test if facing wall
  bool rightWall = rightHit(robot);     // Test if hiting right wall
  uint8_t room = (uint8_t(leftWall) << 2)|(uint8_t(hit) << 1)|uint8_t(rightWall);   // Convert to room type
  return room;
}

// Returns true if there is a wall and false if there is no wall
bool hitWall(myRobot_t robot){
  // index = dir_val
  robot = roomInMaze(robot);                                    // Determine room value
  uint8_t wallVal = pgm_read_byte_near(hit_table + robot.dir);  // Determine wall bit based on direction
  uint8_t outVal = bool(wallVal & robot.room);                  // Clear all bits other than the wall robot is facing
  if (outVal == 0){return false;}                               // If the robot is not hiting a wall outVal will equal zero
  else {return true;}                                           // and the subroutine will return false, else it returns true.
}

// Returns true if there is a wall and false if there is no wall
// on the right side of the robot
bool rightHit(myRobot_t robot){
  robot.turn = 0x01;          // Modify turn value to turn right
  robot = turnInMaze(robot);  // Call turnInMaze to turn the robot (virtually)
  bool hit = hitWall(robot);  // Test hit wall
  return hit;
}

// Returns true if there is a wall and false if there is no wall
// on the left side of the robot
bool leftHit(myRobot_t robot){
  robot.turn = 0x02;          // Modify turn value to turn left
  robot = turnInMaze(robot);  // Call turnInMaze to turn the robot (virtually)
  bool hit = hitWall(robot);  // Test hit wall
  return hit;
}

// Should be modified to create a permanent solution to putting the 32U4 to sleep
// Also, could possibly do something before sleeping.
void inForest(myRobot_t robot){
  if (robot.maze.row == 0xFF){
    bool sleep = true;
    stopRobot(300000);
//    sleep();
  }
}

myRobot_t whichWay(uint8_t RoomType, myRobot_t robot){ // Update for shortest path
  stopRobot(2000);
  if ((RoomType == 0)||(RoomType == 1)||(RoomType == 5)){go_straight(75);robot.turn = 0b00;}
  else if (RoomType == 3){turn_left();robot.turn = 0b10;}
  else if (RoomType == 6){turn_right();robot.turn = 0b01;}
  else if (RoomType == 7){turn_around();robot.turn = 0b11;}
  else if (RoomType == 4){turn_right();robot.turn = 0b01;}
  return robot;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// Arxterra Custom Commands ///////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* moveHandler: RC mode inside the maze */
void moveHandler(uint8_t cmd, uint8_t param[], uint8_t n){
  // Set the turn direction and record it, (ONLY ACCEPT AT INTERSECTIONS)
  if(ArxterraRobot.turn == 4){ // at intersection for turn command
    // 01 2B 01 2B == Foward
    if (param[0] == 0x01 && param[2] == 0x01){
      ArxterraRobot.turn = 0x00;
    // 02 2B 02 2B == Backward
    }else if(param[0] == 0x02 && param[2] == 0x02){
      ArxterraRobot.turn = 0x11;
    // 01 00 02 00 == Right
    }else if(param[0] == 0x01 && param[2] == 0x02){
      ArxterraRobot.turn = 0x01;
    // 02 00 01 00 == Left
    }else if(param[0] == 0x02 && param[2] == 0x01){
      ArxterraRobot.turn = 0x10;
    }
  }
}

/* moveHandler: RC mode outside of maze */
/*void moveHandler(uint8_t cmd, uint8_t param[], uint8_t n){
  digitalWrite(STBY, HIGH);
  digitalWrite(AIN2, HIGH);
  digitalWrite(AIN1, LOW);
  digitalWrite(BIN1, LOW);
  digitalWrite(BIN2, HIGH);
  // 01 2B 01 2B == Foward (0 to 255 over 5 ticks (increments of 51))
  if (param[0] == 0x01 && param[2] == 0x01){
    if((param[1] == 0x2B)||(param[1] == 0x55)||(param[1] == 0x80)||(param[1] == 0xAA)||(param[1] == 0xD5)){
     if (ArxterraMotors.leftSpeed <= 204){ArxterraMotors.leftSpeed += 51;}
      if (ArxterraMotors.rightSpeed <= 204){ArxterraMotors.rightSpeed += 51;}
    }
    analogWrite(PWMA, ArxterraMotors.leftSpeed);
    analogWrite(PWMB, ArxterraMotors.rightSpeed);
    analogWrite(servo_pin,60);
  }
  // 02 2B 02 2B == Stop (255 to 0 over 5 ticks (increments of 51))
  else if(param[0] == 0x02 && param[2] == 0x02){
    if((param[1] == 0x2B)||(param[1] == 0x55)||(param[1] == 0x80)||(param[1] == 0xAA)||(param[1] == 0xD5)){
      if (ArxterraMotors.leftSpeed >= 51){ArxterraMotors.leftSpeed -= 51;}
      if (ArxterraMotors.rightSpeed >= 51){ArxterraMotors.rightSpeed -= 51;}
    }
    analogWrite(PWMA, ArxterraMotors.leftSpeed);
    analogWrite(PWMB, ArxterraMotors.rightSpeed);
    analogWrite(servo_pin,60);
  }
  // 01 00 02 00 == Right (120 to 60 over 5 ticks (increments of 12 degrees))
  else if(param[0] == 0x01 && param[2] == 0x02){
    if((param[1] == 0x2B)||(param[1] == 0x55)||(param[1] == 0x80)||(param[1] == 0xAA)||(param[1] == 0xD5)){
      if (ArxterraMotors.servoAngle >= 72){ArxterraMotors.servoAngle -= 12;}
    }
    analogWrite(servo_pin,ArxterraMotors.servoAngle);
  }
  // 02 00 01 00 == Left (60 to 120 over 5 ticks (increments of 12 degrees))
  else if(param[0] == 0x02 && param[2] == 0x01){
    if((param[1] == 0x2B)||(param[1] == 0x55)||(param[1] == 0x80)||(param[1] == 0xAA)||(param[1] == 0xD5)){
      if (ArxterraMotors.servoAngle <= 108){ArxterraMotors.servoAngle += 12;}
    }
    analogWrite(servo_pin,ArxterraMotors.servoAngle);
  }
//  // 04 00 04 00 return servo to center
//  else if(param[0] == 0x04 && param[2] == 0x04){
//    analogWrite(servo_pin,90);
//  }
}*/

void modeHandler(uint8_t cmd, uint8_t param[], uint8_t n){
 //Changes the set mode 
 // Mode 0 = Predetermined Route
 // Mode 1 = Record Mode (RC in the maze)
 // Mode 2 = Playback Mode (RC in the maze)
 // Mode 3 = RC out of the Maze
  if(param[0] == 0 && ArxterraRobot.mode != 0){
    // change mode and reset needed paramters
    ArxterraRobot.mode = 0;
    resetRobot = true;
    //teleMState.sendPacket(MODESTATE, &data, 1);
  }
  if(param[0] == 1 && ArxterraRobot.mode != 1){
    // change mode and reset needed paramters
    ArxterraRobot.mode = 1;
    EEPROM_Idx = 0x0000;      // first EEPROM index is 0x0000
    //teleMState.sendPacket(MODESTATE, 0x0001);
  }
  if(param[0] == 2 && ArxterraRobot.mode != 2){
    // change mode and reset needed paramters
    ArxterraRobot.mode = 2;
    EEPROM_Idx = 0x0000;      // first EEPROM index is 0x0000
    //teleMState.sendPacket(MODESTATE, 0x0002);
  }
}

void allowHandler(uint8_t cmd, uint8_t param[], uint8_t n){
// // Turns the motors on or off (Stopping the bot) 
// // 0 = Stop!
// // 1 = Start moving again
// if(param[0] == 0){
//    // Stop motors
//    remoteStop = true;
//    robotMovement("Stop");
//    LEDDirection("X");
//  
//  if(param[0] == 1){
//    // Movement True (GO!)
//    remoteStop = false;
//    robotMovement("Go");
//    LEDDirection("Foward");
//  }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// Read/Write EEPROM Subroutines ////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
 * Write data to EEPROM, NOTE: interrupts are disabled while writing
 * @param uiAddress 16 bit interger pointing to the address of the data to write
 * @param ucData 8 bit value signifying the data being written
 */
void EEPROM_write(uint16_t uiAddress, uint8_t ucData) {
  /*Store SREG value before we disable Interrupts*/
  char SREG_save = SREG;
  noInterrupts();
  /* Wait for completion of any Flash Write
        Note:Only necessary if Flash Memory Manipulation is taking place */
  while(SPMCSR &(1<<SPMEN));
  /* Wait for completion of previous write */
  while(EECR & (1<<EEPE));
  /* Set up address and Data Registers */
  EEAR = uiAddress;
  EEDR = ucData;
  /* Write logical one to EEMPE */
  EECR |= (1<<EEMPE);
  /* Start eeprom write by setting EEPE */
  EECR |= (1<<EEPE);
  /*Restore the SREG value*/
  SREG = SREG_save;
}

/*
 * Read data from EEPROM, NOTE: interrupts are disabled while writing
 * @param uiAddress 16 bit interger pointing to the address of the data to read
 * @return 8 bit value signifying the data that was read
 */
uint8_t EEPROM_read(uint16_t uiAddress) {
  /*Store SREG value before we disable Interrupts*/
  char SREG_save = SREG;
  noInterrupts();
  /* Wait for completion of any Flash Write
        Note:Only necessary if Flash Memory Manipulation is taking place */
  while(SPMCSR &(1<<SPMEN));
  /* Wait for completion of previous write */
  while(EECR & (1<<EEPE));
  /* Set up address register */
  EEAR = uiAddress;
  /* Start eeprom read by writing EERE */
  EECR |= (1<<EERE);
  /*Restore the SREG value*/
  SREG = SREG_save;
  /* Return data from Data Register */
  return EEDR;
}

 

Fall 2017: ModWheels Budget

By: Lucas Gutierrez (Project Manager)

 

Discussion

As part of the requirements stated by the customer, a budget was set for the ModWheels project for no more than $200.00 .  To help cut some costs, the EE 400D previous semester’s inventory of electronics, hardware, and materials were provided for use.  The Budget report includes total cost of the project as well as cost of the project offset by inventory.

Budget

Figure: Fall 2017 ModWheels Final Budget

Goliath Fall 2017 Verification and Test

Written by Nornubari Kanabolo MST DM

Verification and Testing

In order to verify and test the Goliath, the requirements needed to be updated. Updated requirements can be found in this blog post. Goliath testing is complete for the most part, but some tests still need to be done before 2pm on December 13th, 2017. The verification and tests that have been/will be done can be seen below in the following test cases from the Verification and Validation document.

Test Cases

TC-01: Goliath move forward

Description: The Goliath will use overwritten MOVE commands uploaded to the 3DoT board in order to move forward.

Test Environment: Occurs inside, on a flat surface with 0% grade

TC-02: Turn Direction

Description: Display turn direction on the LED array.

Test Environment: Inside a classroom

TC-03: Assembly and Disassembly

Description: Assemble and disassemble the Goliath within a specified amount of time.

Test Environment: Inside a classroom

TC-04: 3D printing

Description: Print 3D models in less than 6 hours, and no part takes longer than 2.

Test Environment: In room that has an accessible 3D printer

TC-05: Goliath dimensions

Description: Measure the dimensions of the newly designed and assembled Goliath.

Test Environment: Inside a classroom

TC-06: Arxterra Control Panel

Description: Control the newly designed and assembled Goliath using the Arxterra Control Panel.

Test Environment: Inside a classroom

TC-07: Charging Goliath

Description: Testing the ease of access of the charging port.

Test Environment: Inside a classroom

TC-08: Battery Life

Description: Running the Goliath motors continuously for 1 hour.

Test Environment: Inside a classroom

TC-09: Weighing Goliath

Description: Placing the fully assembled newly designed Goliath on a weighing scale.

Test Environment: Inside a classroom

TC-10: Measuring Goliath Voltage and Current

Description: Measuring the voltage of the sensor header and finding the current rating.

Test Environment: Inside a classroom

TC-11: Detecting objects

Description: Detecting objects within a specified range.

Test Environment: Inside a classroom

Pete-Bot – Troubleshooting the GM6 Motors

Written by Zachary de Bruyn

Troubleshooting Results

During the integration of the peripheral devices it was discovered that the GM6 Motors did not operate accordingly. When the motors were inserted into the JP2 and JP3 ports of the 3DoT board, the motors would not operate with the provided code designed to test the GM6 Motors.

/*
Sample Code to run the Sparkfun TB6612FNG 1A Dual Motor Driver using Arduino UNO R3

This code conducts a few simple manoeuvres to illustrate the functions:
  - motorDrive(motorNumber, motorDirection, motorSpeed)
  - motorBrake(motorNumber)
  - motorStop(motorNumber)
  - motorsStandby

Connections:
- Pin 3 ---> PWMA
- Pin 8 ---> AIN2
- Pin 9 ---> AIN1
- Pin 10 ---> STBY
- Pin 11 ---> BIN1
- Pin 12 ---> BIN2
- Pin 5 ---> PWMB

- Motor 1: A01 and A02
- Motor 2: B01 and B02

*/
// FOR V5.03
//Define the Pins

//Motor 1
int pinAIN1 = 12; //Direction
int pinAIN2 = 4; //Direction
int pinPWMA = 6; //Speed

//Motor 2
int pinBIN1 = 9; //Direction
int pinBIN2 = 5; //Direction
int pinPWMB = 10; //Speed

//Standby
int pinSTBY = 8;

//Constants to help remember the parameters
static boolean turnCW = 0;  //for motorDrive function
static boolean turnCCW = 1; //for motorDrive function
static boolean motor1 = 0;  //for motorDrive, motorStop, motorBrake functions
static boolean motor2 = 1;  //for motorDrive, motorStop, motorBrake functions

void setup()
{
  Serial.begin(9600);
  //Set the PIN Modes
  pinMode(pinPWMA, OUTPUT);
  pinMode(pinAIN1, OUTPUT);
  pinMode(pinAIN2, OUTPUT);

  pinMode(pinPWMB, OUTPUT);
  pinMode(pinBIN1, OUTPUT);
  pinMode(pinBIN2, OUTPUT);

  pinMode(pinSTBY, OUTPUT);
  asm("in r24, 0x35");
  asm("ori r24, 0x80");
  asm("out 0x35, r24");
  asm("out 0x35, r24");
}

void loop()
{
  //Assuming that the input command from the arxterra app can be read as an integer
 
 motorDrive(motor1, turnCW, 255);

//  delay(500);
//  motorBrake(motor1);
//
//  delay(500);
  //Command to make spider walk backward

  motorDrive(motor2, turnCCW, 255);
//  delay(500);
//  motorBrake(motor2);
//  delay(500);
}

void motorDrive(boolean motorNumber, boolean motorDirection, int motorSpeed)
{
  /*
  This Drives a specified motor, in a specific direction, at a specified speed:
    - motorNumber: motor1 or motor2 ---> Motor 1 or Motor 2
    - motorDirection: turnCW or turnCCW ---> clockwise or counter-clockwise
    - motorSpeed: 0 to 255 ---> 0 = stop / 255 = fast
  */

  boolean pinIn1;  //Relates to AIN1 or BIN1 (depending on the motor number specified)


  //Specify the Direction to turn the motor
  //Clockwise: AIN1/BIN1 = HIGH and AIN2/BIN2 = LOW
  //Counter-Clockwise: AIN1/BIN1 = LOW and AIN2/BIN2 = HIGH
  if (motorDirection == turnCW)
    pinIn1 = HIGH;
  else
    pinIn1 = LOW;

  //Select the motor to turn, and set the direction and the speed
  if (motorNumber == motor1)
  {
    digitalWrite(pinAIN1, pinIn1);
    digitalWrite(pinAIN2, !pinIn1);  //This is the opposite of the AIN1
    analogWrite(pinPWMA, motorSpeed);
  }
  else
  {
    digitalWrite(pinBIN1, pinIn1);
    digitalWrite(pinBIN2, !pinIn1);  //This is the opposite of the BIN1
    analogWrite(pinPWMB, motorSpeed);
  }

  //Finally , make sure STBY is disabled - pull it HIGH
  digitalWrite(pinSTBY, HIGH);

}

void motorBrake(boolean motorNumber)
{
  /*
  This "Short Brake"s the specified motor, by setting speed to zero
  */

  if (motorNumber == motor1)
    analogWrite(pinPWMA, 0);
  else
    analogWrite(pinPWMB, 0);
}

void motorStop(boolean motorNumber)
{
  /*
  This stops the specified motor by setting both IN pins to LOW
  */

  if (motorNumber == motor1) {
    digitalWrite(pinAIN1, LOW);
    digitalWrite(pinAIN2, LOW);
  }
  else
  {
    digitalWrite(pinBIN1, LOW);
    digitalWrite(pinBIN2, LOW);
  }
}

void motorsStandby()
{
  /*
  This puts the motors into Standby Mode
  */
  digitalWrite(pinSTBY, LOW);
}

With no consideration for the other peripheral devices, and just the GM6 motors attached to the board, the motors are programmed to operate at the HIGH setting as depicted in the code (255). Instead the motors would only “tick”. The following configurations were utilized to try and troubleshoot the issue with the GM6 Motors.

Figure One – Test Results

The above test results above suggest that the board is not providing the necessary voltages to the motors while both are plugged into the board. I referred to the schematic and started by investigating the voltages being read to the motor driver pins A01 and A02

Figure Two – From 3DoT v5.03 schematic

While operating at nominal conditions, I find that the voltages read from these pins are similar to what I was measuring on the motors. I had similar results when performing the measurements for JP3 under identical conditions. Once both motors were connected, however, those voltages dropped down to 4.2 to 7.2 mV.

Testing the TPS61200

The traces of the motor driver were followed back to the TPS61200 buck/boost converter, suspecting that it might be the culprit. However in order to get to the TPS61200, as well as the other IC’s, the CR123 battery holder needed to be replaced and rigged in such a manner that it would still operate the 3DoT board.

Figure Three – Removed Battery Holder

As shown in the figure, the battery holder was removed by removing the solder from the holder leads on the back side of the 3DoT board. Once the battery holder was removed stripped wire was soldered to the battery holder and then soldered onto the 3DoT board. This setup, while ugly, is only temporary; and what has been done can be undone and the battery holder can be reapplied in the correct way once the troubleshooting of the board has completed.

Once the battery holder jumper wires were soldered onto the board, the 3DoT was operationally checked first by simply powering on the board, and then by verifying the board was still programmable.

Figure Four – Temporary Battery Setup

By reviewing both the data sheet of the TPS61200 and the schematic of the v5.03 board, the power pins of the TPS61200 were measured as well as their corresponding input pins to the dual motor driver.

The first pin measured was the EN pin of the TPS61200 which is directly linked to the power switch of the battery. Measuring the EN pin resulted in 4.18 V. By verifying the fact that the EN pin was reading the same voltage as the battery we could safely assume that the network from the battery holder to the input of the TPS61200 was operating correctly, including the 1.5A fuse which lies between the battery holder and the switch. The output pin VOUT was then measured to assure that it was providing the 5 V necessary to power the dual motor driver. Upon inspection, we find that the VOUT pin indeed does measure 5 V (5.06 V to be exact). This node was then traced back to the motor driver.

While operating under no load (ie no motors connected to the motor driver) the voltages were measured at pins VM1, VM2, and VM3 of the dual motor driver. The voltages measured at the motor driver were approximately 5 V for all three of the inputs to the driver. The SAME voltages were read at the three inputs when both motors loads were attached to the board.

By referring to the previous test of the dual motor driver, it is recalled that while a  motor  was operating at optimal conditions the output nodes of the motor drivers (AO1, AO2, BO1, BO2) were 5 V, however once another motor load was placed onto the motor driver, the voltages across the nodes would read 4 to 7 mV.

Reviewing the datasheet of the motor driver we see the following block diagram, and the blocks H-SWA and H-SWB.

Figure Five – Blocks for H-SWA and H-SWB

These H-SW blocks correspond with the MOSFET current mirror images shown below.

Figure Six – MOSFET current mirror images

By analyzing the diagram, it is my belief that the cause of the motor stall issue is that there is a blown MOSFET somewhere in the H-SW block that causes perhaps a short which causes the motors both to be pulled to ground while simultaneously plugged in to the 3DoT board. The voltage parameters are nominal leading up to the motor driver.

Pete-Bot Integration and Testing

Written by Zachary de Bruyn

Setup

Upon receiving the 3DoT v5.03, the planned peripheral devices were installed and initiated onto the board. The below table displays the ports associated with the 3DoT board, and which pins were connected to the respective peripheral device. Example, the VCC and GND of JP2 are associated with one of the GM6 Motors.

Table One – Ports Associated with 3DoT

As indicated in the below block diagram, there have been a couple peripheral changes in the Pete Bot Chassis from the Preliminary Design Review until now. Reasons for these changes occurred due to several factors. One factor is to consider I2C implementation when designing your system. For our initial design, we were going to utilize a TCS34725 color sensor, however, conflicts arose due to the fact that that sensor utilized I2C address 0x29, which is a common address to use for peripheral devices such as this. Therefore, in order to utilize more than one sensor on the 3DoT, and shield would be needed that would utilize an I2C multiplexer. At the time of the PDR, no viable solution had been discovered which would allow the Pete Bot to implement the ability to sense objects. Many studies were performed, and a VL6180 proximity sensor was chosen to be the best option to be implemented for the 3DoT. The VL6180 operated at a different I2C address and could be easily implemented with the 3DoT peripheral headers, J5 and J6.

Figure One – Final Block Diagram

Measurements

Before physically associating the peripheral devices for their respective ports on the 3DoT board, measurements were performed to check the operating capabilities of the 3DoT board to assure proper functioning. The table below depicts measured values associated with each port, and the required values required to operate the devices.

  1. VL6180 Proximity Sensor
    1. The required voltage to operate the sensor is rated at 2.8 to 5.0 V. Measuring the 3V3.
    2. The measured voltage is 3.28 V
    3. The operating current of the sensor is dependent on the state of the sensor.
      1. Ranging 1.7 mA
      2. Standby is less than 1 uA
    4. The measured current in standby is .10 mA
  2. IR Sensor
    1. The nominal voltage of the sensor is 3.3 V
    2. The measured voltage of the IR shield is 3.28 V.
    3. The measured current draw of the IR shield is 0.1 mA.
  3. GM6 Motors

During testing of the GM6 Motors implemented on the 3DoT board, it was discovered that no readable current or voltages were identified. Various script uploads were performed by different computers, however, it appeared to be a physical limitation with the board.

  1. Measured voltage at a PWM of 255
    1. Motor A
    2. Motor B
  2. Measured current at a PWM of 255
    1. Motor A
    2. Motor B

***** SEE BELOW FOR TROUBLESHOOTING *******

Test/Operational Functioning

The 3DoT board and the above listed peripheral devices were associated with their respective ports in order to perform a baseline operational test on the devices to ensure the devices were operating accordingly. This was performed by writing test script in Arduino and displaying their results in the serial monitor. The following code was used to test the functionality of the devices.

Figure Two – Code for testing functionality of devices

VL6180 Proximity Sensor

The proximity sensor used for the 3DoT board was tested utilizing the following Arduino script.

#define __PROG_TYPES_COMPAT__
#include <avr/pgmspace.h>
#include <Robot3DoTBoard.h>     // instantiated as Robot3DoT at end of class header
#include <Configure.h>
#include <EEPROM.h>
#include <Wire.h>               // I2C support
#include "SparkFun_VL6180X.h"
#include "maze.h"
#include "motor.h"

Robot3DoTBoard Robot3DoT;       // instantiated as Robot3DoT at end of class header

/*Declarations for Proximity Sensor*/
#define VL6180X_ADDRESS 0x29
VL6180xIdentification identification;
VL6180x sensor(VL6180X_ADDRESS);

/* PUT THIS CODE IN THE SETUP SECTION OF THE ARDUINO CODE */

  /* Proximity Setup*/
  Wire.begin(); //Start I2C library
  delay(100); // delay .1s
  sensor.getIdentification(&identification); // Retrieve manufacture info from device memory
//  printIdentification(&identification); // Helper function to print all the Module information

  if(sensor.VL6180xInit() != 0)
  {
    Serial.println("FAILED TO INITALIZE"); //Initialize device and check for errors
  };

  sensor.VL6180xDefautSettings(); //Load default settings to get started.
  delay(1000); // delay 1s

/* PUT THIS CODE IN THE LOOP SECTION OF THE ARDUINO CODE AFTER Robot3Dot.loop() */

/* TEST CODE FOR PROXIMITY SENSOR*/
  //Get Distance and report in cm
  Serial.print("Distance measured (mm) = ");
  Serial.println( sensor.getDistance() );
  int distance = sensor.getDistance();
  delay(500);

The above code was able to utilize the I2C functionality of the 3DoT board, and was able to accurately measure integer distances in units of millimeters. At the end of the above code, an int variable declaration was made which saved the measured distance into a new variable which would be passed to another subroutine in our 3DoT file which would be used to trigger an interrupt and let the robot know of an obstruction in it’s path. A pseudo code for this obstruction was provided by a member of the Goliath project (John Ocampo) HERE.

IR Sensor

The below code was tested and implemented into the 3DoT board to measure the IR of the material/surface under the four implemented QRE1113GR sensors.

/*Declarations for IR Sensor*/
int farRight_IR;
int innerRight_IR;
int innerLeft_IR;
int farLeft_IR;

/* PUT THIS SECTION IN SETUP()*/

/* IR Setup */
  pinMode(A3,INPUT);      //Top view, pins up, far right IR sensor
  pinMode(SDA,INPUT);     //Top view, pins up, second right IR sensor
  pinMode(SCL,INPUT);      //Top view, pins up, second left IR sensor
  pinMode(A2,INPUT);      //Top view, pins up, far left IR sensor

/* PUT THIS SECTION IN LOOP() AFTER 3DoTRobot.loop()*/

/* TEST CODE FOR FOR IR SENSOR */ 
 farRight_IR = analogRead(A3);
 innerRight_IR = analogRead(SDA);
 innerLeft_IR = analogRead(SCL);
 farLeft_IR = analogRead(A2);

 Serial.print("farRight_IR =  ");
 Serial.print(farRight_IR);
 Serial.print("  ");
 Serial.print("innerRight_IR = ");
 Serial.print(innerRight_IR);
 Serial.print("  ");
 Serial.print("innerLeft_IR = ");
 Serial.print(innerLeft_IR);
 Serial.print("  ");
 Serial.print("farLeft_IR = ");
 Serial.println(farLeft_IR);

As demonstrated by the test code above, the 3DoT collected the analog values and I2C values of the QRE1113 sensors and were used to display upon the serial monitor. The IR sensor can detect white or black light, and it is the IR (heat) retention in these surfaces which the sensor reads.

The 3.3 V Pro Micro back-up I&T

Due to the operational conditions of the 3DoT board, a backup system needed to be created that would simulate the software that could complete the mission requirements of the Pete Bot.

Figure Three – Pinout Diagram of Pro Micro

Using the above pinout diagram provided through the SparkFun website, the new pinout diagram is shown in the table below.

Table Two – Pinout Diagram Associated with Drivers, Sensors, and Encoder

The pins not shown are the VCC and GND implementations which are assumed to be known by the reader. As we can see from the above diagram there is no conflict of I2C interfaces with this implementation. SCL and SDA are exclusively used through the proximity sensor. The encoders: Right Motor Encoder (RME) and Left Motor Encoder (LME) are also shown above. Not shown above is the pinout diagram associated with the dual motor driver, which is equivalent to that of the pinout associated with the v5.03 3DoT schematic.

These pins are associated with the ports and registers of the 32U4 through the following code provided by Matt Shellhammer from EE 444.

#define __PROG_TYPES_COMPAT__
#include <avr/pgmspace.h>
#include "maze.h"

// Define motor driver pins                      I USED THE FOLLOWING PINS INSTEAD
#define AIN1 7  // Pro-micro pin 2  (PD1)               PE6 pin 7
#define BIN1 9  // Pro-micro pin 7  (PE6)               PB5 pin 9
#define AIN2 4  // Pro-micro pin 4  (PD4)               PD4 p4
#define BIN2 5  // Pro-micro pin 8  (PB4)               PC6 p5
#define PWMA 6  // Pro-micro pin 5  (PC6)               PD7 p6
#define PWMB 10  // Pro-micro pin 6  (PD7)              PB6 p10
#define STBY 8  // Pro-micro pin 9  (PB5)               PB4 p8

// Define encoder pins
#define LME 14  // Pro-micro pin 10 (PB6)               PB3 p14
#define RME 16  // Pro-micro pin 16 (PB2)              Good as is

void setup() {
  Serial.begin(9600);
  // DDRx & PORTx for IR sensors, 4th PORT ADDED FOR EXTRA IR SENSOR
  DDRF &= ~((1<<PF7)|(1<<PF6)|(1<<PF5)|(1<<PF4));
  PORTF &= ~((1<<PF7)|(1<<PF6)|(1<<PF5)|(1<<PF4));

  // DDRx & PORTx for motor drivers
  DDRD |= (1<<PD4)|(1<<PD7);
  DDRE |= (1<<PE6);
  DDRB |= (1<<PB4)|(1<<PB5)|(1<<PB6);
  DDRC |= (1<<PC6);
  // DDRx & PORTx for Encoders
  DDRB &= ~(1<<PB2);
  PORTB |= (1<<PB2);
  DDRB &= ~(1<<PB3);
  PORTB |= (1<<PB3);
  delay(5000);
}

Test/Operational Functioning

VL6180X

The script to run the proximity sensor is similar to that of the 3DoT program used above. The declarations and functions were placed in a similar manner as well. It is important to remember when using this sensor to also implement the header file so that the software can recognize the specialized functions associated with the sensor. Using the I2C protocol, the sensor is again able to accurately read objects in units of millimeters.

IR Sensor

The utilization of the IR sensor is executed in a different way compared to that of the previous script for the 3DoT board. In the case of this execution of script for the IR sensor the associated ports corresponding to that of the Pro Micro are declared and bit shifted to their respective bit number. Below is a excerpt which highlights the declaration of the registers within the 32U4.

void setup() {
  Serial.begin(9600);
  // DDRx & PORTx for IR sensors, 4th PORT ADDED FOR EXTRA IR SENSOR
  DDRF &= ~((1<<PF7)|(1<<PF6)|(1<<PF5)|(1<<PF4));
  PORTF &= ~((1<<PF7)|(1<<PF6)|(1<<PF5)|(1<<PF4));

As we can see from the above, the IR sensor is associated with the F register within the 32U4, and the values are bit shifted so that the DDRF register will have the following bit sequence: 1 1 1 1 0 0 0 0. The same sequence corresponds with the F Port. A subroutine was created which allowed the program to interpret the information being relayed to the sensors.

     sensors_t readSensors(sensors_t sensors) {
        // readSensors: 0 to 0.4 is white; 0.6 to 1 is black
        // 0.4 to 0.6 determined by previous values (coming from black/white)
        int n = 10;     // number of ADC samples
        float x_i;      // ADC input for sample i
        float A_1;      // current i running average
        float A_0;      // previous i-1 running average

       // read outter left IR sensor           OLD LEFT
       A_0 = 0.0;
       for (int16_t i = 1; i <= n; i++) {
          x_i = analogRead(A0)/1024.0;
          A_1 = A_0 + (x_i - A_0)/i;
          A_0 = A_1;
       }
       sensors.IR0 = A_0;
     Serial.println(sensors.IR0);
       delay(1000);

       // read inner left IR sensor          OLD MIDDLE
       A_0 = 0.0;
       for (int16_t i = 1; i <= n; i++) {
          x_i = analogRead(A1)*(-1.0/1024.0);
          A_1 = A_0 + (x_i - A_0)/i;
          A_0 = A_1;
       }
       sensors.IR1 = A_0;
       Serial.println(sensors.IR1);
       delay(1000);

       // read inner right IR sensor       OLD RIGHT
       A_0 = 0.0;
       for (int16_t i = 1; i <= n; i++) {
          x_i = analogRead(A2)*(-1.0/1024.0);
          A_1 = A_0 + (x_i - A_0)/i;
          A_0 = A_1;
       }
       sensors.IR2 = A_0;
       Serial.println(sensors.IR2);
       delay(1000);

       // read outer right IR sensor
       A_0 = 0.0;
       for (int16_t i = 1; i <= n; i++) {
          x_i = analogRead(A3)/1024.0;
          A_1 = A_0 + (x_i - A_0)/i;
          A_0 = A_1;
       }
       sensors.IR3 = A_0;
       Serial.println(sensors.IR3);
       delay(1000);
       return sensors;
}

The subroutine takes the calculated values from the IR sensor and returns the value to a PID calculator, and then an “If” statement is used to interpret the room that the robot is to be in. The values picked up for the outer IR sensors are usually above 0.98 for black surfaces and below 0.89 for white surfaces. The inner sensors result in a similar response albeit being negative. The IR sensor was tested laying flat on the ground, with the QRE1113 sensors facing down.

GM6 Motors

The motor drivers for the board were associated in a similar way. The driver pins connected to the Pro micro were saved into the various registers of the Pro micro and are shown in the below setup for the drivers.

// DDRx & PORTx for motor drivers
  DDRD |= (1<<PD4)|(1<<PD7);
  DDRE |= (1<<PE6);
  DDRB |= (1<<PB4)|(1<<PB5)|(1<<PB6);
  DDRC |= (1<<PC6);
  // DDRx & PORTx for Encoders
  DDRB &= ~(1<<PB2);
  PORTB |= (1<<PB2);
  DDRB &= ~(1<<PB3);
  PORTB |= (1<<PB3);

As we can see from the above set up the respected ports are bit shifted to specific bit numbers within the  various registers and can be calculated in a binary form similar to how they were calculated in the example for IR sensors. When the script is executed however with the given program, the motors do not respond through the motor driver breakout board.

These values for the motors, and sensors have instances created for them to where when the script is executed.

void loop() {
  Wire.begin(); //Start I2C library
  delay(100); // delay .1s

  static uint8_t type;
  static myRobot_t robot_inst;            // create an instance of myRobot_t called robot_inst
//  static proxSensor_t proxSensor_inst;    // create an instance of proxSensor_t called proxSensor_inst
  static sensors_t sensors_inst;          // create an instance of sensors_t called sensors_inst
  static motors_t motors_inst;            // create an instance of motors_t called motors_inst
  static PID_t PID_inst;                  // create an instance of PID_t called PID_inst

//  proxSensor_inst = readProx(proxSensor_inst);
 int distance = proxSensor.getDistance();
 Serial.println(distance);
 detection(distance);
 /* IF distance is XXXXX
     DO THIS
    Else
     DO THAT
 */

  sensors_inst = readSensors(sensors_inst);             // Read sensor values
  PID_inst = PIDcalc(sensors_inst, PID_inst);           // Calculate PID value
  motors_inst = errorCorrection(PID_inst, motors_inst); // Correct for error with motors
  forward(motors_inst);                                 // send new speed to motors to move forward
  Serial.println("OUT2 Forward");
  // Check if in forest
  inForest(robot_inst);

  // Check if at an intersection
  if ((sensors_inst.IR0 >= 0.89)&&(sensors_inst.IR3 >= 0.89)) // was a negative in front of sensors_inst.IR3, not sure why
  {
    robot_inst = enterRoom(robot_inst);
    Serial.println("After enterRoom");
    type = roomType(robot_inst);
    Serial.println("After roomtype");
    robot_inst = whichWay(type, robot_inst);
    Serial.println("After WhichWay");
  }
  Serial.println("IF statement ended");
}

boolean detection(int distance)
{
  double square_size=66;//mm
  double num_square=ceil(distance/square_size); //length of a square is about 66mm

//Serial.print("Squares Away ");
//Serial.println(num_square);
//delay(100);

  if (num_square<=1)
    {
      boolean inrange= true;
      return(inrange);
    }
  else
    {
      boolean inrange= false;
      return(inrange);
    }
}

Adapt C++ 3DoT Mission Code to SAMB11

Written by Zachary de Bruyn

Purpose

The purpose of this post is to instruct how to implement the 3DoT code for the SAMB11 MCU. Unfortunately, the SAMB11 is not supported by such a large community like that of the ATMega32U4. There is also not built in functions like there are for Arduino which simplify matters for those interested in the SAMB11.

Set-Up

The first step in utilizing the SAMB11 to be implemented for use is to first download Atmel Studios, which can be found HERE. The good thing about Atmel Studios is that there is a plethora of MCU’s one can program through the software, including the SAMB11.

In order to prep Atmel Studios to use the SAMB11 the following steps need to be performed:

First, click on the “New Project” link at the left hand side of the program. A window will pop-up prompting you to choose one of the various options, either a C or C++ executable program. Once you have chosen which to use, be sure to name the file. After which a folder is created in the “Location” directory where you can find your project. It is suggested if you are wanting to use a majority of Arduino syntax, C++ is the best option, considering C does not support some functionality that Arduino does, such as Classes.

Figure One – Opening a New Project

Once you have saved the executable project a “Device Selection” window will appear. This is where you can select a wide variety of microprocessors, including the SAMB11.

Figure Two – Selecting a Device

As we can see from the figure, the device we have opted to use was the ATSAMB11G18A. Once the correct MCU has been selected, Atmel brings you to the editor, and the “main.cpp” file is automatically generated. Within this main.cpp program, you can see a header file “sam.h” is automatically generated.

Adding .h and .cpp files

Once you have gotten to the main editor window, you can begin creating header files and other subordinate files to the main.cpp(). Note, that it is important that when you are creating new files, you do so under the “Project Name” file. By not doing this you will have many issues when it comes to compiling and debugging your code.

If you do not see the Solution Explorer window click CTRL + ALT + L.

Figure Three – Adding .h and .cpp files

To add other files to your project file, right click on your project name, go to Add and select New Item. Here you can implement another .cpp file or a .h file

Figure Four – Adding other files

Implementing the new program in Atmel Studios

Unfortunately, as I’m sure you are aware, implementing the Arduino software in any other IDE is not a simply copy and paste solution. Because there are MANY built in functions in Arduino, a lot of what we incorporate into the .cpp file will not be supported. Functions like pinMode(), analogWrite(), analogRead(), will all have to be created within the project file. The same is true for Arduino libraries, such as: EEPROM, Wire, and Robot. These libraries do not exist in Atmel Studios, therefore a lot of work will be needed to generate these replacement files.

A solution to this is create a new .h file, and implement the various Arduino function in the file so that you can utilize them for your program. Lets say for example we want to create the ability to use pinMode() in our .cpp file. To do so, in your created header file (.h file) you could start by declaring the function like you would for a function in Arduino. Below is an example:

#ifndef FileName_h
#define FileName_h

// LIST DEFINITIONS YOU MAY COMMONLY USE IN ARDUINO, EXAMPLE PI

void pinMode(uint8_t, unit8_t);

#endif

In the above code we created the function pinMode to accept two unsigned 8 bit integers, which is what would normally be implemented in the Arduino built in function. Creating all these files is a tedious process, and requires research into how Arduino implements there custom libraries to utilize various built in functions. There are various resources online that can help with implementing Arduino code to the new program you are writing however, be ready to debug because what you find online may not be applicable.

Atmel Studios Built-in Examples

If you find yourself lost and confused, don’t worry you’re not alone. Atmel Studios actually provides a lot of examples on how to implement various aspects of which ever microprocessor you choose. One suggestion is to invest in a development board which utilizes the SAMB11. This link HERE provides one suggestion to where you can begin testing. In order to access the built-in examples for which ever board you choose (The suggested one is recommended because it’s known for a fact that there are examples associated for that board.) click on the “New Example Project” shown in the first figure right under “New Project”. If you have the Xplained Pro plugged into your computer, Atmel Studio will automatically detect it. If not, click on “Kit” and select the “SAM B11 Xplained Pro”. From the drop-down menu, you can see the plethora of examples you can use. For example, there are a few example projects which discuss BLE implementation and allow you to send and receive texts from your mobile device. Note, in order to use these BLE examples, you must download the mobile app “Atmel SmartConnect”, which will allow you to discover your board to send and receive messages from your computer.

Figure Five – Example Projects