Making robots with Ada
Device Drivers Required
Before we look at the device drivers that will be required to work with the sensors, we should first introduce the constructs of the Ada language that are going to be referenced in that examination. This introduction will not be comprehensive by any means, but will serve to make the discussion understandable to those unfamiliar with Ada. Note that free learning material is available on the AdaCore website. See http://university.adacore.com/ for this material.
In Ada, “packages” represent static modules. Ada packages contain declarations for anything required: types, constants, variables, procedures, functions, tasks (threads), and so on. Packages support strong separation of interfaces from implementations because there are two distinct textual parts to them: the package declaration, known colloquially as the “spec,” and the package body. The package spec contains the declarations for entities intended to be made available to client code (i.e., other code making use of the code in question), whereas the body contains the implementations corresponding to those declarations. Moreover, the package spec and body provide compile-time visibility control: the compiler will not allow client access to the entities located in the package body.
In a very real sense the package body is like the private part of a C++ class. Similarly, the package spec is like the public part of a C++ class, except that it can be further divided into a part at the end into which clients do not have visibility. In Ada terms, this part at the end of the spec is known as the “private” part, but in C++ terms it is more like the protected part of a class because under certain circumstances other, directly related units do have visibility into it.
Recall that we said that packages are static modules. A C++ class serves that purpose but is also used to define types. In Ada we use explicit type declarations, distinct from the package construct, to define types. There are many kinds of types allowed: enumeration types, numeric types, array types, task types, among others. Perhaps the most important kind of type is the so-called “private type.” A private type in Ada is one in which clients do not have compile-time visibility to the representation and, therefore, cannot invoke representation-dependent operations. This is the classic “abstract data type” (ADT) that has been so influential in programming language design, including that of Ada, C++, and Java.
In Ada, to achieve compile-time visibility control for a type’s representation we combine the package construct with the type construct. Specifically, we declare the type in the visible part of the package spec but only give the full representation of the type in the package private part. Doing so allows clients to use the type, e.g., to declare variables, but does not allow them to access the internal representation of those variables. For example, consider the GPIO facility on the typical ARM board. There will usually be a number of GPIO “ports” defined, each containing sixteen pins. The GPIO port can be represented in Ada as a private type (simplified greatly for the sake of illustration):
Line one contains the start of the package spec, for a package named “GPIO” in this case. (The actual package in the driver library has a slightly different name, as will be seen shortly.) Line three contains the partial declaration for the “GPIO_Port” private type. The declaration is located in the visible part of the package so clients have compile-time visibility to the type name. They can use that name in their client code to do whatever is defined for the type. However, clients do not have visibility to the type’s representation because that part occurs on lines seventeen through nineteen, in the private part of the package (which starts on line fifteen). There, the full type for GPIO_Port is a “record type” that corresponds closely to a struct in C++. Clients cannot treat an object of the type as a record, nor can they access the components within it, due to the visibility control afforded by the package.
In this case, and in many cases, allowing copying via the assignment operation does not make sense. In C++ we would hide the assignment operator declaration to prevent client calls. In Ada, we achieve that effect by adding the “limited” property to the type declaration on line three. The type is both limited and private.
The operations defined for the ADT are the procedures and functions declared in the visible part of the same package, in this case on lines nine and eleven (and thirteen). Along with many other capabilities, the package allows us to “set” and “clear” a port’s pins, i.e., to drive them high or low. Of course, we can also query whether they are set or cleared, configure them, and so forth.
Using Device Drivers
Now that we have established the basics of abstract data types in Ada, we can talk about the device drivers.
On any board there are often several instances of a given hardware device. For example, an MCU likely provides several hardware timers, a number of ADC and DAC devices, DMA controllers, UARTs, and so on. Each device typically consists of a collection of registers located at addresses defined by the MCU vendor. Abstract data types are ideal for representing hardware devices. In particular, we can define the representation and operations once, and then have as many variables of the type as there are hardware instances provided by the vendor. Each individual variable can be placed precisely at the address corresponding to the device’s location. As an ADT, the compiler ensures that clients do not access the registers directly. That way, if the driver is not working correctly, the only place the bug could be is in the package that defines the driver ADT, because only that package has the necessary compile-time visibility to operate on the registers.
Therefore, the Ada Drivers Library provides a large number device drivers in the form of abstract data types. There are many other packages provided as well, at higher levels than the device drivers, but we will focus on the drivers.
The ADL provides a complete interface for the GPIO input/output pins as an ADT declared in package STM32.GPIO. (The “STM32” part of the package name indicates that the ADT is for devices in that MCU family by STMicroelectronics.) The term “pin” is used loosely here and throughout the literature. The term refers both to the general purpose input/output line seen by the MCU internally, and to the physical header pin connected to that I/O line. Moreover, these I/O lines are aggregated into GPIO ports, each port containing sixteen I/O lines. Different boards offer different numbers of GPIO ports, thus different numbers of total I/O lines. The combination of a port and an associated I/O line is represented by the type GPIO_Point in the ADL package. We simply refer to such a pair as a “pin” and are more precise only when necessary.