Making robots with Ada, Part 2 - Driving the motors
COTS Motor Interface Hardware
Rather than build our own H-bridge circuit we use a low-cost product dedicated to interfacing with NXT motors and sensors. In addition to the H-bridge circuits, they also provide filters for the rotation sensor’s discrete inputs so that noise does not result in false rotation counts. There are a number of these products available.
One is the “Arduino NXT Shield Version 2” by TKJ Electronics: http://www.tkjelectronics.dk/ in Denmark. The product is described in their blog, here: http://blog.tkjelectronics.dk/2011/10/nxt-shield-ver2/ and is available for sale here: http://shop.tkjelectronics.dk/product_info.php?products_id=29 for a reasonable price. They also provide Arduino software drivers on GitHub.
Figure 4: NXT Shield V2, top-down view (Source: TKJ Electronics)
The “NXT Shield” can control two NXT motors and a Mindstorms NXT Ultrasonic Sensor. Figure 4 shows the NXT Shield with the two motor connectors on the left and the sensor connector on the right. The headers match the Arduino headers so it can be placed on top of those boards, but of course that is not required.
The kit requires assembly from the components but it is just through-board soldering. As long as you get the diodes oriented correctly everything is straightforward. Figure 5 shows our build, already located in an enclosure and connected to the Discovery Kit, power, and two NXT motors.
Figure 5: Completed NXT Shield inside final enclosure (Source: Pat Rogers)
We supply 9 volts for the motors through a DC power jack on the back of the enclosure, seen on the left. The 5 volts for the on-board electronics comes from the Discovery Kit and is bundled with the white and green wires coming in through the left side in the figure. The enclosure itself is the typical tin box. (In this case it is one of the “Make with Ada” boxes. “Make with Ada” is a competition offering serious prize money for cool projects using embedded targets and Ada. See http://www.makewithada.org/ for more information.)
The rotation sensor in the NXT motors is an optical rotary encoder, specifically a “quadrature rotary encoder.” These devices detect rotations and generate pulses accordingly over two discrete output lines. The pulses on these lines are driven by two distinct tracks, with sectors positioned 90 degrees out of phase with each other. By counting the number of signals over some time interval one can determine the rotation rate. By comparing the leading edges of the signals (e.g., the arrival time) we can determine the direction of rotation. Figure 6 illustrates the signals, in this case with clockwise rotation.
Figure 6: Rotary encoder signals (Source: https://en.wikipedia.org/)
The NXT Shield Kit connectors include the two wires for the inputs coming from the NXT rotation sensor so we just connect them to GPIO points on the Discovery Kit.
The interface kit is completely sufficient for interfacing to the motor and its encoder hardware so now we turn to the software.
The Motor ADT
The NXT motors are represented by an abstract data type named Basic_Motor, declared within a package named NXT.Motors. (We can envision more advanced motor abstractions, e.g., with continuous speed regulation, so this is the basic model.) Based on the hardware within the motor, we need the following components: a PWM signal generator for controlling power applied to the motor, a quadrature encoder interface for decoding the rotation signals, and two discrete outputs for controlling the H-bridge (on the NXT Shield Kit) for controlling the motor rotation direction.
As an abstract data type, Basic_Motor is defined as a private type:
We have elided some of the text of the package to hide some of the operations, just to keep things simple for the introduction of the facility.
On line 6 we bring in the Quadrature_Encoders package providing the Rotary_Encoder abstract data type. (Really this is a rotary encoder decoder, but nobody calls them that.) We use that type to declare the Encoder component on line 39. Similarly, on line 4 we bring in the STM32.PWM package and use that to declare the Power component on line 40. The PWM_Modulator type is essentially a wrapper for a hardware timer from package STM32.Timers and uses a timer channel for the output. That is the Channel component of line 41. Finally, the two discrete outputs controlling the H-bridge circuit are declared on lines 42 and 43, from package STM32.GPIO. You can tell that the GPIO, PWM, and Timers packages are part of the Ada Drivers Library because their package names start with STM32, the root of a set of packages declared in that library. The Quadrature_Encoders package is not part of the library but may be, in the future.
Before examining the implementation of the Basic_Motor abstract data type, i.e., the package body, we will show the interfaces for these other ADTs.
The Rotary Encoder ADT
The interface for the quadrature rotary encoder abstract data type is as follows:
Notice that the hidden representation of the Rotary_Encoder type on line 43 is just as an access type (a pointer) able to designate a Timer object. The additional text on line 43 tells the compiler that we never intend to dynamically allocate a Timer using this type. That’s so because all the Timer objects are already declared, in package STM32.Device. That’s the Ada Drivers Library package that represents any given MCU supported, each of which can have different numbers of on-chip devices (GPIO points, A/D and D/A converters, USARTs, and so on). To dedicate a given timer to a Rotary_Encoder object, clients specify one of the Timer objects in STM32.Device as the actual parameter to a call to Initialize_Encoder, via the parameter on line 28. The two discrete inputs from the encoder hardware are connected to GPIO points that are then supplied as parameters to the procedure (lines 26 and 27).
Note the precondition on procedure Initialize_Encoder at lines 31 and 32. These two checks ensure that we are using a timer that has a 32-bit counter and that it can count both up and down – not all timers do.
The reason that a Rotary_Encoder object is just internally represented as a (pointer to) a timer is that the STMicro timers have built-in support for rotary encoders. Once properly configured, the two incoming signals act as the clock that drives the timer so the timer’s counter always reflects the current encoder count. The timer will even tell us the rotation direction. We don’t need to write any interrupt handlers or count signals, much less time them. All that our software need do is configure the timer and then sample the registers whenever we want. No processor overhead and trivial software – beautiful.
The PWM ADT
The interface of the PWM modulator abstract data type is quite involved because the underlying timers have a very extensive set of PWM capabilities. Too many to explain for our purposes here. Therefore we will show only a very small part of it, eliding the rest.
Like the Rotary_Encoder type, the PWM_Modulator type is also based directly on the built-in capabilities of the hardware timers. We need a little more information for this one so we use a record type to hold the additional components. The pertinent capability for our demonstration is setting the duty cycle. This routine sets the pulse width such that the timer’s PWM output is active for the requested percentage of time.