Lab Exercise 7:
The purpose of this lab time is to make our first foray into using classes. In the last project we created a structure and passed the address of the structure to all of our functions. This week we'll create a class, which automatically handles passing around the data between functions. In lab we'll continue to work with the LED bank. In the project, we'll have our chip start recognizing different actions based on the accelerometer data.
Tasks
Board
The lab and project will use the same configuration of the board as last week, though you are welcome to add more LEDs.
Code
- For the last project, you created a struct and a whole set of associated functions. We're going to repeat that process this week but use a class instead of a struct. A class combines the data and the functions together in one programming structure, which can make the design and code organization simpler.
Make a new code file and save it as lights. You can use the following template for it. Don't worry about filling in the details until you have finished making the LEDBank class.
/* Your name, project, and a date go here */ /* include files go here */ #include <Arduino.h> int main() { // initialize the board before executing any Arduino code init(); /* variable declarations here */ /* main code goes here */ return(0); }
- Create a second tab and save it as LEDBank_p7.h. Put the standard header at the top (name, project, date). Use the template below, which uses the pre-processor directives (#ifndef and #define) to ensure the contents of the include file are read only once by the compiler.
/* * Your name/date/project go here * * A useful class for handling a bank of LEDs * */ #ifndef LEDBANK_P7_H #define LEDBANK_P7_H #include <Arduino.h> /* constants defined here */ /* class definition goes here */ #endif
A .h file, or include file, is where we want to put things like constant and macro declarations, and where we define new classes. A file that wants to use them uses #include to import the .h file and it will have access to those constants and class. The remaining steps of the lab involve defining the LEDBank
- Use #define to define a constant LED_MAX_PINS and set its value to 8.
#define LED_MAX_PINS 8
- Continuing in your .h file, begin a new class called LEDBank. The syntax template to use is below.
class LEDBank { private: // most field declarations go here // some function/method declarations for internal use only public: // all function/method declarations for external use };
Your class needs the same fields we used last week: (1) an int array to hold the pin numbers, an (2) int to hold the number of pins, and (3) an int to hold the current status of the lights. You can use whatever names you like; the project description will refer to them as pins, num_pins, and status, respectively. They should all be declared after
private:
and beforepublic:
. This means code in the member functions can access them directly, but code in functions outside the class cannot. - Create a new tab and save it as LEDBank_p7.cpp. Put the standard header at the top of the file. Use #include statements to import Arduino.h, stdlib.h, and string.h, using angle-brackets around these filenames. Finally, #include the file LEDBank_p7.h, using quotes around the file name.
All of the functions in this file will be methods of the LEDBank class. That means each function name must be preceeded by
LEDBank::
to indicate it is a member function of the LEDBank class.Within each function, you have access to the special variable
this
. Thethis
pointer plays the same role thatacs
did last week. To access any of the class fields, you want to use the expressionthis->
in the same way we usedacs->
in the last project. One difference from last week is that we don't have to explicitly pass around our structure. Thethis
pointer is always available in member functions.One other difference with classes is that when you create a variable of a class type, its constructor function is called automatically. This is similar to how we called an
init()
function last week in order to set the data in the struct to known values. We will define several different constructors to permit different ways of initializing a class instance, or object.Write each of the following member functions. After writing each function in the .cpp file, put a prototype of it in the class definition, after the
public:
indicator. When you make the prototype, you need to remove the LEDBank:: from the start of the function's name.void LEDBank::set_pins( int pinNumbers[], int N)
- this function should loop N times and copy the values in the pinNumers array into the pins array of this. Then it should assign to the num_pins field the value in N.void LEDBank::init(void)
- this function should loop over the number of pins (found inthis->num_pins
, and inside the loop call pinMode to set the pin to an OUTPUT and call digitalWrite to set the pin to low. Use your pins array. After the loop, the function should set the status of the LEDBank to 0.LEDBank::LEDBank(void)
- this is the first constructor, which takes no arguments. It should set the num_pins and status fields to 0.LEDBank::LEDBank(int pinNumbers[], int N)
- this is the second constructor. It should call the set_pins member function, passing in pinNumbers and N, then call the init member function.int LEDBank::off( void )
- this function should loop over all of the pins and use digitalWrite to set them all to LOW. It should then set the status field to indicate all of the LEDs are off and return the current status.int LEDBank::off( int time )
- this function should call the off member function above, delay by the specified amount time, and return the current status.int LEDBank::on( void )
- this function should loop over all of the pins and use digitalWrite to set them all to HIGH. It should then set the status field to indicate all of the LEDs are on and return the current status. You will want to use exactly the right number of bits, so you can't just set it to a constant value, as the number of LEDs can be variable. One way to do this is to set the status field to 0 before the loop, then in the body of the loop do a bitwise OR operation with a 1 in the proper position. You can do that with the following code, which bit shifts a 1 into the right location and then does an OR operation with the status field, putting the result back in the status field.this->status |= 1 << i;
int LEDBank::on( int time )
- this function should call the on member function above, delay by the specified amount time, and return the current status.int LEDBank::setAll( int state )
- this function should loop over all of the pins and use digitalWrite to set their value to HIGH or LOW depending on the corresponding bit in the state parameter. It should then set the status field to state and return the current status.int LEDBank::setAll( int state, int time )
- this function should call the setAll member function above, passing in the state, delay by the specified amount time, and return the current status.int LEDBank::set( int which, int value )
- this function should set the LED in the position specified by the which argument to the value specified by the value argument. It should then update the status field of the LEDBank struct and return the current status. You can do that with the following statement (see if you can work out what it is doing).this->status = value ? this->status | (1 << which) : this->status & (~(1 << which));
int LEDBank::set( int which, int value, int time )
- this function should call the set member function above, passing in which and value, delay by the specified amount time, and return the current status.int LEDBank::cur_status( void )
- this function should return the current status value.int LEDBank::num_active_pins( void )
- this function should return the number of active pins.int LEDBank::shift_left( void )
- this function should call the setAll function, passing in the current status shifted left by one position. To shift a variablesomename
, use the expressionsomename << 1
.int LEDBank::shift_left( int time )
- this function should call the shift left function above, delay for by the specified amount of time, and return the current status.int LEDBank::shift_right( void )
- this function should call the setAll function, passing in the current status shifted right by one position. To shift a variablesomename
to the right, use the expressionsomename >> 1
.int LEDBank::shift_right( int time )
- this function should call the shift right function above, delay for by the specified amount of time, and return the current status.int LEDBank::invert( void )
- this function should call the setAll function, passing in the bit inverse of the current status and then return the current status. To get the bit inverse of a variablesomename
, use the expression~somename
.int LEDBank::invert( int time )
- this function should call the invert function above, delay for by the specified amount of time, and return the current status.
This should complete your library. Try compiling it and fix any errors that occur.
- Returning to your lights file, in the function variable declarations section, declare a
const int NPins
and set its value to 5. Then declare an int array of size NPins and initialize it to the pin numbers of the five pins you used for your LEDs. Then declare a variable led of type LEDBank, but do so using the constructor that requires the array of pin numbers and the number of pins.LEDBank led( pinNumbers, NPins );
Use a sequence of calls to on, off, set, and setAll, invert, shift_left, and shift_right to manipulate the LEDs and test each function. Try using both versions of these functions to make sure they are working as expected. A fun thing to do is to turn all the lights on and then use a for loop to shift left (or right) until they all go dark.
Take a short video of your LEDs working and submit it as part of the project report/handin.
When you are done with the lab exercises, you may start on the rest of the project.