Question

My question

I built an inverted pendulum on an Arduino using C (ie. everything was done procedurally). I'm trying to self study application design and would like to refactor my code into a more OO approach with SOLID, loose coupling, and testability in mind.

How can I improve this design to achieve a more loosely coupled system and better testability?


UML

UML-class-diagram


Brief summary of classes

MotorController - An interface for motor controllers.

DrokL928 - Is the motor controller I use, implements MotorController.

Cart - Is just a convenient wrapper for DrokL928. Allows for more intuitive control of the cart.

Encoder - An external library for reading values from rotary encoders.

EncoderWrapper - A wrapper for Encoder. That way I encapsulate the external API into one place.

StateVector - Holds the current state data.

StateUpdater - Processes encoder values from EncoderWrapper and assigns them to StateVector.

LQRController - Computes the PWM signal (based on the current state) to send to the Cart in order to stabilize the pendulum. see: Wikipedia's linear-quadratic-regulator.


Specific design questions

  1. StateUpdater uses constants IDLER_PULLEY_RADIUS and SYSTEM_LOOP_RATE. These smell like they don't belong inside StateUpdater, but they are relevant to the calculation of the state. I suppose all of those private methods inside StateUpdater could be put into a separate class StateCalculator?

  2. LQRController uses the constant pendulumBound. That is, the LQR controller should only calculate the input if the pendulum angle is within a certain bound. For some reason I feel like this doesn't belong here, but maybe I'm wrong on that. For the sake of completeness, maybe I should add a bound for each variable in StateVector.

  3. As of right now I can't instantiate EncoderWrapper into a test harness because it requires a reference to an Encoder. And by extension, I can't instantiate a StateUpdater into a test harness. How can I fix this?

  4. In the most ideal case, I would like for StateVector to not be so hardcoded and the variables should be open to modification. Therefore, LQRController should have a gainVector of any length (right now it is hardcoded to 4). Is this too much abstraction? If not, how can I go about achieving this? Now that I think about it, I believe it would be too much abstraction because then I'm not sure how the StateUpdater would calculate the state on arbitrary state variables, because the algorithm is very specific.

Was it helpful?

Solution

One of the hallmarks of good design is that it is mostly self-explanatory, even to someone without much knowledge about the domain. It so happens that I had no idea what an inverted pendulum was before reading your question, so I tried to gather some insights from your diagram, but unfortunately Cart seemed to be the only object that was possibly related to your problem domain.

After quickly reading about inverted pendulums on Wikipedia, the details of your design started to make more sense but then I realized that you were missing abstractions such as Pendulum, and instead had the state and behavior scattered between StateVector and StateUpdater.

My suggestion would be to think about your system outside-in and focus on the concepts you are trying to model rather than how they are implemented. For example, you mentioned:

Cart - Is just a convenient wrapper for DrokL928. Allows for more intuitive control of the cart.

Cart isn't just a "convenient wrapper", it is one of the primary things you are trying to model. A cart has a position and velocity, and its movement can be controlled. Encoder and MotorController are implementation details used to obtain the current state and control the movement, respectively.

Similarly, you would have a Pendulum class with angle and velocity properties, again using an Encoder internally to obtain these values.

And finally, an InvertedPendulum class that represents the entire system consisting of a Cart and a Pendulum, and uses a Controller to stabilize the system.

This eliminates StateVector and StateUpdater from your design since all the pieces have been moved into more meaningful objects.

As of right now I can't instantiate EncoderWrapper into a test harness because it requires a reference to an Encoder. ... How can I fix this?

By creating an interface just like you did for MotorController. The real implementation will call the external library, and you can create one or more fake implementations that will simply return canned responses for your tests.

OTHER TIPS

Not a full-fledged answer. But since you are designing an embedded system (and by coincidence I just worked on an Arduino supported pendulum): These are usually large state machines. The class design is rather trivial since the controllers have anyway similar interfaces. So that's the minor part. The big one is the desgin of the state machine. And of course since you are dealing with a real-time application you will find that you can not always follow the gods of good design in favor of needed performance :-/

Regaring test: This is especially tricky for RT applications. By adding test you distort the behavior of the system. E.g. I added a fast buffer logger that could eventually be read post mortem. Doing fancy debugging is simply not possible since physics does not allow to stop motion when you want to look at it.

Licensed under: CC-BY-SA with attribution
scroll top