E80 Project Mini-Lab: The Navigation Jump Start
Team size: 4
Prelab help sheet: None, but you’re pros at this point.
Writing: None.
1 Introduction
This week we’re doing work in lab that is designed to accelerate you into the project. We are going to structure the work like a lab from the first half of the class – there are videos, a quiz and a submission sheet – but the goal is to provide you the tools you need to start right away on your project. Specifically, we want you to leave this Jump Start with an autonomously controlled robot and a confident attitude towards understanding and modifying the robot code.
Though this week’s activity looks like a lab. there are several crucial differences from a standard lab.
- There will be no resubmissions allowed for lab 7 material. You’re done with this activity at the end of your lab session this week.
- Consequently, grading standards are going to be a little gentler for lab 7 materials.
- You don’t have to do all parts of this lab. See more discussion below.
In this lab, every team is going to do some activities to learn about your robot software, then test out using IMU data to dead reckon a course, which is an echo of the work that you did in lab 1. After that, your team will select one of the following activities to pursue during this lab, each of which are designed to help prepare you for different types of projects:
- Diving Activity: In this activity you will build a pressure sensor and a P control loop for robot depth. You will test your loop in the tank. Pick this if you plan to have your robot dive or use a depth-controlled winch. It is possible that there will be a queue for the tank if many students in your section are doing the diving activity. Feel free to start on your robot build while you are waiting.
- Surface Navigation Activity: In this activity you will use GPS and your magnetometer to control the bearing of your robot, and you will test that navigation system by walking around with your robot outside of Parsons. Pick this if you plan to do surface navigation, if you intend to have your robot keep station (stay still), or if you plan to do a hybrid diving/surface navigation scheme.
After successful completion of this lab, you will be able to:
2 Software and IMU (All Teams Do this)
This section consists of some exercises to help you understand the E80 robot code followed by an exercise to explore the noise performance of the IMU.
2.1 Set up a Team Github Repository
Though E80 doesn’t require you to do a lot of software modification, it’s a good idea to make sure that your team is all able to work from the same code base. Modern best practices for doing so involve using Git for version control in conjunction with a web-based remote repository like Github. In this section (which can be completed entirely during prelab), your team will set up a shared repository so you can all work on the same code.
- All team members should make a Github account.
- Optional: Consider applying for Student access, which unlocks advanced Github features while you’re a student.
- One team member should
- Run a fetch and pull operation on the current E80 repository using Github Desktop (just to make sure you have the most up-to-date code).
- Make a new repository,
- Invite your team to the new repository.
- Clone your new repository using Github Desktop web integration or typical clone commands.
- Populate your new repository with all of your E80 code by copying and pasting it from your E80 repository folder to your new repository folder.
- Commit and push your copied and pasted code to the new repository.
- (Git savvy students will note that there are slicker ways to track both the E80 repository and your team’s work, for instance by forking the E80 repository. We’re doing this because it’s easy and we expect to push few software updates.)
- All other team members should clone the new repository (see links above).
- All team members should tell Arduino to look for sketches in the new repository by editing
Settings -> Sketchbook Location
.
We’ll be linking to specific files on the E80 Github repository below to talk about them, but remember that you downloaded all the Arduino and library files for this class in both your E80 repository and now in your team’s repository. So don’t download them again from the links, just edit the files in place on your machine.
2.2 Familiarize Yourself With the E80 Software
Read this section and make the requested modification to your code. We recommend reading this section with your E80 software open in a code editor (VScode, Sublime, Notepad++, etc., NOT MS Word) so that you can look for features of the code as we discuss them. We also think every member of your team should do this section even though you’ll only turn in one submission sheet.
The E80 robot code appears in three files: E80_Lab_07_dive.ino
, E80_Lab_07_surface.ino
and Default_Robot.ino
. We intend for you to use Default_Robot.ino
on your final project, but you should modify E80_Lab_07_dive.ino
and E80_Lab_07_surface.ino
this week. (The dive and surface programs are simplified versions of Default_Robot.ino
, so they’re easier to learn from.) Look at E80_Lab_07_dive.ino
in this section.
The E80 robot code all has the .ino
suffix, which means it is encoded in Arduino files (Language tutorials and reference), and as such these files have two extremely important functions: setup()
and loop()
. The setup()
function is run once when your robot powers on, and then the robot runs the loop()
function over and over until power is removed.
The loop()
function contains several if
statements that dispatch to functions. We call these functions “services”, and each represents one thing that the robot is doing. The functions used in many services are defined in libraries, which are described below.
The E80 robot code uses #include
statements at the top to include functionality that is written in C++ (Language tutorials and reference), and we call each such C++ file a “library”. Libraries can be found in our libraries/main/
folder, and they usually consist of a C++ file (e.g.: SensorIMU.cpp
) and a header file that the C++ file #include
s (e.g.: SensorIMU.h
). However, some library files only provide constant values, and therefore only have header files (e.g.: TimingOffsets.h
or Pinouts.h
). Usually we invoke a library function to in at the top of the .ino
file to create a special variable called an “object”, and then we ask that object to use its functions to perform a service.
We can put these ideas together with an example: at line 150 a service called adc.updateSample()
is called, and this service samples and records the Voltage on all of the analog pins. The if
statement at line 148 guarantees that this functions is called exactly once per LOOP_PERIOD
, which is a constant you can look up (or change!) in TimingOffsets.h
. The adc
object is created asa global variable on line 40. The adc
creation code (the init()
function) and the adc.updateSample()
function are defined in the ADCSampler.cpp
library, which depends on ADCSampler.h
.
To put our new understanding to the test, we are going to edit the serial message that the robot displays each cycle.
- Run the
E80_Lab_07_dive.ino
code and look at the printed display by opening the serial monitor. - Modify
SensorIMU.cpp
so that the String returned by theprintRollPitchHeading()
function includes the X magnetometer value. - Modify
E80_Lab_07_dive.ino
so that it displays the output of theprintState()
function ofButtonSampler.cpp
on line 2 of the message instead of theprintStates()
function ofErrorFlagSampler.cpp
. - Run the
E80_Lab_07_dive.ino
code again and look at the new printed display to see your changes. Note that your changed libraries should be automatically recompiled when you recompile your.ino
file. Press the user button on the E80 motherboard to see theButtonSampler.cpp
library at work.
Finally, look at TimingOffsets.h
to figure out how often your main loop runs, which sets how quickly you sample data with the ADC, IMU, etc.
You generally should not include calls to Serial.println()
in your code, even though it’s standard practice for Arduino, because it messes with our printer
object. The printer
takes great pains to keep all the text easy to read as the robot spits out updates, and random print statements can cause it to jitter around. Use printer.printMessage()
for one time messages (see DepthControl.cpp
for an example) and printer.printValue()
to print recurring values (as seen in the loop()
function of the E80 code).
Be sure to read the README
in the libraries folder because it contains a lot of information about the features and adjustable knobs in each file. The adjustments described in the README
file are sufficient to cover most standard student projects, so you might not even need to write code.
For instance, the README contains this important deep cut for the project: It is possible to increase the logging time of your robot by increasing the FILE_BLOCK_COUNT value in Logger.h. This can help you to log data during long deployments.
Exploring the code in this section highlights some of the interaction between the header (.h
), library (.cpp
), and Arduino (.ino
) files. The header files are the interfaces, they hold information on variables that are used in other files and the functions of the library files of the same name. The library files are the implementations of the functions defined in the headers, and they use the variables held in the headers. The Arduino file sketch is the top level of the program, and it invokes library functions and header variables to implement the desired behavior of the robot.
2.3 Position Measurements via IMU
In this section we’ll use our board stack and our IMU to try to calculate position by integrating noisy acceleration data. As the lecture videos suggest, the noise is likely to make this position measurement fail. We’re hoping to see a concrete demonstration of that principle.
- Power your motherboard with a battery
- Run stock
E80_Lab_07_dive.ino
code without your modifications from last section, or runDefault_Robot.ino
if you don’t want to roll changes back. Make sure your IMU is producing sensible accelerations by turning it around in the air and, if need be, reviewing your lab 1 results. - With the data logger running, move your motherboard along a linear path in the horizontal x-y plane, keeping the orientation fixed. In other words, create your own global Cartesian coordinate frame on a table, set the position of the board stack to be (0 m,0 m,0 m), set the orientation of the board stack to be (0°, 0°, 0°). Then, move the board stack through the following positions: (0 m, 0 m, 0 m), (0.5 m, 0 m, 0 m), (0 m, 0 m, 0 m). Don’t move too fast, and note that it may help to wait 5 seconds at the (0.5 m, 0 m, 0 m) coordinate.
- Using MATLAB, load the IMU data and calculate the position of the board stack as a function of time by integrating accelerations.
- Plot the x,y coordinates of the board stack and comment on whether the plotted path resembles the straight 0.5 meter path.
- Plot the y coordinates vs. time and include appropriate measures of uncertainty for integrated values.
3 Diving Activity
In this activity, you will design a P control loop that interacts with a pressure sensor to control the robot’s depth. You will need to modify E80_Lab_07_dive.ino
and the E80 C++ libraries to do so.
3.1 Calibrate and Test a Pressure Sensor
In order to implement closed loop depth control with your AUV, it first needs to be able to sense its current depth, which we will do by interpreting a pressure sensor. You have two options to make your pressure sensor: build the pressure sensor from your project proposal on a breadboard, or use one of our pre-made pressure sensor adapter PCBs (picture, schematic, layout, KiCAD). We recommend building your own design to get a start on your project build, but feel free to use our PCB if you’re short on time. (Do note that our PCB uses a weird op-amp circuit that you’ll need to analyze on your own.)
In a deviation from previous practice, you will measure the operation of this sensor using your Teensy. By measuring your pressure directly in the Teensy, you can write code that interacts with your results in a direct way. We will take advantage of the serial data
- Attach your pressure sensor output to pin A0 on the Teensy.
- Add a line showing your current pressure measurement to your robot’s serial report. Edit
updateState()
inZStateEstimator.cpp
by uncommenting the two lines of code below the calculation of the z position. - Run
E80_Lab_07_dive.ino
. The pressure sensor Voltage should now appear in the serial monitor, and you will use that display to calibrate your pressure sensor. - Use a graduated cylinder and a meter stick with pressure tubing attached to make a calibration curve with at least seven points between depth and Teensy-reported Voltage.
- Modify
depthCal_slope
anddepthCal_intercept
inZStateEstimator.h
so your Teensy display reports the proper depth in meters.
3.2 Write a P Control Algorithm for Depth
Add a Proportional depth control system to your robot by modifying the DepthControl.cpp file
. Add the following steps:
- Assign the desired depth to a variable named
depth_des
. The desired depth can be found in thewayPoints
array at the indexcurrentWayPoint
(i.e.: atwayPoints[currentWayPoint]
). - Assign the current depth to a variable
depth
. The current dept can be found in thestate.z
variable. - Calculate the error,
depth_error
, as the difference betweendepth_des
anddepth
. Pay attention to the order you subtractdepth_des
anddepth
, because a sign error here can make your P control push away from the desired waypoint. - Set the vertical motor control effort,
uV
, to be the product of the control gain,Kp
, and the error,depth_error
. The default value of Kp is 0, so you need to change it inDepthControl.h
.Kp=80
is usually pretty good, but tune it up or down if your motor is too weak or too strong. - Bound the vertical control effort between -200 and 200. These are empirical values that work well for most situations. Depending on your motor, you may need to go beyond these limits. You can go up to a maximum of +/- 250.
You usually need to declare variables in C++, but all the variables in these instructions have already been declared in DepthControl.h
(go look for yourself), so you don’t need to declare them here.
In the library file named ZStateEstimator.cpp
, in the function called ZStateEstimator::updateState()
, you can see that the z position (depth) of the robot is calculated using the function’s input pressure_signal
. The main loop()
of the Arduino code is where z_state_estimator.updateState()
is called. By reviewing this loop()
, you can see that this pressure_signal
input is receiving the output of analogRead(PRESSURE_PIN)
. analogRead()
is an Arduino function that reads the value of a specified pin, and PRESSURE_PIN
is a variable set in Pinouts.h
, currently set to pin A0 (pin 14) of the Teensy. The pressure_signal
input provides the pressure sensor signal in Teensy units. The ZStateEstimator::updateState()
function then converts from Teensy units to Volts, then finally converts from Volts to depth [m] using the variables depthCal_slope
and depthCal_intercept
. This depth measurement is stored in state.z
.
3.3 Test the P Control Algorithm
Test your depth P control using the meter stick with tubing and the graduated cylinder.
- Attach the pressure tubing to your pressure sensor and run
E80_Lab_07_dive.ino
.
- While is running with the serial monitor open, insert the meter stick into the cylinder
- Keep an eye on the
Depth_Des:
,Depth:
, anduV:
values printed to the serial monitor.- Make sure that the
Depth
is reporting a similar value to the current depth of the meter stick to confirm that your calibration works. - Watch the control effort
uV
change as the difference betweenDepth
andDepth_Des
changes, making sure it behaves as expected for proportional control.
- Make sure that the
Improve your P control test by adding a motor to your robot.
- Get an E79 umbilical (found in the lab room) and attach it to your robot.
- Put the motor in the vertical position, and use a ziptie to secure the tube to the frame.
- Connect the tubing to your pressure sensor and the motor wires to the Motor C port on your motherboard.
- Run E80_Lab_07.ino while the serial monitor is open.
- Watch the motor and the reported control values:
- The motor should start spinning to get to the first waypoint at 0.5 meters. If the motor is spinning too fast for you to see what’s going on, you can reduce
Kp
. - Check if it’s rotating in the correct direction; if not, switch the wires on your motherboard (or change the order you subtracted the
depth
anddepth_des
to get the error inDepthControl.cpp
, or change the sign ofKp
). - Blow in the pressure tubing and confirm that the depth changes, and that the motor responds to the change in depth.
- The motor should start spinning to get to the first waypoint at 0.5 meters. If the motor is spinning too fast for you to see what’s going on, you can reduce
Finally, once everything is tested and working, deploy the robot in the tank.
- Bring your robot to the tank room, run
E80_Lab_07_dive.ino
, and put the robot into the tank. Watch out for splashes onto your computer or motherboard as you put the robot in. - Let the robot go to the two depth waypoints at 0.5 m and 1 m while keeping an eye on the values and messages printed to the serial monitor. Be sure to log your data.
- Plot the logged
uV
,depth
anddepth_des
as a function of time.
Be careful with the umbilical because it may not have enough slack for the robot to go down to 1 meter. If that’s the case, you can change the depth waypoints in the setup()
function of E80_Lab_07.ino
. If your robot’s motor is too strong or too weak, you may need to change your Kp
in DepthControl.h
.
You can instruct the robot to stay at each depth waypoint for a specified amount of time by changing the diveDelay
variable defined in the setup()
function of E80_Lab_07.ino.
. For example, setting diveDelay = 3000
will tell the robot to wait at each depth waypoint for 3 seconds before continuing.