Skip to content

Animation Framework

Aron Nopanen edited this page May 1, 2017 · 2 revisions

Introduction

The portal core, consisting of multiple (8 or 9) separate LED strips, will be driven by a single Arduino instance. As such, the Arduino will need to manage animations of all of these LED strips simultaneously. I.e., we cannot make use of blocking 'sleep' calls to orchestrate an animation.

I've adopted a strategy of creating animation implementations with a non-blocking doFrame method that can be called at arbitrary points in time. An animation instance manages a single strand of LEDs, and the doFrame method is responsible for 'drawing' the state of the LEDs in that strand at whatever point in time it is called. The animation remembers the time at which it was first started, and then based on the current time it is able to set LED states appropriately.

The main Arduino loop then simply iterates through the managed animations calling doFrame on each in sequence (interspersed with servicing input on the serial port dictating new portal states). (Prior to invoking doFrame on an animation, it sets the appropriate I/O pin to be active.) This loop runs as fast as it can: i.e., it does not use on a set frame rate, but instead relies on the animations being able to draw themselves appropriately at arbitrary points in time.

Realization

From an OO design perspective, it would make sense for each animation to be implemented as a class containing requisite state associated with that animation, and an instance of the class could be created and disposed of as necessary. Given the Arduino's constrained environment, this has been modified somewhat. Specifically, the state and behavior of the animations have been divided into separate classes/structs. Each animation type has an associated 'state' class (deriving from a CommonState class which contains state relevant to all animations. The animation class itself contains only behavior (i.e., method definitions); these methods accept a reference to an instance of the relevant state class. Animation classes all derive from an abstract Animation class providing some common behavior and a polymorphic interface that can be invoked by the caller. (Technically speaking, the Animation subclasses do contain state in the form of vtables for the virtual method definitions, but these are small, and Animation class instances are singletons.

The state classes for each animation are gathered into an AnimationState union, providing a convenient way to allocate memory to contain state for an arbitrary Animation. The Animation subclasses are gathered into an Animations struct, a static singleton instance of which is allocated by the program.

Animation Engine

The main Arduino service loop must manage Animation and AnimationState instances. Basic requirements are:

  • Support independent animations for multiple strands of LEDs
  • Support sequencing of animations for certain portal transitions (e.g., when a portal goes neutral, show a brief 'red flash', followed by a constant white color

For each strand of LEDs, a small queue/circular buffer of animations is maintained. If there is a single animation in the queue, it is repeated indefinitely. If there are multiple animations in the queue, each animation is played for one cycle (as defined by the animation itself), and then the final animation is repeated indefinitely.

The queue contains a pointer to the (singleton) Animation instances associated with the desired animation. However, as discussed above, the state associated with animation instances is stored separately from the Animation class instance itself. This is accommodated by storing an array of AnimationState unions for each LED strip being managed. These arrays are the same size as the circular buffer of Animation * instances mentioned above. The circular buffer implementation provides visibility to the index within the internal circular buffer associated with the Animation * in question. Calling code makes uses of that index to find the appropriate AnimationState instance to use in each case.

Utilities

Color

NeoPixel LEDs use a 32-bit data type to encode color (with 8-bit red, green, blue, and optionally white components). In order to provide a convenient way to deal with this representation, I've defined a Color union, which provides access to both the individual 8-bit components and the 32-bit integer value. Animation methods accept and AnimationState members store instances of Color rather than uint32_t bitpacked values or uint8_t component values.

In addition, several overrides of method ToColor are provided to create Color instances from RGB or RGBW components, or from 32-bit bitpacked representations.

Clone this wiki locally