views:

113

answers:

4

I have two classes that I'm using to represent some hardware: A Button and an InputPin class which represent a button that will change the value of an IC's input pin when it's pressed down. A simple example of them is:

template <int pinNumber> class InputPin 
{
  static bool IsHigh()
  {
     return ( (*portAddress) & (1<<pinNumber) );
  }
};

template <typename InputPin> class Button 
{
  static bool IsPressed()
  {
    return !InputPin::IsHigh();
  }
};

This works beautifully and by using class templates, the condition below will compile as tightly as if I'd handwritten it in assembly (a single instruction).

Button < InputPin<1> > powerButton;

if (powerButton.IsPressed())
   ........;

However, I am extending it to deal with interrupts and have got a problem with circular references.

Compared to the original InputPin, a new InputPinIRQ class has an extra static member function that will be called automatically by the hardware when the pin value changes. I'd like it to be able to notify the Button class of this, so that the Button class can then notify the main application that it has been pressed/released. I am currently doing this by passing pointers to callback functions. In order for the callback code to be inlined by the compiler, I believe that need to pass these function pointers as template parameters. So now, both of the new classes have an extra template parameter that is a pointer to a callback function. Unfortunately this gives me a circular reference because to instantiate a ButtonIRQ class I now have to do something like this:

ButtonIRQ<InputPinIRQ<1, ButtonIRQ< Inp.... >::OnPinChange>, OnButtonChange> pB;

where the ...... represents the circular reference.

Does anyone know how I can avoid this circular reference? I am new to templates, so am probably missing something really simple.

ps. it's important that the compiler knows exactly what code will be run when the interrupt occurs as it then does some very useful optimisation - it is able to inline the callback function and literally inserts the callback function's code at the exact address that is called on a h/w interrupt.

+1  A: 

I would change the design so that a button is associated with an input pin:

class Button
{
  boost::smart_ptr<InputPin> p_input_pin;
}

Or change the pin class so that it has a list of subscribers. This may be the better design since it allows many subscribers to be notified when the pin has changed its value. You could also add a method (setter) that sets the pin's value.

Use case 1: Button press.
Button Press changes state of input pin.
Input pin notifies subscribers of event.

Use case 2: Interrupt.
Interrupt {object} changes state of input pin.
Input pin notifies subscribers of event.

The Subscriber / Publisher design pattern suits these use cases well.

Thomas Matthews
I might be wrong, but with the traditional subject/observer, aren't pointers to the observers' functions stored in a table which is iterated at run-time? This unfortunately bloats my code and forces me to return to C-style non-object orientated programming. It should be possible to do this - it's deterministic and the compiler knows at compile-time which code should be run when the interrupt occurs. But how do I get it to inline the code, without using function pointers as template parameters? (which leads to the circular reference)? Can I instantiate the classes a different way maybe?
TimYorke34
A: 

I think this might be a case of premature optimisation?

Why do you think you need to have the compile down to straight code, rather than dispatching at runtime?

Douglas Leeder
I am trying to write some very low level drivers that will be used as the basis for every single one of my systems. It might sound drastic, but if an interrupt handler's code is not inlined, the compiler has to save every single register before calling the handler function (that might just set a single bit for example), and then restore all the registers again - maybe a 10-fold increase in time? I can't afford this as the basis for all my embedded systems.
TimYorke34
Why can't you afford it? Your current code doesn't work, so you can't have profiled it?
Douglas Leeder
Hi, the processors I'm working with have as little as 1K. I know of some people who would laugh if I said that I was writing in C instead of assembly, never mind object-orientated programming! I managed to do a proof of concept using test classes that didn't need to reference each other at instantiation (avoiding the circular ref), and so I know it will work if I can just get round the syntax.
TimYorke34
A: 

Maybe look at the Curiously Recurring Template Pattern

What if you started from this

template <int pinNumber> class InputPin 
{
  static bool IsHigh()
  {
     return ( (*portAddress) & (1<<pinNumber) );
  }
};

template <int Pin> class Button 
{
  static bool IsPressed()
  {
    return !InputPin<Pin>::IsHigh();  // might need typename here
  }
};

Button < 1 > powerButton;

if (powerButton.IsPressed())

Perhaps then it might progress to

template <int pinNumber, typename event> class InputPin 
{
  static bool IsHigh()
  {
     event::OnA();
     return ( (*portAddress) & (1<<pinNumber) );
  }
};

template <int Pin> class Button 
{
  static bool IsPressed()
  {
    return !InputPin<Pin,Button>::IsHigh();  // might need typename here
  }

  OnA(){}
  OnB(){}
};
Greg Domjan
This looks promising, but I need static polymorphism in order to use the Button class with any class that has the function "bool IsHigh()". This pin/button example is an analogy for how all my different drivers classes will interact. Buttons are not always connected to input pins.For example, perhaps the Button object needs to look at a comparator output where the physical button is connected to one of the comparator's inputs?If I pass the Pin or Comparator class to the Buttton as a typename, am I back to square one?I'll read the link and report back tomorrow.
TimYorke34
Just to get back to you, I had a look at the CRTP and although its very interesting, it doesn't seem to relate to my problem. It's to do with classes that pass themselves as a template parameter to the base class template they are deriving from. Unless I'm missing something, I don't think it will solve my problem. I'll probably make use of it elsewhere though, so thanks!
TimYorke34
A: 

In case it will help anyone else, I have worked out a way round this:

typedef Button< Pin<B7>,  &OnButtonChange > TestButton;

PinIRQ< B7, &TestButton::InputChangedISR > irqPin; 
TestButton testButton;

The Button object doesn't actually need to know anything about the PinIRQ's interrupt, so I can just declare it using the original (interrupt-less) Pin class, which needs nothing Button related passing as a template parameter. The I can instantiate a full PinIRQ class using this Button declaration and everything works perfectly.

I'm deriving the PinIRQ from the Pin object in order to allow me to "overload" the class template to give me Pin<int PinNumber> and PinIRQ<int PinNumber, void (*ISR)()>

Its not the nicest piece of code I've ever written, but it works at least.

TimYorke34