TVZ Mechatronics Team


Zagreb University of Applied Sciences, Professional Study in Mechatronics

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 and p7),
  • 3 main objects (echo, trigger and timer),
  • 2 functions (startTimer() and stopTimer()),
  • configuration of the rising and falling edges for the echo object at the beggining of the main.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();
};

We still cannot compile our program, because implementation file of the class does not contain the member function prototypes or simply class declaration, as well as the prototypes of functions from mbed.h library. This is also easily solved by adding the #include statements at the beggining of the HCSR04.cpp file:

HCSR04.cpp

#include "mbed.h"
#include "HCSR04.h"

HCSR04::HCSR04(PinName echoPin, PinName triggerPin) : echo(echoPin), trigger(triggerPin) {
    init();
}

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
}

void HCSR04::startTimer() {
    timer.start(); // start the timer
}

void HCSR04::stopTimer() {
    timer.stop(); // stop the timer
}

void HCSR04::startMeasurement() {
    /** Start the measurement by sending the 10us trigger pulse. */
    trigger = 1;
    wait_us(10);
    trigger = 0;
    
    /** Wait for the sensor to finish measurement (generate rise and fall interrupts).
     *  Minimum wait time is determined by maximum measurement distance of 400 cm.
     *  t_min = 400 * 58 = 23200 us = 23.2 ms */
    wait_ms(25); 
    
    /** calculate the distance in cm */
    distance = timer.read() * 1e6 / 58;
    timer.reset(); // reset the timer to 0 after storing the distance
}

float HCSR04::getDistance_cm() {
    startMeasurement();
    return distance;
}

Our code can now be compiled without errors or warnings. One last thing to do is to add so called define guards in the declaration part of the class, to prevent multiple inclusion of our library in some other program. The complete program and developed library is published as revision 2 of our program. Note that a library is now separated from pur initial program and can be modified without changing our main program. Or we can start a new program and import this library.

Import libraryHCSR04

A distance measurement class using ultrasonic sensor HC-SR04.


All wikipages