You are viewing an older revision! See the latest version
Timers interrupts and tasks
In the following exercises you will learn to use Timer
, Timeout
, Ticker
and InterruptIn
classes from the standard mbed library. The program codes are from the following excellent book: Toulson, R. & Wilmshurst, T. (2012). Fast and Effective Embedded Systems Design - Applying the ARM mbed, Newnes, Oxford, ISBN: 9780080977683.
Exercise 1: Using the mbed Timer
¶
Study the API documentation of the Timer
class.
Examine the following code and try to figure out what will happen with the LEDs. Then test the program on an actual mbed and see if your predictions were correct.
#include "mbed.h" Timer timer_fast; Timer timer_slow; DigitalOut ledA(LED1); DigitalOut ledB(LED4); void task_fast(void); void task_slow(void); int main() { timer_fast.start(); timer_slow.start(); while(true){ if (timer_fast.read() > 0.2) { task_fast(); timer_fast.reset(); } if (timer_slow.read() > 1) { task_slow(); timer_slow.reset(); } } } void task_fast(void) { ledA = !ledA; } void task_slow(void) { ledB = !ledB; }
Exercise 2: Using the mbed Timeout
¶
Study the API documentation of the Timeout
class.
Examine the following code and try to figure out what will happen with the LEDs. Then carefully test the program on the mbed and see if your predictions were correct.
#include "mbed.h" Timeout response; DigitalIn button (p14); DigitalOut led1(LED1); DigitalOut led2(LED2); DigitalOut led3(LED3); void blink() { led2 = 1; wait(0.5); led2 = 0; } int main() { while(true) { if(button == 1){ response.attach(&blink, 3.0); led3=1; } else { led3=0; } led1=!led1; wait(0.2); } }
Modify the above code by adding the second button, which will detach the blink()
function from the response
object if pressed.
Exercise 3: Using the mbed Ticker
¶
Study the API documentation of the Ticker
class.
Examine the following code and try to figure out what will happen with the LED. Test the program on the mbed and see if your predictions were correct.
#include "mbed.h" void led_switch(void); Ticker time_up; DigitalOut myled(LED1); void led_switch() { myled=!myled; } int main(){ time_up.attach(&led_switch, 0.2); while(true) { wait(1); } }
Modify the above code to flash another LED every 1 second.
Exercise 4: Using the mbed InterruptIn
¶
Study the API documentation of the InterruptIn
class.
Examine the following code and try to figure out what will happen with the LEDs. Test the program on the mbed and see if your predictions were correct.
#include "mbed.h" InterruptIn button(p14); DigitalOut led(LED1); DigitalOut flash(LED4); void isr1() { led = !led; } int main() { button.rise(&isr1); while(true) { flash = !flash; wait(0.2); } }
Press the button
multiple times and carefully observe the behavior of the LED1 when the button is pressed. Did you remember the bouncing effect?
Exercise 5: Switch debouncing¶
Modify the program from Exercise 4 to remove the bouncing effect, e.g. to debounce the button. Possible solution is given in the following code:
#include "mbed.h" InterruptIn button(p14); DigitalOut led(LED1); DigitalOut flash(LED4); Timer debounce; void isr1() { if (debounce.read_ms() > 200) { led = !led; debounce.reset(); } } int main() { debounce.start(); button.rise(&isr1); while(true) { flash = !flash; wait(0.2); } }
Also read about a DebounceIn
and PinDetect
classes, which are written for the purpose of switch debouncing. A very nice tutorial about pushbutton and switches can be found here.
Exercise 6: Measuring distance using ultrasonic sensor HC-SR04¶
Write the simple program for measuring distance using an ultrasonic sensor HC-SR04.
One possible solution is given in the program bellow (switch to revision 0). Try to write your own solution first.
Import programHC-SR04
A program that demonstrates the development of library, using as an example an ultrasonic distance sensor HC-SR04.
Note
Program revisions and how to use it to collaborate with other users is explained here.
Exercise 7: Creating a HC-SR04 class¶
Although there are already a few classes available, begin to build your own class for ultrasonic distance measurement. This is a suitable way of learning to develop your own classes and libraries.
First take a look at the revision 0 of the program above. The main.cpp
file contains the following parts, which combined together perform the distance measurement:
- 2 microcontroller pins (
p5
andp7
), - 3 main objects (
echo
,trigger
andtimer
), - 2 functions (
startTimer()
andstopTimer()
), - configuration of the rising and falling edges for the
echo
object at the beggining of themain.cpp
file, - part of the code for starting the measurement and calculation of the distance.
Now we are trying to integrate these observations into the class. The class contains its declaration and implementation parts. We will first write the declaration part of the class.
Declaration part of the class¶
First we declare a class named HC-SR04
. In the public
part of the class declaration we declare a constructor. From the above observations we know that we will instatiate our HC-SR04
objects with 2 pins, so our constructor needs to receive two PinName
values, e.g. echoPin
and triggerPin
. Document the meaning of these two values by using the @param
markup.
Next, we integrate our 3 objects into the private part of the class declaration, to protect them from changing outside the class in an unwanted way. Note that you can not initialize the objects or variables at the declaration part of the class, i.e. you cannot assign a pin to the echo
and trigger
objects at this point. We will do that later in the constructor implementation.
Next, we add our 2 functions to the class declaration. We must decide if the functions will be public
or private
. In this case, the users of our class don't need to use these functions explicitly, so they can be declared as private
. When coppying the functions headers, also copy the initial comments. They will later serve for the purpose of class documentation.
Next, the configuration of the rising and falling edges for the echo
object, as well as all other configurations, will be implemented later in the constructor init()
function. So for now, just add the declaration of init()
function in the private part of the class declaration.
Finally, add the declarations of functions for starting the measurement and calculating and returning the distance. Figure out which one should be private
and which one should be public
. Add the distance variable in the private
part of the class declaration.
The declaration of the class should now look something like this:
class HCSR04 { public: /** Receives two PinName variables. * @param echoPin mbed pin to which the echo signal is connected to * @param triggerPin mbed pin to which the trigger signal is connected to */ HCSR04(PinName echoPin, PinName triggerPin); /** Calculates the distance in cm, with the calculation time of 25 ms. * @returns distance of the measuring object in cm. */ float getDistance_cm(); private: InterruptIn echo; // echo pin DigitalOut trigger; // trigger pin Timer timer; // echo pulsewidth measurement float distance; // store the distance in cm /** Start the timer. */ void startTimer(); /** Stop the timer. */ void stopTimer(); /** Initialization. */ void init(); /** Start the measurement. */ void startMeasurement(); };
We can now move to the class implementation.
Implementation part of the class¶
We are implementing the constructor first. As we mentioned in the declaration part of the class, all initializiations must be performed in the constructor. The pin assigment is mandatory here and it is carried out by using the :
operator after the constructor header (line 1 from the code below). All other initializations are carried out in the init()
function, which is called immediately in line 2 of the code:
HCSR04::HCSR04(PinName echoPin, PinName triggerPin) : echo(echoPin), trigger(triggerPin) { init(); }
Why is it a good idea to call another function, like init()
, instead of writing the initialization code directly in the constructor? We can later decide to have two or more constructors with some other options. If we would write the initialization code directly in the constructor, then we would have to copy-paste it in every constructor, which is not very practical thing to do. Now let's see the implementation of the init()
function:
void HCSR04::init() { /** configure the rising edge to start the timer */ echo.rise(this, &HCSR04::startTimer); /** configure the falling edge to stop the timer */ echo.fall(this, &HCSR04::stopTimer); distance = -1; // initial distance }
We now have a this
pointer with configuration of the rising and falling edges on the echo
pin. The reason for this is that the class member functions are stored in a separate place in memory than objects. When an object calls the function, it must pass its own address to that function, so that the function knows on which object it will perform its tasks. The address of the object is stored in the this
pointer.
Another thing to notice is that the distance variable is initialized to an unrealistic value of -1. This value serves the programmer to recognize that measurements have not yet been made or that another event has occured. It is a good programming practice to initialize every variable in the constructor.
The remaining implementations are fairly easy to transfer from the main.cpp
file of the revision 0 of the program. The revision 1 of the program contains the class declaration and implementation, as well as the main()
function with instantiated object and measurement example.
Separating into files¶
The next logical step is separating the class declaration, implementation and main()
function into files. Create two new files in your program folder. Name them HCSR04.h
(for declaration part of the class) and HCSR04.cpp
(implementation part). Then create a new library named HCSR04
and move the created two files in the library. Then cut and paste the appropriate code from the main.cpp
in the library files and try to compile the program. Obviously, we need to add some information to the compiler, so it knows where to look for our code.
Let's first look at our current main.cpp
file.
main.cpp
/** Revision 2: Separating the class declaration, implementation and main() function into files. */ #include "mbed.h" Serial pc(USBTX, USBRX); // communication with terminal int main() { HCSR04 sensor(p5, p7); // instantiate the sensor object while(1) { /** Print the result in cm to the terminal with 1 decimal place * (number 5 after % means that total of 5 digits will be reserved * for printing the number, including the dot and one decimal place). */ pc.printf("Distance: %5.1f cm\r", sensor.getDistance_cm()); wait_ms(1000-25); } }
The compile time error is caused in the line 8, because the comiler doesn't know about the existence of the class HCSR04
. We explicitly need to tell the compiler to include it. So, on the line 4 simply add:
Add to line 4 of main.cpp
#include "HCSR04.h"
The HCSR04.h
is ok as it is, but at this point we want to add some description of the class for auto-generating documentation. We usually add some sample code with basic usage of the class (like our main.cpp
file), because other users will probably import the library into their own programs. The HCSR04.h
file should look something like this:
HCSR04.h
/** A distance measurement class using ultrasonic sensor HC-SR04. * * Example of use: * <<code>> * #include "mbed.h" * #include "HCSR04.h" * * Serial pc(USBTX, USBRX); // communication with terminal * * int main() { * HCSR04 sensor(p5, p7); // instantiate the sensor object * while(1) { * pc.printf("Distance: %5.1f cm\r", sensor.getDistance_cm()); * wait_ms(975); // print the result every 1 second * } * } * <</code>> */ class HCSR04 { public: /** Receives two PinName variables. * @param echoPin mbed pin to which the echo signal is connected to * @param triggerPin mbed pin to which the trigger signal is connected to */ HCSR04(PinName echoPin, PinName triggerPin); /** Calculates the distance in cm, with the calculation time of 25 ms. * @returns distance of the measuring object in cm. */ float getDistance_cm(); private: InterruptIn echo; // echo pin DigitalOut trigger; // trigger pin Timer timer; // echo pulsewidth measurement float distance; // store the distance in cm /** Start the timer. */ void startTimer(); /** Stop the timer. */ void stopTimer(); /** Initialization. */ void init(); /** Start the measurement. */ void startMeasurement(); };