Looking for lightweight signal and slot classes

Status
Not open for further replies.

christoph

Well-known member
Hi,

The title already says it: I'm looking for lightweight signal and slot classes, similar to boost.signals2 or the Qt classes (they will actually be used for widgets). I'm not sure if boost.signals2 would work for me (to be honest - I haven't tried) or if they would introduce too much overhead. Are there any alternatives? I'll use them on Teensy 3.x.

Regards

Christoph
 
Boost signal2 looks worth a try. The "library" is a collection of header files only. Don't know if it counts as "lightweight" -- it's probably light on system resources, but maybe not so light on complexity if you run into any compiler issues.

Maybe tell us a bit more about what you're trying to do? I'm wondering if any of the small operating systems might actually be lightweight enough to meet your needs? An OS can be a few thousand lines of C code.

For the benefit of others like Steve for whom this is an unfamiliar usage of signal and slot, it probably goes back farther than I know, but I'm familiar with it from advanced software development tools from the 1980s. Smalltalk-80, InterLisp-D/Loops, and Intellicorp's KEE were all development environments that had a notion of signals and slots. A signal was a message, but unlike a simple single-threaded function call with dispatch based on the receiver's type, a signal could be used for multi-threaded event-based programming or a network-transparent notification. A slot was a software concept analogous to a hardware port. Essentially a slot is a name for a particular input or output stream, such as "bank balance" or "remaining battery charge".

This concept of signals and slots was used to make it transparent to convert a single system into a distributed system. The concept was reimplemented in Qt and Boost. Slots allow an object to advertise features for any and all to use. Signals allow an object to communicate in a structured way with other object. Often both signals and slots can be enumerated allowing object services to be discoverable.
 
Steve, that would be http://www.boost.org/doc/libs/1_55_0/doc/html/signals2.html.

My application: It is a head-up display that I can attach to an astronomical telescope, similar to the Telrad, but it can display all sorts of information about where I'm looking and it will also tell me where a chosen observation target would be, or what other objects are in sight.

There's quite a number of menus and overlays that need to be displayed, depending on the current mode. Switching between modes involves hiding widgets and showing others, as well as handling focus (focus = which widget gets the button events?). A similar situation arises when the telescope's viewing direction changes so that I need to load a different chunk of my object database, and so on. I could write classes for each and every event, but all this boils down to: When something happens in object A, I want to be able to have other things happen in objects B and C, without object A being aware of that.

Regards

Christoph
 
Last edited:
I just tried to include boost.signals2, but the Teensy headers define a bunch of macros that get in the way of boost's template parameter names. Will I even think about trying to fix that?

untitle.JPG
 
I came up with my own solution and I think it's quite flexible. It does have one small drawback, though: It uses the heap. This was a no-no for me but I couldn't find a way around that. I have attached the header and here is the walkthrough:

First, the upper half of the header guard and includes:
Code:
#ifndef _SIGNALS_H_
#define _SIGNALS_H_

#include <utility>
We need std::forward from <utility> to perfectly forward arguments to the object that will be connected to a signal.

The first class we need is a delegate interface that provides a call operator with any number and type of arguments:
Code:
/** Interface for delegates with a specific set of arguments **/
template<typename... args>
class AbstractDelegate
{
  public:
    virtual void operator()(args&&...) const = 0;
    virtual ~AbstractDelegate() {}
};

The first kind of concrete delegate is for non-static member functions. It stores an object of any type and a pointer to a member function that can return any type and accept any number of types:
Code:
/** Concrete member function delegate that discards the function's return value **/
template<typename T, typename ReturnType, typename... args>
class ObjDelegate : public AbstractDelegate<args...>
{
  public:
    /** member function typedef **/
    typedef ReturnType (T::*ObjMemFn)(args...);

    /** constructor **/
    ObjDelegate(T& obj, ObjMemFn memFn)
      : obj_(obj),
      memFn_(memFn)
    {
    }

    /** call operator that calls the stored function on the stored object **/
    void operator()(args&&... a) const
    {
      (obj_.*memFn_)(std::forward<args>(a)...);
    }

  private:
    /** reference to the object **/
    T& obj_;
    /** member function pointer **/
    const ObjMemFn memFn_;
};

The second concrete delegate class is used for free functions and static member functions:
Code:
/** Concrete function delegate that discards the function's return value **/
template<typename ReturnType, typename... args>
class FnDelegate : public AbstractDelegate<args...>
{
  public:
    /** member function typedef **/
    typedef ReturnType (*Fn)(args...);

    /** constructor **/
    FnDelegate(Fn fn)
      : fn_(fn)
    {
    }

    /** call operator that calls the stored function **/
    void operator()(args&&... a) const
    {
      (*fn_)(std::forward<args>(a)...);
    }

  private:
    /** function pointer **/
    const Fn fn_;
};

No let's have a look at the other side of things. A Signal can call any number of delegates, which are in turn referenced by a Connection - so a signal stores a list of connections. As I don't like the standard containers for embedded work, the list of connections is implemented as an intrusive linked list. This makes a forward declaration of the connection class necessary:
Code:
/** forward declaration **/
template<typename... args>
class Connection;

/** Signal class that can be connected to**/
template<typename... args>
class Signal
{
  public:
    /** connection pointer typedef **/
    typedef Connection<args...>* connection_p;

    /** constructor **/
    Signal()
      : connections_(NULL)
      {
      }

    /** call operator that calls all connected delegates.
      The most recently connected delegate will be called first **/
    void operator()(args&&... a) const
    {
      auto c = connections_;
      while(c != NULL)
      {
        (c->delegate())(std::forward<args>(a)...);
        c = c->next();
      }
    }

    /** connect to this signal **/
    void connect(connection_p p)
    {
      p->next_ = connections_;
      connections_ = p;
      p->signal_ = this;
    }

    /** disconnect from this signal.
      Invalidates the connection's signal pointer
      and removes the connection from the list **/
    void disconnect(connection_p conn)
    {
      // find connection and remove it from the list
      connection_p c = connections_;
      if (c == conn)
      {
        connections_ = connections_->next();
        conn->next_ = NULL;
        conn->signal_ = NULL;
        return;
      }
      while(c != NULL)
      {
        if (c->next() == conn)
        {
          c->next_ = conn->next();
          conn->next_ = NULL;
          conn->signal_ = NULL;
          return;
        }
        c = c->next();
      }
    }

    /** destructor. disconnects all connections **/
    ~Signal()
    {
      connection_p p = connections_;
      while(p != NULL)
      {
        connection_p n = p->next();
        disconnect(p);
        p = n;
      }
    }

  private:
    connection_p connections_;
};
That was a lot of code, but the most complicated parts actually just manage the list of connections. The Signal's call operator loops through all connections and calls all delegates.

Connecting to the Signal is simple: The connection is added to the list, and the connection's signal pointer (see below) is set to this signal.

Disconnecting from a signal is a bit more complicated, as an entry needs to be removed from a singly linked list.

The signal destructor disconnects all connections which are still connected to this signal. The object owning the connection can use the connection's connected() method to see if the signal it refers to is still alive. I don't think if this is very useful, but it was easy to implement.

The Connection class manages a delegate and connects it to a signal. The constructor is templated to allow for non-static and static (or free) functions, making use of the heap necessary: On construction is it known what exact type of delegate is needed, and such a delegate is allocated on the heap. I don't think this is too bad for two reasons:
  • a delegate only stores one or two pointers
  • most embedded applications are more or less static, so no (or few) delete() calls will be made during runtime
Let's have a look:
Code:
/** connection class that can be connected to a signal **/
template<typename... args>
class Connection
{
  public:
    /** template constructor for non-static member functions.
      allocates a new delegate on the heap **/
    template<typename T, typename ReturnType>
    Connection(Signal<args...>* signal, T& obj, ReturnType (T::*memFn)(args...))
      : delegate_(new ObjDelegate<T, ReturnType, args...>(obj, memFn)),
      signal_(NULL),
      next_(NULL)
    {
      signal->connect(this);
    }

    /** template constructor for static member functions and free functions.
      allocates a new delegate on the heap **/
    template<typename ReturnType>
    Connection(Signal<args...>* signal, ReturnType (*Fn)(args...))
      : delegate_(new FnDelegate<ReturnType, args...>(Fn)),
      signal_(NULL),
      next_(NULL)
    {
      signal->connect(this);
    }

    /** get reference to this connection's delegate **/
    AbstractDelegate<args...>& delegate() const
    {
      return *delegate_;
    }

    /** get pointer to next connection in the signal's list **/
    Connection* next() const
    {
      return next_;
    }

    /** is this connection connected to a valid signal? **/
    bool connected() const
    {
      return (signal_ != NULL);
    }

    /** desctructor. If the signal is still alive, disconnects from it **/
    ~Connection()
    {
      if (signal_ != NULL)
      {
        signal_->disconnect(this);
      }
      delete delegate_;
    }

    friend class Signal<args...>;
  private:
    AbstractDelegate<args...>* delegate_;
    Signal<args...>* signal_;
    Connection* next_;
};
Construction really is very simple: The appropriate delegate is constructed an the Connection is added to the given Signal. The delegate(), next() and connected() methods are trivial, they provide information about the connection and the list of Connections for the Signal. The destructor is more interesting: if the Signal is still alive, the connection is removed from it. Then the delegate which was allocated by the constructor is deleted.

So how can we use this? Here's an example:
Code:
/** An Act object can be incremented and provides a Signal that can be used to be notified when that happens: **/
class Act
{
  public:
    Act() : val_(0) {}

    void increment()
    {
      val_++;
      incremented(val_);
      incremented(5);
      incremented(val());
    }

    Signal<const int&> incremented;

  private:
    int val() {return val_;}
    int val_;
};

/** A Watch object watches an Act object, automatically printing the Act objects' signalled value **/
class Watch
{
  public:
    Watch(uint8_t offset = 0)
      : offset_(offset)
    {
    }

    void print(const int& i)
    {
      Serial.print("Watch::print(");Serial.print(i+offset_);Serial.println(")");
    }

  private:
    uint8_t offset_;
};

Act act;
Watch watch(4);
Connection<const int&> conn(&act.incremented, watch, &Watch::print);

void loop()
{
  act.increment();
  delay(500);
}

A warning: Be very careful when choosing the signal and connection parameters. The above example works with incremented(val_) and with incremented(5) only because the argument type is const int&. If you remove constness or referenceness from the template parameter, one of the calls won't work.

If you try these classes, please tell me if you found them easy to use and flexible enough. And, of course, please report any bugs you find.

Regards

Christoph
 

Attachments

  • Signals.h
    5.3 KB · Views: 193
Last edited:
It's also possible to allocate a Connection on the heap, so we don't need to give it a name as in the example above. Two convenience functions are needed:
Code:
/** free connect function: creates a connection (non-static member function) on the heap
  that can be used anonymously **/
template<typename T, typename ReturnType, typename... args>
Connection<args...>* connect(Signal<args...>* signal, T& obj, ReturnType (T::*memFn)(args...))
{
  auto p = new Connection<args...>(signal, obj, memFn);
  return p;
}

/** free connect function: creates a connection (static member or free function) on the heap
  that can be used anonymously **/
template<typename ReturnType, typename... args>
Connection<args...>* connect(Signal<args...>* signal, ReturnType (*fn)(args...))
{
  auto p = new Connection<args...>(signal, fn);
  return p;
}
Usage with the objects in the previous post:
Code:
Act act;
Watch watch(4);

void setup()
{
  connect(&act.incremented, watch, &Watch::print);
}

void loop()
{
    act.increment();
    delay(500);
}
If the connection is never deleted, we don't need to store a pointer to it, and it can be left on the heap until the system resets.
 
I'm wondering why the header file I attached has been downloaded only 4 times - is this a need only a handful of people have? That would mean that I'm writing overly complicated code...
 
It's probably more a question of immediate need or not. I've put a subscription on this thread to follow this, but I don't have time at the moment to get into it. I have a project in mind, but it's quite a ways off.
 
I'm wondering why the header file I attached has been downloaded only 4 times - is this a need only a handful of people have? That would mean that I'm writing overly complicated code...

I believe that's an incorrect assessment. Many people here on the forum are hobbyists that often had marginal, or no formal education on programming. As such what lightweight signal and slot classes are, and what they are used for and what their advantages are compared with different approaches is not exactly self explanatory :)
Once you start explaining these abstract or even obscure (to hobby programmers such as myself) classes it may become more apparent and more widely used.
 
You're probably right about the lack of a good example. What I have included above is just a demonstration, and not a good one, of the classes' usage.

That said, I'm just a hobbyist myself, so no excuses ;) I'll try to come up with a concise example that demonstrates the design pattern and how it can solve a specific type of problem.
 
First of all: Signals.h works only on Teensy 3.x because the C++ support for AVRs is very limited

The example I'll present later is about
  1. reading an analog input (potentiometer),
  2. smoothing the values with a filter (exponential moving average),
  3. using the filtered signal for setting the brightness of an LED,
  4. writing the filtered value on a terminal.
This is sufficiently easy to set up hardware-wise and complex enough to demonstrate the use and benefits of the observer pattern.

Example code will come later today, starting with a spaghetti version and then working towards better encapsulation and code-reuse.

Regards

Christoph
 
Here is the spaghetti version.

Hardware:
20140423_225708.jpg
  • Teensy 3.1
  • A 10k potentiometer is connected as a voltage divider to analog input 0 (pin 14).
  • An LED with series resistor is connected to pin 10 (pwm output). Both pins are defined with macros.

Code:
Code:
#define potPin 14
#define ledPin 10

void setup()
{
  while(!Serial.available());
  while(Serial.available())
  {
    Serial.read();
  }
}

elapsedMillis deadTime;

void loop()
{
  // filter parameters
  static const float alpha = 0.125;
  static const uint8_t scalingExponent = 16;
  static const uint32_t scale = 1 << scalingExponent;
  static const uint32_t uAlpha = alpha*scale;
  static const uint32_t uBeta = scale - uAlpha;
  // filtered analog value
  static uint16_t filteredAnalog = 0;

  deadTime = 0;

  // raw analog value
  uint16_t rawAnalog = analogRead(potPin);
  // exponential filter with fixed point magic
  uint32_t filteredAnalog_unscaled = (uAlpha * rawAnalog + uBeta * filteredAnalog);
  // round up if necessary
  if (filteredAnalog_unscaled & (1 << (scalingExponent-1)))
  {
    filteredAnalog_unscaled += scale;
  }
  // compute new filtered value
  filteredAnalog = filteredAnalog_unscaled >> scalingExponent;

  // output to LED
  analogWrite(ledPin, filteredAnalog);
  // output to USB Serial
  char buf[18];
  snprintf(buf, 18, "r: %04d; f: %04d\r", rawAnalog, filteredAnalog);
  Serial.print(buf);

  // 50 Hz update rate
  while (deadTime < 20)
  {
  }
}

How it works (no details):
The setup() function waits for a character to come in through USB Serial. I like to let my apps start with such a loop so that I can see debug messages from setup(). There are none in this example, though.

loop() uses a global elapsedMillis() object (deadTime) to throttle the update rate of the whole application to 50 Hz (20 ms period). What's actually done in loop():
  • An analog sample is taken and stored.
  • The exponential filter is updated with the newest analog sample.
  • The filtered analog value is written to the LED PWM pin.
  • The raw and filtered analog values are written to Serial.
  • The loop waits until the deadTime is reached.
The LED brightness follows a sudden change in potentiometer position in about a second. This can be seen best when quickly turning from a bright setting to zero (how visible the effect is depends on the LED and resistor combo).

The details, and why the spaghetti code above is bad:
  • The exponential filter needs some parameters. I don't like floats when the MCU doesn't have an FPU and when I can avoid them, so I used fixed point calculations. The only really interesting parameter is alpha which can be used to adjust the filter response. The rest is quite well described here, and there is a lot more to find on the web.
  • The filter parameters are not visible outside of loop(), so they at least hidden from code outside. That's ok, but not good yet.
  • The filter's actual value (filteredAnalog) can be modified by any code inside of loop(), which is bad. We should try to hide that.
  • We should also try to move the filter code into (at least) a function or (better) a class. Changing the filter type is then a matter of just changing one line of code. Now we would probably comment out the filter code and replace it with different code for a different kind of filter (for example a moving average with finite response time).
  • The filter doesn't need to know where it gets its input from, or where the filtered value goes.
  • The LED doesn't need to know where it gets its input from.
  • The Serial output also doesn't need to know where it gets its information from.

The next example will improve on the above points by adding a bunch of classes that at least hide boring and protect sensitive information.
 
Last edited:
protothreads do have their place, but they won't help me make my point here. Thanks for the link, though, they might help me solve a totally different problem!

Next example will come in a few hours.
 
Version 2, still without signals

Here is an improved version of the above code that hides and protects information. There are four new classes:

  • AnalogInput is a simple class template that provides get() and the function call operator for reading from an analog input. That's convenient and expressive.
  • ExponentialFilter is a class that provides an interface to the filter, with feed(), get() and the function call operator.
  • AnalogOutput is again a class template that provides set() to write to an analog output. The function call operator is not implemented, but that would be easy.
  • Report writes the filtered analog value to the USB Serial port, but ony if the value changed.
Attention: You need mk20dx128.c from post #9 in this thread!

The code is now a lot less cluttered and should be quite clear:
Code:
// let's get rid of those macros
static constexpr uint8_t potPin = 14;
static constexpr uint8_t ledPin = 10;

// a simple template that provides an expressive way of reading from an analog input
template<uint8_t pin>
class AnalogInput
{
  public:
    // get raw analog input value
    uint16_t get() const
    {
      return analogRead(pin);
    }
    // additional operator() for function-like access
    uint16_t operator()() const
    {
      return get();
    }
  private:
    // (no state)
};

// the filter algorithm is now a filter class.
class ExponentialFilter
{
  public:
    // this is the type of the signal we want to filter
    typedef uint16_t scaled_type;
    // the constructor takes one argument: the alpha value
    ExponentialFilter(float alpha)
      : alpha_(alpha),
      uAlpha_(alpha_*scale_),
      uBeta_(scale_ - uAlpha_)
    { }
    // feed a value into the filter and update the filtered value
    void feed(scaled_type sample)
    {
      unscaled_type unscaled = (uAlpha_ * sample + uBeta_ * value_);
      // round up if necessary
      if (unscaled & (1 << (scalingExponent_-1)))
      {
        unscaled += scale_;
      }
      // compute new filtered value
      value_ = unscaled >> scalingExponent_;
    }
    // get filtered value
    scaled_type get() const
    {
      return value_;
    }
    // additional function-like access
    scaled_type operator()() const
    {
      return get();
    }
  private:
    // these are equivalent to those in the spaghetti code example
    typedef uint32_t unscaled_type;
    static constexpr uint8_t scalingExponent_ = 16;
    static constexpr unscaled_type scale_ = 1 << scalingExponent_;
    const float alpha_;
    const unscaled_type uAlpha_;
    const unscaled_type uBeta_;
    scaled_type value_;
};

// a simple template that provides an expressive way of writing to an analog input
template<uint8_t pin>
class AnalogOutput
{
  public:
    // set the output value
    void set(uint16_t value)
    {
      analogWrite(pin, value);
    }
    // no function-like write access (could be done, of course)
  private:
    // (no state)
};

// report a value over USB Serial, but only when it changed
class Report
{
  public:
    Report()
      : firstUse_(true)
    {
    }
    void operator()(uint16_t value)
    {
      // only send the value over Serial if it changed or if this is the first call
      if (firstUse_)
      {
        firstUse_ = false;
        write(value);
      }
      else if (value != lastValue_)
      {
        write(value);
      }
    }
  private:
    void write(uint16_t value)
    {
      char buf[6];
      snprintf(buf, 6, "%04d\r", value);
      Serial.print(buf);
      lastValue_ = value;
    }
    bool firstUse_;
    uint16_t lastValue_;
};

void setup()
{
  while(!Serial.available());
  while(Serial.available())
  {
    Serial.read();
  }
}

elapsedMillis deadTime;

void loop()
{
  // the input, filter, and led objects
  /** ATTENTION PLEASE: update your mk20dx128.c with the one from post #9 in this pjrc forum thread:
    http://forum.pjrc.com/threads/25385-static-local-variables-aren-t-static
  **/
  static AnalogInput<potPin> pot;
  static ExponentialFilter filter(0.125);
  static AnalogOutput<ledPin> led;
  static Report report;

  deadTime = 0;

  // read raw analog value and save a copy
  uint16_t rawAnalog = pot();
  // feed it into the filter
  filter.feed(rawAnalog);
  // set LED to filtered value
  led.set(filter());
  // output to USB Serial
  report(filter());

  // 50 Hz update rate
  while (deadTime < 20)
  {
  }
}

report() only takes one argument (the filtered value, in this case) because that value changes while the potentiometer is turned and then settles. When it has settled, updating the displayed value is not necessary any more.

What's bad about this code
  • Determining if the value changed should not be the task of the Report class. The Filter class should know that. We could implement a changed() method in ExponentialFilter, but we would still need to call that method to see if there was any change in the filter output.
  • There is some glue code needed that carries the analog value from the input class to the filter, and filter's output to the led and report objects.
The second problem could be solved by passing the pot object to the filter's constructor and having filter read the input. The same is true for the other classes, which could "pull" values from their sources. This would require us to make the input classes visible to the others, which is usually not desirable: it would require a lot of includes if the classes get their own header (which they should), and the classes would lose flexibility.

Both problems outlined above can be solved by connecting the objects using signals.

Regards

Christoph
 
Now using the observer pattern (signals)

The analog input and filter classes are now "active", i.e. they emit events which can be observed (by a connection). Any object or function that is connected to a signal will receive the event. The connections are created by the setup() function. The main loop doesn't need to care about how the potentiometer value gets to the filter and how the filter output gets to the LED and serial output.

One set of important changes is in the type passed around between the classes. This was a plain uint16_t before, and now it is a const uint16_t&. One major drawback of the connection classes in Signals.h is that they use perfect forwarding between the signals and the function/method that is called in order to avoid unnecessary copies of any argument. This is not too bad because once we pass bigger objects, we would do so by reference and not by value.

Code:
#include <Signals.h>

static constexpr uint8_t potPin = 14;
static constexpr uint8_t ledPin = 10;

// a simple template that provides an expressive way of reading from an analog input
template<uint8_t pin>
class AnalogInput
{
  public:
    // get raw analog input value
    uint16_t get() const
    {
      uint16_t result = analogRead(pin);
      updated(result);
      return result;
    }

    // additional operator() for function-like access
    uint16_t operator()() const
    {
      return get();
    }
    
    // we will connect to this signal to be notified of updates
    Signal<const uint16_t&> updated;
  private:
};

// the filter algorithm is now a filter class.
class ExponentialFilter
{
  public:
    // this is the type of the signal we want to filter
    typedef uint16_t scaled_type;

    // the constructor takes one argument: the alpha value
    ExponentialFilter(float alpha)
      : alpha_(alpha),
      uAlpha_(alpha_*scale_),
      uBeta_(scale_ - uAlpha_),
      firstUse_(true)
    { }

    // feed a value into the filter and update the filtered value
    void feed(const scaled_type& sample)
    {
      unscaled_type unscaled = (uAlpha_ * sample + uBeta_ * value_);

      // round up if necessary
      if (unscaled & (1 << (scalingExponent_-1)))
      {
        unscaled += scale_;
      }

      // compute new filtered value
      scaled_type newValue = unscaled >> scalingExponent_;
      if (firstUse_)
      {
        firstUse_ = false;
        update(newValue);
      }
      else if (newValue != value_)
      {
        update(newValue);
      }
    }

    // get filtered value
    scaled_type get() const
    {
      return value_;
    }

    // additional function-like     access
    scaled_type operator()() const
    {
      return get();
    }
    
    // we will connect to this signal to be notified of changes in the filter output
    Signal<const uint16_t&> changed;
  private:
    void update(scaled_type newValue)
    {
      value_ = newValue;
      changed(newValue);
    }

    // these are equivalent to those in the spaghetti code example
    typedef uint32_t unscaled_type;
    static constexpr uint8_t scalingExponent_ = 16;
    static constexpr unscaled_type scale_ = 1 << scalingExponent_;
    const float alpha_;
    const unscaled_type uAlpha_;
    const unscaled_type uBeta_;
    scaled_type value_;
    bool firstUse_;
};

// a simple template that provides an expressive way of writing to an analog input
template<uint8_t pin>
class AnalogOutput
{
  public:
    // set the output value
    void set(const uint16_t& value)
    {
      analogWrite(pin, value);
    }

    // no function-like write access (could be done, of course)
  private:
    // (no state)
};

// report a value over USB Serial, but only when it changed
class Report
{
  public:
    Report()
      : writes_(0)
    {
    }

    void operator()(const uint16_t& value)
    {
      // the change detection logic is now in the filter class
      write(value);
    }
  private:
    void write(uint16_t value)
    {
      writes_ += 1;
      char buf[20];
      snprintf(buf, 20, "%04u (%u)\r", value, writes_);
      Serial.print(buf);
      lastValue_ = value;
    }

    uint16_t lastValue_;
    uint32_t writes_;
};

// the input, filter, and led objects
/** these could also be static objects in setup(), or reside in their own namespace.
  It doesn't matter as long as setup() can somehow find them
**/
static AnalogInput<potPin> pot;
static ExponentialFilter filter(0.125);
// we need this typedef so that we don't need to write the template argument more than once.
typedef AnalogOutput<ledPin> MyLed;
static MyLed led;
static Report report;

void setup()
{
  while(!Serial.available());
  while(Serial.available())
  {
    Serial.read();
  }
  
  // this is new: create connections between instances
  connect(pot.updated, filter, &ExponentialFilter::feed);
  connect(filter.changed, led, &MyLed::set);
  connect(filter.changed, report, &Report::operator());
  /** we could also connect a signal to a free function, like so:
    void freeFunction(const uint16_t& value)
    {
      ...
    }
    connect(filter.changed, freeFunction);
  **/
}

elapsedMillis deadTime;

void loop()
{
  
  deadTime = 0;

  pot(); // everything else is done by the connections

  // 50 Hz update rate
  while (deadTime < 20)
  {
  }
}

As you can see it's now possible to pass around values between objects without the objects being aware of each other. The only common parameter is the type that is passed, and type matching is enforced. This in turn forces us to use consistent types in the interfaces.

I use the Signals header I posted to pass around events between hardware (buttons) and widgets, configuration parameters that need to be marked for storage once they changed, all sorts of stuff.

Regards

Christoph
 
I finally had a chance to look at this myself today and I have to say - nice templates use! I'll likely use this in future projects myself (nice functionality). My only comment would have been a preference for STL since I plan to use STL extensively anyway. But then again, from a black box viewpoint, who cares. Not dragging in the STL certainly has size advantages.

Job well done, and thanks!
 
Thanks!

If the STL had intrusive linked lists, I would have used those. There is of course a lot more that can be improved, but I'm satisfied with this as long as I don't hit any limitations.
 
I wouldn't be too fussed about CPU overhead because the setup (and teardown if any) is typically done once. But RAM use of course, is vital on these platforms. :)
 
I wouldn't be too fussed about CPU overhead because the setup (and teardown if any) is typically done once.
That is the reason why I decided to use the heap, although I didn't want to do that when I started writing the code. Releasing memory means wasting it (on these simple systems at least), because free()ing memory doesn't necessarily make it useful for later. As you say, setup is usually done once, so using the heap is ok.

It's just not as easy to track memory usage as it is with static objects, because we don't see the numbers after linking.
 
I'm just trying out protothreads and it looks like those + signals could be a very powerful combo. I'll definitely try that...
 
I looked at protothreads briefly, but I'm not fond of systems based upon macros. A macro here, another there, etc. Not my style - too ugly, too many moving parts.

In the light weight Fibers class that I developed for myself, you simply declare one object to manage the fibers. Then each fiber only need call fibers.yield(), to dispatch to the next one round-robin (including the main thread). The template allows you to choose the max # of fibers you wish to support. You choose fiber stack size at creation time.

Optionally you can enable "stack instrumentation" to allow you to query the actual amount of stack actually used by main or fiber threads. That allows you to be more frugal with the stack as long as you leave some allowance for interrupt processing. This should be reflected in the returned byte count, provided you allowed enough exercise of the system.

https://github.com/ve3wwg/teensy3_lib/tree/master/teensy3/fibers

It has passed QEMU testing, but I'm waiting for a cable for a real hardware (teensy3.1) test. In the meantime, if someone wanted to test it, I'd be much obliged. :)

I've used setjmp()/longjmp() on AVR in the past for two fiber support, but it starts to get a bit tricky when go beyond two.
 
I was giving signals.h a test drive tonight, on OSX. I noticed that you don't have the helper routines in signals.h:

Code:
/** free connect function: creates a connection (non-static member function) on the heap
  that can be used anonymously **/
template<typename T, typename ReturnType, typename... args>
Connection<args...>* connect(Signal<args...>* signal, T& obj, ReturnType (T::*memFn)(args...))
{
  auto p = new Connection<args...>(signal, obj, memFn);
  return p;
}

/** free connect function: creates a connection (static member or free function) on the heap
  that can be used anonymously **/
template<typename ReturnType, typename... args>
Connection<args...>* connect(Signal<args...>* signal, ReturnType (*fn)(args...))
{
  auto p = new Connection<args...>(signal, fn);
  return p;
}

Because of that, it took me a while to figure out where the connect() calls in your example were coming from. :) I think the library would be more generally useful with them included.

I am also fond of super simple examples to start with. So I managed to get this function only example going on OSX/Linux (assumes the helper routines are in signals.h):

Code:
#include "stdio.h"

#include "./signals.h"

static void
foo(int arg) {
        printf("foo(int %d)\n",arg);
}

static int
bar(int arg) {
        printf("bar(int %d)\n",arg);
        return 1;
}

int
main() {
        Signal<int> intsigs;                   // Signal with int arg
        Connection<int> foocon(&intsigs,foo);  // This connects foo() to intsigs
        Connection<int> *barcon = 0;           // Just a ptr to the bar() connection
        
        barcon = connect(&intsigs,bar);        // Use helper routine to connect with bar()
        
        intsigs(23);   // Dispatch to all connected with 23
        intsigs(24);   // foo(24) and bar(24)
        
        delete barcon;   // Disconnect bar()
        barcon = 0;   
        
        intsigs(25);    // Only foo(25) is invoked here
        
        return 0;
}

Seems to work as advertised. :) Passed valgrind leak checks also.

What I really like, is that the functions need not return the same type of value. foo() is void here, while bar() returns int. Real nice.
 
Status
Not open for further replies.
Back
Top