Making robots with Ada, Part 3 – Working with analog sensors

December 04, 2017

Pat Rogers-December 04, 2017

This is the third in a series of articles about using a much more powerful hardware and software platform to make robots using NXT® Lego sensors and motors. We’ve replaced the Brick with a modern ARM processor and used an industrial-strength programming language. As a result of replacing the NXT Brick, however, we must write the device drivers and provide some of the electronic circuitry the Brick provided. This series explores doing precisely that, with complete, executable demonstrations included.

In part one of this series, we introduced the general Brick replacement idea and then demonstrated how to use a Discovery Kit and Ada to develop an object-oriented interface to the most basic NXT sensor: the touch sensor. Part two showed how to control the NXT Lego motors with drivers developed using the ADL and a third-party hardware interface board. In this third part, we turn again to the NXT sensors, this time the analog light and sound sensors. We will use primarily the ADC and DMA device drivers provided by the ADL to implement these higher-level sensor drivers, and then demonstrate them in two distinct programs.

Our replacement hardware is the very inexpensive “Discovery Kit” products from STMicroelectronics. The Discovery Kits have ARM Cortex processors and include many on-package devices for interfacing to the external world, including A/D and D/A converters, timers, UARTs, DMA controllers, I2C and SPI communication, and others. Sophisticated external components are also included, depending upon the specific kit. For example, the STM32F429I Discovery Kit has a Cortex M4 MCU, a gyroscope, and an LCD screen (among other components). The STM32F4 Discovery Kit also has a Cortex M4 MCU, with an accelerometer instead of a gyroscope and no LCD. It is even more inexpensive than the F429I kit – approximately $15. We use both kits in this series.

Our industrial-strength programming language is Ada 2012, an object-oriented real-time / embedded-systems language widely used in modern high-integrity applications, such as commercial aircraft – for example, the Boeing Dreamliner – and high-speed trains, among many others. AdaCore’s GNAT compiler implements Ada 2012 for newer ARM targets, including a freely available Community edition for hobbyists. This complete toolchain, including a full-strength IDE, is available for download at

The previous articles introduced many of the important fundamentals of Ada 2012, the most recent version of the language standard, and some advanced concepts as well. Please refer to the other articles if you have any questions. Note too that free learning material is available at Any new language capabilities used in this article will be explained here.

The NXT Brick firmware included drivers for the timers, I2C, A/D and D/A converters, PWM, and other low-level devices required to interact with the sensors and motors. The Ada Drivers Library (ADL) provided by AdaCore and the Ada community supplies many of these device drivers for a variety of development platforms, including the STM32 series boards. The ADL is available on GitHub for both non-proprietary and commercial use here:

Of course, we also require higher-level drivers for the NXT sensors and motors themselves. Those drivers are the primary artifacts developed in this article series. Although the NXT sensor and motor drivers are not part of the ADL (yet), the library does include numerous complete demonstrations for the low-level device drivers. Often a new higher-level client driver can be based directly on one or more of the lower-level driver demonstrations. That was certainly the case in this article series. These demonstrations are easily overlooked so here is the URL for them within the library on GitHub:

NXT Analog Sensors

A number of the NXT sensors are based on analog voltage values, including the sound and light sensors. Some third-party sensors are also analog devices, such as the HiTechnic gyroscope. Strictly speaking, we could have implemented the touch sensor that way, too, designating “pressed” as any value above a certain sensed voltage level, and “not pressed” as any value below that level. Using discrete inputs, though, allowed us to introduce a number of fundamental ARM interfacing topics, and their support in the ADL, in a simple manner.

Because the voltage levels are continuous, non-discrete values, we must have a way to convert them to digital values so that they can be manipulated by the software. Digitizing analog values is a widely-covered topic that has been around for many years. If you are not familiar with it, two good websites are:

The SparkFun page is good for those completely new to the topic because it has links to a number of prerequisite concepts too, but note that it does get specific to the Arduino boards.

Analog-to-Digital Conversion (ADC) Driver

Our Ada driver for converting analog signals to digital quantities in the ADL, for these targets, is in the STM32.ADC package. This package provides the abstract data type Analog_To_Digital_Converter as the primary artifact, along with many other ancillary types representing things such as the desired conversion resolution, input channels, outputs, and so forth. There are many routines provided as well. If that type name seems a bit long, don’t worry, you rarely have to write it and it is highly readable, which is where the bulk of developer time is spent. The package declaration in the ADL is here:

The device is quite sophisticated. For example, there are multiple ways to do the conversions, multiple ways to trigger them, and the device also allows DMA access, interrupt generation on various states, and many other capabilities. The STMF4xxx Reference Manual (RM0090) is the detailed reference for the ADC device but does not go into details of usage. For that information, the Application Note AN3116 “STM32’s ADC modes and their applications” (the document file name is cd00258017.pdf) is available online from STMicro and a number of other sites too. It describes several configurations for individual ADC use, as well as much more advanced configurations in which multiple ADC units are combined to work together. We are going to use one of the basic configurations for a single ADC.

One of the more important types and routines allows clients to configure their required conversions:

1.    type Regular_Channel_Conversions is
2.      array (Regular_Channel_Rank range <>) of Regular_Channel_Conversion;
4.    procedure Configure_Regular_Conversions
5.      (This        : in out Analog_To_Digital_Converter;
6.       Continuous  : Boolean;
7.       Trigger     : Regular_Channel_Conversion_Trigger;
8.       Enable_EOC  : Boolean;
9.       Conversions : Regular_Channel_Conversions)
10.     with
11.       Pre => Conversions'Length > 0,
12.       Post =>
13.         (if Conversions'Length > 1 then Scan_Mode_Enabled (This)) and
14.         (if Enable_EOC then EOC_Selection_Enabled (This)) and
15.          ...


For the sake of brevity we won’t discuss (much less show) all the types and routines, even for the above. Suffice it to say that the procedure configures so-called “regular” conversions, expressed as an array of individual conversion descriptions (lines 1 and 2). The array type is “unconstrained” so the client can have many, a few, or only one conversion description in the array value passed to the procedure (line 9).

Note the parameters for specifying whether the conversions are to be performed continuously or one-time only (line 6), and how the conversions are triggered (line 7). Triggering can be by software or by hardware, for example by a hardware timer for periodic conversions, or other ADC units in a combined mode.

Having configured the conversions to be performed, the client then starts the conversions on that ADC:

1.   procedure Start_Conversion (This : in out Analog_To_Digital_Converter) with
2.     Pre => Enabled (This) and 
3.             Regular_Conversions_Expected (This) > 0;
4.   --  Starts the conversion(s) for the regular channels


The precondition expression on line 2 indicates that, before calling this procedure, we must enable the given ADC object and, on line 3, must have configured at least one “regular” conversion.

After starting the conversion(s) on an ADC we can poll a status flag for their completion, or wait for a completion interrupt. Even better, we can have the ADC device trigger a direct memory access (DMA) transfer from the ADC to one or more memory locations specified by the user. The DMA approach is highly advantageous, since as a separate hardware unit it requires no CPU cycles to transfer the converted values.

There is much more that Analog_To_Digital_Converter objects can do but the above should suffice for now. We will explain additional routines and types as they are encountered in the drivers.

NXT Input Port Circuit

The Ada Drivers Library provides the required ADC conversion capability similar to that provided by the NXT Brick’s firmware, but the Brick also provided necessary interfacing circuitry on the Brick input ports. We must replicate that circuitry since the Brick is no longer present. Fortunately, this “circuit” is as simple as it can be: a single pull-up resistor on one of the input lines, as shown in Figure 1:

Figure 1: NXT Input Port (Source: Lego)

Figure 1 is based on the schematics provided by the Hardware Developer Kit, freely available from Lego for download, here:

As Figure 1 shows, each Brick’s input port had a 10K pull-up resistor attached to line 1 so we must provide the same resistor on whatever we use for that line. This pull-up resistor is essential. Incoming voltage levels are incorrect without it. For our purposes we just put the resistor on a breadboard and ran jumper wires to it from the power pin header and the GPIO pin header attached to line 1.

Figure 1 also indicates that lines 2 and 3 are ground lines, line 4 provides power (+4.5V) to the sensor, and that lines 5 and 6 are digital I/O lines whose use varies with the kind of sensor attached. The NXT sound sensor uses both digital lines, for example, whereas the light sensor only uses one of them.

To connect the sensor to our Discovery Kit header pins we cut off the connector from one end of a standard NXT cable to expose the internal wires, stripped them, and then crimped female connectors to them. These can then be plugged directly into the header pins on the target boards. The other end retains the standard NXT connector so we can plug it into any standard sensor or motor.

We must connect at least one of the red or black ground lines to a ground pin on our target board headers, but either line is sufficient. (Connecting both is easy, so we did.) The green power line must be connected to a +5V header pin, otherwise there will be no voltage to sense on input line one. Line 1 is our analog input so it will be connected to a GPIO pin, as will the two discrete input lines. As mentioned earlier, the analog input line must also have the 10K pull-up resistor attached. The discrete input lines need no external pull-up resistors.

We will show the specific GPIO pin selections for the analog and discrete input lines when we discuss the demonstration programs. No other external electronics are required.

Continue reading on page two >>


< Previous
Page 1 of 3
Next >

Loading comments...