Creating a device driver is in the same time maybe the most fastidious task in general but considering what strictly concerns hardio it is rather simple. Indeed hardio in itself does not defines a strict pattern for drivers. The only thing that makes a driver a hardio driver is that it uses one or more hardio interface(s) created from protocols (using add_interface() function).

Anyway hardiocore package provide a very limited set of very generic drivers that are quite common in the embeddded world, you can find them in the include/hardio/devices folder. But these drivers are essentially utility classes and can be safely ignored by end users. We will just use one of them, the MotorESC to quicly explain how to define device drivers.

Before continuing this tutorial you should first have finished the tutorial on using drivers.

Anatomy of a driver

MotorESC class represents an ESC controlling a motor. It is very common in the world of drones and submarine robots to have ESC piloted through a PPM interface and this is what this class implements. Lets’ have a look at the declaration:

#pragma once

#include <hardio/interfaces/pinio.h>
#include <chrono>
#include <mutex>

namespace hardio {

using namespace std::chrono_literals;
class MotorESC {
public:
    MotorESC() = delete;
    MotorESC(const MotorESC&) = delete;
    MotorESC& operator=(const MotorESC&) = delete;

    MotorESC(PPMInterface& pin,
             const std::chrono::microseconds& min_pulse = 1000us,
             const std::chrono::microseconds& max_pulse = 2000us);
    ~MotorESC() = default;
    void set_motor_properties(
        const std::chrono::microseconds& max_motor_ppm,
        const std::chrono::microseconds& motor_forward_ppm_neutral,
        const std::chrono::microseconds& min_motor_ppm,
        const std::chrono::microseconds& motor_reverse_ppm_neutral);

    void motor_velocity(double percent_vel);
    double velocity() const;

    std::chrono::microseconds ppm() const;
    std::chrono::microseconds as_ppm(double& percent_vel) const;
    const std::chrono::microseconds& max_motor_ppm() const;
    const std::chrono::microseconds& motor_forward_ppm_neutral() const;
    const std::chrono::microseconds& min_motor_ppm() const;
    const std::chrono::microseconds& motor_reverse_ppm_neutral() const;

private:
    mutable std::mutex mutex_; // mutex
    PPMInterface& interface_;
    double current_velocity_;
    std::chrono::microseconds current_ppm_;
    double ppm_neutral_;
    std::chrono::microseconds max_motor_ppm_, motor_forward_ppm_neutral_,
        min_motor_ppm_, motor_reverse_ppm_neutral_;
};
}

The most important part regarding hardio is that the constructor asks for a reference to a PPMInterface and this interface will be memorized into the corresponding attribute interface_.This interface will be used to put in place communication with the real hardware ESC. Basically it is used to send pulses in order to control the motor velocity : depending on the PPM target the motor will reach a given percentage of its maximum forward or backward velocity. The main method in this class is motor_velocity() that is used to set the PPM corresponding to the target percentage of motor velocity.

All other methods are related to the configuration of the ESC and will not be explained.

Implementation

The two important functions are the constructor and motor_velocity:

MotorESC::MotorESC(PPMInterface& pin,
                   const std::chrono::microseconds& min_pulse,
                   const std::chrono::microseconds& max_pulse)
    : interface_{pin},
      ppm_neutral_{(max_pulse.count() + min_pulse.count()) / 2.0},
      max_motor_ppm_(max_pulse.count()),
      ... {
}

void MotorESC::motor_velocity(double percent_vel) {
    auto ppm = as_ppm(percent_vel);
    ...
    interface_.set_pulse(current_ppm_);
}

Concerning the constructor: pin argument is simply memorized as reference into the interface_ attribute in order to be used later in motor_velocity function. Other attributes are related to the configuration of the ESC.

motor_velocity first computes the target ppm corresponding to the target velocity percentage, using as_ppm() function. Then it simply uses the interface_ attribute to set this target ppm using the PPMInterface set_pulse() function. This results in sending the PPM value to the real hardware ESC.

Conclusion

That’s it, there is no much more to understand concerning drivers writing within hardio: drivers uses communication links provided as hardio interfaces to communicate with electronic devices. Writing drivers generally consists in implementing a device specific protocol to discuss with a specific hardware through one or more communication links. hardio is just there to provide the communication links the driver is using.

Nevertheless we highly recommend to use rpc-interfaces to implement your drivers. This package provides a pattern and methodology for writing drivers, which results in simple homogeneous interfaces, clean data/control logic decoupling and device interface standardization enforcement. A very brief explanation is given here if you want to have a rough understanding.