Motion Commands and the Control of Effectors
Real-time systems are slaves to the clock. They achieve the illusion of smooth behavior by rapidly updating a set of control signals many times per second. For example, to smoothly turn a robot's head to the right, the head must accelerate, travel at constant velocity for a while, and then decelerate. This is accomplished by making many small adjustments to the motor torques. Another example: to get the robot's LEDs to blink repeatedly, they must be turned on for a certain period of time, then turned off for another length of time, and so forth. To get them to glow steadily at medium intensity, they must be turned on and off very rapidly.
The robot's operating system updates the states of all the effectors (servos, motors, LEDs, etc.) every few milliseconds. Each update is called a "frame", and can accommodate simultaneous changes to any number of effectors. On the AIBO, updates occur every 8 milliseconds and frames are buffered four at a time, so the application must have a new buffer available every 32 milliseconds; other robots may use different update intervals. In Tekkotsu these buffers of frames are produced by the MotionManager, whose job is to execute a collection of simultaneously active MotionCommands (MCs) of various types every few milliseconds. The results of these MotionCommands are assembled into a buffer that is passed to the operating system (Aperios for the AIBO, or Linux for other robots).
Suppose we want the robot to blink its LEDs on and off at a rate of once per second. What we need is a MotionCommand that will calculate new states for the LEDs each time the MotionManager asks for an update. LedMC, a subclass of both MotionCommand and LedEngine, performs this service. If we create an instance of LedMC, tell it the frequency at which to blink the LEDs, and add it to the MotionManager's list of active MCs, then it will do all the work for us. There's just one catch: our application is running in the Main process, while the MotionManager runs in a separate Motion process. This is necessary to assure that potentially lengthy computations taking place in Main don't prevent Motion from running every few milliseconds. So how can we communicate with our MotionCommand while at the same time making it available to the MotionManager?
The solution is to construct MotionCommands in a memory region that is shared by both processes. Because we have continuous access to the MotionCommand, we can change its parameters even while it's active, to tell it to do different things. But it's dangerous to modify a MotionCommand while the MotionManager is in the midst of invoking it. Therefore, Tekkotsu provides a mutual exclusion mechanism called an MMAccessor that temporarily locks out the MotionManager when we need to invoke a MotionCommand's member functions from within Main. Whenever we want to call such functions, we must lock down the MotionCommand by creating an MMAccessor first. Destroying the MMAccessor unlocks the MotionCommand.
There is one remaining wrinkle to the story. When a MotionCommand is passed to the MotionManager, it is assigned a unique ID called an MC_ID that identifies it within the MotionManager's active list. To lock the MotionCommand, we must pass this MC_ID value to the MMAccessor constructor. The MC_ID is also used when we tell the MotionManager to remove this MotionCommand from its active list. So the MC_ID must be saved somewhere. Normally it is kept in a protected data member within the Behavior instance so it can be shared by the doStart, doStop, and doEvent methods.
To summarize: MotionCommands must be instantiated in shared memory. An MC_ID, which is typically stored locally in the Behavior (not in shared memory), uniquely identifies the MotionCommand within the MotionManager's active list. Certain member functions of the MotionCommand will be called repeatedly from within the Motion process, by the MotionManager, to compute updated effector states. An MMAccessor, created in Main using the MC_ID, must be used to lock down an active MotionCommand so we can safely call its member functions from within the Main process.