None
Arduino is a "platform" designed for rapid development of mechatronic systems, or systems that involve hardware, software, and/or electronics. Mechatronics is also commonly called "physical computing" in the current conversational lexicon.
In this course, we will use the Arduino Mega board for laboratory exercises, and the Pololu Zumo 32u4 for projects. Both of these physical devices can be programmed to interact with their environments using the Arduino programming environment, which is compatible with Mac, Windows, and Linux.
The Arduino website and the Zumo Resource Guide are very well put together, and contain much more information than this document does. This notebook is only intended as a "quick and dirty" guide to get you started, and external resources are linked where appropriate.
But just to make sure everyone is on the same page, here's how "programming an Arduino" works:
Decide what you want the physical device (Arduino or Zumo) to do. Usually, this will be something like: "Measure X, then output Y." Robots and control systems interact with their environments, and the "pins" on an Arduino or Zumo's processor are how we both read information from the outside world and give information back to the outside world.
Using your computer, write some code called a "sketch" that will tell the physical device (Arduino or Zumo) what to do. Part of this code, contained in a special function called "void loop()," runs endlessly, so the microprocessor can react to a moment's notice to changes in its environment (in our case, inputs you or a system give it).
Compile that human-readable code into a format the microprocessor can understand, and upload the instructions onto the physical processor (Arduino or Zumo). After you do this, your computer is no longer needed for the processor to operate!! With that said, we often do use serial communication through a USB port to help us see what the Arduino/Zumo is doing at any moment.
Remember: the Arduino will only do exactly what you tell it to do... nothing more, nothing less!
Many people say that they know how to program "in Arduino," but Arduino isn't really its own language. It is a subset of the C++ programming language, which is very powerful and ubiquitous in robotics, automation, and controls because it is fast and flexible. So when you're learning to program in Arduino, you're developing skills that will transfer to more difficult programming languages.
The picture above shows what the Arduino environment looks like. You'll use this to program your Zumo robot as well as the Arduinos in the lab space. For an introduction to the Arduino Integrated Development Environment (IDE), click here. The basics of the workflow are as follows:
An Arduino program is a bit different than a MATLAB script you'd write to plot data, simulate a dynamic system, etc. because an Arduino program is primarily designed to interact with the outside world. As such, it must do certain things repeatedly (like measure voltages, send signals to a motor amplifier, etc.). This section gives you some bare-bones basics, but this guide from Arduino is much more complete.
There are three basic parts to most common Arduino "sketches."
At the top of the code, a programmer delcares variables, and may give them initial values. For instance, if I know that I'll need to use a variable called "output voltage" in the void loop() function, and I know I'll need that variable to be global in scope, I'll declare it at the top of the code:
float output_voltage;//just a declaration
This allocates memory for the variable, and tells Arduino that it will be a floating-point number, but does not put a number in that memory yet-- I'll have to do that later. If instead I want to give my variable some default value, I can do that as well:
float output_voltage = 0.0;//a declaration and initialization
The top of the code is also where you will tell the compiler if you wish to use libraries in your code. Also note that every line in Arduino should be terminated with a semicolon! Failure to do this will result in a compiler error.
Any code that needs to run only one time will go in the void setup() loop. This includes beginning a serial connection with your computer using:
Serial.begin(115200);
or declaring certain arduino digital pins as outputs or inputs:
pinMode(13,OUTPUT);
pinMode(2,INPUT);
The void loop() function is where the "meat" of your code goes. If you were to describe what your program does to someone, you'd be describing what you put in the void loop() function. This is where, over and over, the processor will read inputs from its environment (digital signals and analog voltages), make calculations, and write analog and/or digital values back to its environment.
All of the variables an Arduino processor works with are stored in memory as binary information. These days, it is fairly common knowledge that "stored as binary information" means "stored as ones and zeros," or stored as a series of low and high voltages. However, there are multiple ways to represent a number in binary. Which way you should choose, or which "data type" you should assign for your variable, depends on, among other things:
This notebook doesn't contain a technical description of how various data types work, but a good description is easy to find. In fact, Arduino's website has specific help pages for each data type. In this course, we will primarily use:
It is also possible that in Project 2 of this course, you will want to use an array of values. Arrays in arduino are a lot like arrays in MATLAB, with some important differences:
my_variable[0]
and NOT my_variable[1]
.[]
. In MATLAB, arrays are defined in square brackets and indexed in parentheses.More detail about arrays in Arduino is available here.
If you will need to use a variable over and over, and need its value to be stored between successive executions of void loop(), it needs to be global in scope. Any variable declared above void setup() and void loop() is global. An example is shown below for a program that increments a variable every time void loop() runs, and prints the value to the serial monitor:
int myCount = 0;
void setup(){
Serial.begin(115200);
}
void loop(){
myCount = myCount+1;
Serial.print(myCount);
Serial.println();//prints a new line for readability
}
Notice that each time loop() runs, it already needs to "know" what the value of myCount is.
If, on the other hand, you do not need a variable to be "persistent" between executions of void loop() (or any other function, for that matter), or if you just need a "counter" variable for a "for" loop that can be "thrown away" after you're done with it, you can declare variables inside of a function. An example of this is shown below for a program that reads an analog voltage value from pin A0 and prints it to the monitor:
int inPin = 0;//which analog pin will we read from?
//this is global just so it isn't repetitively allocated each time.
void setup(){
Serial.begin(115200);
}
void loop(){
int myPotVal = analogRead(inPin);//local because we read a new value each time.
//note that it doesn't HAVE to be local... could have been global without much trouble.
Serial.print(myPotVal);
Serial.println();
}
Pitfall warning! Overwriting Global Variables with Local Ones! One common error that new programmers make is declaring a variable at the top of a sketch (global), but then ALSO delcaring it inside void loop() by accident. When you do this, your program will default to the use of the local variable, not the global one. Here's an example of this mistake, in a simple program that is supposed to print the number "6" but instead prints the number "4:"
int myNumber = 5;
void setup(){
Serial.begin(115200);
}
void loop(){
int myNumber = 3;
int result = myNumber+1;
Serial.print(result);
Serial.println();
Remember that any local variable is only "visible" to the program from within the function or flow control statement (for/if/while) in which it was declared!
There are some other variable scoping issues that may come up during the course, including the #DEFINE concept but these are the big ones.
Some of the programs you'll write in this course can get pretty complex. So, it might be a good idea to learn to write your own functions, which will clean up your void loop() considerably, allowing you to see at a glance what the program is doing. Arduino's official reference guide has a great resource for learning to write your own functions here.
While the link above gives a great overview of functions, one thing that I'd like to mention is that for certain special tasks, it may be a good idea to write functions that don't return any value at all (and thus should be called void myFunctionName() since "void" is the anti-datatype), and just operate on global variables. This isn't a "good programming practice," but for what we'll be doing in this class, it can be very useful for cleaning up your code. Let's look at two examples of a program that has to print several values to the serial monitor at the end. The first does not use a function:
int baseNum = 10;
int loopCount = 0;
int num1;
int num2;
int num3;
void setup(){
Serial.begin(115200);
}
void loop(){
//calculate my three numbers
num1 = baseNum*1+loopCount;
num2 = baseNum*2+loopCount;
num3 = baseNum*3+loopCount;
//print to the serial port for debugging
Serial.print(num1);
Serial.print("\t");//prints a tab to space out the numbers
Serial.print(num2);
Serial.print("\t");//prints a tab to space out the numbers
Serial.print(num3);
Serial.println();//prints a newline to separate individual runs through "loop."
//increment the loop counter
loopCount = loopCount+1;
}
Notice that this program has a lot of repetitive code in it, and that it's not the easiest to read. It's a little "busy" (not really, but you can imagine that some programs will get very busy).
Let's now look at a program that does the same thing, but with two user-defined functions. Each one performs a specific function, and is "called" inside void loop() to keep the main part of the program clean and easy to read.
int baseNum = 10;
int loopCount = 0;
int num1;
int num2;
int num3;
void setup(){
Serial.begin(115200);
}
void loop(){
//calculate my three numbers
calcNumbers();
//print to the serial port for debugging
printData();
//increment the loop counter
loopCount = loopCount+1;
}
void calcNumbers(){
num1 = baseNum*1+loopCount;
num2 = baseNum*2+loopCount;
num3 = baseNum*3+loopCount;
}
void printData(){
//print to the serial port for debugging
Serial.print(num1);
Serial.print("\t");//prints a tab to space out the numbers
Serial.print(num2);
Serial.print("\t");//prints a tab to space out the numbers
Serial.print(num3);
Serial.println();//prints a newline to separate individual runs through "loop."
}
Now notice that the void loop() function, where you'd start if you wanted to know what the program actually does, is only a couple of lines long, making it very easy to read. All of the functions operate on global variables, and none of them returns anything... but they do clean up the code. Doing this also makes it easy to debug your code, because you can debug one function at a time.
Flow control statements we will commonly use in this course are listed in the following subsections. The Arduino pages for each type of statement will give you a concise introduction.
Typecasting is basically changing the datatype of a variable. This is very useful, because many functions in the Arduino language require that you pass them a certain datatype. The documentation page that explains typecasting for Arduino is here. For example, the analogWrite() function requires that you pass the analog value you'd like to write to a pin as an integer between 0 and 255. Let's say your code produced that number as a float. You could typecast that float before sending it to the analogWrite() command, as shown:
float myVal = (float)analogRead(0)*5.0/1023.0*255.0;
analogWrite(6,(int) 1.0);
Notice that I also typecast the analogRead() command to make sure that the number I was working with in the calculation was a float, offering me the best possible precision in the calculation. In the end, the value I get is rounded, but that's OK.
All of the functions available in the Arduino language are located on the reference page, but here are some of the most important ones we'll use in this course. Pay particular attention to what kind of arguments they take, what they return, and in what units they operate. Do not assume. In the case of digital read and write commands, note the particulars of how the pin should be configured in the void setup() function.
(There is more detail in this notebook here-Commands)).
(There is more detail about using millis and micros in this notebook here-and-micros())).
In general, Arduino-compatible devices are 8-bit microprocessors (although more advanced architectures are gaining traction) surrounded by a few supporting devices on a single Printed Circuit Board (PCB) such a USB connectors/converters, voltage regulators, an oscillator that tells the processor how often to make decisions (clock speed), and connectors so that you can interface the device with the outside world.
Arduino-compatible devices are actually faster than the computer my family had in our house when I was a kid! But they're very small, and can be "embedded" into a control system for portability and reliability. Unlike a traditional PC, tablet, or phone, Arduinos generally do not run an operating system, so they don't get interrupted by emails, automatic updates, or other disruptions that could prevent them from doing their job in a control system.
In this course, lab exercises will require use of the Arduino Mega board, shown below. This board is located under the green circuit board on your ME480 workstation.
Project exercises will use the Pololu Zumo 32u4, shown below:
Each one of these devices has its own quirks, but each is easily programmed using the Arduino environment. For instructions on getting started with the Zumo, see the project one notebook or this page.
As with most of the topics in this guide, arduino has its own comprehensive resource for troubleshooting that you should read. The information in this section is not complete, because there is no "magic pill" or recipe for debugging that will always work. However, these tidbits of (hopefully good) advice are distilled from several years of teaching this course with Arduino.
Compiler errors keep the code from being translated to code that the Arduino can understand. Usually, these are caused by typos or omissions, and the Arduino IDE is pretty good at telling you what the problem is. These errors will be reported in the black window in red text when you click either the "verify" or the "upload" button. Before asking for help from your classmates or an instructor, make sure you complete the following steps:
Some suggestions for each are given below, but using Google to help troubleshoot these errors is a good idea.
(Variable_Name) was not declared in this scope
. This error usually has to do with a variable you either forgot to declare or a variable that is local that should be global.
Expected ; before (something)
. If you get this error, you probably have a missing semicolon, parenthesis, or bracket somewhere above the line indicated by the error.
Port not found
If this error occurs, and your Arduino is plugged in, be sure the correct port is selected in the Arduino menu. If it is, you may have a bad cable, or there may be a short circuit preventing your Arduino from booting up.
out of sync error
This is usually related to the Arduino's ability to talk to the computer. Is the right board type selected? Is the Arduino healthy and powered properly?
Run-time errors are much more difficult to track down. If your code compiles and uploads properly, but does not do what it is supposed to do, you have a run-time error. Tracking these down is difficult and takes a lot of practice to do quickly, but if you follow a methodical process, you will save yourself a lot of wasted time. The cardinal rule of finding runtime errors, also called debugging, is:
Arbitrarily changing lines of code, especially more than one at a time, will definitely make your life harder. If you wrote a program, you should know exactly what each line is supposed to do. What this means is that it should be possible to follow a methodical process, working through each thing that the code is supposed to do and testing it (often by printing a particular variable to the Serial monitor), until you find the problem. Here are some general principles that should help you both avoid bugs and find them quickly when they do pop up.
Comment your code.
Although it is true that once you become fluent in a programming language, you should be able to decipher each line in your program and know what it was intended to do. Still, sometimes we forget, or do something silly, so it's best to comment each important line in plain English so that you remember what you were thinking when you wrote it.
Use Serial.print() to learn what is happening at various places in your program.
We can't simple ask the Arduino in English to tell us what it is "thinking." But with the serial monitor, we can have it show us what each key variable is at each key moment in our code. If we're using "if" statements, or if the program structure is complex, it is usually a good idea to make sure the Arduino is running all of the lines of code you think it should be by using Serial.print("I'm Here!"); type statements in all key loops and/or flow control statements
PRO TIP: The advice above is great for finding problems, but it really slows down your code and turns the serial monitor into a mess. Use this method wisely, and delete the extra print statements when your code is running properly!
Functionalize your code.
As explained in the section on functions above, you can organize your code by creating user-defined functions. It's best if you can test these functions independently of the rest of your code. For instance, if you're worried that you might be messing up a particular calculation, having that calculation functionalized will allow you to test that piece of your code by itself. See below.
Create simplified versions of your code that do only one thing
Sometimes, you might now know exactly where the problem is. To isolate it, you can save a few versions of your code, each of which performs a simplified version of what you want the entire code to do. If your code is functionalized, this is actually fairly easy-- you can just comment out the lines in void loop() that call functions other than the one you're trying to test.
Temporarily hard-code values to check inputs and outputs
It is easy to accidentally pass an argument to a function that it inappropriate, and will cause weird behavior. For example we use the analogWrite() function pretty extensively in this course. Looking at the documentation for this function, we see that the input "value" must be an int between 0 and 255. Passing this function a float, or passing it a value outside the range [0,255]
will cause strange behavior that is very hard to detect.
The strategy of hard-coding values will help you separate what might be a hardware problem from problems with your code. For instance, if you are supposed to send a value of "255" to the analogWrite command when a potentiometer knob is turned all the way up, but this doesn't seem to correspond with the behavior you're seeing, actually send 255 to the function to determine whether the problem disappears.
Use a multimeter or oscilloscope to make sure that input and output values are what they're supposed to be.
Debugging the hardware attached to your arduino is a whole different topic, but consider for a moment that your code requires a "high" voltage level on pin 5 to execute. Are you sure that when you flip a switch, you're getting a "high" voltage on pin 5? You can use a Serial.print command to check whether the Arduino believes the input voltage to be high, but it is even better to corroborate or refute its belief using a multimeter or oscilloscope!!
There are countless ways to make a mistake in programming, but there are a couple of common "traps" that are so prevalent that they're worth calling out specifically here:
You used the wrong datatype for a variable
This one happens a lot. Many functions in Arduino require a certain datatype to work properly, and if you're trying to do floating point math (math with decimal places), and you mix integers into your calculations, you could end up in trouble. For example, the two serial print lines below
Serial.print
**You passed an argument to a function that is too big, the wrong sign, or the wrong datatype**
**You overwrote a variable**
This happens a lot when you accidentally declare a variable global, but then re-declare a local version of it within a function. An example of this is shown in the "variable scope" section above. It's also easy to overwrite a variable if you miss an equals sign in a logical statement. here's an example:
```C++
//let us know if a is one.
if(a=1){
Serial.println("a is one.");
}
else{
Serial.println("a is not one");
}
No matter what you set "a" to earlier in your code, you will always see "a is one" on the serial monitor. Why? Because the code is missing an equals sign!! The line a=1
assigns the value "1" to the variable "a." That's not what we wanted... we wanted to test if a was equal to one. If we assign the value 1 to a every loop, right before we print, and "1" is interpreted by Arduino as "true" (which it is), of course we're always going to see that a is one!!
What we needed to write was:
if(a==1){...
because logical tests require a double equals sign.
Something critical happens in a flow control statement, but your code never enters that statement
Look at the example above once more, and ask yourself what would happen if you only told the Arduino to drive a motor if it was in the "else" part of that if statement. It would never get there, so we'd never be able to drive the motor! In hindsight, we can see very easily what the problem is. But if you're writing code at 3AM the night before an assignment is due (which I don't recommend), it's easy to miss that pesky equals sign. How would you find that error? The key is to use Serial.print() commands in all key places of your code. Make the Arduino tell you where it is going in the code, and how often, and what all key variables are when you get there.
You are doing math improperly with mixed variable types
Be careful about mixing datatypes when your code is performing calculations. If you'd like a float to come out of a calculation, make sure everything in the calculation is a float or is typecast as a float. This is explained fairly well here.
This course is going to require you to collect data from the serial monitor (see this video for more information). Doing this is relatively easy, but you should follow the following general format when printing serial data to the monitor:
Serial.println()
statement to create a new line after all data has been printed. This will allow you to easily load the data into Jupyter/MATLAB.Serial.print("\t");
to insert a tab in between variablesHere is an example of a printData() function that has all of the right pieces:
void printData(){
Serial.print(t);//print time
Serial.print("\t");//separate with a tab
Serial.print(var1);//print first variable of interest
Serial.print("\t");//separate with a tab
Serial.print(var2);//print second variable of interest
Serial.print("\t");//separate with a tab
Serial.print(var3);//print third variable of interest
Serial.println();
}
This function prints three numbers separated by tabs, but you can print more! Just remember that lots of printing will slow down you overall "loop time," or the time it takes the Arduino to complete one pass through void loop(). Since most of the control systems we'll be implementing in Arduino will be designed assuming that the Arduino operates infinitely fast (continuous-time math), we want to keep the loop time as small as possible. If you keep track of a "dt" or loop time in your code, you can always check this! Loop times of around 100 Hz are good.
To use the data we get from Arduino for validating dynamic models, or for calculating approximate integrals and derivatives inside of our Arduino code, we need to keep track of time. To do this, we can use either millis, which gives us the milliseconds elapsed since the Arduino was powered up, or micros, which gives us this value in microseconds. Which should you use? Probably micros if you're calculating numerical derivatives (for velocities, etc.) because its precision is better, but probably millis if you need to keep track of a long period of time. Microseconds will "overflow" or roll over to 0 much more quickly than millis.
Tips
Here is an example of a program that calculates the loop time in microseconds, and then converts this to a $\Delta t$ in seconds:
unsigned long microsNow;//what is the time right now?
unsigned long microsLast;//what was the time last time I checked?
unsigned long dtMicros;//loop time in microseconds
float dt;//loop time in seconds, suitable for calculating derivatives.
void setup(){
Serial.begin(115200);
microsLast = micros();
delay(1);//delay for one millisecond
microsNow = micros();//initialize both of these. BE CAREFUL that you never get a dT of 0!!!
}
void loop(){
//check the time
microsNow = micros();
//compare to old time;
dtMicros = microsNow-microsLast;
//convert to seconds
dt = (float)dtMicros/1000000000.0;
//store "old time" right now before doing anything else! But make sure we don't need "microsNow" anywhere else
microsLast = micros;
//do something useful. read a value from A0 I guess
int someVal = analogRead(0);
//print micros, dt, and value
Serial.print(microsNow);
Serial.print("\t");
Serial.print(dt);
Serial.print("\t");
Serial.print(someVal);
Serial.println();
}
The Zumo robot projects will require the use of the Zumo library. See the Zumo intro for more details, but in general, a library is a set of new, specialized functions that you can use if you "include" the library in your code. For information about how to include and install libraries, see the official Arduino page about libraries.
Libraries in arduino are collections of code that add functionality to your Arduino program. They allow you to perform complex tasks with simple code, because more complex code lives "under the hood" inside the library. If a library for Arduino is installed on your computer, then you can "include" the library in your code in order to access its functions and variables.
Libraries in Arduino are usually organized into code structures called "classes" that describe "objects" you can declare and then use in your program, each of which has some useful functionality. In ME352 and ME240, you used the MATLAB programming environment, and you likely used "functions" to perform certain tasks (plot()
in MATLAB is a function!). Like functions, organizing code into classes that describe objects can simplify your main program. However, when compared to functions, classes are a more modern, sophisticated concept common in modern programming languages.
So what is a class? It is a piece of code that describes an object. What is an object? It is mostly what it sounds like. For example, a person could be conceived as an object. That person can perform functions (eat, drink, sleep) but that person also has properties (hair color, gender identity, age). An object in code, like an "object" such as a person, can be considered as a collection of properties (sometimes called "member variables") and functions (sometimes called "methods"). If we had a "person" library in Arduino, we might want to declare the existence of several "people" in our code. That would go at the top of our program, and look like this:
//tell Arduino we want to use the Person library
#include <Person.h>
//declare people (like variables).
Person Sam;
Person Jess;
Let's say we initialized these peoples' properties appropriately. Sam and Jess are both instances of the Class (an instance of a class is an object) Person. Each person can have different properties, and calling their respective functions can be done independently. For instance, we can tell Sam and Jess each to eat independently in our code, and we can also access their individual properties to help us make decisions about what they should do. That might lead to a void loop()
function that looked something like this pseudocode:
void loop(){
if(Sam.haircolor=="grey"){
Sam.eat(more_vegetables);//tell sam to eat more vegetables to slow aging
}
if(Jess.age>40){
Jess.eat(banana);
}
}
So in our code, we are accessing the functions that define a person (in this case, eat), but we also have the option of querying (and sometimes setting) the properties of each person. Many Arduino libraries, including the Zumo library, are set up this way.
For a more concrete example, let's talk about the "timer" functionality of the ME480FSMTimerCounter library, which you will use in this notebook's challenge. A timer as an object has functions (e.g. update the timer), but it also has properties (e.g. elapsed time since activated). When we use the TimerCounter library, we can create a "timer object" that has its own elapsed time, and that can be updated to tell us when our preset amount of elapsed time has passed.
The following code "includes" the TimerCounter library (assuming it is installed on your system), and sets up two timers that are activated by the Zumo's "button A." It updates the timers in each pass of the void loop()
function, and prints the status bit of each timer to the serial monitor.
You can try this code out yourself on your Zumo, or on your ME480 lab workstation (be sure to read the comments to know how to modify the code for use on the lab workstation).
//tell Arduino to load the library.
#include <ME480FSM.h>
//declare a variable for each timer.
FSMTimer timer1(500);//this timer's status bit goes true after being activated for 500ms
FSMTimer timer2(1000);//this timer's status bit goes true after being activated for 1000ms
//declare the pin number for the button I'll use to activate the timer.
//for Zumo, it is pin 12 for button A. For ME480 workstation, it is pin 29 for BTN 1.
int btnA_pin = 14;
void setup(){
//set the input button's pin as an INPUT_PULLUP type.
pinMode(btnA_pin,INPUT_PULLUP);//see note above where btnA_pin is defined.
Serial.begin(115200);//start a serial monitor
}
void loop(){
//store the state of the button in a local variable.
bool btnA_state = digitalRead(btnA_pin); //true if not pressed, false if pressed (must be this way based on wiring of these buttons).
//now update each timer. Both are activated by holding button A.
//note that we "not" the button reading, so that the timer is activated when the button is HELD.
timer1.update(!btnA_state);
timer2.update(!btnA_state);
//Print each timer's status bit, as well as elapsed time, to the serial monitor so we can see what this does!
Serial.print("Timer1 elapsed:\t");
Serial.print(timer1.elapsed);//elapsed is a variable that each timer "object" keeps track of.
Serial.print("\t Timer1 status: \t");
Serial.print(timer1.TMR);
Serial.print("\t\t");
Serial.print("Timer2 elapsed:\t");
Serial.print(timer2.elapsed);//elapsed is a variable that each timer "object" keeps track of.
Serial.print("\t Timer2 status: \t");
Serial.print(timer2.TMR);
Serial.println();
}
If you try this code, hold down Button A (zumo) or user BTN 1 (workstation) and watch what happens on the serial monitor. The timers each activate when button A is held down, and each respective timer's status bit goes true after the timer has remained active for the preset time.
Now that we have a general sense of what an Arduino library is, we need to understand how to install one. We cannot use a library in Arduino without first making sure it is installed.
when you open the Arduino programming environment, you can install a library by going to the "Sketch$\rightarrow$Include Library$\rightarrow$Add .ZIP Library" option from the menu.
By clicking this link, you can download a file called "ME480FSM-main.zip." This library contains classes that implement timers and a rising edge counter. To install the ME480FSM library on your machine's Arduino installation, download this file, rename it to ME480FSM.zip, and then add this Library to your Arduino installation using the instructions above.
Once you have done this, you should find the TimerCounter Library in the list of libraries under "Sketch$\rightarrow$Include Library." Click this library in the menu to automatically add the appropriate #include <ME480FSM.h>
statement to the top of any sketch in which you wish to use the ME480FSM library.
Whenever we come across a library that we think might be helpful for us as we design and implement a controller, we have to learn to use its particular classes and functions! While it's always nice when we can consult an "expert" who knows everything there is to know about a library, it's much more common that we'll find libraries on our own that we need to teach ourselves about.
Luckily, libraries are usually accompanied by documentation that is designed so that programmers can teach themselves how to use the library's functionality. For ME480, the libraries you'll be using are all accompanied by web-based documentation. As an example, see the ME480FSM library's documentation here.
Looking at the landing page for the library, we see an overview of what the library is for, along with a navigation bar that lets us explore the library's details. See the screenshot below.
At the top, you will see a button called "Classes," which explains what classes (objects) the library provides for your use. Digging in, let's look at the page for the FSMTimer class. On that page, you will see a list of all of the methods (functions) in that class, as shown below.
The page for each class usually contains a description of what it is used for, along with a list (with variable types) of all member functions and attributes (properties) that can be accessed if you declare an instance of this class. Essentially, these pages should tell you everything you need to know in order to use the library in your own code.
The methodology we used to explore the ME480FSM library will work for any Arduino library!