TVZ Mechatronics Team


Zagreb University of Applied Sciences, Professional Study in Mechatronics

You are viewing an older revision! See the latest version

Primjer pisanja vlastite mbed biblioteke

Kreiranje HC-SR04 klase

Iako već postoje klase za ultrazvučno mjerenje udaljenosti, u ovoj vježbi izgradit ćemo vlastitu klasu za senzor HC-SR04. To je prikladan način za učenje i vježbanje razvoja vlastitih klasa i biblioteka.

Najprije ćemo pogledati reviziju 0 programa napisanog u prethodnoj vježbi Tajmeri i prekidi. Program možete uvesti s donje poveznice:

Import programHC-SR04

A program that demonstrates the development of library, using as an example an ultrasonic distance sensor HC-SR04.

Napomena

Revizije programa i način korištenja u svrhu suradnje s drugim korisnicima opisane su ovdje.

Datoteka main.cpp sadrži sljedeće dijelove, koji zajedno obavljaju zadatak mjerenja udaljenosti:

  • 2 mikroupravljačka pina (p5 i p7),
  • 3 glavna objekta (echo, trigger i timer),
  • 2 funkcije (startTimer() i stopTimer()),
  • konfiguraciju za rastući i padajući brid echo objekta na početku main.cpp datoteke,
  • dio kôda za pokretanje mjerenja i izračun udaljenosti.

Ova zapažanja ćemo sada pokušati integrirati u klasu. Klasa sadrži deklaracijski dio (prototip) i implementacijski dio. Prvo ćemo napisati deklaracijski dio klase.

Deklaracijski dio klase

Prvo deklariramo klasu imena HC-SR04. U javnom dijelu klase (public) najprije deklariramo (napišemo prototip) konstruktor. Iz gornjih opažanja znamo da ćemo instancirati naše HC-SR04 objekte s dva pina, tako da naš konstruktor mora primiti dva argumenta tipa PinName, npr. echoPin i triggerPin. Ako prilikom deklaracije javne funkcije imamo potrebu za argumentima funkcije, kao u ovom slučaju, poželjno je odmah te argumente i dokumentirati pomoću markup opcije @param (linije 6 i 7 u kôdu ispod).

Nadalje, integriramo naša 3 objekta u privatni dio deklaracije klase (private), kako bi ih zaštitili od neželjenih promjena izvan klase. U ovom trenutku tim objektima nije moguće pridijeliti i pinove (npr. objektima echo i trigger), koji se inače traže pri instanciraju. Razlog tome je što se deklaracijom klase još uvijek ne instanciraju objekti. Budući da se objekti instanciraju pozivom konstruktora, ovo pridruživanje pinova objektima ćemo riješiti kasnije u implementacijskom dijelu konstruktora.

U idućem koraku dodajemo naše dvije funkcije u deklaraciju klase. Pri tome moramo odlučiti da li ćemo ih smjestiti u public ili private dio klase. Budući da korisnici klase nemaju potrebu za eksplicitnim korištenjem tih funkcija, one se smještaju pod private dijelom klase. Prilikom kopiranja zaglavlja funkcija poželjno je obuhvatiti i komentare iznad zaglavlja, koji će kasnije poslužiti za automatsko dokumentiranje klase.

Nadalje, konfiguracije rastućih i padajućih bridova echo objekta, kao i sve ostale konfiguracije, bit će implementirane pozivom init() funkcije u konstruktoru. Zbog toga ćemo zasad samo dodati deklaraciju init() funkcije u private dio deklaracije klase.

Konačno, dodajmo deklaracije funkcija za pokretanje mjerenja te izračun i vraćanje vrijednosti izračunate udaljenosti. Koja od tih funkcija bi trebala biti private, a koja public? Kod funkcija koje vraćaju neku vrijednost možemo dokumentirati i povratni tip, tj. varijablu koju funkcija vraća, korištenjem markup opcije @returns. Dodajmo i varijablu za smještaj podatka o izračunatoj udaljenosti u private dio deklaracijskog dijela klase.

Deklaracijski dio klase bi sada trebao izgledati nekako ovako:

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();
};

Sada možemo prijeći na implementacijski dio klase.

Implementacijski dio klase

Prvo ćemo implementirati konstruktor. Kao što je spomenuto u deklaracijskom dijelu klase, sve inicijalizacije moraju se izvesti u konstruktoru. Pridruživanje pinova je obvezno i izvodi se pomoću : operatora, neposredno nakon zaglavlja konstruktora (linija 1 kôda ispod). Sve ostale inicijalizacije se izvode pozivom init() funkcije (linija 2):

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

Zašto je dobra ideja pozivati posebnu funkciju, poput init(), umjesto pisanja inicijalizacijskog kôda izravno u konstruktoru? Kasnije se možemo odlučiti implementirati drugi konstruktor (ili više njih), u kojima se mogu realizirate i neke dodatne ili drukčije opcije. Kad bi pisali inicijalizacijski kôd u konstruktoru, onda bi morali kopirati taj dio u svaki konstruktor, što nije baš praktično. Nakon eventualnih promjena prilikom razvoja kôda, te promjene bi trebalo raditi na svim mjestima gdje je kôd kopiran. Pogledajmo implementaciju init() funkcije:

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
}

U konfiguracijama rastućeg i padajućeg brida echo pina sada imamo i pokazivač this. Razlog tome je što su funkcije pripadnice klase smještene u zasebnom dijelu memorije, odvojeno od instanciranih objekata. Kad objekt poziva funkciju, on mora proslijediti svoju adresu toj funkciji, kako bi funkcija znala nad kojim objektom mora izvršiti svoje zadatke. Adresa objekta je spremljena upravo u pokazivaču this.

Još jedna sitnica koju možemo primijetiti je inicijalizacija varijable za spremanje udaljenosti (distance) na nerealnu vrijednost -1. Ta vrijednost nam može poslužiti kao indikator da mjerenje još nije bilo pokrenuto. Dobra programerska praksa je inicijalizirati sve varijable u konstruktoru.

Ostatak implementacija je relativno jednostavno prenijeti iz main.cpp datoteke revizije 0 programa. Revizija 1 programa sadrži dosad napravljeni deklaracijski i implementacijski dio klase, kao i main() funkciju s instanciranim objektom i primjerom mjerenja.

Razdvajanje u datoteke

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
 *     }
 * }
 * @endcode
 */
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 our 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.

At this point of library development, we have the initial revision of the library and 2 small revisions related to the documentation. So switch to revision 2 of the library and have a look at the auto-generated documentation. Everything should be clear enough. If we are satisfied with the current state of the library and its documentation, we can begin with testing the sensor and use it in some real application.

Improving the library

While testing the library with the HC-SR04 ultrasonic sensor, we notice some strange readings when the measurement range is below 4 cm and above 400 cm. These values should be set as sensor limit values, and should be interpreted as <4 cm and >400 cm. Try to implement these limitations into our class. The possible solution is given in the revision 3 of the library.

Next, add a member function which enables the user to set these limits within the manufacturer declared range of 2 cm and 400 cm. Adjust the default settings of the class to be in this manufacturer declared range. In addittion, enable the user to get the distance in the units of milimeters, and to get the minimum and maximum distance set by the user. The possible solution is given in the revision 5 of the library, and the example of usage is shown in the revision 3 of the program.

Try to notice the classic example of a bug in the member function setRanges() and implement the function correctly.

Usage of the library with filtering

Revision 5 of the program shows the usage of the library with basic filtering using PT1 filter with a time constant of 2 seconds. The measuring results of longer distances (>100 cm) are now more stable, but when the distance suddenly changes, filtered results need about 10 seconds to stabilize the measurement (approximately 5 time constants). Such delay must be taken into account when designing a control system with this sensor and filter.


All wikipages