Skip to content

🔗 Interfaces in Software Development and libhal

In software development, an interface is a shared boundary across which two separate components of a computer program exchange information. The exchange can be between software, computer hardware, peripheral devices, humans, and combinations of these. Technically, it refers to a software component (a class, a module, etc.) that encapsulates a specific functionality and that can be used by other components.

Interfaces work by specifying the methods that a class must implement, without including the implementation of the methods themselves. This allows different classes to implement the same interface in different ways, providing a level of abstraction between the interface and the implementation.

In the context of libhal (Hardware Abstraction Layer Library), interfaces are used to define a standard way of interacting with different types of hardware. This makes the software portable because it can interact with the hardware through the interface, without needing to know the specifics of how the hardware is implemented. This means that if the underlying hardware changes, only the implementation of the interface needs to change, not the software that uses it.

Driver Types in libhal

Digital

See API: hal::input_pin

The Input Pin interface (hal::input_pin) in libhal is used for reading the state of a digital input pin. This is useful in situations where you need to determine if the voltage on a pin is HIGH or LOW.

In the libhal library, the Input Pin interface provides methods to configure the input pin according to the settings supplied and to read the state of the input pin.

See API: hal::output_pin

The Output Pin interface in libhal is used for controlling the state of a digital output pin. This is useful in situations where you need to send a control signal or turn on or off an LED.

In the libhal library, the Output Pin interface provides methods to configure the output pin according to the settings supplied, to set the state of the pin, and to read the current state of the output pin.

See API: hal::interrupt_pin

The Interrupt Pin interface in libhal is used for automatically calling a function when a pin's state has transitioned. This is useful in situations where you need to respond to changes in the state of a pin.

In the libhal library, the Interrupt Pin interface provides methods to configure the interrupt pin according to the settings supplied and to set the callback for when the interrupt occurs.

Analog

See API: hal::adc

The ADC interface in libhal is used to convert an analog signal into a digital one. This is useful in situations where the hardware device is producing an analog signal, such as a sensor reading, but the software works with digital values.

In the libhal library, the ADC interface provides methods to read the voltage from the ADC as a percentage of the reference voltage.

See API: hal::dac

The DAC interface in libhal is used to convert a digital signal into an analog one. This is useful in situations where the software is producing digital values, but the hardware device requires an analog signal.

In the libhal library, the DAC interface provides methods to write a voltage to the DAC, as a percentage of the reference voltage.

See API: hal::pwm

The PWM interface in libhal is used to control the waveform generation of a square wave and its properties such as frequency and duty cycle. PWM is used for power control like motor control, lighting, transmitting signals to servos, sending telemetry and much more.

In the libhal library, the PWM interface provides methods to set the PWM waveform frequency and duty cycle.

Time & Timers

See API: hal::timer

The Timer interface in libhal is used for scheduling events to occur after a specified amount of time. This is useful in situations where you need to perform an action after a certain amount of time has passed.

In the libhal library, the Timer interface provides methods to check if the timer is running, cancel a scheduled event, and schedule a new event.

See API: hal::steady_clock

The Steady Clock interface in libhal is used for a steady clock mechanism. This clock is steady meaning that subsequent calls to get the uptime of this clock cannot decrease as physical time moves forward and the time between ticks of this clock are constant and defined by the clock's frequency.

In the libhal library, the Steady Clock interface provides methods to get the operating frequency of the steady clock and to get the current value of the steady clock.

This completes the descriptions for all the interfaces in the libhal library. Each of these interfaces provides a way to interact with different types of hardware in a consistent and portable way, making it easier to write embedded software that can run on different platforms.

Serial Communication Protocols

See API: hal::spi

The SPI interface in libhal is used for communication over a Serial Peripheral Interface (SPI). This is a synchronous serial communication interface specification used for short-distance communication, primarily in embedded systems.

In the libhal library, the SPI interface provides methods to configure the SPI bus and perform an SPI transaction.

See API: hal::i2c

The I2C interface in libhal is used for communication over an Inter-Integrated Circuit (I2C) bus. This is a multi-master, multi-slave, packet switched, single-ended, serial computer bus invented by Philips Semiconductor.

In the libhal library, the I2C interface provides methods to configure the I2C bus and perform an I2C transaction.

See API: hal::serial

The Serial interface in libhal is used for hardware that implements a serial protocol like UART, RS232, RS485, and others that use a similar communication protocol but may use different voltage schemes.

In the libhal library, the Serial interface provides methods to configure the serial to match the settings supplied, to write data to the transmitter line of the serial port, to copy bytes from the working buffer into the passed buffer, and to flush the working buffer.

Unfortunately, I encountered an error while trying to access the Steady Clock interface file in the libhal repository. I'll try again to retrieve its content.

See API: hal::can

The CAN interface in libhal is used for communication over a Controller Area Network (CAN bus). This is a robust vehicle bus standard designed to allow microcontrollers and devices to communicate with each other's applications without a host computer.

In the libhal library, the CAN interface provides methods to configure the CAN bus, send a CAN message, and set a message reception handler.

Actuators

See API: hal::motor

The Motor interface in libhal is used for controlling an open loop rotational actuator. This can represent a variety of things such as a driver for a motor controller IC like the DRV8801, a driver for a motor with an integrated controller & serial interface, a unidirectional motor controlled by a single transistor, or a servo with open loop motor control.

In the libhal library, the Motor driver provides methods to apply power to the motor. The power applied is a percentage of the total available power.

See API: hal::servo

The Servo interface in libhal is used for controlling a closed loop position controlled rotational actuator. Servos are devices that can rotate to a specified position.

In the libhal library, the Servo interface provides methods to set the position of the servo's output shaft.

Sensors

See API: hal::temperature_sensor

The Temperature Sensor interface in libhal is used for reading the current temperature measured by a device. This is useful in situations where you need to monitor the temperature of a device or environment.

In the libhal library, the Temperature Sensor interface provides a method to read the current temperature.

See API: hal::accelerometer

The Accelerometer interface in libhal is used for sensing acceleration. Accelerometers are devices that measure the rate of change of velocity with respect to time (acceleration).

In the libhal library, the Accelerometer interface provides methods to read the acceleration sensed by the device in the X, Y, and Z axes.

Accelerometers are commonly used to determine orientation or tilt of an object.

See API: hal::gyroscope

The Gyroscope interface in libhal is used for sensing angular velocity. Gyroscopes are devices that measure the rotational speed around an axis.

In the libhal library, the Gyroscope interface provides methods to read the angular velocity sensed by the device in the X, Y, and Z axes.

See API: hal::magnetometer

The Magnetometer interface in libhal is used for sensing magnetic field strength. Magnetometers are devices that measure the strength and direction of a magnetic field.

In the libhal library, the Magnetometer interface provides methods to read the magnetic field strength sensed by the device in the X, Y, and Z axes.

Such devices are commonly used as a compass in order to provide heading information to a device.

See API: hal::distance_sensor

The Distance Sensor interface in libhal is used for sensing linear distance. Distance sensors can be used in a variety of applications, such as measuring the distance to an object or determining the position of an object.

In

the libhal library, the Distance Sensor interface provides methods to read the current distance measured by the device.

See API: hal::rotation_sensor

The Rotation Sensor interface in libhal is used for sensing a single axis of rotation. Rotation sensors can be used in a variety of applications, such as measuring the rotation to an object or determining the angular position of an object.

In the libhal library, the Rotation Sensor interface provides methods to read the current angle, in degrees, measured by the device.


API not available yet

A Current Sensor driver in libhal would be used for sensing electrical current. Current sensors are devices that detect the current of current flowing through a circuit.

In the libhal library, the Current Sensor interface provides methods to read the current as a unit of amperes.

API not available yet

A Voltage Sensor driver in libhal would be used for sensing electrical voltage. Voltage sensors are devices that can measure the voltage potential difference between two points in an electrical circuit. When available, the Voltage Sensor driver would provide methods to read the voltage sensed by the device.

In the libhal library, the Voltage Sensor interface provides methods to read the current as a unit of volts.

API not available yet

A GPS (Global Positioning System) driver in libhal would be used for receiving and interpreting GPS signals, which provide geolocation and time information to a GPS receiver anywhere on or near the Earth. When available, the GPS driver would provide methods to read the current location, speed, time, and other relevant data from the GPS signals.

In the libhal library, the GPS interface provides methods to read location in longitude and latitude, get the current time, and get velocity.

NOTE: that more investigation is needed to determine what fields make sense for this sensor and if it should be broken up into multiple interfaces like hal::geolocation, hal::heading, hal::clock and hal::velocity.

The Myth about Virtual: Understanding Dynamic Polymorphism in C++

There's a common misconception in the C++ community that dynamic polymorphism inherently requires dynamic memory (heap memory), and that it's slower or more memory-intensive than other forms of polymorphism. This is not necessarily true. Let's debunk these myths.

Dynamic Polymorphism and Dynamic Memory

Dynamic polymorphism in C++ is achieved through virtual functions, which allow us to override functions in derived classes. This provides a way to use a base class pointer or reference to call the appropriate function based on the actual object type at runtime.

While it's true that dynamic memory can be used in conjunction with dynamic polymorphism (for instance, when creating objects of derived classes and storing them in base class pointers), it's not a requirement. You can have dynamic polymorphism without dynamic memory.

Consider a scenario where you have a base class Base and a derived class Derived. If you have a function that takes a reference to Base, you can pass an instance of Derived to that function without needing to allocate Derived on the heap:

void someFunction(Base& baseRef) {
    baseRef.someVirtualFunction();
}

Derived derived;
someFunction(derived);  // No dynamic memory involved

In this case, someFunction will call the correct version of someVirtualFunction based on the actual type of the object, even though it's passed as a reference to Base. This is dynamic polymorphism in action, without any dynamic memory.

Performance of Dynamic Polymorphism

Another myth is that dynamic polymorphism is slower than other forms of polymorphism, such as using a struct with function pointers (akin to C-style interfaces). In reality, the performance difference is negligible in most cases.

When a virtual function is called, the compiler needs to look up the function address in the virtual table (vtable) of the object. This is essentially a pointer dereference, which is the same operation needed to call a function through a function pointer in a struct. Therefore, the performance of these two approaches is comparable.

Memory Overhead of Virtual Polymorphism

The memory overhead of dynamic polymorphism is also often overstated. Each class with virtual functions has a vtable, which is essentially a static array of function pointers. This vtable is shared among all instances of the class, so it doesn't increase the per-instance memory overhead.

The vtable does increase the size of the binary, but the increase is usually small. Each vtable entry is just a function pointer. For a class hierarchy with a reasonable number of virtual functions, this overhead is typically negligible.

In addition to the function pointers, the vtable contains two more pointers: one for the parent class's vtable (for supporting inheritance) and one for Run-Time Type Information (RTTI, used for dynamic_cast and typeid). These are fixed overheads per class, not per object.

In conclusion, while dynamic polymorphism in C++ does have some overhead, it's often smaller than people think. It doesn't inherently require dynamic memory, its performance is comparable to other forms of polymorphism, and its memory overhead is typically small. Therefore, it's a powerful tool for creating flexible and reusable code in C++.