Upcoming Events:

Dancebot - Dancebot Software Updates

Matthew again. One of our firmware members recently completed a driver for the LED eyes that will be installed on the new Baby Dancebots. This is a pretty cool driver since it’ll provide the prototype functionality for us to extend code for all sorts of Adafruit NeoPixel lighting drivers that are part of our other Demobots, like the Mothership eyes and the Tower of Power disco ball, windows, and clock face.

I’ll be talking more about this driver today, and explaining a little bit about how we’re designing for extensibility and plug and play for our robots.


The Code

Warning that some of the code may be not be perfectly formatted. I blame our syntax highlighter.


The Base

First, we have a base wrapper class that provides the basic skeleton we want all of our relevant NeoPixel drivers to use.

#pragma once
#include <Adafruit_NeoPixel.h>

/**
 * A wrapper around the Adafruit Neopixel class with a bare bones set of
 * functionality.
 */
class NeoPixelBase {
    public:
        /**
         * The base constructor for a generic NeoPixelBase object.
         *
         * @param[in] pin The pin of the uC that any control signals should be
         *            sent from.
         * @param[in] numPixels The number of LEDs that will be commanded by
         *            this object.
         */
        explicit NeoPixelBase(const int pin, const int numPixels);

        /**
         * Sets the brightness of all the pixels in the object at once.
         *
         * @param[in] brightness The brightness of each NeoPixel in the object.
         */
        void setBrightness(const int brightness);

        /**
         * Turns on the object and puts it into a DEFAULT state.
         * It can be overridden by derived classes to define what exactly
         * DEFAULT does.
         */
        void startup();

        /**
         * Shuts down the object and resets any internal variables not tied to
         * the constructor.
         * It can be overridden by derived classes to define what exactly
         * should be shut off.
         */
        void shutdown();

        /**
         * Sets a new state, or command for the object to perform. The commands
         * are strictly limited to those predefined by the developer.
         *
         * @param[in] state The state the NeoPixelBase should be in.
         */
        void updateState(const State state);

        /**
         * Returns the state enum of the current operating state.
         *
         * @return State The current state of the NeoPixelBase.
         */
        State getState();

    public:
        /**
         * Our default state enum. This is used for sending discrete commands to
         * the class to update the internal state.
         *
         * Derived classes will override this enum.
         */
        static enum State{
            DEFAULT
        };

    private:
        /** We utilise the Adafruit Neopixel object to execute HAL functions. */
        Adafruit_NeoPixel pixels;

        /** By default, we set our base state to DEFAULT. */
        State state = State::DEFAULT;

        /**
         * The brightness of our pixels. setBrightness converts its int
         * parameter into a float.
         */
        float brightness;
}

This wrapper class gives us a bit of insight into the operation of the drivers we are developing for the Dancebots and potentially other Demobots. In particular, we are using a state scheme, where the backend operation of the class is dependent on its current state.

There’s less customizability to this method (i.e. the user can’t specify specifics to turn on whenever they want), but it allows developers greater control over what can and will happen, which simplifies debugging and prevents unwanted behavior.

For our application, where we want the Dancebot to use a set of discrete expressions, this is perfect.


Building on the Base

Next is the derived class that is inheriting NeoPixelBase and is defining the actual actions our specific hardware (the NeoPixel Jewel) can do.

#pragma once
#include <math.h>

#define PI 3.142
#define State Expression

class DBEyes: public NeoPixelBase  {
    public:
        /**
         * The derived constructor, which sets up the NeoPixelBase for the
         * NeoPixel Jewel hardware.
         *
         * @param[in] pin The pin on the uC to control the NeoPixels.
         */
        explicit DBEyes(int pin);

        /**
         * We overload this function with our own brightness mapping function.
         * Our mapping function will result in an output of [0, 150].
         */
        void setBrightness(const int brightness);

        /**
         * We overload this function to implement specific tasks that correspond
         * with the overloaded State enum below.
         */
        void updateState(State state);

    public:
        /**
         * The overloaded state enum defines the expressions all DBEyes derived
         * classes will utilize for operation.
         */
        static enum State{
            DEFAULT,
            CLEAR,
            LOADING,
            HAPPY,
            SAD,
            LOWKEY,
            ANGRY,
            WINKY,
            BLINK
        };

    private:
        /**
         * This private method is used by some States to cycle the brightness
         * in a sinusoidal pattern over time to make it look like the eyes
         * are 'breathing'.
         */
        void cycleBrightness();

        /**
         * Selectively turns off specific NeoPixels to make the robot appear as
         * if its blinking over time.
         */
        void blink();
};

There’s a couple more new things that are introduced in this class. Probably the most important is that we overloaded the State enumerator, which originally had the DEFAULT state, with a bunch of new states. In particular, those states have another alias, Expression. I could technically write the following code and have it be valid:

dbEyes.updateState(Expression::HAPPY);

Cool.

Anyways, we’ve also overloaded a couple methods with our own implementation and added new ones. For the latter, we added two new methods, cycleBrightness and blink. What these methods do are in their docstring comments.

The private methods and variables (we don’t have any defined here right now) defined in this derived class are specific to this class. We will have different private methods and variables for the Mothership eyes or the Robotathon Tower of Power disco ball.

There’s one more thing I’ve left out of this implementation.

For methods like cycleBrightness and blink that operate over time, how exactly do they work? We only call updateState once per expression change. We can take advantage of interrupts to do this work for us (and free up CPU processing time to do other things as well rather than stall on polling loops).

What that essentially means is that in updateState, we can set an interrupt to trigger cycleBrightness or blink every X ms after we leave the method. This runs in its own context and allows us to do other things in the main thread between interrupts, like actuate motors or manage networking data to get the next state command.


Finally…

I didn’t show any implementation code here since it would probably take two more blog posts to cover if I get around to it. Perhaps I’ll do that in the next post, and have a gif to show y’all to boot!

Thanks for sticking around and reading this.

Author: Matthew Yu