From: Jay Gowdy [Jay_Gowdy@flute.msl.ri.cmu.edu]
Sent: Tuesday, September 23, 2003 11:36 PM
To: G. Clark Haynes
Cc: arizzi@cs.cmu.edu
Subject: Re: hardware class redesign 

Clark,

I've actually spent about a day doing some strawman work on this over the last
week, and got some initial compilation results to clarify what will be needed.
It does get slightly more complicated than we hoped, as you will see.  I needed
to actually implement things to get an idea of how they would turn out, I hope
you don't mind.

So, the base hardware header looks like this,

#ifndef _HARDWARE_HH
#define _HARDWARE_HH

#include <utils/String.h>
#include <utils/List.h>

template <class T> class Hardware {
 private:
  struct HardwareInstance {
    utils::String name;
    T* instance;
  };

 public:
  // register the "default" instance
  static void registerInstance(T* instance) { 
    if ( _default_instance)
      MMFatalError("Hardware::registerInstance", 
                   "Cannot override default instance");
    _default_instance = instance;
  }

  // register a named instance
  static void registerInstance(const char* name, T* instance) {
    if (!name) {
      registerInstance(instance);
      return;
    }
    if (lookupInstance(name)) {
      char buffer[300];
      snprintf(buffer, 300, "Cannot override instance '%s'", name);
      MMFatalError("Hardware::registerInstance", buffer);
    }
    HardwareInstance* hwi = new HardwareInstance;
    hwi->name = name;
    hwi->instance = instance;
    _named_instances.append(hwi);
  }

  // lookup an instance by name.  NULL or no parameters looks up the default
  static T* lookupInstance(const char* name = NULL) {
    if (!name)
      return _default_instance;

    utils::ListIterator<HardwareInstance> iter(_named_instances);
    for (HardwareInstance* i=iter.first(); i; i = iter.next()) {
      if (i->name == name) 
        return i->instance;
    }

    return NULL;
    ;
  }

  static void clearInstances() {
    _default_instance = NULL;
    _named_instances.clear();
  }

 private:
  static T* _default_instance;
  static utils::ManagedList<HardwareInstance> _named_instances;
};

#ifdef HARDWARE_BODY
#define HARDWARE_IMPL(T) \
  T* Hardware<T>::_default_instance = NULL; \
  utils::ManagedList<Hardware<T>::HardwareInstance>
#Hardware<T>::_named_instances; 
#else
#define HARDWARE_IMPL(T)
#endif

#define HARDWARE_DECL(T) class T : public Hardware<T>

#endif

Then, you use the macros HARDWARE_DECL to declare a hardware item (this avoids
the user having to remember the cryptic C++ stuff to type.  

Then, what I suggest is that in a hardware header file you include
HARDWARE_IMPL(T), and then in the "initialization" file you define
HARDWARE_BODY.  This is the best compromise I can find that deals with the C++
cruft of portably needing to have the static member variables defined in some
C++ object file somewhere and to avoid making users type weird C++ incantations
in odd places (just take a look at the definition of _named_instances.  I
myself had to force a syntax error in order to figure out exactly what it
should be.  Yoiks).

Note that the Hardware support class adds static methods that support both the
"singleton" approach and the "named" approach, i.e., you can lookup and registe
r with no values and avoid the whole name lookup problem, or you can use string
names.  The C++ file I used to test this looks like this,

#include <stdio.h>

// note these are cheats to avoid including ModuleManager.hh for now
#define MMFatalError(where, what) { fprintf(stderr, "%s: %s\n", where, what);
 exit(-1); } 
typedef short uint16;

// implementations of hardware static member variables happen in this C++
// object file 
#define HARDWARE_BODY 1

#include "Hardware.hh"

// this is what would be in an encoder header
HARDWARE_DECL(EncoderHW)
{
public:
  virtual ~EncoderHW( ) { };

  virtual void   enable( uint index ) = 0; // Enable an individual encoder ch.
  virtual void   disable( uint index ) = 0;// Disable an individual encoder ch.

  virtual uint16 read( uint index ) = 0;   // Read the 16 bit value from a ch.
  virtual void   reset( uint index ) = 0;  // Reset the encoder count to zero.
};

HARDWARE_IMPL(EncoderHW);

// this is what woudl be in an analog header
class AnalogHW : public Hardware<AnalogHW>
{
public:
  virtual ~AnalogHW( ) { };

  virtual float read( uint index ) = 0;  // Return the latest analog read ( V )
  virtual void  write( uint index, float value ) = 0; // Set analog value ( V )
  virtual void  outputRange( uint index, float *min, float *max ) = 0;
};

HARDWARE_IMPL(AnalogHW);

// here is code that would go in a hardware specific library
class MyEncoder : public EncoderHW
{
public:
  virtual ~MyEncoder( ) { };

  virtual void   enable( uint index ) { printf("Enabled %d\n", index); }
  virtual void   disable( uint index ) { printf("Disabled %d\n", index); }

  virtual uint16 read( uint index ) { return 25; }
  virtual void   reset( uint index ) { printf("Reset %d\n", index); }
};
  
// and here too
class MyAnalog : public AnalogHW
{
public:
  virtual ~MyAnalog( ) { };

  virtual float read( uint index ) { return 0.0; }
  virtual void  write( uint index, float value ) {}
  virtual void  outputRange( uint index, float *min, float *max ) {
    *min = *max = 0;
  }
};

main(int argc, char** argv)
{
  EncoderHW* e1 = new MyEncoder;
  EncoderHW* e2 = new MyEncoder;
  EncoderHW* e3 = new MyEncoder;
  AnalogHW* a1 = new MyAnalog;
  AnalogHW* a2 = new MyAnalog;

  printf("Registering encoder default %x\n", (unsigned) e1);
  EncoderHW::registerInstance(e1);
  printf("Registering encoder Bank2 %x\n", (unsigned) e2);
  EncoderHW::registerInstance("Bank2", e2);
  printf("Registering encoder Bank3 %x\n", (unsigned) e3);
  EncoderHW::registerInstance("Bank3", e3);

  printf("Got encoder default %x\n", (unsigned) EncoderHW::lookupInstance());
  printf("Got encoder Bank2 %x\n", (unsigned)
  EncoderHW::lookupInstance("Bank2"));
  printf("Got encoder Bank3 %x\n", (unsigned)
  EncoderHW::lookupInstance("Bank3"));
  printf("Should be null %x\n", (unsigned) EncoderHW::lookupInstance("Bank4"));

  EncoderHW::clearInstances();
  printf("After clear\n");

  printf("Got encoder default %x\n", (unsigned) EncoderHW::lookupInstance());
  printf("Got encoder Bank2 %x\n", (unsigned)
  EncoderHW::lookupInstance("Bank2"));
  printf("Got encoder Bank3 %x\n", (unsigned)
  EncoderHW::lookupInstance("Bank3"));
  printf("Should be null %x\n", (unsigned) EncoderHW::lookupInstance("Bank4"));

  printf("Registering a/d default %x\n", (unsigned) a1);
  AnalogHW::registerInstance(a1);
  printf("Registering a/d Bank2 %x\n", (unsigned) a2);
  AnalogHW::registerInstance("Bank2", a2);

  printf("Got a/d default %x\n", (unsigned) AnalogHW::lookupInstance());
  printf("Got a/d Bank2 %x\n", (unsigned) AnalogHW::lookupInstance("Bank2"));
  printf("Should be null %x\n", (unsigned) AnalogHW::lookupInstance("Bank4"));

  AnalogHW::clearInstances();
  printf("After clear\n");

  printf("Got a/d default %x\n", (unsigned) AnalogHW::lookupInstance());
  printf("Got a/d Bank2 %x\n", (unsigned) AnalogHW::lookupInstance("Bank2"));
  printf("Should be null %x\n", (unsigned) AnalogHW::lookupInstance("Bank4"));

  delete e1;
  delete e2;
  delete e3;
  delete a1;
  delete a2;
}

So, then the question becomes, how to integrate this into the rhex lib
initialization procedure.

The first possibility that occured to me is this: In the Module manager
initialization a "initHardware" routine is invoked.  This initHardware routine
would be defined in a hardware specific library.  Unfortunately, there are
problems.  Look at the linking order:

-lhardware -lbase

base will try and get initHardware out of hardware, but hardware depends on
support routines in base.  This will not work.  On some platforms it would, but
I believe Linux is not one, and it is a maintanence and portability nightmare
waiting to happen.

Alternatively, what I suggest is actually close to the moral equivalent of what
we do now.  Replace ChooseHardware.hh with something like this,

#ifndef CHOOSEHARDWARE_HH
#define CHOOSEHARDWARE_HH
// If we are running QNX, determine which Hardware we should use.
// Note that the RHEX_HARDWARE environment variable determines this.

#ifdef _MCRHIO_
extern void initMcRHIOHardware();
#define INIT_HARDWARE initMcRHIOHardware()
#endif

#ifdef _RUGGED_
extern void initRuggedHardware();
#define INIT_HARDWARE initRuggedHardware()
#endif

#ifdef _SIMSECT_
extern void initSimSectHardware();
#define INIT_HARDWARE initSimSectHardware()
#endif

#ifdef _MICHIGAN_
extern void initMichiganHardware();
#define INIT_HARDWARE initMichiganHardware()
#endif

#endif  // ifndef CHOOSEHARWARE_HH

Then, in the main somewhere, you do

#include "ChooseHardware.hh"

main()
{
.
.
INIT_HARDWARE();
.
.
}

Anyway, not as clean as I would have wanted, but I think this is sufficient.
This is also not set in stone, but what I have provides a good starting place
that at least is syntactically correct.  For example, we may also want to
implement a "RELEASE_HARDWARE" macro to bookend the INIT_HARDWARE macro.  There
does remain a fair amount of drudgery left involved in splitting all the
hardware classes out of the current Hardware.hh and reformatting them, and then
going into the various hardware libraries and implementing the INIT_HARDWARE
routine in a HARDEWARE_BODY enabled C++ file, but it should be fairly
straightforward.

                                             Jay

-- ClarkHaynes? - 29 Sep 2003

 
This site is powered by the TWiki collaboration platformCopyright &© by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback