Separate Logic from Electrical Connections

This is not a question, but sharing in knowledge and ideas.

Although the Arduino libraries are great for short demo's and small prototypes, I very early on got into trouble when trying to combine things. Also I wanted for instance to use the LCD library but I had mine connected through a multiplexer/shift register. Most libraries that drive (hardware) devices will allow you to configure the pins but that is about it.

So I thought I write some on how to design a more flexible library, one that separates the control logic from the actual electrical connections.

Take for instance a motor driver chip (or board - doesn't matter). The actual type doesn't matter for this example but realize that there is some logic to the way you interact with this chip. It performs certain functions, like rotate the motor clockwise or counter clockwise or stop, when it sees certain signal levels on its inputs.

When you want to use that chip in your project you need to write some code that sends the correct levels to the chips inputs in order to make it do what you want (forward, reverse, turn, stop, break). How those levels are getting there is a different matter and should be coded separately. The simplest way is to connect the chip inputs directly to the Arduino pins. But in more complex designs that arduino (or other MCU) might not have a direct electrical path to the motor chip. Like I mentioned earlier, it could be multiplexed or some other custom way of getting those signal out there.

I suggest that you write two classes in your code that each have a distinct responsibility: one implements the logic that you need to talk to the chip - lets call that the controller. The other 'just' implements the way the signals are passed to the chip- lets call that the driver. These names might need some more work, but ... XD

So basically your program talks to the Controller, the Controller uses the Driver to output the signals and the Driver knows how to get these signals to the chip.

Program -> Controller -> Driver -> ChipX

This Controller class is a template class that derives from its template parameter. This neat little trick saves you on having to implement virtuals or maintain pointers and thus saves RAM - which is good.

The Controller class might look something like this:

template<class DriverT>
class ChipX_Controller : public DriverT
{
public:
	inline void Stop()
	{
		DriverT::Send(false, false, 0);
	}

	inline void Break()
	{
		DriverT::Send(true, true, 0);
	}

	inline void Clockwise(unsigned char speed)
	{
		DriverT::Send(true, false, speed);
	}

	inline void CounterClockwise(unsigned char speed)
	{
		DriverT::Send(false, true, speed);
	}
};

The Driver class uses template parameters to pass in the pins the specific connections are made on. This driver class implements the direct connections to the Arduino pins.

template<const unsigned char In1_BoardPinNumber, const unsigned char In2_BoardPinNumber, const unsigned char Pwm_BoardPinNumber>
class ChipX_Driver
{
public:
    void Send(bool in1, bool in2, unsigned char pwm)
    {
        // code that sets the two digital and one analog outputs.
    }
};

To make it convenient in your program you can use a typedef to declare what controller-driver pair you are using:

typedef ChipX_Controller<ChipX_Driver<..pins..>> LeftWheelController;
typedef ChipX_Controller<ChipX_Driver<..pins..>> RightWheelController;

LeftWheelController leftWheel;
RightWheelController rightWheel;

// spin the wheels
leftWheel.Clockwise(100);
rightWheel.Clockwise(100);

We have two typedef's because the left and right wheel use different pins to each access different chips or different channels on the same chip.

So now when another developer wants to use your library and finds that it needs to connect to the driver chip in a different way - say with multi-plexing - he/she just writes a different Driver class.

#define IN1 6
#define IN2 7
#define PWM 9

template<class IODriverT>
class MultiPlexedChipX_Driver : IODriver
{
public:
    void Send(bool in1, bool in2, unsigned char pwm)
    {
        // Some interface for shifting out IO bits;
        IODriverT::SetBit(IN1, in1);
        IODriverT::SetBit(IN2, in2);
        IODriverT::Send();

        analogWrite(PWM, pwm)
    }
};

Now the developer can typedef his/her own Controller-Driver pair combination and still reuse the logic part that is in your library.

Because this example is so simple it demonstrates the point without getting confusing. But you can imagine that when interfacing with more complex chips that the logic part will grow more complex. So that is the part you want to reuse. Those electrical connections are dependent on what choices the design of the electronics has made and easily fixed.

I hope it helps and am looking forward to seeing more and more libraries that separate these concerns.