Gem #115: Lego Mindstorms Ada Environment — Part 2
by Pat Rogers —AdaCore
Let’s get started…
The Mindstorms NXT “brick” contains both a 32-bit ARM processor and an 8-bit AVR processor. The ARM executes the application code, while the AVR is responsible for the lower-level device functionality, including controlling outputs, gathering sensor inputs, and even shutting down the brick itself.
The two processors coordinate by sending an unending stream of messages to each other. Upon initialization, the AVR begins sending messages containing information such as the current buttons pressed, if any, and the battery status, among other data. The ARM sends messages back to the AVR, for example to set power levels on outputs for motors or to command an analog-to-digital conversion to take place. The messages go back and forth continually at a fixed rate. In other words, they are not event-driven, although their content certainly reflects external events and internally generated commands. Look in the “drivers” directory for the package body of NXT.AVR if you want to see how these messages are sent and received. The one task declared in the entire API is located in that package body for that purpose.
The timing constraints on message handling are fixed by the hardware and are very strict. It is therefore possible for messages to be lost or garbled occasionally. A checksum is calculated to detect these errors, resulting in the problematic message being discarded.
The Ada API hides all these details behind high-level abstract data types for the sensors and motors, and procedural interfaces for the other lower-level facilities such as the A/D converter and discrete I/O capabilities of the ports. Nonetheless, application code must be written with some of the above architecture in mind. Specifically, incoming data are not available until the AVR has been initialized and has sent at least one message to the ARM. Although the AVR is initialized automatically by the Ada drivers, the application programmer must still await receipt of that message. For example, the current voltage of the battery is stored in a variable declared in the NXT.AVR package. The value of that variable is dependent upon arrival of a message from the AVR and is undefined beforehand. The accessor functions that decode the voltage representation simply process the variable as if the value is already defined. (There are ways to mitigate use of the undefined value, of course, but none are ideal.) Other values, such as the raw button readings, are also stored as variables that are set by the decoded AVR messages.
Therefore, the NXT.AVR package provides a procedure that awaits AVR initialization and initial message receipt. That procedure is named Await_Data_Available. A call to this procedure should typically be the first thing done by the application.
Another procedure is provided to power down the brick for those applications that are intended to complete (unlike, say, embedded control systems that only stop when external power is removed). This is procedure Power_Down, also found in the NXT.AVR package declaration. The AVR is responsible for actually shutting down the power, so the procedure injects an appropriate message to the AVR into the message stream. However, as mentioned, messages can be lost or garbled, so the power-down request can be lost. As a result, it is best to use an infinite loop that calls Power_Down repeatedly. In effect, the loop “exits” when the AVR disconnects power to the brick. For example:
loop NXT.AVR.Power_Down; delay until Clock + Seconds (1); end loop;
The absolute delay statement emulates a relative delay (as the Ravenscar subset doesn’t include relative delays) by calling the Ada.Real_Time.Clock function and adding an interval to it. The duration of the interval is arbitrary and need not be one second.
Note that the NXT.AVR package also detects the situation in which the Power button on the brick is held down for an interval, and will shut down the brick automatically in response.
In summary, although the API goes to some lengths to hide the gory details of the ARM/AVR interactions, ramifications of the message-based architecture remain visible.
In the next Gem in this series we will explore the high-level abstract data types representing the sensors and motors.