Project 6: Electronic Tools
The purpose of this lab is to give you yet more practice in code organization. In addition, we'll continue to use structs and pointers to pass around information between functions.
The two applications for the project will be an electronic level and an electronic compass, both using the capabilities of the LSM3038 chip.
Tasks
- Create a new Arduino sketch and save it as compass. Once you have done that, copy your led_p6.h and led_p6.cpp files into the directory containing compass.ino. Close and re-open the compass file to make sure the editor opens all three files as three tabs.
- Create a new tab and save it as ACSensor_p6.h. Create a second tab and save it as ACSensor_p6.cpp. These two files will be the include file and code file for a library of functions for dealing with the accelerometer/magnetic sensor. Then Download these Adafruit packages. There should be two folders after you nzip the file. Put them both in your libraries folder. There is example code for using the sensor in the LSM303 folder, and they should show up in the example sketches.
- In your ACSensor_p6.h include file, use typedef to define a struct called ACSensor. The struct should have a field of type Adafruit_LSM303_Accel_Unified, a field of type Adafruit_LSM303_Mag_Unified, an array of type float of length 3 to hold the current accelerometer reading, an array of type float of length 2 to hold the current magnetic sensor reading, and an array of type float of length 3 to hold the gravity estimate. You can use the following template.
/* * Name and header here */ #ifndef ACSENSOR_H #define ACSENSOR_H #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_LSM303_U.h> // use typedef to define the ACSensor struct here // prototypes go here #endif
- In your ACSensor_p6.cpp file, define the following functions.
int acs_init( ACSensor *acs )
- this function should initialize the Accel and Mag sensor objects, call their begin functions, and initialize the rest of the fields to zero. To initialize the sensors, you can use the following code (using the names of the fields you created in the prior step).// initialize the Mag and Accel objects, each with a unique identifier number acs->mag = Adafruit_LSM303_Mag_Unified(12345); acs->acc = Adafruit_LSM303_Accel_Unified(54321); // startup mag if( !acs->mag.begin() ) return(-1); // startup acc if( !acs->acc.begin() ) return(-2);
Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
void acs_calibrate( ACSensor *acs, int N )
- this function should collect N sensor readings from the accelerometer and calculate their average, then store the result in the gravity field of the ACSensor struct. First create three local float variables (initialized to 0) to compute the average of the x, y, and z accelerometer values. Then declare a local variable event of type sensors_event_t. Then loop N times. Each time through the loop, useacs->acc.getEvent( &event )
to put the latest sensor reading into the event variable. Then use the expressionevent.acceleration.x
to get the x component of the acceleration, and a similar expression for the y and z components. Add these values to your local x, y, and z values. AFter the loop, divide the x, y, and z sums by N and assign the results to the gravity array in your acs struct.Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
void acs_update( ACSensor *acs )
- this function should use the getEvent methods of both the acc and mag objects (see prior step) to get the most recent data, then store the data in current data fields of the ACSensor struct.Declare a variable of type sensors_event_t Call the getEvent function of your acs acc field In three assignments, assign to your current acc reading array the event.acceleration.x (y and z) values Call the getEvent function of your acs mag field In two assignments, assign to your current mag reading array the event.magnetic.x (and y) values
Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
float acs_gravityCosAngleX( ACSensor *acs )
- this function should return the cosine of the angle between gravity and the current reading as measured perpendicular to the X-axis. The computation is represented by the equation below, where g is the gravity estimate in the acs struct and a is the current accelerometer value, also stored in the acs struct.Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
float acs_accx( ACSensor *acs )
- this function should return the current accelerometer x value stored in the acs struct.Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
float acs_accy( ACSensor *acs )
- this function should return the current accelerometer y value stored in the acs struct.Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
float acs_accz( ACSensor *acs )
- this function should return the current accelerometer z value stored in the acs struct.Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
void acs_gravity( ACSensor *acs, float grav[] )
- this function should copy the current gravity estimate stored in the acs struct into the array grav.Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
Part 2: Compass
float acs_compass( ACSensor *acs )
- this function should return a heading given the current compass readings. The expression for calculating the value is below, where mag_cur is your ACSensor field holding the current magnetic field readings. The function atan2 returns a value between -pi and pi, so the expression below returns a value from -180 to 180.heading = 180 * atan2(acs->mag_cur[1], acs->mag_cur[0]) / 3.14159267;
Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
void acs_mag( ACSensor *acs, float mag[] )
- this function copies the current magnetic sensor reading stored in acs to the array mag.Once you have completed this function, put a prototype for it into your ACSensor_p6.h file.
- In your compass file copy and fill out the template below. Run and test your compass.
/* Name and header here */ #include "led_p6.h" #include "ACSensor_p6.h" /* given a heading, return the proper value to pass to led_setAll */ int compassPattern( float heading ) { // for example, if the heading is between -2 and 2, light only the middle LED // if the heading is further out to either side, light up the appropriate LEDs } int main() { // declare a variable of type ACSensor // declare a variable of type LEDBank // declare a const int and initialize it to the number of LEDs // declare an in array and initialize it to the LED pin numbers init(); Serial.begin(9600); // initialize the ACSensor variable by calling acs_init // initialize the LEDBank variable by calling led_init with the necessary arguments // start the main loop for(;;) { // call acs_update // use acs_compass to get the current heading // use compassPattern and led_setAll to set the LED values // delay for 100ms } }
- Once everything is working, make a new directory in your Arduino libraries folder called cs153_p6. Move your led_p6.h led_p6.cpp, ACSensor_p6.h, and ACSensor_p6.cpp into the cs153_p6 folder. Then recompile your code to make sure it all still works. This is how you can create your own library that is accessible from any sketch.
- Take a video of your compass working and include in in your wiki or handin folder.
- Using the compass template as a guide, create an electronic level. Use the acs_gravityCosAngleX instead of the acs_compass function to get the cosine of the angle of the board relative to gravity, and replace the compassPattern function with a levelPattern function. In the levelPattern function, the center LED should be lit if the cosine value is greater than .9998. You can use the sign of the current y value of the accelerometer (accessible using acs_accy()) to determine the orientation of the board.
- Take a video of your level working and include in in your wiki or handin folder.
Setup
Part 1: Accelerometer/Magnetic Sensor Library
Part 3: Level
Follow-up Questions
- What is a struct?
- Why might we want to pass the address of a struct to a function instead of the struct?
- Why is it a good idea to encapsulate code and hide implementation details?
- Why might we want to create a library of code?
Extensions
- Make your level or compass fancier.
- Use more LEDs. If you do this, comment on how difficult it was to make the change.
- Use sound and/or lights. If you do this, think about how to organize your code.
- Move to a level with 2 dimensions.
- Add functions like shift left, shift right, or rotate to your LEDBank library. Then get creative with making patterns or fancier ways to read the compass or level.
- Borrow a battery pack and get it working independent of your computer.
- Do some analysis of the precision of the sensor. How finely can you detect orientations, using either the accelerometer or the compass?
Report
Each week you will write a brief report about your project. In general, your intended audience for your write-up is your peers not in the class. From week to week you can assume your audience has read your prior reports. Your wiki report should explain to friends what you accomplished in this project and to give them a sense of how you did it.
Your project report should contain the following elements.
- Abstract: a brief summary (200 words or less) of
the task, in your own words. give the reader context and
identify the key purpose(s) of the project. You can assume the
reader has read your prior assignments.
Writing an effective abstract is an important skill. Consider the following questions while writing it.
- Does it describe the CS concepts of the project (e.g. writing loops and conditionals)?
- Does it describe the specific project application (e.g. creating applications with the LCD)?
- Does it describe your the solution or how it was developed (e.g. what code did you write/circuits did you build)?
- Does it describe the results or outputs (e.g. did your code and circuit work as expected)?
- Is it concise?
- Are all of the terms well-defined?
- Does it read logically and in the proper order?
- A description of your solution to the tasks. This should be a description of the form and functionality of your final code and the design of your breadboard circuits. Try to describe your algorithm or code without including actual code in your report. Using 1-2 lines as an example is acceptable. Using simple diagrams or pictures of your board may be helpful when describing your circuits. Note any unique computational solutions or hardware circuits you developed.
- A description of any extensions you undertook, including images, videos, or diagrams demonstrating those extensions. If you added any functions, or other design components, note their structure and the algorithms you used.
- The answers to any follow-up questions.
- A brief description (1-3 sentences) of what you learned.
- A list of people you worked with, including TAs, and professors. Include in that list anyone whose code you may have seen, such as those of friends who have taken the course in a previous semester.
- Don't forget to label your writeup so that it is easy for others to find. For this lab, use cs153f18project6
Handin
Mount the Courses volume. Navigate to the Private sub-directory. Create a new folder called project01. (It's best to avoid spces in the directory name.) Each week, the following items should be submitted here.
- Code that should graded and any required supporting materials. This week, submit your code from all of the project tasks. Code from lab exercises will generally not be graded unless it is used as part of the project tasks.
- Videos of your work. You can also submit videos as part of your report on the wiki, but please put a copy of the video here.
Your report should be submitted as a wiki page with the appropriate label, as noted above.