Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with Raspberry PI GPIO - Do I want a virtual / abstract / interface class?

Intro

I am doing some work with Raspberry PI GPIO. Until now I was writing code as you would in C, using functions to group together sections of code.

My work has got to the point where I am happy that everything is working, but now things are starting to get messy and so I would like to move towards an object oriented approach.

Problem

Here is the problem I face.

At the moment I have a class which represents my "device". (The hardware I built which is attached to the GPIO port.) The hardware has 2 distinct sections. One section is an "input" section, and the other is an "output" section.

To help you understand this better, the "input" section is an ADC. (Analog-to-digital converter.) This is a device which converts an analog signal to a 10bit binary representation. (In case you were not familiar with electronics.)

The "output" section is just a transistor which switches a set of LEDs on and off.

I wanted to have a class which represented the ADC Board, and one which represented the LED Board, as they are 2 conceptually different devices, as they are not "linked" in any way.

This causes a problem, as the GPIO pins must be set to certain values before their modes are set. By values I mean "HIGH" or "LOW", 1 or 0. By modes I mean "INPUT" or "OUTPUT". This sounds strange, but basically, the ADC will become de-synchronized if the control lines are not set to their correct LOGIC HIGH and LOGIC LOW values before it is powered up. (This is a very strange device, it does not power up until it is told to, even though it is connected to power. (VCC or VDD 5.0V) One of the control lines sends a signal to power up the device.

In order to carry out the above, consider that the GPIO pins are initially in INPUT mode. To "make the ADC work properly" we first set the data values (HIGH / LOW) which are to be present on the pins BEFORE they are changed to OUTPUT mode. This way, when the mode is changed from INPUT to OUTPUT the data is present which the correct values, and we won't upset the ADC.

My initial idea was to have a constructor for the ADC which firstly sets the values for output data and then changes the requires pins from INPUT mode to OUTPUT mode. But this forces us to construct the ADC Board class before the LED Board class.

This can be fixed by both constructors carrying out the same code to set the output modes, but this seems like a bad idea because we are calling 2 bits of code twice - not a very elegant solution.

Another solution is to have a GPIOPort class which combines the input and output devices together, but this isn't very elegant either and it would be difficult to modify if we were to ever add a second, identical, LED Board. (For example.)

What I think I want but I'm not sure...

I think what I want is another class which represents the GPIOPort itself. (A kind of abstract idea, I guess?) Then I think I want "a class within a class" to represent the ADC Board and "a class within a class" to represent the LED Board, also. I can't remember what this technique is called, but usually the "outer class" is like a shell, with a pointer to an object of the type which is the "inner class" and a create method and a destroy method. The outer class does something like pointer = new type; in the create method and delete pointer in the destroy method. This allows the constructor to be called when required, and the class destructor to be called when required.

The point of this is that the GPIOPort class constructor handles the order in which these objects are created, which hides everything from main(). Within main, the programmer just does something like GPIOPort myGPIOPort;, and this handles everything you need, so you don't have to include 20 lines of code in main() to setup the data for the output pins, which is the only other solution. (Which I didn't mention above.)

Questions

So my first question is what is this technique known as? I thought it was called a wrapper class, but my understanding is a wrapper class is for using fundamental types like double and int as objects. (And adding methods like clear() or reset() or something like that.) Is this what I actually want to be doing, or is there a better method? (I guess it comes down to "how do I fix my problem".)

My second question is that, as far as I can remember, I have to make some of the methods (the destructor?) virtual methods, but I can't remember why. (Or perhaps I don't, and I'm just confused.)

My third question is are there any examples of this which I can use to help myself understand it, or alternatively, where can I go to improve my understanding. (Resources.)

Thanks, obviously this is quite a long question. I tried to include as much information as I could to help explain the situation. If you want clarification, then I'll try and improve what I said.

Edit: More info on devices

The data must be sent to the GPIO pins before their mode is changed from input to output.

The GPIO pins look like all zeros, as there are pull-down resistors on the pins, and they are still set as inputs. The data which as been sent does not appear until their mode is changed.

The pins are then set to output mode. (Or some of them are anyway.) Now the data which was sent appears on the pins.

If the pins are set to output mode before the data is sent, we cannot prevent the ADC from powering up, as the data pin which controls the powering up of the ADC may be set to HIGH. It may be set to LOW, but there is no way of telling, its state is undefined, until we tell the GPIO what values we want before setting the mode to output. Luckily, we can guarantee that all the pins will be in input mode.

like image 919
FreelanceConsultant Avatar asked Jul 15 '14 12:07

FreelanceConsultant


People also ask

What precautions should be taken with Raspberry Pi GPIO pins?

Be careful with the GPIO pins: It is very difficult to find a given pin by counting, and connecting the wrong one could fry your Pi! Use a GPIO reference guide that slips over the pins if possible. If you don't have a guide, count carefully, then re-check.

Is GPIO a Raspberry Pi interface?

In essence, GPIO allows our Raspberry Pi to interface with a variety of external components, which makes it suitable for a wide variety of projects ranging from a weather station to a self-driving robot. For GPIO pins to work, software configurations will be required.

Which programming language is widely used to interface with Raspberry Pi GPIO port?

Raspberry-gpio-python or RPi. GPIO, is a Python module to control the GPIO interface on the Raspberry Pi.


2 Answers

Some definitions:

  • Device: The entire device that is attached to the GPIO (Input and Output)
  • LEDBoard: The output parts of a Device
  • ADCBoard: The input parts of a Device

I highly recomment to not use a singleton. Some day you may connect a second device to other GPIO Pins and you will be in trouble.

If you create separate classes for LEDBoard and ADCBoard, you have to ask: "What do I need to create a LEDBoard/ADCBoard?" Well... You need a Device!

So my design would be the following:

struct DeviceDescriptor
{
  int portNumber;
  // add additional variables to identify the device
}

class Device
{
  Device(DeviceDescriptor descriptor)
  {
    //Insert your initialization...
    //You can maintain a static vector of already opened Devices to throw an
    //error if an allready opened device is reopened
  }
  ~Device()
  {
    //Deinit device
  }

  // A device should not be copyable
  Device(const& Device) = delete;
  //TODO do the same for copy assignment
  //TODO implement move ctr and move assignment operator

  //TODO add needed mamber variables
}

class LEDBoard
{
  LEDBoard(std::shared_ptr<Device> device) : m_device(device)
  {
    //Do init stuff
  }

  //Your functions

  private:
  std::shared_ptr<Device> m_device;
}

 //ADCBoard is analog to LEDBoard

You can use the classes like this:

int main(void)
{
   auto device = std::make_shared<Device>(DeviceDescriptor());
   LEDBoard led1(device);
   ADCBoard adc1(device);
   //Your stuff...
}

Benefits:

  • You can have multiple Devices
  • More LEDBoards can be added easily
  • By the use of shared_ptr, the device object will be destoryed when the last LEDBoard/ADCBoard is destroyed
like image 171
Markus Mayer Avatar answered Oct 03 '22 00:10

Markus Mayer


Edit: I make a big edit of this post, because I think that's currently not the best solution. This edit is really similar to @MarkusMayer answer, but I came to it in a very different way of thinking (I think), so maybe it will help you.

First, let define a GPIO pin, which can be anything you want (a class would be good, then you can do pin.setOutput() or pin.set(), etc. I let you define it the way you want, let's just assume we have a GPIOPin class.

First, I define an abstract board as a set of pin, which look quite correct to me:

template <int N>
class Board {
protected:
    Board (std::array <GPIOPin, N> const& pins) : _pins(pins) { }
    std::array <GPIOPin, N> _pins ;
};

Then I define interface for ADC and LEDs which are also abstract:

class ADC {
public:
    ADC () { }
    float read () { }
} ;

class LEDs {
public:
    LEDs () { }
    void set (int) { }
} ;

Now I can create what represent the real board with ADC and LED:

class MyBoard : public Board <5> { // Let's assume it's connect to 5 bits
public:
    MyBoard (std::array <GPIOPin, N> const& pins) : Board<5>(pins) {
        // Here you can initialize what you want
    }
} ;

Then you create your own ADC and LED:

class AD7813 : public ADC {
    Board <5> _board ;
public:
    AD7813 (Board <5> *board) : ADC(), _board(board) { }
} ;

// Same for the LED

Finally, you can simply use it as follow:

Board <5> *board = new MyBoard(/* The corresponding GPIO pins. */) ;
ADC *adc = new AD7813(board) ;
LEDs *led = new MyLEDs(board) ;

I did not define destructor for MyBoard or Board but of course you can. You can also use shared_ptr like @MarkusMayer.

End of edit.

I think there are different approaches to this problem, I'll present here what I would have done. It is often difficult to use OO design on embedded system, first thing is that you should have singleton class almost everywhere because you've only one ADC (you cannot instanciate multiple ADC), so your ADC class (and LEDBoard class) should look like:

class ADC {
public:
    static ADC *getInstance () {
        if (_instance == nullptr) {
            _instance = new ADC () ;
        }
        return _instance ;
    }
private:
    ADC () ;
};

To answer your problem, I would create a base class that do your initialization, and will do it only once (use of a static member to know if ports are already initialized).

class GPIOs {
protected:
    GPIOs () {
        if (!GPIOs::_init) {
            /* Do what you want. */
            GPIOs::_init = true ;
        }
    }
private:
    static bool _init ;
} ;

bool GPIOs::_init = false ;

Then your ADC and LEDBoard class inherit from GPIOs:

class ADC : public GPIOs {
public:
    ADC *getInstance () { /* ... */ }
private:
    ADC () : GPIOs () { } // Call constructor
} ;

Then in your code, you simply do:

ADC *adc = ADC::getInstance () ;

You could also use a singleton for the GPIOs class but since it's an abstract class than is only used by ADC and LEDBoard which are already singleton, it's not the most useful.

I'm sure there are plenty of other ways to deal with your problem, but the main idea I wanted to show is the use of init method / class that you can call multiple time without doing mutiple initialization because of the _init boolean.

like image 23
Holt Avatar answered Oct 03 '22 01:10

Holt